2
0

More than 3 years have passed since last update.

CAPとJob Scheduling Serviceでユーザとロール情報を取得する仕組みを作る

Last updated at Posted at 2021-08-28

はじめに

この記事は chillSAP 夏の自由研究2021 の記事として執筆しています。

私は最近、SAP Workflow Serviceの個人的PoCと称してBTPのWorkflowにからめた検証を行っています。その中で、以下のような要件が出てきました。

  • ユーザが画面で指定した承認者が、適切な権限を持っているかチェックしたい
  • 画面(UI5アプリ)のバックエンドであるCAPの中で上記のチェックを行う

「ログインユーザの権限」を見るのであれば、CAPの中でスコープのチェックができます。しかし、承認者として設定されたユーザはログインユーザではないので、権限チェックは簡単にはできません。

BTPにおける権限の仕組み

BTPにおける権限の最小単位はスコープです。スコープはデータに対する読み込み、書き込みなどの権限を表します。一つまたは複数のスコープがロールテンプレートを介してロールに割り当てられます。ロールはロールコレクションを介してユーザに割り当てられます。

よって、「ユーザが特定のスコープを持っているか」をチェックする場合、ユーザ⇒ロールコレクション⇒ロール⇒スコープの順に展開する必要があります。
image.png
図:User Management in SAP HANA Cloud Cockpitより引用

考えた権限チェックの仕組み

以下の図は、PoCで実現しようとしてる仕組みの全体です。CAP1はUI5アプリから呼ばれ、ワークフローを起動したり、承認、却下などのタスクを実行します。CAP2は、ユーザやロールコレクションなどのマスタデータを持ち、ユーザの権限チェックのためのAPIを提供します。CAP2を中心とする、黄色い枠で囲った部分が今回の記事で説明する範囲です。
image.png

CAP2では、Authorization and Trust Management ServiceのAPIを使い、BTPのサブアカウントに登録されているユーザの一覧、ロールコレクション、およびロールを取得してHANA DBに格納します。この処理を、Job Scheduling Serviceを使用して日次のジョブで実行します。
image.png

権限チェックでは、上記のマスタをもとに承認者に設定されたユーザが持っているスコープを取得します(下図)。ユーザやロールコレクションといったマスタの更新頻度に比べ、ユーザへのロール割り当て更新の頻度は高い(日中にも行う可能性がある)という想定で、ユーザに割り当たったロールコレクションを取得するところだけはリアルタイムに行うようにしました。
image.png

技術的なポイント

技術的なポイント(私が未経験だったこと)は、以下の2点です。

  • CAPからAuthorization and Trust Management ServiceのAPI(REST API)を呼ぶ
  • Job Scheduling ServiceでCAPのアクションを呼ぶ

実装方法

以下で実装方法について説明していきます。ステップは大きく分けて3つです。

  1. Authorization and Trust Management ServiceのDestination登録
  2. CAPサービスの実装
  3. Job Scheduling ServiceからCAPのサービスを呼ぶ

1. Authorization and Trust Management ServiceのDestination登録

CAPからAuthorization and Trust Management ServiceのAPIを呼ぶため、サブアカウントにDestinationを登録します。

1.1. APIにアクセスするためのXSUAAサービスインスタンスを作成

参考:Access UAA Admin APIs

アプリケーションにバインドするXSUAAサービスインスタンスは通常、タイプapplicationで作成しますが、ここではapiaccessで作成するのがポイントです。

cf create-service xsuaa apiaccess <access_name>

以下のコマンドでサービスキーを作成します。

cf create-service-key <access_name> <key_name>

サービスキーを作ったら、以下のコマンドでサービスキーを表示させます。

cf service-key <access_name> <key_name>

1.2. Destinationを登録

以下の内容でDestinationを登録します。

項目 設定値
Name 任意
Type HTTP
Description 任意
URL サービスキーのapiurl
Proxy Type Internet
Authentication OAuth2ClientCredentials
Client ID サービスキーのclientid
Client Secret サービスキーのclientsecret
Token Service URL サービスキーのurl + /oauth/token

image.png

2. CAPサービスの実装

簡単にするために、ここではUsersテーブルにデータを格納する部分に絞って説明します。
全体のコードは以下のGitリポジトリに格納しています。
https://github.com/miyasuta/cap-user-roles

2.1. プロジェクトを登録

以下のコマンドでCAPのプロジェクトを登録します。

cds init user-roles

2.1. db/schema.cds

以下がスキーマの定義です。

using {managed} from '@sap/cds/common';

namespace miyasuta.users;

entity Users : managed {
    key id         : UUID;
        externalId : String;
        userName   : String;
        familyName : String;
        givenName  : String;
}

2.2. srv/user-service.cds

以下がサービスの定義です。loadUsersというアクションで、ユーザ情報をAPIで取得してテーブルに格納します。

using { miyasuta.users as db } from '../db/schema';

service UserService {
    entity Users as projection on db.Users
    action loadUsers() returns String;
}

2.3. srv/user-service.js

アクションのイベントハンドラを登録します。BTPのサブアカウントに登録したDestinationを使いたいので、SAP Cloud SDKのGeneric HTTP Clientを使うのがポイントです。
参考:Use CloudSDK for http request in NodeJS on SCP CF

const core = require('@sap-cloud-sdk/core')

module.exports = async function () {
    this.on('loadUsers', async(req) => {
        const { Users } = cds.entities
        const response = await core.executeHttpRequest({ destinationName: '<1.2.で登録したDestination>'},{
            method: 'GET',
            url: '/Users'
        })

        const users = response.data.resources.map((user)=> {
            return {
                id: user.id,
                externalId: user.externalId,
                userName: user.userName,
                familyName: user.name.familyName,
                givenName: user.name.givenName
            }
        })

        if (users.length > 0) {
            await DELETE.from(Users)
            await INSERT (users) .into (Users)
            return `${users.length} records have been inserted.`
        }
        else {
            req.error({
                message: 'User data could not be fetched!'
            })
        }       
    })
}

2.4. mta.yaml

mta.yamlの設定は以下のようになります。user-roles-srvに、destinationとxsuaaのサービスインスタンスをバインドしています。

## Generated mta.yaml based on template version 0.4.0
## appName = user-roles
## language=nodejs; multitenant=false
## approuter=
_schema-version: '3.1'
ID: user-roles
version: 1.0.0
description: "A simple CAP project."
parameters:
  enable-parallel-deployments: true

build-parameters:
  before-all:
   - builder: custom
     commands:
      - npm install --production
      - npx -p @sap/cds-dk cds build --production

modules:
 # --------------------- SERVER MODULE ------------------------
 - name: user-roles-srv
 # ------------------------------------------------------------
   type: nodejs
   path: gen/srv
   parameters:
     buildpack: nodejs_buildpack
   requires:
    # Resources extracted from CAP configuration
    - name: user-roles-db
    - name: user-roles_destination
    - name: user-roles_uaa
   provides:
    - name: srv-api      # required by consumers of CAP services (e.g. approuter)
      properties:
        srv-url: ${default-url}

 # -------------------- SIDECAR MODULE ------------------------
 - name: user-roles-db-deployer
 # ------------------------------------------------------------
   type: hdb
   path: gen/db  
   parameters:
     buildpack: nodejs_buildpack
   requires:
    # 'hana' and 'xsuaa' resources extracted from CAP configuration
    - name: user-roles-db


resources:
 # services extracted from CAP configuration
 # 'service-plan' can be configured via 'cds.requires.<name>.vcap.plan'
# ------------------------------------------------------------
 - name: user-roles-db
# ------------------------------------------------------------
   type: com.sap.xs.hdi-container
   parameters:
     service: hanatrial  # or 'hanatrial' on trial landscapes
     service-plan: hdi-shared
   properties:
     hdi-service-name: ${service-name}

 - name: user-roles_destination
   type: org.cloudfoundry.managed-service
   parameters:
     service-plan: lite
     service: destination
     config:
       HTML5Runtime_enabled: true
       version: 1.0.0     

 - name: user-roles_uaa
   type: org.cloudfoundry.managed-service
   parameters:
     path: ./xs-security.json
     service-plan: application
     service: xsuaa  

xs-security.jsonは以下のようになっています。

{
  "xsappname": "user-roles",
  "tenant-mode": "dedicated",
  "scopes": [],
  "attributes": [],
  "role-templates": []
}

2.5. デプロイ

ビルド、デプロイして、BTPに登録されたURLを実行してみます。

mbt build
cf deploy mta_archives/user-roles_1.0.0.mtar

アクションを実行するにはPOSTリクエストを投げる必要があるので、VS CodeのREST Clientを使ってテストします。任意の場所にtest.httpというファイルを作って以下のリクエストを送信してみます。

test.http
POST https://<route>.cfapps.eu10.hana.ondemand.com/user/loadUsers
Content-Type: application/json

以下のレスポンスが返ってくれば成功です。

{
  "@odata.context": "$metadata#Edm.String",
  "value": "3 records have been inserted."
}

/user/Usersを開くと、以下のようにユーザデータが表示されます。
image.png

2.6. ローカルでテスト

以下は、ローカル実行でもDestinationにアクセスできるようにするための設定です。
まず、設定なしにlocalhostに向けて以下のリクエストを実行してみます。

POST http://localhost:4004/user/loadUsers
Content-Type: application/json

すると、以下のエラーになります。

{
  "error": {
    "code": "500",
    "message": "Failed to load destination."
  }
}

設定
デプロイしたサービスの環境変数から、VCAP_SERVICESの部分をコピーしてプロジェクト直下のdefault-env.jsonに貼り付けます。
image.png
以下は"hanatrial", "destination", "xsuaa"の各セクションを折りたたんだ状態です。
image.png
このあと、先ほどと同じリクエストを実行してみましょう。以下のような結果が返ってくれば成功です。

{
  "@odata.context": "$metadata#Edm.String",
  "value": "3 records have been inserted."
}

3. Job Scheduling ServiceからCAPのサービスを呼ぶ

ここでは、Job Scheduling Serviceのトライアル版であるliteプランを使います。

3.1. サービスインスタンスの作成

以下のコマンドでJob Scheduling Serviceのインスタンスを登録します。
※以下の-c以降の指定方法は、Windowsの場合です。

cf create-service jobscheduler lite job-scheduler -c '{\"enable-xsuaa-support\": true}'

※はじめはBTPのコックピットから登録しようとしたのですが、以下のエラーが出たためCLIで登録しました。
image.png

3.2. CAPのサービスにJob Scheduling Serviceのインスタンスをバインド

CAPのmta.yamlを以下のように変更します。

  • resourcesセクションにjob-schedulerを追加
resources:
#...
 - name: job-scheduler
   type: org.cloudfoundry.existing-service
  • user-roles-srvjob-schedulerをバインド
modules:
 # --------------------- SERVER MODULE ------------------------
 - name: user-roles-srv
 # ------------------------------------------------------------
   type: nodejs
   path: gen/srv
   parameters:
     buildpack: nodejs_buildpack
   requires:
    # Resources extracted from CAP configuration
    - name: user-roles-db
    - name: user-roles_destination
    - name: user-roles_uaa
    - name: job-scheduler #<--追加
   provides:
    - name: srv-api      # required by consumers of CAP services (e.g. approuter)
      properties:
        srv-url: ${default-url}

以上の設定をしたら、CAPプロジェクトを再度ビルド、デプロイします。

3.3. ジョブの設定

Job Scheduling Serviceのインスタンスからダッシュボードを開きます。
image.png

  • ジョブの登録 image.png
項目 設定値
Name 任意
Description 任意
Target Application user-role-srv ※バインドしたアプリケーションがドロップダウンで選べる
Action user-role-srvのURL
HTTP Method POST
Activate Job YES

image.png

  • スケジュールの登録

登録したジョブをクリックして中に入ります。
image.png
スケジュールを登録します。
image.png
スケジュールのパターンは以下が選べます。今回はOne Timeを選択します。その他のパターンについてはドキュメントをご参照ください。
image.png
以下のようにスケジュールを指定しました。Valueにはnow10 hours from nowなど、英語の表現を指定できます。Dataには、POSTリクエストで渡すデータを指定します。今回、CAPのアクションはパラメータを必要としませんが、ヘッダのContent-Typeは必須なので、ヘッダをつけるために空のデータ{}を渡します。
image.png
nowを指定したので、スケジュールを保存するとすぐに実行されます。"Run Logs"のメニューから実行結果を確認できます。
image.png
image.png

※時刻を指定する場合、UTCで指定する必要があります。以下は日本時間の16:30に毎日実行したい場合の指定方法です。
image.png

(補足)認証が必要なアプリケーションにアクセスする場合

Job Scheduling Serviceから認証で守られたアプリケーションを呼ぶためには、アプリケーションにバインドされたXSUAAの設定で、以下のようにJob Scheduling Serviceからの呼び出しを許可する必要があります。

{
    "xsappname": "<app name>",
    "scopes": [{
        "name": "$XSAPPNAME.Jobs",
        "description": "SAP Job Scheduling
                                service Scope",
        "grant-as-authority-to-apps": [
            "$XSSERVICENAME(<jobscheduler instance name>)"
            ]
    }]
}

参考:Secure Acces

トライアル版で上記の設定を試したところ、job-scheduler(Job Scheduling Serviceのサービスインスタンス)に割り当てられたxsuaaサービスが見つからないというエラーになりました。トライアル版ではOAuth2がサポートされていないので、この設定はできないのかもしれません。
image.png

まとめ

このブログでは、「ログインしていないユーザの権限をチェック」するため、以下の仕組みを実装しました。

  • Authorization and Trust Management ServiceのAPIを使い、BTPのサブアカウントに登録されているユーザの一覧、ロールコレクション、およびロールを取得する
  • 上記の処理を、Job Scheduling Serviceを使用して日次のジョブで実行する

参考

Authorization and Trust Management Serviceについて

SAP Cloud SDKのGeneric HTTP Clientについて

Job Scheduling Serviceについて

2
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
2
0