FormsとPower AutomateでEntra IDのゲスト招待システム&棚卸システムをつくってみる(その5)

2025/05/01

ゲスト招待システム

ローコード・ノーコードで作るゲスト招待システム。5回目です。前回はEntra IDにゲストを招待する基幹部分を実装しました。今回は、追加にエラーが出た場合などの後処理と、成功したことをSharePointリストに追加するような管理系の処理。それにくわえて申請を行ったユーザーに対する通知などを組み込んでいきます。

前回までのおさらい

ここまでの道のりです。少しずつ開発をすすめていますので、必要なところだけつまみ食いしてもらうのも良いかと思います。

  1. その1)Formsでユーザーがゲスト招待申請を投稿する。
  2. その1)クラウドフその1,その2ローがSPOリストの追加によってトリガーされる。
  3. その2)入力されたゲストEmailがメールアドレスとして妥当か(正規表現が使える?)確認して、受付または却下メールを申請者へ送る(却下ならステータスを「却下」にして、備考に理由を書き込む。妥当ならステータスを「承認待ち」にして申請日を書き込む)。
  4. その2)承認者ユーザーに承認要求を送る。
  5. その3)承認が通ったら承認日時に書き込み、ステータスを「承認済み」に更新して次へ進む。
  6. その3)却下の場合は、承認者のコメントを含めたメールを申請者に送る。
  7. その3)14日間承認されない場合は、ステータスを「承認待ち14日期限切れ」に変えて申請者へメールを送る。
  8. その3)各ステータスをSPOリストに書き込む。
  9. その4)サービスプリンシパルを使ってHTTP要求で対象の外部ユーザーをEntra IDのゲストとして登録する。
  10. (今回)登録が成功したら、SPOリストの棚卸期限列に、ゲスト招待期間の数字を加えた日付を登録する。
  11. (今回)すでにゲストがテナントに登録済みならば、申請者へ伝える。
  12. (今回)登録が成功したら、承認者と申請者にメールで通知する。
  13. 登録が失敗したら、管理者へ通知する(チャネル投稿)→Teamsチャネルを見て、エラーを確認し、手動でゲスト登録をしてSPOリストを書き換え、チャネルに行った対応を投稿する運用で回避。SPOリストにTeamsの投稿URLを入力できる列があっても良いかも。

前回はHTTP要求でゲスト追加できたところまで

Entra IDにゲストとして追加するには既存のアクションでは無理で、サービスプリンシパルを作成して権限を与え、HTTP要求のアクションを利用して追加する必要がありました。

今回は処理が成功した場合の処理を加えていきましょう。

テストが簡単になる「静的な結果」を設定する

さて、ここらへんで毎回テスト実行するたびに承認者ユーザーをブラウザで開いて都度承認するのが面倒になってきました。こういう場合には各アクションが決まったJSONを必ず返すように設定することができる「静的な結果」機能を使いましょう。

まずは、前回処理が成功している適当な履歴をクリックします。

前回手動で「ゲストを承認する」を選択した「開始して承認を待機」アクション部分のbody部分に表示されているJSONを全部そのままコピーします。

「静的な結果(プレビュー)」をクリックします。

「静的な結果の無効化(プレビュー)」トグルをONにして、「出力」の項目の右横にある「省略可能なフィールドを選択する」をクリックして、bodyにチェックをいれるとbodyフィールドが現れるので、その中に先程コピーしたJSON文字列を貼り付けます。同じく、ヘッダーも必須項目なので、Content-Typeに application/json と記述しておきましょう。

完了をクリックするとアクションの右上角にフラスコのような黄色いマークが現れました。この設定によって、アクションが呼ばれた際に実際には承認要求を承認者に送信せずに、結果だけ先程貼り付けたJSONを返す動作になります。繰り返しテストするには最適です。

処理の中にメール送信やDBへの書き込みなどがあるようなケースにも、この方法は使えるので覚えておいて損なしです。

招待メールを送信するには?

テストが容易になったので、再度招待してみましたが招待された外部ユーザーのところに何故か招待メールが届きませんでした。HTTP要求の結果を見てみると、以下のようなメッセージが入っていました。
“sendInvitationMessage": false
臭いですね。Trueにした状態でHTTPリクエストに加えてみます。

trueにした状態でHTTP要求の本文に追加してみます。カンマは忘れずに。

ゲストさんの元に招待メールが無事に届きました!

招待されたユーザーが「招待の承諾」をクリックすると、自分の会社のパスワードでゲストとしてサインインできるようになります。下記のようなアクセス要求が表示されます。ゲストは「承諾」して進みます。

招待されたユーザーは、マイアカウントの「組織」の中を除くと、自社以外に「共同作業を行う他の組織」のなかに招待を送ってきたテナントが表示されているのがわかります。これでゲスト招待が成立です。

おまけで監査ログをチェック

ゲスト招待メールを受けて、サインインしたので監査ログを見てみます。開始者(アクター)のところに「ゲスト招待システム用」と表示されているのは、サービスプリンシパルにつけた名前です。招待自体はサービスプリンシパルが行ったことになっているんですね。

送られてきた招待状を受け取ってサインインしたことが一番上のB2B Auth / Redeem external user invite として記録されているのがわかります。

ちなみに、すでにゲスト招待されているユーザーをもう一度招待すると?

すでにあるユーザーをゲスト招待済みの状態で、もういちど同じユーザーをゲストに追加しようとするとどうなるでしょうか?ゲストをEntra IDから消さない状態でもう一度実行してみます。

すると、普通にもう一通招待状を送信してしまいました・・・。これはいけない。

Copilotくんに、ゲストの招待ステータスについて聞いてみました。

先程招待して届いたメールを承諾したユーザーのプロパティ情報を見てみると、招待の状態は「承諾済み」となっていました。これはおそらくAcceptedでしょうね。

いちおう英語表示にして確認しました。間違いない!

プロフィールにはステータスが含まれてない?

テスト用のフローを新たに作って、「ユーザー プロフィールの取得(V2)」でゲスト情報を調べてみましたが、残念ながらここにゲスト招待のステータスは含まれていませんでした。しかし、気になるのがcontextに表示されている値です。

"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users(aboutMe,accountEnabled,birthday,businessPhones,city,companyName,country,department,displayName,givenName,hireDate,id,interests,jobTitle,mail,mailNickname,mobilePhone,mySite,officeLocation,pastProjects,postalCode,preferredLanguage,preferredName,responsibilities,schools,skills,state,streetAddress,surname,userPrincipalName,userType)/$entity",

何やら項目が細かく指定されています。

Graph APIの該当項目を確認してみました。 https://learn.microsoft.com/ja-jp/graph/api/user-get?view=graph-rest-1.0&tabs=http

特定のプロパティを取得するには $select= で指定してやれば良いようです。標準のプロパティ取得では取れなかったならば、必要なプロパティを指定してやれば良いはず。

ならば、ゲストの招待の状態を知るには、適切なプロパティを$select= で指定してやればよいはずです。こちらもCopilotくんに聞いてみます。

GET https://graph.microsoft.com/v1.0/users/{user-id}?$select=displayName,externalUserState,externalUserStateChangeDate

どうやら、必要なプロパティは externalUserState と言うもののようです。

ゲスト追加のときのようにHTTP要求を使う場合には、サービスプリンシパルに追加の権限を与えてやらないといけませんが、おそらくOffice 365 ユーザーコネクタのHTTP要求アクションでも答えてくれそうな気がするのでやってみます。

よく見ると、「ユーザー プロフィールの取得(V2)」にもフィールドの選択がありました。HTTP要求でも大丈夫でしたが、こちらを使ったほうが簡単ですね。

ゲストのメールアドレスからUPN文字列を作り出す

ゲストユーザーのUPNはゲストが所属する組織のメールアドレスそのものではなく、以下のように変換されて登録されています。

daddy_mocabrown.com#EXT#@0phny.onmicrosoft.com

アットマークがアンダーバーになっていますね。さらに#EXT#以下が続きます。これはゲスト管理をしている人なら解ると思います。

テストで成功したフィールドの選択に入力した値をつかって、本番のクラウドフローに設置してみます。

UPNを指定する場所には、以下のように式を指定してみました。Split関数で@マークをアンダーバーに変換し、その結果に後ろ部分を加えます。

concat(replace(triggerOutputs()?['body/GuestEmail'],'@','_'),'#EXT#@0phny.onmicrosoft.com')

ちなみに、私の場合は0phnyとなっている部分は、テナントによって異なりますので適宜変更してください。

結果はこちらのとおり。ゲストの氏名(Display Name)と、すでにゲストとして承諾してサインインできる状態であることを示す「Accepted」の値が取れました。ここまでできれば、Acceptedになっていればすでに招待済みでテナントにゲストとして登録されていることを申請者に通知することができそうです。

ステータスなどの情報をSPOリストに書き込む

SPOリストをおさらいします。

ApplovalDatetimeは承認日時なので現在の日時、InventoryDeadline(棚卸期限)は承認日時にGuestInvitationPeriod(ゲスト招待期間)を足したものなので、日付を計算して更新してやれば良さそうです。

ApprovalDateTimeの欄には現在日時を渡すutcnow関数を。

utcnow()

InventoryDeadlineには招待期間の数字を足すので、以下のような関数を加えてみました。

addDays(utcNow(),int(triggerOutputs()?['body/GuestInvitationPeriod']))

こちらが結果です。デッドラインは180日加算された日付で更新することができました。ステータスも「ゲスト」になりましたので、SPOリストを見ればゲスト招待システムを使って招待された外部のユーザー(棚卸で削除されていない現在のゲスト)を管理することができていることになります。

招待済みはAcceptedとCompletedかな?

もういちど招待ステータスを確認します。

ゲスト招待は、招待メールが届いてそれに対してゲストさんが承諾することでAcceptedとなりますが、どうやったらCompletedになるんでしょう?

ふたたびCopilot Chatくんに質問です。

なるほど。SharePointやTeamsなどでログインされることでCompletedになるらしい。ということは、とりあえずAcceptedまたはCompletedステータスならばすでに招待済みとして良さそうですね。

ゲストとしてすでにEntra IDに登録されているかどうかは、早い段階で判定するべきなので、この場所に持ってきました。条件2の左辺にはユーザープロフィールから取得した外部ユーザーのステータスをとってくるため下記のように式を指定します。

すでにゲストならそれ以上処理する必要がないので、終了も加えておくことを忘れずに。

//条件2の左辺
body('ユーザー_プロフィールの取得_(V2)')?['externalUserState']

招待済みのユーザーは、誰がいつ招待していて、いつまでゲストなのか教えてあげよう

申請者がゲスト招待する前に、他のユーザーがすでに招待していたならば、ゲスト招待システムの記録がSPOリストに残っていることになるので、その情報を教えてあげましょう。

条件2でAcceptedまたはCompletedなら「はい」に流れます。このとき、SPOリストにすでに登録されているゲストのメールアドレスを探して、最新の1件だけを取得します。

続くメール送信で申請者に対してすでに他のユーザーが申請しているゲストであることを教えます。

//複数項目の取得(フィルタークエリ)
GuestEmail eq '@{triggerOutputs()?['body/GuestEmail']}' and ApprovalStatus eq 'ゲスト'

//複数項目の取得(並べ替え順)
ApprovalDateTime desc

//以前の申請者
outputs('複数の項目の取得')?['body/value'][0]?['Author']?['Email']

//以前の承認日時
outputs('複数の項目の取得')?['body/value'][0]?['ApprovalDateTime']

//棚卸期限
outputs('複数の項目の取得')?['body/value'][0]?['InventoryDeadline']

//以前の申請理由
outputs('複数の項目の取得')?['body/value'][0]?['ApplicationReason']

他の招待者がすでに申請を行っていて承認済みであることを伝えるメールが申請者に届きました。

なにか大切なことを忘れている気がする・・・ゲストに存在しなかったらどうなる?

さっきのテスト用別フローに、ゲストとしてテナントにまだ存在しないユーザーを指定してみるとどうなるかもチェックしておきます。hogehogeさんはテナントにいません。

答えは、404エラーでした。リクエストをURLとして投げるから、そんなアドレス無いぜってことですね。

とういうことは、まだ招待されていないゲストを招待したいためのシステムなのに、このままだと処理がユーザープロフィールの取得アクションで止まってしまいます。

ユーザープロフィールの取得(V2)がエラーになっていても、後続の処理が動作し続けるようにしなければいけません。次の処理は「条件2」なので、3点リーダーから「実行条件の構成」をクリックします。

「☑に失敗しました」にも☑を入れておくと、処理は進むはずです。

まだテナントに存在しないユーザーを申請してみます。

まだテナントに存在しないユーザーなので、「ユーザープロフィールの取得(V2)」はエラーになりましたが、処理が進みました。 条件2ではゲストステータスが「Accepted」または「Completed」ならば「はい」に進みますが、エラーだとそもそもこのステータス項目が無いので条件判定が「いいえ」となり、後続の処理に進めたのでちょうど良かったです。

まとめ

今回もいろいろと試行錯誤しただけあって、学びが多かったです。

  • 承認は毎回行うのが面倒なので、「静的な結果の無効化」機能をつかって、テストが簡単にできるような設定を行いました。
  • HTTPリクエストでゲストを追加する際には、"sendInvitationMessage":true を加えて招待メールを送り、招待された外部ユーザーは承諾することでステータスが変わることがわかりました。
  • ゲストのステータスはユーザープロフィールの取得(V2)で取れるけれど、フィールドの選択で「externalUserState」を加えてやる必要がありました。
  • そもそもまだテナントに存在しないユーザーをユーザープロフィールの取得(V2)に与えると、404エラーになるので、それでも処理が進むように実行条件の設定変更が必要でした。
  • ゲストのUPNは #EXT#がついた特殊な型式なので、split関数とconcat関数で加工してやりました。
  • SPOリストからODataフィルタークエリをつかって以前に申請したユーザーを拾い上げました。

あとはゲスト追加に失敗した場合の例外処理を追加すれば、ほぼ完成ですね。もう一息!

こんな人が書いてます。

お仕事でPower BIやPower Automateクラウドフローを中心にちょっとした自動化などを行っています。

QiitaやこのブログではPower Automateの話題を中心に投稿を行っていますので、ぜひ「いいね」してくださると励みになります。