からめもぶろぐ。

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

Microsoft Graph Toolkit の Teams MSAL2 Provider について

Microsoft Graph Toolkit にはいくつかのプロバイダーがあって SharePoint や Microsoft Teams などのいくつかのシナリオでは最適化されているのですが、Microsoft Teams 用のプロバイダーはシングル サインオンに対応しておらず正直イケていませんでした。ところが最近になって Teams MSAL2 Provider というのができてシングル サインオンにも対応したらしいです。ただし肝心のトークン交換のところは「サンプル コードを見てね」と書いてあるのにリンク先には一切の記載がないという。*1 仕方ないのでコードを見てみます。

github.com

トークン交換のところを抜き出すとこんな感じです。

response = await fetch(url.href, {
  method: 'POST',
  headers: {
    Content-Type': 'application/json',
    authorization: `Bearer ${clientToken}`
  },
  body: JSON.stringify({
    scopes: scopes,
    clientid: this.clientId
  }),
  mode: 'cors',
  cache: 'default'
});

const data = await response.json().catch(this.unhandledFetchError);

...

return data.access_token;

うーん、なんか独自の実装になっていて TeamsFx の SimpleAuth と連携しているわけではなさそうです。

github.com

こういうところはちゃんとやってほしいなあと思っていたら Issue と Pull Request が上がっていました。next/teamsfx ブランチに入っているので近い将来リリースされると思われます。

github.com

これはかなり便利になりそうですね!

*1:よくあることですね

Power Automate の承認ワークフローを API から取得する

Power Automate の承認ワークフローは便利な機能ですが、残念ながら「自分にきた承認の一覧を確認する」というのが Power Automate のサイト (https://flow.microsoft.com) からしか行うことができません。例えば SharePoint などからも見られるようになると利便性が向上すると考える方もいるかと思います。Power Automate の承認ワークフローのデータは Dataverse に保存されるので、Power Apps、Power Automate、Dynamics 365 などのライセンスを持っているユーザーであれば、REST API を使ってデータを取ることができます。しかし、Office 365 や Microsoft 365 のライセンスのユーザーは REST API を使うことができません。

とはいえ、Office 365 や Microsoft 365 のユーザーでも Power Automate のサイトからであれば、承認ワークフローのデータを見ることができるので、なんとかできないかと頑張ってみました。この方法は docs がないため現時点では公式に推奨されている方法ではありません。試す場合は自己責任でお願いします。

調べてみる

なにはともあれ F12 開発者ツールで Power Automate のサイトから呼んでいる API を覗いてみるとこんな感じのエンドポイントを叩いているのがわかります。

https://api.flow.microsoft.com/providers/Microsoft.ProcessSimple/environments/{{envname}}/approvalViews?api-version=2016-11-01&$filter=properties%2FuserRole%20eq%20%27Approver%27

Authorization ヘッダーの JWT をデコードしてみます。aud (Audience) が https://service.flow.microsoft.com/ になっていますね。

{
  "aud": "https://service.flow.microsoft.com/",
  "iss": "https://sts.windows.net/a380c832-56bb-4e90-836c-b45ca50c74a1/",
  ...
}

Azure AD PowerShell で該当する Azure AD アプリケーションを特定します。アプリケーション (クライアント) ID が「7df0a125-d3be-4c96-aa54-591f83ff541c」であることがわかります。*1

PS> Get-AzureADServicePrincipal -All $true | ?{ $_.ServicePrincipalNames -contains "https://service.flow.microsoft.com/" }

ObjectId                             AppId                                DisplayName
--------                             -----                                -----------
9faf35a1-cbbf-43a9-a6a2-c65918318858 7df0a125-d3be-4c96-aa54-591f83ff541c Microsoft Flow Service

続いてアクセス許可の一覧を抜き出します。Approvals.Read.All というアクセス許可がなんかそれっぽい気がしますね。

AdminConsentDescription : Allow applications to write plans
AdminConsentDisplayName : Allow applications to write plans
Id                      : 27c61d54-eb42-4315-8793-6e5715a19746
IsEnabled               : True
Type                    : User
UserConsentDescription  : Allow applications to write plans
UserConsentDisplayName  : Allow applications to write plans
Value                   : Flows.Write.Plans

AdminConsentDescription : Allow the application read only permission for flow
AdminConsentDisplayName : Allow the application read only permission for flow
Id                      : 0b575251-2c95-46df-bc75-c8c43e6b7116
IsEnabled               : True
Type                    : User
UserConsentDescription  : Allow the application read only permission for flow
UserConsentDisplayName  : Allow the application read only permission for flow
Value                   : Flows.Read.Plans

AdminConsentDescription : Allow the application to read flows
AdminConsentDisplayName : Allow the application to read flows
Id                      : e45c5562-459d-4d1b-8148-83eb1b6dcf83
IsEnabled               : True
Type                    : User
UserConsentDescription  : Allow the application to read flows
UserConsentDisplayName  : Allow the application to read flows
Value                   : Flows.Read.All

AdminConsentDescription : Allow the application to create and edit flows
AdminConsentDisplayName : Allow the application to manage flows
Id                      : 30b2d850-00c3-4802-b7ae-ece9af9de5c6
IsEnabled               : True
Type                    : User
UserConsentDescription  : Allow the application to create and edit flows
UserConsentDisplayName  : Allow the application to manage flows
Value                   : Flows.Manage.All

AdminConsentDescription : Allow the application to read activities
AdminConsentDisplayName : Allow the application to read activities
Id                      : 822a9cde-503a-472d-a530-d1dc9cd0d52b
IsEnabled               : True
Type                    : User
UserConsentDescription  : Allow the application to read activities
UserConsentDisplayName  : Allow the application to read activities
Value                   : Activity.Read.All

AdminConsentDescription : Allow the application to read approvals
AdminConsentDisplayName : Allow the application to read approvals
Id                      : e9cbd5de-0031-493e-8fb1-60712d03cc8b
IsEnabled               : True
Type                    : User
UserConsentDescription  : Allow the application to read approvals
UserConsentDisplayName  : Allow the application to read approvals
Value                   : Approvals.Read.All

AdminConsentDescription : Allow the application to manage approvals
AdminConsentDisplayName : Allow the application to manage approvals
Id                      : d05743e4-fb24-4dff-8a33-0f6c73c964bd
IsEnabled               : True
Type                    : User
UserConsentDescription  : Allow the application to manage approvals
UserConsentDisplayName  : Allow the application to manage approvals
Value                   : Approvals.Manage.All

AdminConsentDescription : Access Microsoft Flow as signed in user
AdminConsentDisplayName : Access Microsoft Flow as signed in user
Id                      : 44d8c55c-9ffc-4546-883e-9041b5bb0b01
IsEnabled               : True
Type                    : Admin
UserConsentDescription  : Allow access to all features in Microsoft Flow
UserConsentDisplayName  : Access Microsoft Flow as signed in user
Value                   : User

試してみる

Azure ポータルで Azure AD に新しいアプリケーションを作成します。[API のアクセス許可] を見ると「Flow Service」というのがあります。

f:id:karamem0:20211117104741p:plain

「Approvals.Read.All」を選択します。

f:id:karamem0:20211117104753p:plain

追加できました。これで API が呼べるようになります。

f:id:karamem0:20211117104903p:plain

試しに Postman を使って API を呼んでみます。200 OK が返ってきます。

f:id:karamem0:20211117104915p:plain

レスポンスの JSON はこんな感じ。ちゃんと取れてますね。

{
    "value": [
        {
            "name": "026092a1-3e66-48a3-bc89-60b80d187f7d",
            "id": "/providers/Microsoft.ProcessSimple/environments/Default-.../approvalViews/026092a1-3e66-48a3-bc89-60b80d187f7d",
            "type": "/providers/Microsoft.ProcessSimple/environments/approvalViews",
            "properties": {
                "type": "Basic",
                "isActive": false,
                "userRoles": [
                    "Owner",
                    "Approver"
                ],
                "owner": {
                    "id": "8f7f93f0-...",
                    "type": "User",
                    "tenantId": "a380c832-..."
                },
                "title": "承認リクエスト from Takashi Shinohara",
                "result": "Approve",
                "allowCancel": true,
                "creationDate": "2021-11-11T23:20:52Z",
                "dueDate": "9999-12-31T23:59:59Z",
                "expirationDate": "9999-12-31T23:59:59Z",
                "completionDate": "2021-11-11T23:37:31Z",
                "approvers": [
                    "8f7f93f0-..."
                ],
                "principals": [
                    {
                        "id": "8f7f93f0-...",
                        "displayName": "Takashi Shinohara",
                        "email": "takashi.shinohara@example.onmicrosoft.com",
                        "type": "User",
                        "tenantId": "a380c832-...",
                        "userPrincipalName": "takashi.shinohara@example.onmicrosoft.com"
                    }
                ],
                "priority": "Medium",
                "requestType": "Basic"
            }
        }
    ]
}

まとめ

「Approvals.Manage.All」を使えば API からの承認や却下もできそうです。繰り返しますが docs では公開されていない機能のようなので自己責任でお願いしたいですが、ちゃんと公開されたらとても便利そうですね。

*1:オブジェクト ID はテナントごとに異なります。

Fluent UI の Menu をテストすると FocusZone が謎のエラーを発生させる

事象

こんな感じのコードがあったとします。

import React from 'react';
import { Menu } from '@fluentui/react-northstar';
import './App.css';

const App: React.FC = () => {

  return (
    <div>
      <Menu
        items={[
          {
            key: 'hop',
            content: 'Hop'
          },
          {
            key: 'step',
            content: 'Step'
          },
          {
            key: 'jump',
            content: 'Jump'
          }
        ]}
      />
    </div>
  );
}

export default App;

これに対してスナップショット テストを実行してみます。

import React from 'react';
import App from './App';
import renderer from 'react-test-renderer';

describe('App', () => {

  it('renders correctly', () => {
    const tree = renderer
      .create(<App />)
      .toJSON();
    expect(tree).toMatchSnapshot();
  });

});

コードは Jest のドキュメントを参考にしています。

jestjs.io

ところがこれを実行するとエラーになります。

console.error
  The above error occurred in the <FocusZone> component:
      in FocusZone (created by Menu)
      in Menu (created by App)
      in div (created by App)
      in App

  Consider adding an error boundary to your tree to customize error handling behavior.
  Visit https://fb.me/react-error-boundaries to learn more about error boundaries.

    at logCapturedError (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:10141:21)
    at logError (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:10178:5)
    at update.callback (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:11288:5)
    at callCallback (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:3259:12)
    at commitUpdateQueue (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:3280:9)
    at commitLifeCycles (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:10497:11)
    at commitLayoutEffects (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:13295:7)

回避策

react-test-renderer じゃなくて Testing Library を使いましょう。

import React from 'react';
import App from './App';
import { render } from '@testing-library/react';

describe('App', () => {

  it('renders correctly', () => {
    const { container } = render(<App />);
    expect(container).toMatchSnapshot();
  });

});

Teamtile 1.7.1 を公開しました

Teamtile 1.7.1 を公開しました。Teamtile は自分が参加しているチームの一覧をタイル状に表示する Microsoft Teams のアプリです。

github.com

1.7.0 の機能変更については以下の通りです。

  • /beta/teams/{id}/photo/$value を使っていたのを /v1.0/groups/{id}/photo/$value に変更しました。
  • チーム、メンバー、チャネルに対してフィルターを追加しました。
  • アプリのアイコンを追加しました。

1.7.1 の機能変更については以下の通りです。

  • 管理者の同意に対するバグを修正しました。
  • node.js の v16 に変更しました。
  • Visual Studio Code の設定を変更しました。

SharePoint Framework Community Call で react-star-ratings を紹介していただきました

PnP のサンプル リポジトリに Pull Request を投げつけておいたのですが、SharePoint Framework Community Call で紹介されていました。レコーディングを見たところ日本語で「subarashiidesu!」と言っていただいてびっくりしました。

techcommunity.microsoft.com

肝心の Pull Request についてはまだマージされていないのですが、簡単にご紹介だけ。

SharePoint News Star Ratings

簡単にいうと SharePoint ニュースで星評価がつけられる Web パーツです。SharePoint ニュースは標準で「いいね!」をつけることはできるのですが、星評価を行うことはできません。この Web パーツを使うことで、表示されている SharePoint ニュースに対して、星評価をつけることができます。前提として、サイトに対して評価の設定を有効にしておく必要があります。

blog.karamem0.dev