4
2

More than 3 years have passed since last update.

【AWS CLI 】Cognitoの一時パスワードの有効期限の切れたユーザの一覧を取得する

Posted at

はじめに

パスワード再発行の挙動確認のため、一時パスワードの有効期限の切れたユーザを取得したかったので試行錯誤した件。

Cognito Console 上ではできなさそうだったので、AWS CLI を用いて取得した。

結論

aws cognito-idp list-users \
  --user-pool-id $YOUR_USER_POOL_ID \
  --output text \
  --query 'Users[? UserCreateDate<=`2021-01-22`
                   && UserStatus==`FORCE_CHANGE_PASSWORD`
                   && Enabled]
                .Attributes[] | [? Name==`email`].[Value] '

※UserCreateDate は、本日付から Cognito の User Pool に設定した一時パスワードの有効期限を引いた日付を指定する。

詳細

一時パスワードの有効期限の切れたユーザをどう表現するか

NOTE: AWS のドキュメントには、この情報は掲載されていなかったため、あくまでも推測と実際に一時パスワードの有効期限が切れていたユーザの情報から判断した。

1. UserCreateDate<=${本日付から Cognito の User Pool に設定した一時パスワードの有効期限を引いた日付}

一時パスワードの有効期限は以下で確認できる。
デフォルトは 7 日

aws cognito-idp describe-user-pool \
  --user-pool-id $YOUR_USER_POOL_ID \
  --query 'UserPool.Policies.PasswordPolicy.TemporaryPasswordValidityDays'

GUI から確認する場合は以下の通り。

  1. Amazon Cognito Console
  2. Manage User Pools
  3. 対象のUser Poolをクリック
  4. General Settings > Policies
  5. How quickly should temporary passwords set by administrators expire if not used? の下の Days to expire を確認

2. UserStatus==FORCE_CHANGE_PASSWORD

こちらは公式のユーザーアカウント確認の概要に記載がある。

image.png

Force Change Password
The user account is confirmed and the user can sign in using a temporary password, but on first sign-in, the user must change his or her password to a new value before doing anything else.

User accounts that are created by an administrator or developer start in this state.

3. Enabled

上の図にある通り、Disabled なユーザはそもそもパスワードの再発行ができないので、今回は除外する。
ちなみに、ユーザの Enabled /Disabled は 2.の UserStatus では管理しておらず、Enabledという Boolean の項目で管理されている。

参考: list-users | Output

上記をどう Query (JMESPath) で表現するか

必要な情報は出揃ったので、上の情報を query に落とし込む。
ここにあるように、list-usersは以下の形式で出力される。

尚、JMESPath はここで試せるため、ここで検証すると楽。

{
  "Users": [
    {
      "Username": "22704aa3-fc10-479a-97eb-2af5806bd327",
      "Enabled": true,
      "UserStatus": "FORCE_CHANGE_PASSWORD",
      "UserCreateDate": "2020-12-25T13:24:26.818000+09:00",
      "UserLastModifiedDate": "2020-12-25T13:33:37.450000+09:00",
      "Attributes": [
        {
          "Name": "sub",
          "Value": "22704aa3-fc10-479a-97eb-2af5806bd327"
        },
        {
          "Name": "email_verified",
          "Value": "true"
        },
        {
          "Name": "email",
          "Value": "mary@example.com"
        }
      ]
    },
    {
      "Username": "22704aa3-fc10-479a-97eb-2af5806bd327",
      "Enabled": true,
      "UserStatus": "FORCE_CHANGE_PASSWORD",
      "UserCreateDate": "2020-12-25T13:24:26.818000+09:00",
      "UserLastModifiedDate": "2020-12-25T13:33:37.450000+09:00",
      "Attributes": [
        {
          "Name": "sub",
          "Value": "22704aa3-fc10-479a-97eb-2af5806bd327"
        },
        {
          "Name": "email_verified",
          "Value": "true"
        },
        {
          "Name": "email",
          "Value": "mary@example.com"
        }
      ]
    },
    {
      "Username": "22704aa3-fc10-479a-97eb-2af5806bd327",
      "Enabled": false,
      "UserStatus": "FORCE_CHANGE_PASSWORD",
      "UserCreateDate": "2020-12-25T13:24:26.818000+09:00",
      "UserLastModifiedDate": "2020-12-25T13:33:37.450000+09:00",
      "Attributes": [
        {
          "Name": "sub",
          "Value": "22704aa3-fc10-479a-97eb-2af5806bd327"
        },
        {
          "Name": "email_verified",
          "Value": "true"
        },
        {
          "Name": "email",
          "Value": "mary@example.com"
        }
      ]
    }
  ]
}

1. ユーザの絞り込み

最初に Users を絞り込みたい。絞り込みは、Filter Expressionを使用する。
Filter Expression は、[? ${任意の式}]で表現される式である。
任意の式には各種演算子(==, '<=', etc)が使える。
今回の例でいうと、任意の式に以下の 1 つの式を入れる必要がある。

  • UserCreateDate<=2021-01-22
  • UserStatus==FORCE_CHANGE_PASSWORD
  • Enabled
    • Enabled は真偽値なので、単体で OK

AND 条件を使いたい場合は、And Expressionを使う。

まとめると、

Users[? UserCreateDate<=`2021-01-22`
      && UserStatus==`FORCE_CHANGE_PASSWORD`
      && Enabled]

でユーザを絞り込める。
絞り込んだ結果は以下の通り。
"Enabled": true,のデータのみ絞り込まれる。

[
  {
    "Username": "22704aa3-fc10-479a-97eb-2af5806bd327",
    "Enabled": true,
    "UserStatus": "FORCE_CHANGE_PASSWORD",
    "UserCreateDate": "2020-12-25T13:24:26.818000+09:00",
    "UserLastModifiedDate": "2020-12-25T13:33:37.450000+09:00",
    "Attributes": [
      {
        "Name": "sub",
        "Value": "22704aa3-fc10-479a-97eb-2af5806bd327"
      },
      {
        "Name": "email_verified",
        "Value": "true"
      },
      {
        "Name": "email",
        "Value": "mary@example.com"
      }
    ]
  },
  {
    "Username": "22704aa3-fc10-479a-97eb-2af5806bd327",
    "Enabled": true,
    "UserStatus": "FORCE_CHANGE_PASSWORD",
    "UserCreateDate": "2020-12-25T13:24:26.818000+09:00",
    "UserLastModifiedDate": "2020-12-25T13:33:37.450000+09:00",
    "Attributes": [
      {
        "Name": "sub",
        "Value": "22704aa3-fc10-479a-97eb-2af5806bd327"
      },
      {
        "Name": "email_verified",
        "Value": "true"
      },
      {
        "Name": "email",
        "Value": "mary@example.com"
      }
    ]
  }
]

2. email の出力

この時点で欲しかった情報は得られたが、ここから一歩踏み込んで、絞り込まれたユーザの email のみを出力するようにする。
まず、絞り込んだユーザのAttributesのみを出力する。

Users[? UserCreateDate<=`2021-01-22`
     && UserStatus==`FORCE_CHANGE_PASSWORD`
     && Enabled]
.Attributes
[
  [
    {
      "Name": "sub",
      "Value": "22704aa3-fc10-479a-97eb-2af5806bd327"
    },
    {
      "Name": "email_verified",
      "Value": "true"
    },
    {
      "Name": "email",
      "Value": "mary@example.com"
    }
  ],
  [
    {
      "Name": "sub",
      "Value": "22704aa3-fc10-479a-97eb-2af5806bd327"
    },
    {
      "Name": "email_verified",
      "Value": "true"
    },
    {
      "Name": "email",
      "Value": "mary@example.com"
    }
  ]
]

出力された Attributes のうち、email のみを取得したい。
email のみ、は[? Name=='email']で表現できる。
ただ、現状だと二重配列になっており、オブジェクト要素にアクセスできないため、フラット演算子を使って配列をフラットにする。
フラット演算子は、添字(1,2,3...)を使うとその添字の値のみを取得してフラットな配列を作るが、添字の無い空配列を使うと全件取得する。

空配列
Users[? UserCreateDate<=`2021-01-22`
     && UserStatus==`FORCE_CHANGE_PASSWORD`
     && Enabled]
.Attributes[]

各ユーザの 全ての Attributes を取得する。

[
  {
    "Name": "sub",
    "Value": "22704aa3-fc10-479a-97eb-2af5806bd327"
  },
  {
    "Name": "email_verified",
    "Value": "true"
  },
  {
    "Name": "email",
    "Value": "mary@example.com"
  },
  {
    "Name": "sub",
    "Value": "22704aa3-fc10-479a-97eb-2af5806bd327"
  },
  {
    "Name": "email_verified",
    "Value": "true"
  },
  {
    "Name": "email",
    "Value": "mary@example.com"
  }
]
添字 == 0 (1 番目)
Users[? UserCreateDate<=`2021-01-22`
     && UserStatus==`FORCE_CHANGE_PASSWORD`
     && Enabled]
.Attributes[0]

各ユーザの Attributes のうち、1 番目の要素を取得する。

[
  {
    "Name": "sub",
    "Value": "22704aa3-fc10-479a-97eb-2af5806bd327"
  },
  {
    "Name": "sub",
    "Value": "22704aa3-fc10-479a-97eb-2af5806bd327"
  }
]
添字 == 2 (3 番目)

今回の場合、"Name": "email"な要素は 3 番目にあるため、添字に 2 を指定することで email だけを抽出することは可能である。
ただし、今後 Attributes が増えた際に困るので、今回は空配列[]を用いることにする。

Users[? UserCreateDate<=`2021-01-22`
     && UserStatus==`FORCE_CHANGE_PASSWORD`
     && Enabled]
.Attributes[2]

各ユーザの Attributes のうち、1 番目の要素を取得する。

[
  {
    "Name": "email",
    "Value": "mary@example.com"
  },
  {
    "Name": "email",
    "Value": "mary@example.com"
  }
]

次に、[? Name=='email']で絞り込みたい。
しかし、素直に Filter Expression を使っても、期待した結果が返ってこない。

Users[? UserCreateDate<=`2021-01-22`
     && UserStatus==`FORCE_CHANGE_PASSWORD`
     && Enabled]
.Attributes[][? Name==`email`]
[]

これは、投影(Projection)と呼ばれる現状です。
Wildcard Expressionsには以下のようにあります。

The [] syntax (referred to as a list wildcard expression) will return all the elements in a list. Any subsequent expressions will be evaluated against each individual element. Given an expression [].child-expr, and a list of N elements, the evaluation of this expression would be [child-expr(el-0), child-expr(el-2), ..., child-expr(el-N)]. This is referred to as a projection, and the child-expr expression is projected onto the elements of the resulting list.

Once a projection has been created, all subsequent expressions are projected onto the resulting list.

The * syntax (referred to as a hash wildcard expression) will return a list of the hash element’s values. Any subsequent expression will be evaluated against each individual element in the list (this is also referred to as a projection).

簡単にいうと、任意の条件で絞り込んだ場合、後続の式は絞り込んだ要素に対して適用する。
今回の場合、User[? ~~][]を用いて絞り込みを行っている状態なので、後続の式 (今回であれば| [? Name=='email']) は絞り込まれた各要素である {"Name": XX, "Value": YY}に対して適用される。

つまり、

Users[? UserCreateDate<=`2021-01-22`
     && UserStatus==`FORCE_CHANGE_PASSWORD`
     && Enabled]
.Attributes[].Name

とすると、各要素の Name プロパティが出力される形となる。

["sub", "email_verified", "email", "sub", "email_verified", "email"]

この投影状態を無視し、出力されたオブジェクト全体に対して絞り込みを行いたい場合、Pipe Expressionsを使用する。

Users[? UserCreateDate<=`2021-01-22`
     && UserStatus==`FORCE_CHANGE_PASSWORD`
     && Enabled]
.Attributes[] | [? Name==`email`]
[
  {
    "Name": "email",
    "Value": "mary@example.com"
  },
  {
    "Name": "email",
    "Value": "mary@example.com"
  }
]

ようやく email 要素だけを絞り込むことができた。

ここらへんの挙動について以下の記事にまとめた見たので読んでいただけると嬉しい。

【JMESPath】foo[*][0]foo[*] | [0] の違い

さて、この状態も各要素が投影されている状態なので、.Valueで各 Value 要素だけを絞り込むことができる。

Users[? UserCreateDate<=`2021-01-22`
     && UserStatus==`FORCE_CHANGE_PASSWORD`
     && Enabled]
.Attributes[] | [? Name==`email`].Value
["mary@example.com", "mary@example.com"]

出力方法

最後に出力方法を変更する。
AWS CLI は、デフォルトで JSON 形式で出力される。しかし、今回の場合は"[]も無い純粋なテキストデータがほしい。
その場合、--output textオプションを用いると、出力がテキスト形式になる。

aws cognito-idp list-users \
  --user-pool-id $YOUR_USER_POOL_ID \
  --output text \
  --query 'Users[? UserCreateDate<=`2021-01-22`
                   && UserStatus==`FORCE_CHANGE_PASSWORD`
                   && Enabled]
                .Attributes[] | [? Name==`email`].Value'
mary@example.com       mary@example.com

……。

これは AWS CLI の仕様になります。

テキストの出力形式のヒントには以下のようにある。

テキスト出力を行い、--query パラメータを使用して出力を単一のフィールドにフィルタリングすると、出力は 1 行のタブ区切り値になります。次の例に示すように、各値を別々の行に入れるには、出力フィールドを角括弧で囲みます。

つまり、1 つの配列内の要素はタブ区切りで 1 行で表示されてしまうようです。
これの解決策は、query の最後で指定したValueを括弧で括れば良い。

aws cognito-idp list-users \
  --user-pool-id $YOUR_USER_POOL_ID \
  --output text \
  --query 'Users[? UserCreateDate<=`2021-01-22`
                   && UserStatus==`FORCE_CHANGE_PASSWORD`
                   && Enabled]
                .Attributes[] | [? Name==`email`].[Value]'
mary@example.com
mary@example.com

JSON だとこのように出力されている。

[["mary@example.com"], ["mary@example.com"]]

蛇足

重複を排除したい場合は、JMESPath では対応できないので、uniq コマンドを使用する。

aws cognito-idp list-users \
  --user-pool-id $YOUR_USER_POOL_ID \
  --output text \
  --query 'Users[? UserCreateDate<=`2021-01-22`
                   && UserStatus==`FORCE_CHANGE_PASSWORD`
                   && Enabled]
                .Attributes[] | [? Name==`email`].[Value]' \
  | sort | uniq
mary@example.com

参考

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2