Repository: getmoto/moto

Issue:
cognito_idp.list_users() allows to use custom attributes in Filter parameter
Per [boto3 docs](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/cognito-idp.html#CognitoIdentityProvider.Client.list_users), an attribute filter can be used only for a specific set of attribute names:

> You can only search for the following standard attributes:
> 
>     username (case-sensitive)
>     email
>     phone_number
>     name
>     given_name
>     family_name
>     preferred_username
>     cognito:user_status (called Status in the Console) (case-insensitive)
>     status (called **Enabled** in the Console) (case-sensitive)
>     sub
> 
> Custom attributes are not searchable.

However, moto implementation allows any attribute.

```python
@mock_cognitoidp
def test_list_users():
    conn = boto3.client("cognito-idp", "us-west-2")

    username = str(uuid.uuid4())
    user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"]
    conn.admin_create_user(UserPoolId=user_pool_id, Username=username)
    result = conn.list_users(UserPoolId=user_pool_id)
    result["Users"].should.have.length_of(1)
    result["Users"][0]["Username"].should.equal(username)

    username_bis = str(uuid.uuid4())
    conn.admin_create_user(
        UserPoolId=user_pool_id,
        Username=username_bis,
        UserAttributes=[{"Name": "phone_number", "Value": "+33666666666"}, {"Name": "custom:foo", "Value": "bar"}],
    )
    result = conn.list_users(
        UserPoolId=user_pool_id, Filter='phone_number="+33666666666"'
    )
    result["Users"].should.have.length_of(1)
    result["Users"][0]["Username"].should.equal(username_bis)

    result = conn.list_users(
        UserPoolId=user_pool_id, Filter='custom:foo = "bar"'
    )
    result["Users"].should.have.length_of(1)  # <-- PASSED
    result["Users"][0]["Username"].should.equal(username_bis)
```

In order to properly reflect behavior of `cognito-idp` client, filter using any attribute name outside of the list quoted should be rejected by throwing an exception:
```
botocore.errorfactory.InvalidParameterException: An error occurred (InvalidParameterException) when calling the ListUsers operation: Invalid search attribute: custom:foo
```


Discussion:

Resolving patch:
diff --git a/moto/cognitoidp/responses.py b/moto/cognitoidp/responses.py
--- a/moto/cognitoidp/responses.py
+++ b/moto/cognitoidp/responses.py
@@ -334,12 +334,26 @@ def list_users(self):
                 "username": lambda u: u.username,
             }
             comparisons = {"=": lambda x, y: x == y, "^=": lambda x, y: x.startswith(y)}
+            allowed_attributes = [
+                "username",
+                "email",
+                "phone_number",
+                "name",
+                "given_name",
+                "family_name",
+                "preferred_username",
+                "cognito:user_status",
+                "status",
+                "sub",
+            ]
 
             match = re.match(r"([\w:]+)\s*(=|\^=)\s*\"(.*)\"", filt)
             if match:
                 name, op, value = match.groups()
             else:
                 raise InvalidParameterException("Error while parsing filter")
+            if name not in allowed_attributes:
+                raise InvalidParameterException(f"Invalid search attribute: {name}")
             compare = comparisons[op]
             users = [
                 user


Test patch:
diff --git a/tests/test_cognitoidp/test_cognitoidp.py b/tests/test_cognitoidp/test_cognitoidp.py
--- a/tests/test_cognitoidp/test_cognitoidp.py
+++ b/tests/test_cognitoidp/test_cognitoidp.py
@@ -1259,6 +1259,19 @@ def _assert_filter_parsing_error(exc):
     assert err["Message"].should.equal("Error while parsing filter")
 
 
+@mock_cognitoidp
+def test_list_users_invalid_attributes():
+    conn = boto3.client("cognito-idp", "us-west-2")
+
+    user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"]
+
+    with pytest.raises(conn.exceptions.InvalidParameterException) as exc:
+        conn.list_users(UserPoolId=user_pool_id, Filter='custom:foo = "bar"')
+    err = exc.value.response["Error"]
+    assert err["Code"].should.equal("InvalidParameterException")
+    assert err["Message"].should.equal("Invalid search attribute: custom:foo")
+
+
 @mock_cognitoidp
 def test_list_users_inherent_attributes():
     conn = boto3.client("cognito-idp", "us-west-2")