Power BI カスタムコネクター開発 : Microsoft Graph コネクターを開発する

[前回の記事では](https://qiita.com/kenakamu/items/c32b46c985040a703a8c) M スクリプトからカスタムコネクターを作成する手順を紹介しました。利用したサンプルは匿名認証でしたが、普段使うサービスはおそらく何かしらの認証があるため、今回は OAuth 認証の方法を見ていきます。

尚、OAuth の詳細は説明しないため、気になる人は

- [Microsoft Graph を使ってみよう : Azure AD における認証概要](https://qiita.com/kenakamu/items/3ea0a8d149eb44a9b08b) 
- [Microsoft ID プラットフォームと OAuth 2.0 認証コード フロー](https://docs.microsoft.com/ja-jp/azure/active-directory/develop/v2-oauth2-auth-code-flow)

などを参照してください。


今回のコードはは [こちら](https://github.com/microsoft/DataConnectors/tree/master/samples/MyGraph) のコードをベースにしていますが、説明をシンプルにするため多少変更しています。

# Microsoft Graph

Microsoft 365 (旧 Office 365 含む) サービスである Exchange オンラインや SharePoint オンラインまたは Microsoft Teams 等を使っている場合、これらのデータの解析に興味があるでしょう。Microsoft Graph はこれらすべてのサービスにアクセスできるエンドポイントを提供します。
![](https://docs.microsoft.com/en-us/graph/images/microsoft-graph-dataconnect-connectors-rebrand-800.png)

実際取得できるデータをすぐ見たい場合は [Graph Explorer](https://developer.microsoft.com/ja-jp/graph/graph-explorer) を試してください。

# カスタムコネクタ用 Azure AD 認証の設定

認証を実施するには、以下 2 つのステップが必要です。

- Azure AD にアプリケーションを登録する
- 登録したアプリケーションの情報をカスタムコネクタで利用する

つまり Azure AD に対してアプリケーションを作成する権限が必要ですが、権限がない場合、トライアル環境などを利用してみてください。Microsoft E3/E5 トライアル版であれば検証ができます。

# アプリ―ションの登録

1\. [管理ポータル](https://portal.office.com) より、管理センターにある **Azure Active Directory** をクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/e9b1855f-e3fe-2717-b7c8-52c4a5206976.png)

2\. Azure Active Directory | アプリの登録 より「新規登録」をクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/a1023223-73fd-f5ac-7689-69354feb5b5e.png)

3\. 任意のアプリケーション名を指定。サポートされているアカウントの種類は既定のまま。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/df23e290-39bd-bf4a-e92a-99081effa1e1.png)

4\. リダイレクト URI では「パブリック クライアント/ネイティブ」を選択し、https://oauth.powerbi.com/views/oauthredirect.html をアドレスに入力。「登録」をクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/315837b6-dfb5-4edc-6f8b-6d2e82a575c4.png)

5\. アプリケーションの画面に遷移したら「API のアクセス許可」より「アクセス許可の追加」をクリック。　
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/011e1c38-b87b-0b9e-9475-4f217d5183a8.png)

6\. コネクターで利用したい権限を選択。ここでは「Microsoft Graph」を選択。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/f3943c61-adcb-64fb-8082-7525ed9716ca.png)

7\. Microsoft Graph ユーザーが使う「委任されたアクセス許可」とバックエンドプロセスなどで使う「アプリケーションの許可」がある。ここでは「委任されたアクセス許可」をクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/5eff94c0-2433-3929-7c2b-5b51c91e0a82.png)

8\. サンプルは多くの権限を使っているが、ここでは以下の権限を指定。 

- User.Read (既定で選択されている)
- Calendars.Read
- People.Read
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/62da0f60-90b1-e023-c15b-5c029acd0ccb.png)

9\. 「概要」に戻り、アプリケーション ID を保存。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/3fed6436-37bb-0371-0cbc-dcad4ed87596.png)

# カスタムコネクターの開発

既にあるコードをそのまま使ってもいいのですが、今回は新規にプロジェクトを作っていきます。

## プロジェクトの作成

[前回の記事](https://qiita.com/kenakamu/items/c32b46c985040a703a8c) の手順と同様にプロジェクトを作成。名前は **MyGraphConnector** とした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/409710de-f36a-37bb-fbcc-11e5d9c7eaf7.png)

## DataSource Kind の更新

DataSource.Kind では認証の設定を行います。既定では匿名認証 (Implicit) だけです。また名前は既定でプロジェクトと同じです。

```fsharp
// Data Source Kind description
MyGraphConnector = [
    Authentication = [
        // Key = [],
        // UsernamePassword = [],
        // Windows = [],
        Implicit = []
    ],
    Label = Extension.LoadString("DataSourceLabel")
];
```
今回は以下のように OAuth 要素だけにします。また OAuth 要素に StartLogin と FinishLogin
FinishLogin を指定しています。OAuth で使えるプロパティは [Handling Authentication](https://docs.microsoft.com/en-us/power-query/handlingauthentication) を参照してください。

```fsharp
MyGraphConnector = [
    Authentication = [
        // Key = [],
        // UsernamePassword = [],
        // Windows = [],
        // Implicit = [],
        OAuth = [
            StartLogin=StartLogin,
            FinishLogin=FinishLogin
        ]
    ],
    Label = Extension.LoadString("DataSourceLabel")
];
```

- StartLogin: ユーザーがログインを始めた際に実行される
- FinishLogin: ログイン後に呼ばれる。OAuth フローではアクセストークンの取得が行われる

この時点では関連する関数がないため、赤線が表示されています。

### StartLogin の追加

以下は [サンプルコード](https://github.com/microsoft/DataConnectors/tree/master/samples/MyGraph) にある StartLogin を少し変更したものです。任意の場所に張り付けます。

また client_id は先ほど作成したアプリケーション ID を指定します。

```fsharp
client_id = "c83bce1f-cdce-41c2-a07d-9f4c78a074be";

StartLogin = (resourceUrl, state, display) =>
    let
        authorizeUrl = "https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize?" & Uri.BuildQueryString([
            client_id = client_id,  
            redirect_uri = "https://oauth.powerbi.com/views/oauthredirect.html",
            state = state,
            scope = "offline_access User.Read Calendars.Read People.Read",
            response_type = "code",
            response_mode = "query",
            login = "login"
        ])
    in
        [
            LoginUri = authorizeUrl,
            CallbackUri = "https://oauth.powerbi.com/views/oauthredirect.html",
            WindowHeight = 720,
            WindowWidth = 1024,
            Context = null
        ];
```
開発者の方は上記コードに出てくるアドレスや scope などに馴染みがあると思います。認証に詳しくない場合も、上記コードは「認証に必要なエンドポイント」に対してやりたい操作 (scope) を指定して認証を行っているという程度の認識で大丈夫です。

### FinishLogin の追加

StartLogin が成功すると FinishLogin が実行されます。StartLogin ではアクセストークン取得に必要なコードを得たので、 FinishLogin で実際のアクセストークンを取得します。

```fsharp
FinishLogin = (context, callbackUri, state) =>
    let
        parts = Uri.Parts(callbackUri)[Query],
        result = if (Record.HasFields(parts, {"error", "error_description"})) then 
                    error Error.Record(parts[error], parts[error_description], parts)
                 else
                    TokenMethod("authorization_code", "code", parts[code])
    in
        result;

TokenMethod = (grantType, tokenField, code) =>
    let
        queryString = [
            client_id = client_id,
            scope = "offline_access User.Read Calendars.Read People.Read",
            grant_type = grantType,
            redirect_uri = "https://oauth.powerbi.com/views/oauthredirect.html"
        ],
        queryWithCode = Record.AddField(queryString, tokenField, code),

        tokenResponse = Web.Contents("https://login.microsoftonline.com/organizations/oauth2/v2.0/token", [
            Content = Text.ToBinary(Uri.BuildQueryString(queryWithCode)),
            Headers = [
                #"Content-type" = "application/x-www-form-urlencoded",
                #"Accept" = "application/json"
            ],
            ManualStatusHandling = {400} 
        ]),
        body = Json.Document(tokenResponse),
        result = if (Record.HasFields(body, {"error", "error_description"})) then 
                    error Error.Record(body[error], body[error_description], body)
                 else
                    body
    in
        result;
```

少しコードは長いものの、取得した code を使ってアクセストークンを取得しているとだけ理解すれば大丈夫です。

## shared 関数の更新

今回は /me を取得して返します。既存の share 関数を以下に書き換えます。

```fsharp
[DataSource.Kind="MyGraphConnector", Publish="MyGraphConnector.Publish"]
shared MyGraphConnector.Contents = () =>
    let
        source = OData.Feed("https://graph.microsoft.com/v1.0/me/", null, [ ODataVersion = 4, MoreColumns = true ])
    in
        source;
```

## コネクターのテスト

F5 キーを押下してテストを実行します。まず認証が聞かれるため OAuth2 を選択してログインします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/02666a97-94b2-5841-fe4f-8d4c367700c6.png)

委任のプロンプトが出るので「Accept」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/bed86051-580d-69fc-572c-aee7dbb301c6.png)

ログインが成功するとアクセストークンが表示されるため、画面一番下にある「Set Credential」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/780f4e96-43b9-0bf9-52e4-4f78b0826171.png)

再度 F5 キーを押下すると結果が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/5e4bfbce-7051-c2a1-1fdb-7d9287604903.png)

# Power BI デスクトップ で使う

[前回記事の展開セクション](https://qiita.com/kenakamu/items/c32b46c985040a703a8c#%E5%B1%95%E9%96%8B) を参照して Power BI デスクトップにカスタムコネクターを展開します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/4cee70da-8db6-e8ea-81f6-123e64e21657.png)

Calendars.Read 権限があるため取得されたデータを展開すると予定が確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/93c7ef3c-29c5-db3d-68e6-87a26e88366f.png)

# まとめ

この記事では OAuth 認証の使い方と Microsoft Graph を使ったデータ取得を見ていきました。実際にはトークンのリフレッシュやログアウトを含めもう少しコードがあった方がいいのですが、詳細はサンプルを参照してください。