IBM Cloud App ID User関連APIを叩いてみる

## はじめに

[前回の記事](https://qiita.com/yo24/items/09d6b480cb3f033ddd25)で、IBM Cloud AppIdのOAuthフローを手動で実施する記事を投稿しました。
今回はその続きで、取得したアクセストークンを用いて、User関連のAPIをcurlコマンドで叩いて見ます。

## 本記事で扱うAPI

AppIDのAPIのうち、今回扱うAPIは下図赤枠です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135384/ea089dad-6c4f-e395-dfb6-904517d9f044.png)

## Profiles - Attributes

このAPIでは、ログインユーザーのカスタム属性を取得、更新ができます。

### カスタム属性

カスタム属性とは、個々のユーザーに付与できる属性で、任意のKey-Valueの組み合わせが指定できます。
IBM Cloudのコンソールでは、クラウドディレクトリー->ユーザー画面、もしくはユーザー・プロファイル画面から個々のユーザー選択後に編集可能です。
今回は、下図の通り「tel」属性を追加してみました。 

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135384/2b1685cc-0469-6748-b7fa-74ef9282fbe6.png)

### GET - /api/v1/attributes

GETメソッドでは、指定したアクセストークンのカスタム属性が取得できます。

```console:GET-/api/v1/attributes
$ curl https://jp-tok.appid.cloud.ibm.com/api/v1/attributes \
-H "Authorization: Bearer <アクセストークン>"

{"tel":"03-xxxx-xxxx"}
```
※ここで指定するアクセストークンは、[前回の記事](https://qiita.com/yo24/items/09d6b480cb3f033ddd25)のOAuthフローで取得したものです。

### PUT - /api/v1/attributes

PUTではカスタム属性の追加、更新が可能です。
ここでは[state]属性を追加してみます。
URLの末尾に属性名が入ることに注意してください。

```console:PUT-/api/v1/attributes(Insert)
$ curl -X PUT https://jp-tok.appid.cloud.ibm.com/api/v1/attributes/state \
-H "Authorization: Bearer <アクセストークン>" \
-H "Content-Type:text/plain" \
-d 'tokyo'
```

GETメソッドで値を再取得すると、state属性が追加されたことが確認できます。

```console:GET-/api/v1/attributes
$ curl https://jp-tok.appid.cloud.ibm.com/api/v1/attributes \
-H "Authorization: Bearer <アクセストークン>"

{"tel":"03-xxxx-xxxx","state":"tokyo"}
```

既存の値を更新する場合も、PUTメソッドを用います

```console:PUT-/api/v1/attributes(Update)
$ curl -X PUT https://jp-tok.appid.cloud.ibm.com/api/v1/attributes/state \
-H "Authorization: Bearer <アクセストークン>" \
-H "Content-Type:text/plain" \
-d 'tokyo-upd

{"state":"tokyo-upd"}'
```

### DELETE - /api/v1/attributes

カスタム属性を削除する場合は、DELETEメソッドを用います。
成功時、HTTP Status Code =204(No Content)が応答されます。

```console:PUT-/api/v1/attributes(Update)
$ curl -i -X DELETE https://jp-tok.appid.cloud.ibm.com/api/v1/attributes/state \
 -H "Authorization: Bearer <アクセストークン>"

 HTTP/2 204
 date: Fri, 24 Jan 2020 05:46:21 GMT
 <以下、省略>
```

## Management API - Cloud Directory Users

このAPIは、AppIDのクラウド・ディレクトリーで管理するユーザーID一覧を取得、更新できます。
Google等を利用しログインしたユーザーは対象外です。

いくつかAPIが存在しますが、 /cloud_directory/Users APIを利用してみます。

### (前提知識)IAMユーザトークン

Management APIも実行にアクセストークンが必要です。
ただし、ここで必要なトークンはIBM CloudのIAMユーザーのアクセストークンです。 
AppIDで管理するユーザーのトークンではないことに注意してください。

IAMユーザーのトークンは以下のAPIで取得できます。

```json:/identity/token
$ curl-X POST \
 --header "Content-Type: application/x-www-form-urlencoded" \
 --header "Accept: application/json" \
 --data-urlencode "grant_type=urn:ibm:params:oauth:grant-type:apikey" \
 --data-urlencode "apikey=<IAM UserのAPI Key>" \
 https://iam.cloud.ibm.com/identity/token

{ 
  "access_token":"eyJraWQiO<以降、省略>",
  "refresh_token":"ReVk5aJcB<以降、省略>",
  "token_type":"Bearer",
  "expires_in":3600,
  "expiration":1579242502,
  "scope":"ibm openid"
}

```

上記API実行には、IAMユーザーのAPIキーが必要になります。
APIキーは、IBM Cloudのコンソールから発行できます。
(管理 => アクセス(IAM)からユーザー一覧を表示し、取得対象ユーザーを選択)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135384/95f6ce50-9ad2-bae7-2702-9a2f7a9c75c3.png)


### GET - /cloud_directory/Users

このAPIでは、クラウド・ディレクトリーのユーザー一覧を取得できます。

```json:/cloud_directory/Users
$ curl https://jp-tok.appid.cloud.ibm.com/management/v4/<tenantId>/cloud_directory/Users \
-H "Authorization: Bearer <アクセストークン>"

{
  "totalResults": 1,
  "itemsPerPage": 1,
  "schemas": [
    "urn:ietf:params:scim:api:messages:2.0:ListResponse"
  ],
  "Resources": [
    {
      "displayName": "hoge hoge",
      "active": true,
      "mfaContext": {},
      "emails": [
        {
          "value": "hogehoge@fuga.com",
          "primary": true
        }
      ],
      "meta": {
        "lastLogin": "2020-01-24T05:57:20.861Z",
        "created": "2019-12-05T05:43:53.507Z",
        "location": "/v1/fa5ca1dd-xxxxx/Users/e188eeca-9b78-44ca-95ce-f207a48981ed",
        "lastModified": "2020-01-24T07:29:57.063Z",
        "resourceType": "User"
      },
      "schemas": [
        "urn:ietf:params:scim:schemas:core:2.0:User"
      ],
      "name": {
        "givenName": "hoge",
        "familyName": "hoge",
        "formatted": "hoge hoge"
      },
      "id": "e188eeca-9b78-44ca-95ce-f207a48981ed",
      "status": "CONFIRMED"
    }
  ],
  "requestOptions": {}
}

```

### POST - /cloud_directory/Users

POSTメソッドでユーザーを新規作成できます。
Bodyに指定するデータ形式は、[SCIM-Core Schema(RFC7643)](https://tools.ietf.org/html/rfc7643)に沿う必要があります。

```json:POST-/cloud_directory/Users
$ curl -X POST \
https://jp-tok.appid.cloud.ibm.com/management/v4/<tenantId>/cloud_directory/Users \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <アクセストークン>" \
-d ' { "active": true, "emails": [ { "value": "newhoge@fuga.com", "primary": true } ], "password": "hogehoge", "name":{ "formatted": "New Hoge", "familyName": "new", "givenName": "hoge"} } '

{
  "displayName": "hoge new",
  "active": true,
  "emails": [
    {
      "value": "newhoge@fuga.com",
      "primary": true
    }
  ],
  "meta": {
    "created": "2020-01-27T02:12:02.736Z",
    "location": "/v1/fa5ca1dd-xxxxx/Users/b9de83ef-e010-4282-b7cf-2e2d98b03ee1",
    "lastModified": "2020-01-27T02:12:02.736Z",
    "resourceType": "User"
  },
  "schemas": [
    "urn:ietf:params:scim:schemas:core:2.0:User"
  ],
  "name": {
    "formatted": "hoge new",
    "familyName": "new",
    "givenName": "hoge"
  },
  "id": "b9de83ef-e010-4282-b7cf-2e2d98b03ee1"
}
```

PUT後、一覧を再取得するとユーザーが追加されていることを確認できます。

```json:/cloud_directory/Users-afterput
{
  "totalResults": 2,
  "itemsPerPage": 2,
  "schemas": [
    "urn:ietf:params:scim:api:messages:2.0:ListResponse"
  ],
  "Resources": [
    {
      "displayName": "hoge new",
      "active": true,
      "emails": [
        {
          "value": "newhoge@fuga.com",
          "primary": true
        }
      ],
      "meta": {
        "created": "2020-01-27T02:12:02.736Z",
        "location": "/v1/fa5ca1dd-xxxxx/Users/b9de83ef-e010-4282-b7cf-2e2d98b03ee1",
        "lastModified": "2020-01-27T02:12:02.736Z",
        "resourceType": "User"
      },
      "schemas": [
        "urn:ietf:params:scim:schemas:core:2.0:User"
      ],
      "name": {
        "formatted": "hoge new",
        "familyName": "new",
        "givenName": "hoge"
      },
      "id": "b9de83ef-e010-4282-b7cf-2e2d98b03ee1"
    },
    {
      "displayName": "hoge2 hoge3",
      "active": true,
      "mfaContext": {},
      "emails": [
        {
          "value": "hogehoge@fuga.com",
          "primary": true
        }
      ],
      "meta": {
        "lastLogin": "2020-01-24T05:57:20.861Z",
        "created": "2019-12-05T05:43:53.507Z",
        "location": "/v1/fa5ca1dd-xxxxx/Users/e188eeca-9b78-44ca-95ce-f207a48981ed",
        "lastModified": "2020-01-24T07:29:57.063Z",
        "resourceType": "User"
      },
      "schemas": [
        "urn:ietf:params:scim:schemas:core:2.0:User"
      ],
      "name": {
        "givenName": "hoge",
        "familyName": "hoge",
        "formatted": "hoge hoge"
      },
      "id": "e188eeca-9b78-44ca-95ce-f207a48981ed",
      "status": "CONFIRMED"
    }
  ],
  "requestOptions": {}
}
```

PUT（更新）、DELETE（削除）も可能ですが、ここでは記載は割愛します。

## Management API - Users

このAPIでは、ユーザー・プロファイルの取得および更新ができます。
ユーザー・プロファイルとは、IDPから取得するユーザー属性および、AppIDで管理するカスタム属性を包括した概念になります。詳細は[マニュアル](https://cloud.ibm.com/docs/services/appid?topic=appid-profiles)を参照してください。

いくつかAPIがありますが、ここでは/usersと/users/{id}/profileを使ってみます。

### GET - /users

このAPIでは、ユーザー・プロファイルの一覧を取得できます。
管理者向け画面等での利用できそうです。

クラウドディレクトリーにはユーザーが２件ありますが、検索結果は１件です。
クラウド・ディレクトリーとユーザー・プロファイルはエンティティは別物ということですね。

```json:/users?dataScope=index
$ curl -i https://jp-tok.appid.cloud.ibm.com/management/v4/<tenantId>/users?dataScope=index \
-H "Authorization: Bearer <アクセストークン>" 

{ 
  "totalResults": 1,
  "itemsPerPage": 1,
  "requestOptions":{},
  "users":[ 
    { 
      "idp":"cloud_directory",
      "id":"96d17139-ae58-4964-af19-80dbc8c62405",
      "email":"hogehoge@fuga.com"
    }
  ]
}

```

dataScopeオプションは必須で、index以外にfullが指定できます。

```json:/users?dataScope=full

$ curl -i https://jp-tok.appid.cloud.ibm.com/management/v4/<tenantId>/users?dataScope=full \
-H "Authorization: Bearer <アクセストークン>" 

{
  "totalResults": 1,
  "itemsPerPage": 1,
  "requestOptions": {},
  "users": [
    {
      "id": "96d17139-ae58-4964-af19-80dbc8c62405",
      "name": "hoge hoge",
      "email": "hogehoge@fuga.com",
      "given_name": "hoge",
      "family_name": "hoge",
      "identities": [
        {
          "provider": "cloud_directory",
          "id": "e188eeca-9b78-44ca-95ce-f207a48981ed",
          "idpUserInfo": {
            "displayName": "hoge hoge",
            "active": true,
            "mfaContext": {},
            "emails": [
              {
                "value": "hogehoge@fuga.com",
                "primary": true
              }
            ],
            "meta": {
              "lastLogin": "2020-01-24T05:57:20.861Z",
              "created": "2019-12-05T05:43:53.507Z",
              "location": "/v1/fa5ca1dd-xxxxx/Users/e188eeca-9b78-44ca-95ce-f207a48981ed",
              "lastModified": "2020-01-24T05:57:20.871Z",
              "resourceType": "User"
            },
            "schemas": [
              "urn:ietf:params:scim:schemas:core:2.0:User"
            ],
            "name": {
              "givenName": "hoge",
              "familyName": "hoge",
              "formatted": "hoge hoge"
            },
            "id": "e188eeca-9b78-44ca-95ce-f207a48981ed",
            "status": "CONFIRMED",
            "idpType": "cloud_directory"
          }
        }
      ],
      "attributes": {
        "tel": "03-xxxx-xxxx"
      }
    }
  ]
}
```

上記以外に、emailなどの条件でフィルタリングや、開始位置指定(startIndexオプション)、件数上限(countオプション)などが指定可能です。

### POST - /users

このAPIでは、(将来の)ユーザープロファイルを新規作成できます。
ユーザー・プロファイルは、IDPからの属性とカスタム属性で構成されると記載しましたが、このAPIで登録可能な情報はカスタム属性のみです。
(将来の)と記載した理由は、IDPからの情報取得前にカスタム属性を事前に登録できるという意味です。

```json:POST-/usrs
curl -X POST https://jp-tok.appid.cloud.ibm.com/management/v4/<tenantId>/users \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <アクセストークn>" \
-d '{ "idp": "cloud_directory", "idp-identity": "b9de83ef-e010-4282-b7cf-2e2d98b03ee1", "profile": { "attributes": { "tel": "06-xxxx-xxxx" } } }'

{"id":"9068675a-0aa5-44d2-8741-f3fac86078d7"}
```

一覧を再取得すると、件数が２件に増えていることがわかります。
しかし、新規追加したレコード(2件目)には、emailフィールドは存在しません。
emailは、IDPから取得する情報なので、この時点では情報として含まれないわけです。
これらの情報は、初回ログイン時に設定されます。

```json:/users?dataScope=index
$ curl -i https://jp-tok.appid.cloud.ibm.com/management/v4/<tenantId>/users?dataScope=index \
-H "Authorization: Bearer <アクセストークン>" 

{
  "totalResults": 2,
  "itemsPerPage": 2,
  "requestOptions": {},
  "users": [
    {
      "idp": "cloud_directory",
      "id": "96d17139-ae58-4964-af19-80dbc8c62405",
      "email": "hogehoge@fuga.com"
    },
    {
      "idp": "nominated_cloud_directory",
      "id": "9068675a-0aa5-44d2-8741-f3fac86078d7"
    }
  ]
}
```
ということで、新規ユーザーでログインしてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135384/5fc48789-4cbe-12aa-ac71-093d8e341ccd.png)

ログイン後のレスポンスは以下の通りです。
2件目のemail属性が更新されていることがわかります。
なお、email属性以外にもnameなどの属性も合わせて更新されます。

```json:/users?dataScope=index
$ curl -i https://jp-tok.appid.cloud.ibm.com/management/v4/<tenantId>/users?dataScope=index \
-H "Authorization: Bearer <アクセストークン>" 

{
  "totalResults": 2,
  "itemsPerPage": 2,
  "requestOptions": {},
  "users": [
    {
      "idp": "cloud_directory",
      "id": "96d17139-ae58-4964-af19-80dbc8c62405",
      "email": "hogehoge@fuga.com"
    },
    {
      "idp": "cloud_directory",
      "id": "9068675a-0aa5-44d2-8741-f3fac86078d7",
      "email": "newhoge@fuga.com"
    }
  ]
}

```

### GET - /users/{id}/profile

単一のユーザー・プロファイルを取得するAPIです。
ここでは、APIで新規登録したユーザー・プロファイルを取得してみます。

レスポンスの結果から、POST/users APIで登録したカスタム属性(tel:06-xxxx-xxxx)とIDPから取得した属性が取得できることが確認できます。

```json:/users/<id>/profile
$ curl https://jp-tok.appid.cloud.ibm.com/management/v4/<tenantId>/users/9068675a-0aa5-44d2-8741-f3fac86078d7/profile \
-H "Authorization: Bearer <アクセストークン>"

{
  "id": "9068675a-0aa5-44d2-8741-f3fac86078d7",
  "name": "hoge new",
  "email": "newhoge@fuga.com",
  "given_name": "hoge",
  "family_name": "new",
  "identities": [
    {
      "provider": "cloud_directory",
      "id": "b9de83ef-e010-4282-b7cf-2e2d98b03ee1",
      "idpUserInfo": {
        "displayName": "hoge new",
        "active": true,
        "mfaContext": {},
        "emails": [
          {
            "value": "newhoge@fuga.com",
            "primary": true
          }
        ],
        "meta": {
          "lastLogin": "2020-01-27T04:41:00.826Z",
          "created": "2020-01-27T02:12:02.736Z",
          "location": "/v1/fa5ca1dd-xxxxx/Users/b9de83ef-e010-4282-b7cf-2e2d98b03ee1",
          "lastModified": "2020-01-27T04:41:00.837Z",
          "resourceType": "User"
        },
        "schemas": [
          "urn:ietf:params:scim:schemas:core:2.0:User"
        ],
        "name": {
          "formatted": "hoge new",
          "familyName": "new",
          "givenName": "hoge"
        },
        "id": "b9de83ef-e010-4282-b7cf-2e2d98b03ee1",
        "idpType": "cloud_directory"
      }
    }
  ],
  "attributes": {
    "tel": "06-xxxx-xxxx"
  }
}
```

### PUT - /users/{id}/profile

ユーザープロファイルのうち、カスタム属性を更新するAPIです。

なお、IDPから取得する属性は更新できません。
更新しようとすると以下のようなエラーが応答されます。

```json:PUT-/users/<id>/profile(error)
curl -X PUT https://jp-tok.appid.cloud.ibm.com/management/v4/<tenantId>/users/9068675a-0aa5-44d2-8741-f3fac86078d7/profile \
-H "Content-Type:application/json" \
-H "Authorization: Bearer <アクセスID>" \
-d '{"name":"upd hoge new", "attributes": { "tel":"06-xxxx-xxxx", "state":"osaka" } }'

{"errorCode":"INVALID_REQUEST","message":"data should NOT have additional properties"}
```

成功時のレスポンスは以下の通りです。
レスポンスから、カスタム属性が更新されていることがわかります。

```json:PUT-/users/<id>/profile(success)
curl -X PUT https://jp-tok.appid.cloud.ibm.com/management/v4/<tenantId>/users/9068675a-0aa5-44d2-8741-f3fac86078d7/profile \
-H "Content-Type:application/json" \
-H "Authorization: Bearer <アクセスID>" \
-d '{"attributes": { "tel":"06-xxxx-xxxx", "state":"osaka" } }'

{
  "id": "9068675a-0aa5-44d2-8741-f3fac86078d7",
  "name": "hoge new",
  "email": "newhoge@fuga.com",
  "given_name": "hoge",
  "family_name": "new",
  "identities": [
    {
      "provider": "cloud_directory",
      "id": "b9de83ef-e010-4282-b7cf-2e2d98b03ee1",
      "idpUserInfo": {
        "displayName": "hoge new",
        "active": true,
        "mfaContext": {},
        "emails": [
          {
            "value": "newhoge@fuga.com",
            "primary": true
          }
        ],
        "meta": {
          "lastLogin": "2020-01-27T04:41:00.826Z",
          "created": "2020-01-27T02:12:02.736Z",
          "location": "/v1/fa5ca1dd-xxxxx/Users/b9de83ef-e010-4282-b7cf-2e2d98b03ee1",
          "lastModified": "2020-01-27T04:41:00.837Z",
          "resourceType": "User"
        },
        "schemas": [
          "urn:ietf:params:scim:schemas:core:2.0:User"
        ],
        "name": {
          "formatted": "hoge new",
          "familyName": "new",
          "givenName": "hoge"
        },
        "id": "b9de83ef-e010-4282-b7cf-2e2d98b03ee1",
        "idpType": "cloud_directory"
      }
    }
  ],
  "attributes": {
    "tel": "06-xxxx-xxxx",
    "state": "osaka"
  }
}

```