1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

BoxとServiceNow連携:Box UI Elementsを埋め込む

Last updated at Posted at 2024-10-15

概要

前回の記事(BoxとServiceNow連携:コラボレーション作成)でServiceNowのサービスポータルからBox上のフォルダ作成を依頼することを紹介しました。

依頼フォームでフォルダ名、親フォルダIDとコラボレーターのメールアドレスを入力する必要がありますが、普通はフォルダIDをわかる人が少ないと思います。URLから取得することができますが、それより使いやすくするため、本記事でBox UI Elementsレコードプロデューサーに埋め込み、簡単に親フォルダを指定する方法を紹介します。

準備

以前紹介したBoxフォルダ作成依頼のレコードプロデューサーが用意されていることを仮定します。本記事で作成するカスタムウィジェットを利用し、既存のレコードプロデューサーを拡張します。レコードプロデューサーの作成について、BoxとServiceNow連携:コラボレーション作成を参考してください。

追加準備として、Box UI Elementsを利用するために、Boxカスタムアプリの「構成」タブの「CORSドメイン」セクションでServiceNowのインスタンスURLを設定する必要があります。例えば、https://dev12345.service-now.com.

Screenshot 2024-11-15 at 17.23.17.png

実装

Box UI ElementsはJavaScriptとCSSので提供されていますが、レコードプロデューサーで直接にJavaScriptを埋め込むことができなさそうです。ワークアラウンドとして、カスタムウィジェットにBox UI Elementsを埋め込んで、カスタムウィジェットをレコードプロデューサーの変数として設定します。

親フォルダを選択するには、Box UI ElementsのContent Pickerを使いますので、ServiceNowのRESTメソッド(サーバー側)でトークンを取得し、Content Picker(クライアント側)にトークンを渡します。ただ、そのままトークンを渡すと、トークンを発行したアカウントが見える全てのファイル・フォルダはContent Pickerで表示されてしまいます。また、クライアント側から他の目的にそのトークンが使え、セキュリティ面でリスクが発生します。

対策として、Content Pickerにトークンを渡す前に、トークンダウンスコープでトークンの権限を絞ります。それで、表示されるコンテンツ(ファイル・フォルダ)が制限され、そのトークンできることは限られます。

上記を実現するには以下のステップを実装します:

  1. トークンダウンスコープのHTTPメソッドを追加
  2. カスタムウィジェットを作成
  3. レコードプロデューサーを拡張

最後に、動作確認を行います。

この記事の最初バージョンに「トークン取得のHTTPメソッドを追加」というステップもありましたが、ServiceNowは自動的に取得するトークンを使った方がシンプルだとわかりました。そのため、明示的にトークンを取得することが不要で、略します。

トークンダウンスコープのHTTPメソッドを追加

取得したトークンの権限を絞るため、またhttps://api.box.com/oauth2/tokenのエンドポイントを使いますが、リクエストの本文で指定するパラメータが異なります(ガイド):

  • subject_token:ダウンスコープされるトークンを渡しますので、変数にします
  • subject_token_type:トークンダウンスコープの場合はurn:ietf:params:oauth:token-type:access_tokenで固定されています
  • scope:目的に応じて変わりますが、レコードプロデューサーのフォームでの親フォルダ選択に使いますので、「base_picker」にします
  • client_secret:Boxカスタムアプリの設定画面から取得できます
  • grant_type:トークンダウンスコープの場合はurn:ietf:params:oauth:grant-type:token-exchangeで固定されています

「resource」という任意パラメータを指定すると、ダウンスコープされるトークンアクセスできるコンテンツを特定のファイル・フォルダに制限できます。

トークンをダウンスコープするには新規なHTTPメソッドを作成します:

  • 名前:<任意の名前>(例:Boxトークンをダウンスコープ)
  • HTTP メソッド:POST
  • REST エンドポイント: https://api.box.com/oauth2/token

HTTP要求」タブの「HTTPヘッダー」として、Content-Typeヘッダーを追加し、値をapplication/jsonにします。

最後に、「HTTP要求」タブの「コンテンツ」の内容は以下のJSONです。JSONではaccess_tokenを変数として指定します

{
  "subject_token": "${access_token}",
  "subject_token_type": "urn:ietf:params:oauth:token-type:access_token",
  "scope": "base_picker",
  "client_secret": "8a5da52ed126447d359e70c05721a8aa",
  "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange"
}

トークンダウンスコープHTTPメソッド.png

レコードを保存すると、画面の下に「REST メッセージ関数パラメーター」タブが表示されます。「関連リンク」の「変数の自動生成」をクリックすると、JSONで指定した変数がパラメータとして設定されます。

カスタムウィジェットを作成

レコードプロデューサーの変数として使うカスタムウィジェットは取得・ダウンスコープしたトークンを使って、ユーザーが選択できるフォルダを表示します。ユーザーはフォルダを選んだら、レコードプロデューサーの「u_parent_folder_id」変数の値が自動的に変更されることを実装します。

まずは、新規なポータルのウィジェットを作成し、以下のように設定します:

  • 名前:<任意の名前>(例:Boxフォルダピッカー)
  • ID:<任意のID>(例:box_folder_picker_widget)
  • プレビューあり:チェック

「本文 HTML テンプレート」を以下のHTMLにします。

<div id="box-picker" style="width:800px" class="container"></div>

カスタムウィジェット1.png

「サーバースクリプト」を以下のJavaScriptコードにします。このコードは作成したHTTPメソッドを使い、アクセストークンを取得・ダウンスコープします。
RESTメソッドで明示的にアクセストークンを取得するより、ServiceNowのRESTメッセージで取得されるトークンを使います

(function() {
	var oauthProfileName = 'Boxサーバー認証(CCG) default_profile'; // RESTメッセージで設定されているOAuthプロファイル名
	var boxRestMessageName = 'Box'; // Box用のRESTメッセージ名
	var boxRestMessageDefaultRequestName = 'Default GET'; // Box用のRESTメッセージのデフォルトメソッド名
	var boxRestMessageTokenDownscopeRequestName = 'Boxトークンをダウンスコープ';  // Box用のRESTメッセージのトークンダウンスコープメソッド名
	var oauthProfileId, requestorId, oauthEntityProfileGR, requestorProfileGR = '';
	var restMessage, response, responseBody, responseData = '';
	var oAuthClient, oauthToken, accessToken, downscopedAccessToken = '';	
	
	// アクセストーク取得で利用されるOAuth エンティティプロファイルとOAuth 要求者プロファイルを取得する
	// 参照:https://developer.servicenow.com/dev.do#!/reference/api/washingtondc/server/sn_auth-namespace/c_GlideOAuthClient#r_GlideOAuthClientRequestgetToken
    oauthEntityProfileGR = new GlideRecord('oauth_entity_profile');
    oauthEntityProfileGR.addQuery('name', oauthProfileName); 
    oauthEntityProfileGR.query();	
    if (oauthEntityProfileGR.next()) {
        oauthProfileId = oauthEntityProfileGR.getValue('sys_id'); 
		requestorProfileGR = new GlideRecord('oauth_requestor_profile');
		requestorProfileGR.addQuery('oauth_entity_profile', oauthProfileId);
		requestorProfileGR.query();
		if (requestorProfileGR.next()) {
			requestorId = requestorProfileGR.getValue('requestor_id');
		}
    }

	// OAuth エンティティプロファイルとOAuth 要求者プロファイルを利用し、アクセストークを取得してみる
	oAuthClient = new  sn_auth.GlideOAuthClient();
	oauthToken = oAuthClient.getToken(requestorId, oauthProfileId);
	accessToken = oauthToken.getAccessToken();

	// アクセストークンが存在しない(有効期限が切れた)場合は、デフォルトのリクエストを実施し、アクセストークンが取得される
	if (!accessToken) {
		restMessage = new sn_ws.RESTMessageV2(boxRestMessageName, boxRestMessageDefaultRequestName);
		response = restMessage.execute();
		oAuthClient = new  sn_auth.GlideOAuthClient();
		oauthToken = oAuthClient.getToken(requestorId, oauthProfileId);
		accessToken = oauthToken.getAccessToken();		
	} 
	
	// アクセストークンをダウンスコープ
	restMessage = new sn_ws.RESTMessageV2(boxRestMessageName, boxRestMessageTokenDownscopeRequestName);
	restMessage.setStringParameterNoEscape('access_token', accessToken);
	response = restMessage.execute();
	responseBody = response.getBody();
	responseData = JSON.parse(responseBody);
	downscopedAccessToken = responseData.access_token;

	// ダウンスコープされたトークンをクライアントに渡す
	data.downscopedAccessToken = downscopedAccessToken;
})();

「クライアントコントローラー」を以下のJavaScriptコードにします。

function($scope) {
	var controller = this;
	var accessToken = controller.data.downscopedAccessToken;
    var rootFolderId = '0'; // 最初に表示されるフォルダのID
    var folderPicker = new Box.FolderPicker();

    // 親フォルダが選択されたら、レコードプロデューサーのフォーム変数にIDを保存
    folderPicker.addListener('choose', function(items) {		
     	$scope.page.g_form.setValue('u_parent_folder_id', items[0].id);
    });

    folderPicker.show(rootFolderId, accessToken, {
        container: '#box-picker', // 「本文 HTML テンプレート」で設定したdivのID
		maxSelectable: 1, // フォルダ1つしか選択できない
		canUpload: false, // Boxへのアップロードができない
		canSetShareAccess: false, // アクセス権権の調整ができない
		canCreateNewFolder: false, // フォルダ作成ができない
		logoUrl: "box" // デフォルトのBoxロゴを利用する
    });
};

Content Pickerの見た目や機能などをオプションパラメータで調整できますので、ガイドを参考にしてください。

カスタムウィジェット2.png

この記事では、なるべくわかりやすくするため、コードを極力省いています。
セキュリティ面での行うべき考慮を省略し、エラーハンドリングなども実装されていません。
具体的な実装時には非機能面の考慮と実装方法を開発者が行う必要があります。

ウィジェットレコードを保存すると、画面の下に「依存関係」タブが表示されます。ここでContent Pickerのインストールで利用するJavaScriptとCSSを設定しますので、新規な依存関係を作ります:

  • 名前:<任意の名前>(例:Boxコンテンツピッカー)
  • ページロード時にインクルード:チェック

Boxコンテンツピッカー.png

依存関係レコードを保存すると、画面の下に「JS インクルード」と「CSS インクルード」タブが表示されます。まずは、新規なJSインクルードを作成します:

JSインクルード.png

JSインクルードレコードを保存して、依存関係一覧に戻ります。次に、新規なCSSインクルードを作成します:

CSSインクルード.png

Box UI Elementsが対応する言語の一覧はガイドで記載されています。

CSSインクルードレコードを保存して、ウィジェットレコードに戻り、保存します。

レコードプロデューサーを拡張

次に、「Boxファイル作成依頼」レコードプロデューサーにカスタムウィジェットを埋め込みます。

まずは、レコードプロデューサーの新規な変数を作成します:

  • タイプ:ラベル付きカスタム
  • 「質問」タブの「質問」:<任意>(例:親フォルダを選択してください)
  • 「質問」タブの「名前」:<任意>(例:parent_folder_picker)

レコードプロデューサーの新規な変数1.png

  • 「タイプ仕様」タブの「ウィジェット」:作成したカスタムウィジェットを選択します(例:Boxフォルダピッカー)

レコードプロデューサーの新規な変数2.png

本記事の実装が以上で、次に結果を確認します。

動作確認

これでContent Pickerがレコードプロデューサーのフォームに表示されます。そして、フォルダを選んだら、カスタムウィジェットが親フォルダIDのテキストボックスの値を変更します。

親フォルダID変更.gif

フォームの他の項目に変更がありませんので、入力して送信すると、Box上のフォルダが作成され、コラボレーターが追加されます。

まとめ

この記事では、ServiceNowにBox UI Elementsを埋め込む方法を紹介しました。

フォルダ選択だけではなく、Box UI Elementsは柔軟性が高く、ServiceNowの様々なプロセスで活用できます。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?