From 82dbaadfc4b9e040b5caf13ce920d8eaa852aef4 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Fri, 25 Sep 2020 11:55:29 -0400 Subject: [PATCH] =?UTF-8?q?added=20organizations=20detach=5Fpolicy=20respo?= =?UTF-8?q?nse,=20model,=20and=20tests,=20issue=20#=E2=80=A6=20(#3278)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * added organizations detach_policy response, model, and tests, issue #3239 Signed-off-by: Ben * Created individual tests for detach_policy exceptions, updated regex statements for Root, OU, and Account Id --- moto/organizations/models.py | 31 ++++ moto/organizations/responses.py | 5 + .../test_organizations_boto3.py | 135 +++++++++++++++++- 3 files changed, 170 insertions(+), 1 deletion(-) diff --git a/moto/organizations/models.py b/moto/organizations/models.py index 09bd62b79..5655326c0 100644 --- a/moto/organizations/models.py +++ b/moto/organizations/models.py @@ -821,5 +821,36 @@ class OrganizationsBackend(BaseBackend): return dict(Root=root.describe()) + def detach_policy(self, **kwargs): + policy = self.get_policy_by_id(kwargs["PolicyId"]) + root_id_regex = utils.ROOT_ID_REGEX + ou_id_regex = utils.OU_ID_REGEX + account_id_regex = utils.ACCOUNT_ID_REGEX + target_id = kwargs["TargetId"] + + if re.match(root_id_regex, target_id) or re.match(ou_id_regex, target_id): + ou = next((ou for ou in self.ou if ou.id == target_id), None) + if ou is not None: + if ou in ou.attached_policies: + ou.attached_policies.remove(policy) + policy.attachments.remove(ou) + else: + raise RESTError( + "OrganizationalUnitNotFoundException", + "You specified an organizational unit that doesn't exist.", + ) + elif re.match(account_id_regex, target_id): + account = next( + (account for account in self.accounts if account.id == target_id), None, + ) + if account is not None: + if account in account.attached_policies: + account.attached_policies.remove(policy) + policy.attachments.remove(account) + else: + raise AccountNotFoundException + else: + raise InvalidInputException("You specified an invalid value.") + organizations_backend = OrganizationsBackend() diff --git a/moto/organizations/responses.py b/moto/organizations/responses.py index ae0bb731b..73e25178a 100644 --- a/moto/organizations/responses.py +++ b/moto/organizations/responses.py @@ -201,3 +201,8 @@ class OrganizationsResponse(BaseResponse): return json.dumps( self.organizations_backend.disable_policy_type(**self.request_params) ) + + def detach_policy(self): + return json.dumps( + self.organizations_backend.detach_policy(**self.request_params) + ) diff --git a/tests/test_organizations/test_organizations_boto3.py b/tests/test_organizations/test_organizations_boto3.py index 647236118..65f964082 100644 --- a/tests/test_organizations/test_organizations_boto3.py +++ b/tests/test_organizations/test_organizations_boto3.py @@ -467,6 +467,139 @@ def test_attach_policy(): response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200) +@mock_organizations +def test_detach_policy(): + client = boto3.client("organizations", region_name="us-east-1") + org = client.create_organization(FeatureSet="ALL")["Organization"] + root_id = client.list_roots()["Roots"][0]["Id"] + ou_id = client.create_organizational_unit(ParentId=root_id, Name="ou01")[ + "OrganizationalUnit" + ]["Id"] + account_id = client.create_account(AccountName=mockname, Email=mockemail)[ + "CreateAccountStatus" + ]["AccountId"] + policy_id = client.create_policy( + Content=json.dumps(policy_doc01), + Description="A dummy service control policy", + Name="MockServiceControlPolicy", + Type="SERVICE_CONTROL_POLICY", + )["Policy"]["PolicySummary"]["Id"] + client.attach_policy(PolicyId=policy_id, TargetId=ou_id) + client.attach_policy(PolicyId=policy_id, TargetId=root_id) + client.attach_policy(PolicyId=policy_id, TargetId=account_id) + response = client.detach_policy(PolicyId=policy_id, TargetId=ou_id) + response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200) + response = client.detach_policy(PolicyId=policy_id, TargetId=root_id) + response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200) + response = client.detach_policy(PolicyId=policy_id, TargetId=account_id) + response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200) + + +@mock_organizations +def test_detach_policy_root_ou_not_found_exception(): + client = boto3.client("organizations", region_name="us-east-1") + org = client.create_organization(FeatureSet="ALL")["Organization"] + root_id = client.list_roots()["Roots"][0]["Id"] + ou_id = client.create_organizational_unit(ParentId=root_id, Name="ou01")[ + "OrganizationalUnit" + ]["Id"] + account_id = client.create_account(AccountName=mockname, Email=mockemail)[ + "CreateAccountStatus" + ]["AccountId"] + policy_id = client.create_policy( + Content=json.dumps(policy_doc01), + Description="A dummy service control policy", + Name="MockServiceControlPolicy", + Type="SERVICE_CONTROL_POLICY", + )["Policy"]["PolicySummary"]["Id"] + client.attach_policy(PolicyId=policy_id, TargetId=root_id) + client.attach_policy(PolicyId=policy_id, TargetId=account_id) + with assert_raises(ClientError) as e: + response = client.detach_policy(PolicyId=policy_id, TargetId="r-xy85") + ex = e.exception + ex.operation_name.should.equal("DetachPolicy") + ex.response["Error"]["Code"].should.equal("400") + ex.response["Error"]["Message"].should.contain( + "OrganizationalUnitNotFoundException" + ) + + +@mock_organizations +def test_detach_policy_ou_not_found_exception(): + client = boto3.client("organizations", region_name="us-east-1") + org = client.create_organization(FeatureSet="ALL")["Organization"] + root_id = client.list_roots()["Roots"][0]["Id"] + ou_id = client.create_organizational_unit(ParentId=root_id, Name="ou01")[ + "OrganizationalUnit" + ]["Id"] + policy_id = client.create_policy( + Content=json.dumps(policy_doc01), + Description="A dummy service control policy", + Name="MockServiceControlPolicy", + Type="SERVICE_CONTROL_POLICY", + )["Policy"]["PolicySummary"]["Id"] + client.attach_policy(PolicyId=policy_id, TargetId=ou_id) + with assert_raises(ClientError) as e: + response = client.detach_policy( + PolicyId=policy_id, TargetId="ou-zx86-z3x4yr2t7" + ) + ex = e.exception + ex.operation_name.should.equal("DetachPolicy") + ex.response["Error"]["Code"].should.equal("400") + ex.response["Error"]["Message"].should.contain( + "OrganizationalUnitNotFoundException" + ) + + +@mock_organizations +def test_detach_policy_account_id_not_found_exception(): + client = boto3.client("organizations", region_name="us-east-1") + org = client.create_organization(FeatureSet="ALL")["Organization"] + account_id = client.create_account(AccountName=mockname, Email=mockemail)[ + "CreateAccountStatus" + ]["AccountId"] + policy_id = client.create_policy( + Content=json.dumps(policy_doc01), + Description="A dummy service control policy", + Name="MockServiceControlPolicy", + Type="SERVICE_CONTROL_POLICY", + )["Policy"]["PolicySummary"]["Id"] + client.attach_policy(PolicyId=policy_id, TargetId=account_id) + with assert_raises(ClientError) as e: + response = client.detach_policy(PolicyId=policy_id, TargetId="111619863336") + ex = e.exception + ex.operation_name.should.equal("DetachPolicy") + ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.response["Error"]["Code"].should.contain("AccountNotFoundException") + ex.response["Error"]["Message"].should.equal( + "You specified an account that doesn't exist." + ) + + +@mock_organizations +def test_detach_policy_invalid_target_exception(): + client = boto3.client("organizations", region_name="us-east-1") + org = client.create_organization(FeatureSet="ALL")["Organization"] + root_id = client.list_roots()["Roots"][0]["Id"] + ou_id = client.create_organizational_unit(ParentId=root_id, Name="ou01")[ + "OrganizationalUnit" + ]["Id"] + policy_id = client.create_policy( + Content=json.dumps(policy_doc01), + Description="A dummy service control policy", + Name="MockServiceControlPolicy", + Type="SERVICE_CONTROL_POLICY", + )["Policy"]["PolicySummary"]["Id"] + client.attach_policy(PolicyId=policy_id, TargetId=ou_id) + with assert_raises(ClientError) as e: + response = client.detach_policy(PolicyId=policy_id, TargetId="invalidtargetid") + ex = e.exception + ex.operation_name.should.equal("DetachPolicy") + ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.response["Error"]["Code"].should.contain("InvalidInputException") + ex.response["Error"]["Message"].should.equal("You specified an invalid value.") + + @mock_organizations def test_delete_policy(): client = boto3.client("organizations", region_name="us-east-1") @@ -798,7 +931,7 @@ def test_tag_resource_errors(): with assert_raises(ClientError) as e: client.tag_resource( - ResourceId="000000000000", Tags=[{"Key": "key", "Value": "value"},] + ResourceId="000000000000", Tags=[{"Key": "key", "Value": "value"},], ) ex = e.exception ex.operation_name.should.equal("TagResource")