概要
前回の記事(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
.
実装
Box UI ElementsはJavaScriptとCSSので提供されていますが、レコードプロデューサーで直接にJavaScriptを埋め込むことができなさそうです。ワークアラウンドとして、カスタムウィジェットにBox UI Elementsを埋め込んで、カスタムウィジェットをレコードプロデューサーの変数として設定します。
親フォルダを選択するには、Box UI ElementsのContent Pickerを使いますので、ServiceNowのRESTメソッド(サーバー側)でトークンを取得し、Content Picker(クライアント側)にトークンを渡します。ただ、そのままトークンを渡すと、トークンを発行したアカウントが見える全てのファイル・フォルダはContent Pickerで表示されてしまいます。また、クライアント側から他の目的にそのトークンが使え、セキュリティ面でリスクが発生します。
対策として、Content Pickerにトークンを渡す前に、トークンダウンスコープでトークンの権限を絞ります。それで、表示されるコンテンツ(ファイル・フォルダ)が制限され、そのトークンできることは限られます。
上記を実現するには以下のステップを実装します:
- トークンダウンスコープのHTTPメソッドを追加
- カスタムウィジェットを作成
- レコードプロデューサーを拡張
最後に、動作確認を行います。
この記事の最初バージョンに「トークン取得の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"
}
レコードを保存すると、画面の下に「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>
「サーバースクリプト」を以下の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の見た目や機能などをオプションパラメータで調整できますので、ガイドを参考にしてください。
この記事では、なるべくわかりやすくするため、コードを極力省いています。
セキュリティ面での行うべき考慮を省略し、エラーハンドリングなども実装されていません。
具体的な実装時には非機能面の考慮と実装方法を開発者が行う必要があります。
ウィジェットレコードを保存すると、画面の下に「依存関係」タブが表示されます。ここでContent Pickerのインストールで利用するJavaScriptとCSSを設定しますので、新規な依存関係を作ります:
- 名前:<任意の名前>(例:Boxコンテンツピッカー)
- ページロード時にインクルード:チェック
依存関係レコードを保存すると、画面の下に「JS インクルード」と「CSS インクルード」タブが表示されます。まずは、新規なJSインクルードを作成します:
- 表示名:<任意の名前>(例:BoxコンテンツピッカーJS)
- ソース:URL
- JS ファイル URL:https://cdn01.boxcdn.net/platform/elements/21.0.0/ja-JP/picker.js
JSインクルードレコードを保存して、依存関係一覧に戻ります。次に、新規なCSSインクルードを作成します:
- 表示名:<任意の名前>(例:BoxコンテンツピッカーCSS)
- ソース:URL
- JS ファイル URL:https://cdn01.boxcdn.net/platform/elements/21.0.0/ja-JP/picker.css
Box UI Elementsが対応する言語の一覧はガイドで記載されています。
CSSインクルードレコードを保存して、ウィジェットレコードに戻り、保存します。
レコードプロデューサーを拡張
次に、「Boxファイル作成依頼」レコードプロデューサーにカスタムウィジェットを埋め込みます。
まずは、レコードプロデューサーの新規な変数を作成します:
- タイプ:ラベル付きカスタム
- 「質問」タブの「質問」:<任意>(例:親フォルダを選択してください)
- 「質問」タブの「名前」:<任意>(例:parent_folder_picker)
- 「タイプ仕様」タブの「ウィジェット」:作成したカスタムウィジェットを選択します(例:Boxフォルダピッカー)
本記事の実装が以上で、次に結果を確認します。
動作確認
これでContent Pickerがレコードプロデューサーのフォームに表示されます。そして、フォルダを選んだら、カスタムウィジェットが親フォルダIDのテキストボックスの値を変更します。
フォームの他の項目に変更がありませんので、入力して送信すると、Box上のフォルダが作成され、コラボレーターが追加されます。
まとめ
この記事では、ServiceNowにBox UI Elementsを埋め込む方法を紹介しました。
フォルダ選択だけではなく、Box UI Elementsは柔軟性が高く、ServiceNowの様々なプロセスで活用できます。