Microsoft PurviewのCollectionに登録されたユーザIDの一覧を取得する



# この記事でやること
この記事ではMicrosoft Purviewのコレクション情報をREST APIで取得して各コレクションに登録されたユーザやマネージドIDのオブジェクトIDの一覧を取得します。

# コレクション情報の取得

## 接続情報などの設定
今回はInteractiveBrowserCredentialを使っているので、アプリ登録せずにデータ取得できます。トークン取得時にブラウザが立ち上がって認証情報を入力する流れになります。

```python
import json
import sys
from pprint import pprint

import pandas as pd
import requests
from azure.identity import InteractiveBrowserCredential
from flatten_json import flatten

TENANT_ID = ""
RESOURCE_URL = "https://purview.azure.net/.default"
PURVIEW_ACCOUNT_NAME = ""

# アクセストークンの取得
endpoint = f"https://{PURVIEW_ACCOUNT_NAME}.purview.azure.com"
credential = InteractiveBrowserCredential(tenant_id=TENANT_ID)
token = credential.get_token(RESOURCE_URL).token
```

## コレクション情報の取得
コレクションの一覧を取得してデータフレームにします。
```python
def get_collection_list():
    url = f"{endpoint}/collections?api-version=2019-11-01-preview"
    headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json"
    }
    response = json.loads(requests.request(
        "GET", url, headers=headers).content)

    return response

collections = get_collection_list()
df_collections = (pd.json_normalize(collections["value"])
    .filter(["name", "friendlyName", "parentCollection.referenceName"])
    .rename(columns={"name": "collection_name", "parentCollection.referenceName": "parent_collection_name"})
)
```

ルートコレクション以外はシステムに付与されたコレクション名が付き、表示されるコレクション名はfriendlyNameに格納されているようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89512/14a0896d-ee75-e79e-e99f-846ca31c49e4.png)

## コレクションに設定されているロールとユーザ情報を取得
コレクションに設定されているロールやユーザの情報がJSONで返るので必要な情報だけ抜き出します。
```python
def get_collection_policies():
    url = f"{endpoint}/policystore/metadataPolicies?api-version=2021-07-01-preview"
    headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json"
    }
    response = json.loads(requests.request("GET", url, headers=headers).content)

    return response

collection_policy = get_collection_policies()
df_collection_policy = (pd.json_normalize(collection_policy["values"])
    .filter(["name", "properties.collection.referenceName", "properties.attributeRules"])
    .rename(columns={"properties.collection.referenceName": "collection_name"})
)
```
properties.attributeRulesにコレクション管理者やデータ キュレーターなどのロールのIDとそこにアサインされているユーザやサービスプリンシパルのオブジェクトIDがJSONの配列として格納されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89512/d916c3ef-ebdd-8a95-aec5-979611009237.png)

JSONをフラット化してコレクションのリストを作ります。
```python
collection_policy_flattened = [flatten(d) for d in collection_policy["values"]]
df_collection_policy_flattened = pd.json_normalize(collection_policy_flattened)
df_collection = (df_collection_policy_flattened
    .filter(["properties_decisionRules_0_dnfCondition_0_0_attributeValueIncludes"])
    .rename(columns={"properties_decisionRules_0_dnfCondition_0_0_attributeValueIncludes": "collection_name"})
)
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89512/22f0b56f-966c-cc76-8d32-be07904bdb97.png" width="30%" />

JSONをフラット化したデータフレームからオブジェクトIDを抜き出します。結合用に元のインデックスをold_indexとして残しておきます。また、結合用にもう一つJSON Pathから作成された情報をsub_pathとして追加します。
```python
def sub_path(x):
    y = x.split("_")[0:5]
    return "_".join(y)

df_object_id = (df_collection_policy_flattened.filter(like="attributeValueIncludedIn")
    .stack()
    .explode()
    .reset_index()
    .rename(columns={"level_0": "old_index", "level_1": "path", 0: "object_id"})
)
df_object_id["sub_path"] = df_object_id["path"].apply(sub_path)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89512/59a5a7aa-b81d-d4db-70f3-fff0fe5b62b8.png)

ロールも同じように抜き出します。

```python
df_role = (df_collection_policy_flattened
    .filter(regex="^properties_attributeRules.*attributeValueIncludes$")
    .stack()
    .explode()
    .reset_index()
    .rename(columns={"level_0": "old_index", "level_1": "path", 0: "role_id"})
)
df_role["sub_path"] = df_role["path"].apply(sub_path)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89512/a761066e-93d1-8e35-59b9-b97a7b0a7c97.png)

## データの加工
コレクション毎のロールとユーザなどのオブジェクトIDを結合します。

```python
df_role_object = (pd.merge(df_role, df_object_id, on=["old_index", "sub_path"])
    .filter(["old_index", "role_id", "object_id"])
)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89512/3a399e83-63b6-7101-42c0-5ac38f8b9c82.png)
コレクションと結合します。

```python
df_collection_role_object = (pd.merge(df_collection, df_role_object, left_index=True, right_on="old_index")
    .filter(["collection_name", "role_id", "object_id"])
)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89512/e1ce31ed-ff45-459b-f626-1999270eaf4b.png)
さらにコレクションのフレンドリ名や親コレクションの情報と結合します。

```python
df_collection2 = pd.merge(df_collections, df_collection_role_object)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89512/e27be110-c05a-7edd-d77c-bc2ac7c7420e.png)
ロール名が分かりにくいので情報を与えます。

```python
df_role = pd.DataFrame(
    data={
        "role_id": [
            "purviewmetadatarole_builtin_collection-administrator",
            "purviewmetadatarole_builtin_data-source-administrator",
            "purviewmetadatarole_builtin_data-curator",
            "purviewmetadatarole_builtin_purview-reader",
            "purviewmetadatarole_builtin_data-share-contributor",
            "purviewmetadatarole_builtin_insights-reader",
            "purviewmetadatarole_builtin_workflow-administrator",
            "purviewmetadatarole_builtin_policy-author"
        ],
        "role_display_name": [
            "Collection admins",
            "Data source admins",
            "Data curators",
            "Data readers",
            "Data share contributors",
            "Insights readers",
            "Workflow admins",
            "(non-display) Policy authers",
        ],
    }
)

df_collection3 = (pd.merge(df_collection2, df_role, on="role_id")
    .filter(["collection_name", "friendlyName", "parent_collection_name", "role_display_name", "object_id"])
)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89512/4d3e57aa-17a0-a0a3-e57c-6d4ace9ad4ca.png)
これでコレクションの表示名とそこに設定されたロール名と登録されたユーザのオブジェクトIDが一覧になりました。
オブジェクトIDからユーザ名やManaged IDの情報を得るには[Microsoft Graph Core Python client library](https://github.com/microsoftgraph/msgraph-sdk-python-core)あたりが使えるかなと思います。