からめもぶろぐ。

俺たちは雰囲気で OAuth をやっている

SharePoint Framework で Common Data Service のデータを読み取る

SharePoint Framework では AadHttpClient を使うことで Azure Active Directory で保護された API に対してのアクセスを OAuth の設定を行うことなく簡単に実装することができます。ということは Common Data Service (Dynamics 365) のデータにもアクセスできるはずなので試してみたいと思います。

事前準備

通常、SahrePoint Framework に対して Web API へのアクセス許可を与えるためには、package-solution.json に webApiPermissionRequests を記述する必要があります。この記述にしたがって、SharePoint 管理センターで API のアクセス許可を承認することができるようになるのですが、Common Data Service の場合、次のように記述しても実はうまくいきません。

{
  "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
  "solution": {
    "name": "MyApplication",
    "id": "ad9139c7-12db-470b-937d-54967f418959",
    "version": "1.0.0.0",
    "includeClientSideAssets": true,
    "isDomainIsolated": false,
    "webApiPermissionRequests": [
      {
        "resource": "Common Data Service",
        "scope": "user_impersonation"
      }
    ]
  },
  "paths": {
    "zippedPackage": "solution/sharepoint-framework-read-dynamics.sppkg"
  }
}

これは、Common Data Service という名前のサービス プリンシパルが複数あり、表示名で紐づけしようとすると別のサービス プリンシパルに結びついてしまうことが原因のようです。SharePoint Framework の問題なのですが、公式でも表示名を指定するように注釈があるため、なんらかの原因があると考えています。

resource プロパティの値には、アクセス許可を要求するアプリケーションの displayName を指定する必要があります。 リソースの指定に objectId を使用すると、アクセス許可の要求を承認しようとしたときにエラーが発生します。

docs.microsoft.com

ということで、Common Data Service へのアクセス許可を与えるために、Azure ポータルで操作することになります。SharePoint Framework は「SharePoint Online Client Extensibility Web Application Principal」というアプリケーションに対してアクセス許可を与えています。直接このアプリケーションを編集することで SharePoint 管理ポータルでの承認と同じことを行うことができます。

f:id:karamem0:20200420140324p:plain

SharePoint 管理ポータルから見たときも承認された要求として表示されています。ただし SharePoint Framework のアプリ名などのメタ情報は表示されないみたいですね。

f:id:karamem0:20200420140643p:plain

ともあれ、これで Common Data Service に接続できるようになりました。

サンプル コード

github.com

src/webparts/MyApplication/MyApplicationWebPart.ts

お約束として context を渡しておくのを忘れないようにします。endpoint は Dynamics 365 の URL を表します。

export default class MyApplicationWebPart extends BaseClientSideWebPart<IMyApplicationWebPartProps> {

  public render(): void {
    const element: React.ReactElement<IMyApplicationProps> = React.createElement(
      MyApplication,
      {
        context: this.context,
        endpoint: this.properties.endpoint
      }
    );

    ReactDom.render(element, this.domElement);
  }

}

src/webparts/MyApplication/components/MyApplication.tsx

componentDidMount メソッドで AadHttpClient を使って Common Data Service Web API への要求を行います。今回は標準のエンティティのデータを取得しますが、カスタム エンティティについても同様に取得が可能です。

export default class MyApplication extends React.Component<IMyApplicationProps, IMyApplicationState> {

  public componentDidMount(): void {
    if (this.props.endpoint == null) {
      return;
    }
    this.props.context.aadHttpClientFactory
      .getClient(this.props.endpoint)
      .then((client: AadHttpClient) => {
        client
          .get(this.props.endpoint + "/api/data/v9.0/accounts", AadHttpClient.configurations.v1)
          .then((response: HttpClientResponse) => response.json())
          .then((response: any) => {
            this.setState({ accounts: response.value as Array<IAccount> });
          });
      });
  }

}

実行結果

実行すると以下のようにエンティティのデータが取得できていることを確認できます。なお、Common Data Service のライセンス (Power Apps や Dynamics 365 Sales など) がないユーザーの場合は表示できませんのでご注意ください。

f:id:karamem0:20200420143134p:plain

まとめ

直接 Azure Active Directory のアプリケーションを操作するというのがいささかトリッキーではあるのですが、使えるテクニックなのでぜひお試しいただければと思います。