Microsoft Graph API + PythonでAzure ADのサインインログを取得してみた
どうも、ベルリンオフィスの小西です。
最近Mirosoft Graph APIを触ることがありました。
日本語の情報があまりなかったので、これをいい機会に認証方法やデータの取得方法をまとめてみたいと思います。
Microsoft Graphとは
Microsoftの中の人がQiitaで記事を書いてくれているので引用します。
Microsoft のサービスである Office 365 や Azure AD など、様々なサービスのデータをグラフ形式で扱えるエンドポイントです。グラフ形式でデータを扱えるので、「自分の予定」や「自分の上司」などデータを関連から取得できます。
単一のREST APIエンドポイントでMicrosoftの各サービスのデータを取得・処理でき、認証も一括で管理できるため非常に使いやすいと感じました。
なお、これに伴い Microsoft Azure AD Graph や Outlook REST API ・ Discovery Service APIsなどのOffice 365 API は Microsoft Graph への移行を推奨されています。
例えば下記のようなことが可能です(あくまで一例)。
- 自分のプロファイルの取得:
https://graph.microsoft.com/v1.0/me
- 自分のメールの取得:
https://graph.microsoft.com/v1.0/me/messages
- 自分の所属組織のユーザーの取得:
https://graph.microsoft.com/v1.0/users
Microsoft Graph Explorer
GUIで簡単に Graph API リクエストとレスポンスを確認できる Graph エクスプローラー というサービスもあります。
https://developer.microsoft.com/ja-jp/graph/graph-explorer
アカウントに許可されている権限によって取得できないデータがありますが、まずは下記のエンドポイントによる自分の情報の取得はどなたでも試せますので、どんな感じでGraphがデータを返すか見ていただければと思います。
https://graph.microsoft.com/v1.0/me
細かい使い方などはこちら: https://docs.microsoft.com/ja-jp/graph/graph-explorer/graph-explorer-overview
事前準備: Microsoftのテナントトークン取得
1. Developer Accountの作成
MicrosoftのDeveloper Accountに登録します(無料)。下記開発者用ページの [今すぐ参加] から登録します。
https://developer.microsoft.com/ja-jp/microsoft-365/dev-program
登録が完了してポータルから確認し、
Microsoft 365 E5 Developer
となっていればOKです。
Overviewでもライセンスが [Azure AD Premium P2] になっています。これは後で紹介するAzure ADのサインインログの取得のために必要なライセンスになります。
2. Appの作成
https://portal.azure.com/ にログインし、[App Registration]に移動します
その後[Register an application]からアプリを登録します。
Appが作成されたら、後で使う[Application (client) ID]と[Directory (tenant) ID]をメモしておきます。
また[Add a certificate or secret]をクリックして進みます。
secretが発行されたら value をコピーします(ページを移動すると見えなくなるので忘れずに)。
また次に、AppにAzure ADの読み取り権限を与えます。
デフォルトでUser.Read
は付与されていますが、下記の権限も付与します。
- Directory.Read.All
- AuditLog.Read.All
※上記の同じ画面でAuditLog.Read.Allも付与する
また、上記で取得した権限に同意を付与します(これをしないと権限が付与されない)。
下記の通り、Directory.Read.All と AuditLog.Read.All が Grantedになれば準備は完了です。
実践: PythonからAPIを叩く
1. 準備: 認証用パッケージの追加
Pythonからアクセスする際の認証トークンを取得する必要があります。
Python用には MSAL (Microsoft Authentication Library) が公開されていますのでそれを利用します。
https://github.com/AzureAD/microsoft-authentication-library-for-python
パッケージをインストールします。
% pip install msal
2. ユーザーアカウント一覧の取得
コードは下記になります。 tenant_id
, client_id
, client_secret
に先ほどAzureポータルのダッシュボードからコピーした値を貼り付けます。
処理の中身としては、
msgraph_auth()
でアクセストークンを取得msgraph_request()
で ADに紐づくユーザー一覧を取得するようGraphにリクエスト- 返り値の中から
PrincipalName
をリスト表示
という感じです。
import json import requests import msal import traceback #Graph API configuration graph_url = 'https://graph.microsoft.com' tenant_id = "XXXXX" client_id = "XXXXX" client_secret = "XXXXX" def msgraph_auth(): authority = 'https://login.microsoftonline.com/' + tenant_id scope = ['https://graph.microsoft.com/.default'] try: app = msal.ConfidentialClientApplication(client_id, authority = authority, client_credential = client_secret) access_token = app.acquire_token_for_client(scopes = scope) if access_token['access_token']: print('New access token retrieved....') request_headers = {'Authorization': 'Bearer ' + access_token['access_token']} return request_headers else: print('ERROR: No "access_token" in the result.') except: print('ERROR: Could not acquired authorization token. Check your tenant_id, client_id and client_secret.') traceback.print_exc() exit(1) def msgraph_request(resource,request_headers): results = requests.get(resource, headers = request_headers).json() return results def show_users(): # ユーザー一覧を取得するエンドポイントのパス q = "users" # Graphへのクエリ実行 request_headers = msgraph_auth() results = msgraph_request(graph_url + '/v1.0/' + q, request_headers) if results: try: for r in results["value"]: print("userPrincipalName:", r["userPrincipalName"]) except: print('ERROR: No "value" or "userPrincipalName" in the result.') traceback.print_exc() exit(1) show_users()
実行してみます。
% python get-azure-ad-users.py New access token retrieved.... userPrincipalName: [email protected] userPrincipalName: [email protected] userPrincipalName: [email protected] userPrincipalName: [email protected] userPrincipalName: [email protected] userPrincipalName: [email protected] userPrincipalName: [email protected] userPrincipalName: [email protected] userPrincipalName: [email protected] userPrincipalName: [email protected] userPrincipalName: [email protected] userPrincipalName: [email protected] userPrincipalName: [email protected] userPrincipalName: [email protected] userPrincipalName: [email protected] userPrincipalName: [email protected] userPrincipalName: [email protected]
成功!
サンプルで登録されているユーザーと自分の userPrincipalName
(メールアドレス)が取得できました。サンプルデータが入っているのはいいですね。
3. サインインログの取得
例えばダッシュボードの下記からAD所属ユーザーのアクティビティが確認できるのですが、これをAPIでも取得できます。
注意:
サインインログや監査ログは Activity reports API の一部となり、プレミアムライセンス(P1/P2)が必要です。本記事で紹介したDeveloperアカウントであればプレミアムライセンス相当の権限があるので問題ありませんが、ご自身のビジネスアカウントでお試しいただいている際は、Neither tenant is B2C or tenant doesn't have premium license
というエラーメッセージが返ってきてしまいますのでご注意ください。
コードは先ほどとほぼ一緒ですが、先ほどのPythonコードの36行目以降(show_users()
)を下記コードと置き換えてください。
エンドポイントパスを変更しているのと、ログの期間をフィルターしています(2022/02/12から02/18終日までのデータのみ取得)が、このフィルターについては次で詳しく説明します。
def show_signin_logs(): # サインインログを取得するエンドポイントのパス q = "auditLogs/signIns" # データのフィルタリング f = "(createdDateTime ge 2022-02-12T00:00:00Z and createdDateTime le 2022-02-18T23:59:59Z)" # Graphへのクエリ実行 request_headers = msgraph_auth() results = msgraph_request(graph_url + '/v1.0/' + q + '?$filter=' + f, request_headers) if results: try: for r in results["value"]: print(r["createdDateTime"], "|", r["userPrincipalName"]) except: print('ERROR: No "value", "createdDateTime" or "userPrincipalName" in the result.') traceback.print_exc() exit(1) show_signin_logs()
上記実行してみます。
% python get-azure-ad-singin-logs.py New access token retrieved.... 2022-02-18T11:32:22Z | [email protected] 2022-02-18T11:32:21Z | [email protected] 2022-02-18T11:32:21Z | [email protected] 2022-02-18T11:31:52Z | [email protected] 2022-02-18T11:31:52Z | [email protected] 2022-02-18T11:31:52Z | [email protected] 2022-02-18T11:31:47Z | [email protected] 2022-02-18T11:02:17Z | [email protected] 2022-02-18T11:02:17Z | [email protected] 2022-02-18T11:02:17Z | [email protected] 2022-02-18T11:01:59Z | [email protected] 2022-02-18T10:59:23Z | [email protected]
無事、特定期間のサインインログからログイン時間とユーザーメアドが取得できました。
なお上記ではデータをシンプルに改変して出力していますが、返り値全体としては下記のようになります。
{ "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#auditLogs/signIns", "@odata.nextLink": "https://graph.microsoft.com/v1.0/auditLogs/signIns?$top=1&$skiptoken=9177f2e3532fcd4c4d225f68f7b9bdf7_1", "value": [ { "id": "66ea54eb-6301-4ee5-be62-ff5a759b0100", "createdDateTime": "2020-03-13T19:15:41.6195833Z", "userDisplayName": "Test Contoso", "userPrincipalName": "[email protected]", "userId": "26be570a-ae82-4189-b4e2-a37c6808512d", "appId": "de8bc8b5-d9f9-48b1-a8ad-b748da725064", "appDisplayName": "Graph explorer", "ipAddress": "131.107.159.37", "clientAppUsed": "Browser", "correlationId": "d79f5bee-5860-4832-928f-3133e22ae912", "conditionalAccessStatus": "notApplied", "isInteractive": true, "riskDetail": "none", "riskLevelAggregated": "none", "riskLevelDuringSignIn": "none", "riskState": "none", "riskEventTypes": [], "resourceDisplayName": "Microsoft Graph", "resourceId": "00000003-0000-0000-c000-000000000000", "status": { "errorCode": 0, "failureReason": null, "additionalDetails": null }, "deviceDetail": { "deviceId": "", "displayName": null, "operatingSystem": "Windows 10", "browser": "Edge 80.0.361", "isCompliant": null, "isManaged": null, "trustType": null }, "location": { "city": "Redmond", "state": "Washington", "countryOrRegion": "US", "geoCoordinates": { "altitude": null, "latitude": 47.68050003051758, "longitude": -122.12094116210938 } }, "appliedConditionalAccessPolicies": [ { "id": "de7e60eb-ed89-4d73-8205-2227def6b7c9", "displayName": "SharePoint limited access for guest workers", "enforcedGrantControls": [], "enforcedSessionControls": [], "result": "notEnabled" }, { "id": "6701123a-b4c6-48af-8565-565c8bf7cabc", "displayName": "Medium signin risk block", "enforcedGrantControls": [], "enforcedSessionControls": [], "result": "notEnabled" }, ] } ] }
クエリのカスタマイズ
Microsoft Graph ではオプションとして、リクエストURLにクエリパラメーターを付与することでレスポンスをカスタマイズすることができます。
データのフィルタリング、並び替え、カウント、スキップなどかなり色々なことがAPIリクエスト時点で指定できるのは嬉しい点です。
Name | 説明 | 例 |
---|---|---|
$count | 一致するリソースの総数を取得します。 | /me/messages?$top=2&$count=true |
$expand | 関連リソースを取得します。 | /groups?$expand=members |
$filter | 結果 (行) をフィルターします。 | /users?$filter=startswith(givenName,'J') |
$format | 指定したメディア形式で結果を返します。 | /users?$format=json |
$orderby | 結果を並べます。 | /users?$orderby=displayName desc |
$search | 検索条件に基づいて結果を返します。 | /me/messages?$search=pizza |
$select | プロパティ (列) をフィルターします。 | /users?$select=givenName,surname |
$skip | 結果セットにインデックスを作成します。また一部の API でページングを実装するために使用されており、$top と組み合わせて手動で結果をページングすることもできます。 | /me/messages?$skip=11 |
$top | 結果のページ サイズを設定します。 | /users?$top=2 |
詳しい仕様は https://docs.microsoft.com/ja-jp/graph/query-parameters からご確認ください。
先ほどの例ではフィルターを下記のようにしていました。
?$filter=(createdDateTime ge 2022-02-12T00:00:00Z and createdDateTime le 2022-02-18T23:59:59Z)
上記は「UCT2022-02-12の00:00:00以降で(2022-02-12の00:00:00ピッタリを含む)、かつ2022-02-18の23:59:59より以前(2022-02-18の23:59:59ピッタリを含む)のデータ」にフィルタリングをしています。
フィルタリングしたいデータによって利用できる属性が異なりますので、下記を確認して使う必要があります。
https://docs.microsoft.com/ja-jp/graph/aad-advanced-queries
例えば createdDateTime
に対して ge
(以上)や le
(以下)は使えますが、 文字列ではないので startsWith
(文字が**から始まる)や contains
(文字列を含む)は使えません。
最後に
Microsoft OfficeのAPIをいじるにはSubscriptionが複雑な印象でしたが、諸々の権限を付与可能でサンプルデータも用意されているDeveloperアカウントがあるのは非常に助かりました。Graph自体はとても使いやすく、今後も知見がたまったら共有していきたいと思います。
参考記事まとめ
- https://docs.microsoft.com/ja-jp/graph/
- https://docs.microsoft.com/ja-jp/graph/reportroot-concept-overview
- https://docs.microsoft.com/ja-jp/graph/query-parameters
- https://azure.microsoft.com/en-gb/pricing/details/active-directory/
- https://github.com/AzureAD/microsoft-authentication-library-for-python
- https://gist.github.com/darrenjrobinson/8fb22f39aa65e9481c3fd3604ea1aa37