diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md index 5d9f18ebf..6a3b80cbd 100644 --- a/IMPLEMENTATION_COVERAGE.md +++ b/IMPLEMENTATION_COVERAGE.md @@ -3447,7 +3447,7 @@ - [X] list_signing_certificates - [ ] list_ssh_public_keys - [X] list_user_policies -- [ ] list_user_tags +- [X] list_user_tags - [X] list_users - [X] list_virtual_mfa_devices - [X] put_group_policy diff --git a/moto/iam/models.py b/moto/iam/models.py index 5bbd9235d..18b3a7a6f 100644 --- a/moto/iam/models.py +++ b/moto/iam/models.py @@ -543,7 +543,7 @@ class Group(BaseModel): class User(BaseModel): - def __init__(self, name, path=None): + def __init__(self, name, path=None, tags=None): self.name = name self.id = random_resource_id() self.path = path if path else "/" @@ -556,6 +556,7 @@ class User(BaseModel): self.password = None self.password_reset_required = False self.signing_certificates = {} + self.tags = tags @property def arn(self): @@ -1421,13 +1422,13 @@ class IAMBackend(BaseBackend): "The group with name {0} cannot be found.".format(group_name) ) - def create_user(self, user_name, path="/"): + def create_user(self, user_name, path="/", tags=None): if user_name in self.users: raise IAMConflictException( "EntityAlreadyExists", "User {0} already exists".format(user_name) ) - user = User(user_name, path) + user = User(user_name, path, tags) self.users[user_name] = user return user @@ -1583,6 +1584,10 @@ class IAMBackend(BaseBackend): user = self.get_user(user_name) return user.policies.keys() + def list_user_tags(self, user_name): + user = self.get_user(user_name) + return user.tags + def put_user_policy(self, user_name, policy_name, policy_json): user = self.get_user(user_name) diff --git a/moto/iam/responses.py b/moto/iam/responses.py index 45bd28c36..06561d4c4 100644 --- a/moto/iam/responses.py +++ b/moto/iam/responses.py @@ -440,8 +440,8 @@ class IamResponse(BaseResponse): def create_user(self): user_name = self._get_param("UserName") path = self._get_param("Path") - - user = iam_backend.create_user(user_name, path) + tags = self._get_multi_param("Tags.member") + user = iam_backend.create_user(user_name, path, tags) template = self.response_template(USER_TEMPLATE) return template.render(action="Create", user=user) @@ -538,6 +538,12 @@ class IamResponse(BaseResponse): template = self.response_template(LIST_USER_POLICIES_TEMPLATE) return template.render(policies=policies) + def list_user_tags(self): + user_name = self._get_param("UserName") + tags = iam_backend.list_user_tags(user_name) + template = self.response_template(LIST_USER_TAGS_TEMPLATE) + return template.render(user_tags=tags or []) + def put_user_policy(self): user_name = self._get_param("UserName") policy_name = self._get_param("PolicyName") @@ -1699,6 +1705,23 @@ LIST_USER_POLICIES_TEMPLATE = """ """ +LIST_USER_TAGS_TEMPLATE = """ + + + {% for tag in user_tags %} + + {{ tag.Key }} + {{ tag.Value }} + + {% endfor %} + + false + + + 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE + +""" + CREATE_ACCESS_KEY_TEMPLATE = """ diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py index 6311dce9c..cabb6d037 100644 --- a/tests/test_iam/test_iam.py +++ b/tests/test_iam/test_iam.py @@ -1737,9 +1737,7 @@ def test_delete_saml_provider(): def test_create_role_defaults(): """Tests default values""" conn = boto3.client("iam", region_name="us-east-1") - conn.create_role( - RoleName="my-role", AssumeRolePolicyDocument="{}", - ) + conn.create_role(RoleName="my-role", AssumeRolePolicyDocument="{}") # Get role: role = conn.get_role(RoleName="my-role")["Role"] @@ -2672,3 +2670,56 @@ def test_get_account_summary(): "GroupsQuota": 300, } ) + + +@mock_iam() +def test_list_user_tags(): + """Tests both setting a tags on a user in create_user and list_user_tags""" + conn = boto3.client("iam", region_name="us-east-1") + conn.create_user(UserName="kenny-bania") + conn.create_user( + UserName="jackie-chiles", Tags=[{"Key": "Sue-Allen", "Value": "Oh-Henry"}] + ) + conn.create_user( + UserName="cosmo", + Tags=[ + {"Key": "Stan", "Value": "The Caddy"}, + {"Key": "like-a", "Value": "glove"}, + ], + ) + + assert conn.list_user_tags(UserName="kenny-bania") == { + "Tags": [], + "IsTruncated": False, + "ResponseMetadata": { + "RequestId": "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE", + "HTTPStatusCode": 200, + "HTTPHeaders": {"server": "amazon.com"}, + "RetryAttempts": 0, + }, + } + + assert conn.list_user_tags(UserName="jackie-chiles") == { + "Tags": [{"Key": "Sue-Allen", "Value": "Oh-Henry"}], + "IsTruncated": False, + "ResponseMetadata": { + "RequestId": "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE", + "HTTPStatusCode": 200, + "HTTPHeaders": {"server": "amazon.com"}, + "RetryAttempts": 0, + }, + } + + assert conn.list_user_tags(UserName="cosmo") == { + "Tags": [ + {"Key": "Stan", "Value": "The Caddy"}, + {"Key": "like-a", "Value": "glove"}, + ], + "IsTruncated": False, + "ResponseMetadata": { + "RequestId": "7a62c49f-347e-4fc4-9331-6e8eEXAMPLE", + "HTTPStatusCode": 200, + "HTTPHeaders": {"server": "amazon.com"}, + "RetryAttempts": 0, + }, + }