diff --git a/moto/cognitoidp/models.py b/moto/cognitoidp/models.py index 46b46c5a9..bb5284f80 100644 --- a/moto/cognitoidp/models.py +++ b/moto/cognitoidp/models.py @@ -355,6 +355,28 @@ class CognitoIdpUser(BaseModel): self.attribute_lookup = flat_attributes self.attributes = expand_attrs(flat_attributes) + def delete_attributes(self, attrs_to_delete): + flat_attributes = flatten_attrs(self.attributes) + wrong_attrs = [] + for attr in attrs_to_delete: + try: + flat_attributes.pop(attr) + except KeyError: + wrong_attrs.append(attr) + if wrong_attrs: + raise InvalidParameterException( + "Invalid user attributes: " + + "\n".join( + [ + f"user.{w}: Attribute does not exist in the schema." + for w in wrong_attrs + ] + ) + + "\\n" + ) + self.attribute_lookup = flat_attributes + self.attributes = expand_attrs(flat_attributes) + class CognitoResourceServer(BaseModel): def __init__(self, user_pool_id, identifier, name, scopes): @@ -975,6 +997,9 @@ class CognitoIdpBackend(BaseBackend): user.update_attributes(attributes) + def admin_delete_user_attributes(self, user_pool_id, username, attributes): + self.admin_get_user(user_pool_id, username).delete_attributes(attributes) + def admin_user_global_sign_out(self, user_pool_id, username): user_pool = self.describe_user_pool(user_pool_id) self.admin_get_user(user_pool_id, username) diff --git a/moto/cognitoidp/responses.py b/moto/cognitoidp/responses.py index 9a6468847..901441f6a 100644 --- a/moto/cognitoidp/responses.py +++ b/moto/cognitoidp/responses.py @@ -478,6 +478,15 @@ class CognitoIdpResponse(BaseResponse): ) return "" + def admin_delete_user_attributes(self): + user_pool_id = self._get_param("UserPoolId") + username = self._get_param("Username") + attributes = self._get_param("UserAttributeNames") + cognitoidp_backends[self.region].admin_delete_user_attributes( + user_pool_id, username, attributes + ) + return "" + def admin_user_global_sign_out(self): user_pool_id = self._get_param("UserPoolId") username = self._get_param("Username") diff --git a/tests/test_cognitoidp/test_cognitoidp.py b/tests/test_cognitoidp/test_cognitoidp.py index 3ba986174..65d145e5d 100644 --- a/tests/test_cognitoidp/test_cognitoidp.py +++ b/tests/test_cognitoidp/test_cognitoidp.py @@ -2443,6 +2443,114 @@ def test_admin_update_user_attributes(): val.should.equal("Jane") +@mock_cognitoidp +def test_admin_delete_user_attributes(): + conn = boto3.client("cognito-idp", "us-east-1") + + username = str(uuid.uuid4()) + user_pool_id = conn.create_user_pool( + PoolName=str(uuid.uuid4()), + Schema=[ + { + "Name": "foo", + "AttributeDataType": "String", + "Mutable": True, + "Required": False, + } + ], + )["UserPool"]["Id"] + + conn.admin_create_user( + UserPoolId=user_pool_id, + Username=username, + UserAttributes=[ + {"Name": "family_name", "Value": "Doe"}, + {"Name": "given_name", "Value": "John"}, + {"Name": "nickname", "Value": "Joe"}, + {"Name": "custom:foo", "Value": "bar"}, + ], + ) + + conn.admin_delete_user_attributes( + UserPoolId=user_pool_id, + Username=username, + UserAttributeNames=["nickname", "custom:foo"], + ) + + user = conn.admin_get_user(UserPoolId=user_pool_id, Username=username) + + user["UserAttributes"].should.have.length_of(3) # family_name, given_name and sub + user["UserAttributes"].should.contain({"Name": "family_name", "Value": "Doe"}) + user["UserAttributes"].should.contain({"Name": "given_name", "Value": "John"}) + user["UserAttributes"].should_not.contain({"Name": "nickname", "Value": "Joe"}) + user["UserAttributes"].should_not.contain({"Name": "custom:foo", "Value": "bar"}) + + +@mock_cognitoidp +def test_admin_delete_user_attributes_non_existing_attribute(): + conn = boto3.client("cognito-idp", "us-east-1") + + 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, + UserAttributes=[ + {"Name": "family_name", "Value": "Doe"}, + {"Name": "given_name", "Value": "John"}, + {"Name": "nickname", "Value": "Joe"}, + ], + ) + + with pytest.raises(ClientError) as exc: + conn.admin_delete_user_attributes( + UserPoolId=user_pool_id, + Username=username, + UserAttributeNames=["nickname", "custom:foo"], + ) + err = exc.value.response["Error"] + err["Code"].should.equal("InvalidParameterException") + err["Message"].should.equal( + "Invalid user attributes: user.custom:foo: Attribute does not exist in the schema.\n" + ) + + +@mock_cognitoidp +def test_admin_delete_user_attributes_non_existing_user(): + conn = boto3.client("cognito-idp", "us-east-1") + + username = str(uuid.uuid4()) + user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"] + + with pytest.raises(ClientError) as exc: + conn.admin_delete_user_attributes( + UserPoolId=user_pool_id, + Username=username, + UserAttributeNames=["nickname", "custom:foo"], + ) + err = exc.value.response["Error"] + err["Code"].should.equal("UserNotFoundException") + err["Message"].should.equal("User does not exist.") + + +@mock_cognitoidp +def test_admin_delete_user_attributes_non_existing_pool(): + conn = boto3.client("cognito-idp", "us-east-1") + + user_pool_id = "us-east-1_aaaaaaaa" + with pytest.raises(ClientError) as exc: + conn.admin_delete_user_attributes( + UserPoolId=user_pool_id, + Username=str(uuid.uuid4()), + UserAttributeNames=["nickname"], + ) + + err = exc.value.response["Error"] + err["Code"].should.equal("ResourceNotFoundException") + err["Message"].should.equal(f"User pool {user_pool_id} does not exist.") + + @mock_cognitoidp def test_resource_server():