AWS Cognitoのユーザー属性をAWS CLIから変更する

## 背景

AWS Cognitoユーザープール管理下のユーザの標準属性・カスタム属性値をAdmin権限的に変更するときに少し手間取ったので自分用メモとして...。
2020/03/12現在、コンソールからこの機能は利用できないので、AWS CLIで操作した場合の方法です。この操作が行えるIAM権限が必要となりますのでご注意ください。

## 環境

- AWS CLI: `aws-cli/1.15.11 Python/2.7.16 Darwin/19.2.0 botocore/1.12.33`
- AWS CLI Command Referenceバージョン： 1.18.19


## 解説

今回利用するコマンドの公式ドキュメントです。

[admin-update-user-attributes -- AWS CLI Command Reference](https://docs.aws.amazon.com/cli/latest/reference/cognito-idp/admin-update-user-attributes.html)

以下に、属性の値によって考慮すべきところを説明しています。


### 属性の値に階層構造がない場合

例えば下記のような一対一のキーバリューになったデータ構造の場合、標準属性・カスタム属性それぞれの変更は比較的簡単です。

```json
{
  "nickname": "USISI"
}
```

#### 標準属性の値を変更する

```shell
$ aws cognito-idp admin-update-user-attributes \
--user-pool-id ap-northeast-1_xxxxxxxxx \
--username USI \
--user-attributes Name="nickname",Value="USISI"
```

#### カスタム属性の値を変更する

 `--user-attributes`で属性名を指定する際にプレフィックス `custom:` をつけます。
カスタム属性の変更可能性は、カスタム属性作成時に指定します。変更不可な値として作成したカスタム属性は書き換えられないはずですのでご注意ください。

```shell
$ aws cognito-idp admin-update-user-attributes \
--user-pool-id ap-northeast-1_xxxxxxxxx \
--username USI \
--user-attributes Name="custom:timeZone",Value="+9:00"
```

##### 補足

上記の属性名指定部分の記述方法はAWS CLIが用意している短縮構文（shorthand syntax）に当たるもので、ネストされていない（階層構造がない）データの入力に非常に便利です。

参考：[AWS CLI を使用した短縮構文の使用 -- AWS Command Line Interface](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-usage-shorthand.html)

### 属性の値に階層構造がある場合

上記の短縮構文は、階層構造のあるデータの入力には使えないように思われます（少なくとも私が試した感じでは...）。

「そもそも、属性の値に階層構造を持たせるべきではない」というご意見もあるかと思いますが、実際に持たせられるので、こちらのパターンについても触れます（というか自分が使っているので...）。


例えば下記のようなデータ構造を短縮構文で書こうとすると、どう頑張ってもシンタックスエラーに...。

```json
{
  "custom:userPreferences": {
    "favoriteNums": [
      5, 11
    ],
    "luckyNums": [],
    "colors": {
      "warm": [],
      "cold": [
        "cyan", "indigo"
      ]
    }
  }
}
```

こういった場合、**AWS CLIスケルトンで**JSON(or YAML)形式の設定ファイルを出してから、このファイルを入力に利用するのが便利です。

参考：[JSON または YAML 入力ファイルからの AWS CLI スケルトンと入力パラメータの生成 -- AWS Command Line Interface](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-usage-skeleton.html)

- `--generate-cli-skeleton > {出力先}`で、 `admin-update-user-attributes`コマンド入力用の設定ファイルを出力（出力ファイルの名前は可変）

```shell
$ aws cognito-idp admin-update-user-attributes --generate-cli-skeleton > skel_cognito.json

# ディレクトリの指定も可能
$ aws cognito-idp admin-update-user-attributes --generate-cli-skeleton > ~/Documents/skel_cognito.json
```

- 作成したスケルトンファイルを編集する

例えば、先ほど例に出した階層構造のデータを記述すると、こんな感じ

```json:skel_cognito.json
{
    "UserPoolId": "ap-northeast-1_xxxxxxxxx", 
    "Username": "USI", 
    "UserAttributes": [
        {
            "Name": "custom:userPreferences", 
            "Value": "{\"favoriteNums\":[5,11],\"luckyNums\":[],\"colors\":{\"warm\":[],\"cold\":[\"cyan\",\"indigo\"]}}"
        }
    ]
}
```

3. `--cli-input-json file://{ファイル名}`で、設定ファイルの内容を適用

```shell
$ aws cognito-idp admin-update-user-attributes --cli-input-json file://skel_cognito.json

# ディレクトリの指定を変えることも可能（上と同じコマンドです）
$ aws cognito-idp admin-update-user-attributes --cli-input-json file://~/Documents/skel_cognito.json
```


#### 補足

カスタム属性に階層構造のあるデータを持たせた背景に以下のようなことがあります。

- カスタム属性は文字列型か数値型しか持たせられない
- 作成できるカスタム属性の数は最大25（現在）
- JSON文字列のように処理することができる

これがベストプラクティスかは私にはわかりません...。また、属性値の最大文字列長に注意する必要があります。


## おまけ

### 

##### ユーザーのSMS認証・メール認証をパスする

`admin-update-user-attributes`では属性の他に認証ルートにも手が加えられるようです。

```shell
$ aws cognito-idp admin-update-user-attributes \
--user-pool-id ap-northeast-1_xxxxxxxxx \
--username USI \
--user-attributes Name="email_verified",Value=true
```

### ユーザープール内のユーザーを指定して属性含むデータを取得する

- 公式ドキュメント：[admin-get-user -- AWS CLI Command Reference](https://docs.aws.amazon.com/cli/latest/reference/cognito-idp/admin-get-user.html)

```shell
$ aws cognito-idp admin-get-user --user-pool-id ap-northeast-1_xxxxxxxxx --username USI
{
    "Username": "USI", 
    "Enabled": true, 
    "UserStatus": "CONFIRMED", 
    "UserCreateDate": 1584024000.000, 
    "UserAttributes": [
        # (略)
        {
            "Name": "email", 
            "Value": "USI@usiusi.com"
        }, 
        {
            "Name": "custom:userPreferences", 
            "Value": "{\"favoriteNums\":[5,11],\"luckyNums\":[],\"colors\":{\"warm\":[],\"cold\":[\"cyan\",\"indigo\"]}}"
        }
    ], 
    "UserLastModifiedDate": 1584024363.000
}

```

### ユーザープール内のユーザーが自分の属性を変更する

- 公式ドキュメント：[update-user-attributes -- AWS CLI Command Reference](https://docs.aws.amazon.com/cli/latest/reference/cognito-idp/update-user-attributes.html)

こちらは実際に叩いてないのでドキュメントのみで。。
ユーザープールIDやユーザ名の指定が不要で、代わりに自分のCognitoアクセストークンが必要となります。
アクセストークンがそのユーザーと紐づいているから、ユーザ名の指定がいらない、という仕組みです。