From ff6d7a13c0124a56510b68a91bcda2324558841e Mon Sep 17 00:00:00 2001 From: Sam Attridge Date: Wed, 3 Nov 2021 20:58:40 +0000 Subject: [PATCH] Added policy tagging that was previously marked as missing (#4520) --- IMPLEMENTATION_COVERAGE.md | 6 +- moto/iam/models.py | 55 +++- moto/iam/responses.py | 71 ++++- tests/test_iam/test_iam.py | 540 +++++++++++++++++++++++++++++++++++++ 4 files changed, 663 insertions(+), 9 deletions(-) diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md index 2c298934c..fb2e5f17d 100644 --- a/IMPLEMENTATION_COVERAGE.md +++ b/IMPLEMENTATION_COVERAGE.md @@ -2364,7 +2364,7 @@ - [X] list_open_id_connect_providers - [X] list_policies - [ ] list_policies_granting_service_access -- [ ] list_policy_tags +- [X] list_policy_tags - [X] list_policy_versions - [X] list_role_policies - [X] list_role_tags @@ -2397,7 +2397,7 @@ - [ ] tag_instance_profile - [ ] tag_mfa_device - [ ] tag_open_id_connect_provider -- [ ] tag_policy +- [X] tag_policy - [X] tag_role - [ ] tag_saml_provider - [ ] tag_server_certificate @@ -2405,7 +2405,7 @@ - [ ] untag_instance_profile - [ ] untag_mfa_device - [ ] untag_open_id_connect_provider -- [ ] untag_policy +- [X] untag_policy - [X] untag_role - [ ] untag_saml_provider - [ ] untag_server_certificate diff --git a/moto/iam/models.py b/moto/iam/models.py index 922179bf1..e713c7f0b 100644 --- a/moto/iam/models.py +++ b/moto/iam/models.py @@ -108,7 +108,7 @@ class Policy(CloudFormationModel): self.description = description or "" self.id = random_policy_id() self.path = path or "/" - self.tags = {tag["Key"]: tag["Value"] for tag in tags or []} + self.tags = tags if default_version_id: self.default_version_id = default_version_id @@ -141,6 +141,9 @@ class Policy(CloudFormationModel): def updated_iso_8601(self): return iso_8601_datetime_with_milliseconds(self.update_date) + def get_tags(self): + return [self.tags[tag] for tag in self.tags] + class SAMLProvider(BaseModel): def __init__(self, name, saml_metadata_document=None): @@ -284,6 +287,7 @@ class ManagedPolicy(Policy, CloudFormationModel): "awsRegion": "global", "availabilityZone": "Not Applicable", "resourceCreationTime": str(self.create_date), + "tags": self.tags, "configuration": { "policyName": self.name, "policyId": self.id, @@ -296,6 +300,12 @@ class ManagedPolicy(Policy, CloudFormationModel): "description": self.description, "createDate": str(self.create_date.isoformat()), "updateDate": str(self.create_date.isoformat()), + "tags": list( + map( + lambda key: {"key": key, "value": self.tags[key]["Value"]}, + self.tags, + ) + ), "policyVersionList": list( map( lambda version: { @@ -331,12 +341,14 @@ class ManagedPolicy(Policy, CloudFormationModel): group_names = properties.get("Groups", []) user_names = properties.get("Users", []) role_names = properties.get("Roles", []) + tags = properties.get("Tags", {}) policy = iam_backend.create_policy( description=description, path=path, policy_document=policy_document, policy_name=name, + tags=tags, ) for group_name in group_names: iam_backend.attach_group_policy( @@ -1568,16 +1580,17 @@ class IAMBackend(BaseBackend): raise IAMNotFoundException("Policy {0} was not found.".format(policy_arn)) policy.detach_from(self.get_user(user_name)) - def create_policy(self, description, path, policy_document, policy_name, tags=None): + def create_policy(self, description, path, policy_document, policy_name, tags): iam_policy_document_validator = IAMPolicyDocumentValidator(policy_document) iam_policy_document_validator.validate() + clean_tags = self._tag_verification(tags) policy = ManagedPolicy( policy_name, description=description, document=policy_document, path=path, - tags=tags, + tags=clean_tags, ) if policy.arn in self.managed_policies: raise EntityAlreadyExists( @@ -1849,6 +1862,42 @@ class IAMBackend(BaseBackend): role.tags.pop(ref_key, None) + def list_policy_tags(self, policy_arn, marker, max_items=100): + policy = self.get_policy(policy_arn) + + max_items = int(max_items) + tag_index = sorted(policy.tags) + start_idx = int(marker) if marker else 0 + + tag_index = tag_index[start_idx : start_idx + max_items] + + if len(policy.tags) <= (start_idx + max_items): + marker = None + else: + marker = str(start_idx + max_items) + + # Make the tag list of dict's: + tags = [policy.tags[tag] for tag in tag_index] + + return tags, marker + + def tag_policy(self, policy_arn, tags): + clean_tags = self._tag_verification(tags) + policy = self.get_policy(policy_arn) + policy.tags.update(clean_tags) + + def untag_policy(self, policy_arn, tag_keys): + if len(tag_keys) > 50: + raise TooManyTags(tag_keys, param="tagKeys") + + policy = self.get_policy(policy_arn) + + for key in tag_keys: + ref_key = key.lower() + self._validate_tag_key(key, exception_param="tagKeys") + + policy.tags.pop(ref_key, None) + def create_policy_version(self, policy_arn, policy_document, set_as_default): iam_policy_document_validator = IAMPolicyDocumentValidator(policy_document) iam_policy_document_validator.validate() diff --git a/moto/iam/responses.py b/moto/iam/responses.py index 617d96dcf..6d5261037 100644 --- a/moto/iam/responses.py +++ b/moto/iam/responses.py @@ -308,6 +308,34 @@ class IamResponse(BaseResponse): template = self.response_template(LIST_POLICY_VERSIONS_TEMPLATE) return template.render(policy_versions=policy_versions) + def list_policy_tags(self): + policy_arn = self._get_param("PolicyArn") + marker = self._get_param("Marker") + max_items = self._get_param("MaxItems", 100) + + tags, marker = iam_backend.list_policy_tags(policy_arn, marker, max_items) + + template = self.response_template(LIST_POLICY_TAG_TEMPLATE) + return template.render(tags=tags, marker=marker) + + def tag_policy(self): + policy_arn = self._get_param("PolicyArn") + tags = self._get_multi_param("Tags.member") + + iam_backend.tag_policy(policy_arn, tags) + + template = self.response_template(TAG_POLICY_TEMPLATE) + return template.render() + + def untag_policy(self): + policy_arn = self._get_param("PolicyArn") + tag_keys = self._get_multi_param("TagKeys.member") + + iam_backend.untag_policy(policy_arn, tag_keys) + + template = self.response_template(UNTAG_POLICY_TEMPLATE) + return template.render() + def delete_policy_version(self): policy_arn = self._get_param("PolicyArn") version_id = self._get_param("VersionId") @@ -1129,14 +1157,16 @@ GET_POLICY_TEMPLATE = """ {{ policy.attachment_count }} {{ policy.created_iso_8601 }} {{ policy.updated_iso_8601 }} + {% if policy.tags %} - {% for tag_key, tag_value in policy.tags.items() %} + {% for tag in policy.get_tags() %} - {{ tag_key }} - {{ tag_value }} + {{ tag['Key'] }} + {{ tag['Value'] }} {% endfor %} + {% endif %} @@ -2495,6 +2525,41 @@ UNTAG_ROLE_TEMPLATE = """ + + EXAMPLE8-90ab-cdef-fedc-ba987EXAMPLE + +""" + + +LIST_POLICY_TAG_TEMPLATE = """ + + {{ 'true' if marker else 'false' }} + {% if marker %} + {{ marker }} + {% endif %} + + {% for tag in tags %} + + {{ tag['Key'] }} + {{ tag['Value'] }} + + {% endfor %} + + + + EXAMPLE8-90ab-cdef-fedc-ba987EXAMPLE + +""" + + +UNTAG_POLICY_TEMPLATE = """ + + EXAMPLE8-90ab-cdef-fedc-ba987EXAMPLE + +""" + + CREATE_OPEN_ID_CONNECT_PROVIDER_TEMPLATE = """ {{ open_id_provider.arn }} diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py index faf0cfcda..041116a11 100644 --- a/tests/test_iam/test_iam.py +++ b/tests/test_iam/test_iam.py @@ -960,6 +960,543 @@ def test_delete_default_policy_version(): ) +@mock_iam() +def test_create_policy_with_tags(): + conn = boto3.client("iam", region_name="us-east-1") + conn.create_policy( + PolicyName="TestCreatePolicyWithTags1", + PolicyDocument=MOCK_POLICY, + Tags=[ + {"Key": "somekey", "Value": "somevalue"}, + {"Key": "someotherkey", "Value": "someothervalue"}, + ], + Description="testing", + ) + + # Get policy: + policy = conn.get_policy( + PolicyArn="arn:aws:iam::{}:policy/{}".format( + ACCOUNT_ID, "TestCreatePolicyWithTags1" + ) + )["Policy"] + assert len(policy["Tags"]) == 2 + assert policy["Tags"][0]["Key"] == "somekey" + assert policy["Tags"][0]["Value"] == "somevalue" + assert policy["Tags"][1]["Key"] == "someotherkey" + assert policy["Tags"][1]["Value"] == "someothervalue" + assert policy["Description"] == "testing" + + +@mock_iam() +def test_create_policy_with_empty_tag_value(): + conn = boto3.client("iam", region_name="us-east-1") + + # Empty is good: + conn.create_policy( + PolicyName="TestCreatePolicyWithTags2", + PolicyDocument=MOCK_POLICY, + Tags=[{"Key": "somekey", "Value": ""}], + ) + tags = conn.list_policy_tags( + PolicyArn="arn:aws:iam::{}:policy/{}".format( + ACCOUNT_ID, "TestCreatePolicyWithTags2" + ) + ) + assert len(tags["Tags"]) == 1 + assert tags["Tags"][0]["Key"] == "somekey" + assert tags["Tags"][0]["Value"] == "" + + +@mock_iam() +def test_create_policy_with_too_many_tags(): + conn = boto3.client("iam", region_name="us-east-1") + + # With more than 50 tags: + with pytest.raises(ClientError) as ce: + too_many_tags = list( + map(lambda x: {"Key": str(x), "Value": str(x)}, range(0, 51)) + ) + conn.create_policy( + PolicyName="TestCreatePolicyWithTags3", + PolicyDocument=MOCK_POLICY, + Tags=too_many_tags, + ) + assert ( + "failed to satisfy constraint: Member must have length less than or equal to 50." + in ce.value.response["Error"]["Message"] + ) + + +@mock_iam() +def test_create_policy_with_duplicate_tag(): + conn = boto3.client("iam", region_name="us-east-1") + + # With a duplicate tag: + with pytest.raises(ClientError) as ce: + conn.create_policy( + PolicyName="TestCreatePolicyWithTags3", + PolicyDocument=MOCK_POLICY, + Tags=[{"Key": "0", "Value": ""}, {"Key": "0", "Value": ""}], + ) + assert ( + "Duplicate tag keys found. Please note that Tag keys are case insensitive." + in ce.value.response["Error"]["Message"] + ) + + +@mock_iam() +def test_create_policy_with_duplicate_tag_different_casing(): + conn = boto3.client("iam", region_name="us-east-1") + + # Duplicate tag with different casing: + with pytest.raises(ClientError) as ce: + conn.create_policy( + PolicyName="TestCreatePolicyWithTags3", + PolicyDocument=MOCK_POLICY, + Tags=[{"Key": "a", "Value": ""}, {"Key": "A", "Value": ""}], + ) + assert ( + "Duplicate tag keys found. Please note that Tag keys are case insensitive." + in ce.value.response["Error"]["Message"] + ) + + +@mock_iam() +def test_create_policy_with_tag_containing_large_key(): + conn = boto3.client("iam", region_name="us-east-1") + + # With a really big key: + with pytest.raises(ClientError) as ce: + conn.create_policy( + PolicyName="TestCreatePolicyWithTags3", + PolicyDocument=MOCK_POLICY, + Tags=[{"Key": "0" * 129, "Value": ""}], + ) + assert ( + "Member must have length less than or equal to 128." + in ce.value.response["Error"]["Message"] + ) + + +@mock_iam() +def test_create_policy_with_tag_containing_large_value(): + conn = boto3.client("iam", region_name="us-east-1") + + # With a really big value: + with pytest.raises(ClientError) as ce: + conn.create_policy( + PolicyName="TestCreatePolicyWithTags3", + PolicyDocument=MOCK_POLICY, + Tags=[{"Key": "0", "Value": "0" * 257}], + ) + assert ( + "Member must have length less than or equal to 256." + in ce.value.response["Error"]["Message"] + ) + + +@mock_iam() +def test_create_policy_with_tag_containing_invalid_character(): + conn = boto3.client("iam", region_name="us-east-1") + + # With an invalid character: + with pytest.raises(ClientError) as ce: + conn.create_policy( + PolicyName="TestCreatePolicyWithTags3", + PolicyDocument=MOCK_POLICY, + Tags=[{"Key": "NOWAY!", "Value": ""}], + ) + assert ( + "Member must satisfy regular expression pattern: [\\p{L}\\p{Z}\\p{N}_.:/=+\\-@]+" + in ce.value.response["Error"]["Message"] + ) + + +@mock_iam() +def test_create_policy_with_no_tags(): + """Tests both the tag_policy and get_policy_tags capability""" + conn = boto3.client("iam", region_name="us-east-1") + conn.create_policy(PolicyName="TestTagPolicy", PolicyDocument=MOCK_POLICY) + + # Get without tags: + policy = conn.get_policy( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestTagPolicy") + )["Policy"] + assert not policy.get("Tags") + + +@mock_iam() +def test_get_policy_with_tags(): + conn = boto3.client("iam", region_name="us-east-1") + conn.create_policy(PolicyName="TestTagPolicy", PolicyDocument=MOCK_POLICY) + conn.tag_policy( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestTagPolicy"), + Tags=[ + {"Key": "somekey", "Value": "somevalue"}, + {"Key": "someotherkey", "Value": "someothervalue"}, + ], + ) + + # Get policy: + policy = conn.get_policy( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestTagPolicy") + )["Policy"] + assert len(policy["Tags"]) == 2 + assert policy["Tags"][0]["Key"] == "somekey" + assert policy["Tags"][0]["Value"] == "somevalue" + assert policy["Tags"][1]["Key"] == "someotherkey" + assert policy["Tags"][1]["Value"] == "someothervalue" + + +@mock_iam() +def test_list_policy_tags(): + conn = boto3.client("iam", region_name="us-east-1") + conn.create_policy(PolicyName="TestTagPolicy", PolicyDocument=MOCK_POLICY) + conn.tag_policy( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestTagPolicy"), + Tags=[ + {"Key": "somekey", "Value": "somevalue"}, + {"Key": "someotherkey", "Value": "someothervalue"}, + ], + ) + + # List_policy_tags: + tags = conn.list_policy_tags( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestTagPolicy") + ) + assert len(tags["Tags"]) == 2 + assert tags["Tags"][0]["Key"] == "somekey" + assert tags["Tags"][0]["Value"] == "somevalue" + assert tags["Tags"][1]["Key"] == "someotherkey" + assert tags["Tags"][1]["Value"] == "someothervalue" + assert not tags["IsTruncated"] + assert not tags.get("Marker") + + +@mock_iam() +def test_list_policy_tags_pagination(): + conn = boto3.client("iam", region_name="us-east-1") + conn.create_policy(PolicyName="TestTagPolicy", PolicyDocument=MOCK_POLICY) + conn.tag_policy( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestTagPolicy"), + Tags=[ + {"Key": "somekey", "Value": "somevalue"}, + {"Key": "someotherkey", "Value": "someothervalue"}, + ], + ) + + # Test pagination: + tags = conn.list_policy_tags( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestTagPolicy"), + MaxItems=1, + ) + assert len(tags["Tags"]) == 1 + assert tags["IsTruncated"] + assert tags["Tags"][0]["Key"] == "somekey" + assert tags["Tags"][0]["Value"] == "somevalue" + assert tags["Marker"] == "1" + + tags = conn.list_policy_tags( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestTagPolicy"), + Marker=tags["Marker"], + ) + assert len(tags["Tags"]) == 1 + assert tags["Tags"][0]["Key"] == "someotherkey" + assert tags["Tags"][0]["Value"] == "someothervalue" + assert not tags["IsTruncated"] + assert not tags.get("Marker") + + +@mock_iam() +def test_updating_existing_tag(): + conn = boto3.client("iam", region_name="us-east-1") + conn.create_policy(PolicyName="TestTagPolicy", PolicyDocument=MOCK_POLICY) + conn.tag_policy( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestTagPolicy"), + Tags=[ + {"Key": "somekey", "Value": "somevalue"}, + {"Key": "someotherkey", "Value": "someothervalue"}, + ], + ) + + # Test updating an existing tag: + conn.tag_policy( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestTagPolicy"), + Tags=[{"Key": "somekey", "Value": "somenewvalue"}], + ) + tags = conn.list_policy_tags( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestTagPolicy") + ) + assert len(tags["Tags"]) == 2 + assert tags["Tags"][0]["Key"] == "somekey" + assert tags["Tags"][0]["Value"] == "somenewvalue" + + +@mock_iam() +def test_updating_existing_tag_with_empty_value(): + conn = boto3.client("iam", region_name="us-east-1") + conn.create_policy(PolicyName="TestTagPolicy", PolicyDocument=MOCK_POLICY) + conn.tag_policy( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestTagPolicy"), + Tags=[ + {"Key": "somekey", "Value": "somevalue"}, + {"Key": "someotherkey", "Value": "someothervalue"}, + ], + ) + + # Empty is good: + conn.tag_policy( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestTagPolicy"), + Tags=[{"Key": "somekey", "Value": ""}], + ) + tags = conn.list_policy_tags( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestTagPolicy") + ) + assert len(tags["Tags"]) == 2 + assert tags["Tags"][0]["Key"] == "somekey" + assert tags["Tags"][0]["Value"] == "" + + +@mock_iam() +def test_updating_existing_tagged_policy_with_too_many_tags(): + conn = boto3.client("iam", region_name="us-east-1") + conn.create_policy(PolicyName="TestTagPolicy", PolicyDocument=MOCK_POLICY) + conn.tag_policy( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestTagPolicy"), + Tags=[ + {"Key": "somekey", "Value": "somevalue"}, + {"Key": "someotherkey", "Value": "someothervalue"}, + ], + ) + + # With more than 50 tags: + with pytest.raises(ClientError) as ce: + too_many_tags = list( + map(lambda x: {"Key": str(x), "Value": str(x)}, range(0, 51)) + ) + conn.tag_policy( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestTagPolicy"), + Tags=too_many_tags, + ) + assert ( + "failed to satisfy constraint: Member must have length less than or equal to 50." + in ce.value.response["Error"]["Message"] + ) + + +@mock_iam() +def test_updating_existing_tagged_policy_with_duplicate_tag(): + conn = boto3.client("iam", region_name="us-east-1") + conn.create_policy(PolicyName="TestTagPolicy", PolicyDocument=MOCK_POLICY) + conn.tag_policy( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestTagPolicy"), + Tags=[ + {"Key": "somekey", "Value": "somevalue"}, + {"Key": "someotherkey", "Value": "someothervalue"}, + ], + ) + + # With a duplicate tag: + with pytest.raises(ClientError) as ce: + conn.tag_policy( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestTagPolicy"), + Tags=[{"Key": "0", "Value": ""}, {"Key": "0", "Value": ""}], + ) + assert ( + "Duplicate tag keys found. Please note that Tag keys are case insensitive." + in ce.value.response["Error"]["Message"] + ) + + +@mock_iam() +def test_updating_existing_tagged_policy_with_duplicate_tag_different_casing(): + conn = boto3.client("iam", region_name="us-east-1") + conn.create_policy(PolicyName="TestTagPolicy", PolicyDocument=MOCK_POLICY) + conn.tag_policy( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestTagPolicy"), + Tags=[ + {"Key": "somekey", "Value": "somevalue"}, + {"Key": "someotherkey", "Value": "someothervalue"}, + ], + ) + + # Duplicate tag with different casing: + with pytest.raises(ClientError) as ce: + conn.tag_policy( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestTagPolicy"), + Tags=[{"Key": "a", "Value": ""}, {"Key": "A", "Value": ""}], + ) + assert ( + "Duplicate tag keys found. Please note that Tag keys are case insensitive." + in ce.value.response["Error"]["Message"] + ) + + +@mock_iam() +def test_updating_existing_tagged_policy_with_large_key(): + conn = boto3.client("iam", region_name="us-east-1") + conn.create_policy(PolicyName="TestTagPolicy", PolicyDocument=MOCK_POLICY) + conn.tag_policy( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestTagPolicy"), + Tags=[ + {"Key": "somekey", "Value": "somevalue"}, + {"Key": "someotherkey", "Value": "someothervalue"}, + ], + ) + + # With a really big key: + with pytest.raises(ClientError) as ce: + conn.tag_policy( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestTagPolicy"), + Tags=[{"Key": "0" * 129, "Value": ""}], + ) + assert ( + "Member must have length less than or equal to 128." + in ce.value.response["Error"]["Message"] + ) + + +@mock_iam() +def test_updating_existing_tagged_policy_with_large_value(): + conn = boto3.client("iam", region_name="us-east-1") + conn.create_policy(PolicyName="TestTagPolicy", PolicyDocument=MOCK_POLICY) + conn.tag_policy( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestTagPolicy"), + Tags=[ + {"Key": "somekey", "Value": "somevalue"}, + {"Key": "someotherkey", "Value": "someothervalue"}, + ], + ) + + # With a really big value: + with pytest.raises(ClientError) as ce: + conn.tag_policy( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestTagPolicy"), + Tags=[{"Key": "0", "Value": "0" * 257}], + ) + assert ( + "Member must have length less than or equal to 256." + in ce.value.response["Error"]["Message"] + ) + + +@mock_iam() +def test_updating_existing_tagged_policy_with_invalid_character(): + conn = boto3.client("iam", region_name="us-east-1") + conn.create_policy(PolicyName="TestTagPolicy", PolicyDocument=MOCK_POLICY) + conn.tag_policy( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestTagPolicy"), + Tags=[ + {"Key": "somekey", "Value": "somevalue"}, + {"Key": "someotherkey", "Value": "someothervalue"}, + ], + ) + + # With an invalid character: + with pytest.raises(ClientError) as ce: + conn.tag_policy( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestTagPolicy"), + Tags=[{"Key": "NOWAY!", "Value": ""}], + ) + assert ( + "Member must satisfy regular expression pattern: [\\p{L}\\p{Z}\\p{N}_.:/=+\\-@]+" + in ce.value.response["Error"]["Message"] + ) + + +@mock_iam() +def test_tag_non_existant_policy(): + conn = boto3.client("iam", region_name="us-east-1") + + # With a policy that doesn't exist: + with pytest.raises(ClientError): + conn.tag_policy( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "NotAPolicy"), + Tags=[{"Key": "some", "Value": "value"}], + ) + + +@mock_iam +def test_untag_policy(): + conn = boto3.client("iam", region_name="us-east-1") + conn.create_policy(PolicyName="TestUnTagPolicy", PolicyDocument=MOCK_POLICY) + + # With proper tag values: + conn.tag_policy( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestUnTagPolicy"), + Tags=[ + {"Key": "somekey", "Value": "somevalue"}, + {"Key": "someotherkey", "Value": "someothervalue"}, + ], + ) + + # Remove them: + conn.untag_policy( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestUnTagPolicy"), + TagKeys=["somekey"], + ) + tags = conn.list_policy_tags( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestUnTagPolicy") + ) + assert len(tags["Tags"]) == 1 + assert tags["Tags"][0]["Key"] == "someotherkey" + assert tags["Tags"][0]["Value"] == "someothervalue" + + # And again: + conn.untag_policy( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestUnTagPolicy"), + TagKeys=["someotherkey"], + ) + tags = conn.list_policy_tags( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestUnTagPolicy") + ) + assert not tags["Tags"] + + # Test removing tags with invalid values: + # With more than 50 tags: + with pytest.raises(ClientError) as ce: + conn.untag_policy( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestUnTagPolicy"), + TagKeys=[str(x) for x in range(0, 51)], + ) + assert ( + "failed to satisfy constraint: Member must have length less than or equal to 50." + in ce.value.response["Error"]["Message"] + ) + assert "tagKeys" in ce.value.response["Error"]["Message"] + + # With a really big key: + with pytest.raises(ClientError) as ce: + conn.untag_policy( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestUnTagPolicy"), + TagKeys=["0" * 129], + ) + assert ( + "Member must have length less than or equal to 128." + in ce.value.response["Error"]["Message"] + ) + assert "tagKeys" in ce.value.response["Error"]["Message"] + + # With an invalid character: + with pytest.raises(ClientError) as ce: + conn.untag_policy( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "TestUnTagPolicy"), + TagKeys=["NOWAY!"], + ) + assert ( + "Member must satisfy regular expression pattern: [\\p{L}\\p{Z}\\p{N}_.:/=+\\-@]+" + in ce.value.response["Error"]["Message"] + ) + assert "tagKeys" in ce.value.response["Error"]["Message"] + + # With a policy that doesn't exist: + with pytest.raises(ClientError): + conn.untag_policy( + PolicyArn="arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, "NotAPolicy"), + TagKeys=["somevalue"], + ) + + # Has boto3 equivalent @mock_iam_deprecated() def test_create_user(): @@ -3473,6 +4010,7 @@ def test_role_config_dict(): path="/", policy_document=json.dumps(basic_policy), policy_name="basic_policy", + tags=[], ) .arn ) @@ -3974,6 +4512,7 @@ def test_policy_list_config_discovered_resources(): path="", policy_document=json.dumps(basic_policy), policy_name="policy{}".format(ix), + tags=[], ) policies.append( {"id": this_policy.id, "name": this_policy.name,} @@ -4057,6 +4596,7 @@ def test_policy_config_dict(): path="/", policy_document=json.dumps(basic_policy), policy_name="basic_policy", + tags=[], ) .arn )