サービスプリンシパルに SharePoint Online の特定サイトのみに権限を付与してみた

Power Automate や Azure Logic Apps の SharePoint コネクターを使用すると、ユーザー権限で簡単に SharePoint Online に接続する事が可能です。今回はサービスプリンシパルを用いて、Graph API でテナントの全ての SharePoint サイトを許可するのではなく、ユーザーと同じように特定のサイトのみに権限を付与する事を想定して試してみました。

## 検証用のサービスプリンシパルを作成し環境変数を設定

お好きな方法でサービスプリンシパルを作成し、下記のような環境変数を設定します。

```bash:bash
# testsp という名前のサービスプリンシパルを作成したとします
appid=78f5ad1b-0ea0-4172-af98-3134ae69af34
apppw=i0g8Q~0000~0000_0000SUCLijPUTqZT3kz14a3Z
tenantid=f51181ef-0000-0000-0000-9bf13f4dceba
tenant=mnrsdev
```

API のアクセス許可で `Sites.FullControll.All` と `Sites.Seleted` を付与し「管理者の同意」を与えます。

![az-app-spo-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675885/88ee2491-88f6-a5fb-9783-d1e65ce4011f.png)

## アクセストークンを取得してパーミッションを確認

`/sites/test` のパーミッションは空です。

```bash:bash
token=$(curl -s "https://login.microsoftonline.com/$tenantid/oauth2/v2.0/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "client_id=$appid" \
  -d "client_secret=$apppw" \
  -d "scope=https://graph.microsoft.com/.default" \
  -d "grant_type=client_credentials" \
  | jq -r .access_token)

siteid=$(curl -s "https://graph.microsoft.com/v1.0/sites/$tenant.sharepoint.com:/sites/test" \
  -H "Authorization: Bearer $token" \
  | jq -r .id)

curl -s "https://graph.microsoft.com/v1.0/sites/$siteid/permissions" \
  -H "Authorization: Bearer $token" \
  | jq .

{
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#sites('mnrsdev.sharepoint.com%2Ca91c7823-586c-41e9-acd6-1d376097ce2a%2Cd4092f58-6ec6-4b6d-943d-5e3812423b17')/permissions",
  "value": []
}
```

## サービスプリンシパルにパーミッションを設定

```bash:bash
curl -s "https://graph.microsoft.com/v1.0/sites/$siteid/permissions" \
  -H "Authorization: Bearer $token" \
  -H "Content-Type: application/json" \
  -d '{
  "roles": ["read"],
  "grantedToIdentities": [{
    "application": {
      "id": "78f5ad1b-0ea0-4172-af98-3134ae69af34",
      "displayName": "testspo-sp"
    }
  }]
}' | jq .

{
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#sites('mnrsdev.sharepoint.com%2Ca91c7823-586c-41e9-acd6-1d376097ce2a%2Cd4092f58-6ec6-4b6d-943d-5e3812423b17')/permissions/$entity",
  "id": "aTowaS50fG1zLnNwLmV4dHw3OGY1YWQxYi0wZWEwLTQxNzItYWY5OC0zMTM0YWU2OWFmMzRAZjUxMTgxZWYtN2M0NS00OTA0LWIwNzUtOWJmMTNmNGRjZWJh",
  "roles": [
    "read"
  ],
  "grantedToIdentitiesV2": [
    {
      "application": {
        "displayName": "testspo-sp",
        "id": "78f5ad1b-0ea0-4172-af98-3134ae69af34"
      }
    }
  ],
  "grantedToIdentities": [
    {
      "application": {
        "displayName": "testspo-sp",
        "id": "78f5ad1b-0ea0-4172-af98-3134ae69af34"
      }
    }
  ]
}
```

## Sites.FullControll.All 権限を外す

![az-app-spo-02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675885/6c3fc12b-8979-60a9-6b2e-edee2e43f801.png)

## もう一度アクセストークンを取得してサイトにアクセスできるか確認

`/sites/test` にアクセス。

```bash:bash
token=$(curl -s "https://login.microsoftonline.com/$tenantid/oauth2/v2.0/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "client_id=$appid" \
  -d "client_secret=$apppw" \
  -d "scope=https://graph.microsoft.com/.default" \
  -d "grant_type=client_credentials" \
  | jq -r .access_token)

curl -s "https://graph.microsoft.com/v1.0/sites/$tenant.sharepoint.com:/sites/test" \
  -H "Authorization: Bearer $token" \
  | jq .

{
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#sites/$entity",
  "createdDateTime": "2022-05-31T04:50:51.067Z",
  "description": "test",
  "id": "mnrsdev.sharepoint.com,a91c7823-586c-41e9-acd6-1d376097ce2a,d4092f58-6ec6-4b6d-943d-5e3812423b17",
  "lastModifiedDateTime": "2024-02-24T07:59:20Z",
  "name": "test",
  "webUrl": "https://mnrsdev.sharepoint.com/sites/test",
  "displayName": "test",
  "root": {},
  "siteCollection": {
    "hostname": "mnrsdev.sharepoint.com"
  }
}
```

## 権限がないサイトにアクセスした場合

`/sites/share001` にアクセス。

```bash:bash
curl -s "https://graph.microsoft.com/v1.0/sites/$tenant.sharepoint.com:/sites/share001" \
  -H "Authorization: Bearer $token" \
  | jq .
  
{
  "error": {
    "code": "accessDenied",
    "message": "Access denied",
    "innerError": {
      "date": "2024-02-24T08:24:08",
      "request-id": "294dd197-8391-45a7-9b97-56f410d3b8bb",
      "client-request-id": "294dd197-8391-45a7-9b97-56f410d3b8bb"
    }
  }
}
```

## 存在しないサイトにアクセスした場合

`/sites/notfound` にアクセス。

```bash:bash
curl -s "https://graph.microsoft.com/v1.0/sites/$tenant.sharepoint.com:/sites/notfound" \
  -H "Authorization: Bearer $token" \
  | jq .

{
  "error": {
    "code": "itemNotFound",
    "message": "Requested site could not be found",
    "innerError": {
      "date": "2024-02-24T08:23:55",
      "request-id": "f18b3b37-ab99-497a-baf5-fcaaca5acedc",
      "client-request-id": "f18b3b37-ab99-497a-baf5-fcaaca5acedc"
    }
  }
}
```

## 参考

https://techcommunity.microsoft.com/t5/microsoft-sharepoint-blog/develop-applications-that-use-sites-selected-permissions-for-spo/ba-p/3790476

https://learn.microsoft.com/ja-jp/graph/api/resources/permission?view=graph-rest-1.0