import datetime import re import json from typing import Any, Dict, List, Optional from moto.core import BaseBackend, BackendDict, BaseModel from moto.core.exceptions import RESTError from moto.core.utils import unix_time from moto.organizations import utils from moto.organizations.exceptions import ( InvalidInputException, DuplicateOrganizationalUnitException, DuplicatePolicyException, AccountNotFoundException, ConstraintViolationException, AccountAlreadyRegisteredException, AWSOrganizationsNotInUseException, AccountNotRegisteredException, RootNotFoundException, PolicyTypeAlreadyEnabledException, PolicyTypeNotEnabledException, TargetNotFoundException, ) from moto.utilities.paginator import paginate from .utils import PAGINATION_MODEL class FakeOrganization(BaseModel): def __init__(self, account_id: str, feature_set: str): self.id = utils.make_random_org_id() self.root_id = utils.make_random_root_id() self.feature_set = feature_set self.master_account_id = account_id self.master_account_email = utils.MASTER_ACCOUNT_EMAIL self.available_policy_types = [ # This policy is available, but not applied # User should use enable_policy_type/disable_policy_type to do anything else # This field is deprecated in AWS, but we'll return it for old time's sake {"Type": "SERVICE_CONTROL_POLICY", "Status": "ENABLED"} ] @property def arn(self) -> str: return utils.ORGANIZATION_ARN_FORMAT.format(self.master_account_id, self.id) @property def master_account_arn(self) -> str: return utils.MASTER_ACCOUNT_ARN_FORMAT.format(self.master_account_id, self.id) def describe(self) -> Dict[str, Any]: return { "Organization": { "Id": self.id, "Arn": self.arn, "FeatureSet": self.feature_set, "MasterAccountArn": self.master_account_arn, "MasterAccountId": self.master_account_id, "MasterAccountEmail": self.master_account_email, "AvailablePolicyTypes": self.available_policy_types, } } class FakeAccount(BaseModel): def __init__(self, organization: FakeOrganization, **kwargs: Any): self.type = "ACCOUNT" self.organization_id = organization.id self.master_account_id = organization.master_account_id self.create_account_status_id = utils.make_random_create_account_status_id() self.id = utils.make_random_account_id() self.name = kwargs["AccountName"] self.email = kwargs["Email"] self.create_time = datetime.datetime.utcnow() self.status = "ACTIVE" self.joined_method = "CREATED" self.parent_id = organization.root_id self.attached_policies: List[FakePolicy] = [] self.tags = {tag["Key"]: tag["Value"] for tag in kwargs.get("Tags", [])} @property def arn(self) -> str: return utils.ACCOUNT_ARN_FORMAT.format( self.master_account_id, self.organization_id, self.id ) @property def create_account_status(self) -> Dict[str, Any]: # type: ignore[misc] return { "CreateAccountStatus": { "Id": self.create_account_status_id, "AccountName": self.name, "State": "SUCCEEDED", "RequestedTimestamp": unix_time(self.create_time), "CompletedTimestamp": unix_time(self.create_time), "AccountId": self.id, } } def describe(self) -> Dict[str, Any]: return { "Id": self.id, "Arn": self.arn, "Email": self.email, "Name": self.name, "Status": self.status, "JoinedMethod": self.joined_method, "JoinedTimestamp": unix_time(self.create_time), } def close(self) -> None: # TODO: The CloseAccount spec allows the account to pass through a # "PENDING_CLOSURE" state before reaching the SUSPENDED state. self.status = "SUSPENDED" class FakeOrganizationalUnit(BaseModel): def __init__(self, organization: FakeOrganization, **kwargs: Any): self.type = "ORGANIZATIONAL_UNIT" self.organization_id = organization.id self.master_account_id = organization.master_account_id self.id = utils.make_random_ou_id(organization.root_id) self.name = kwargs.get("Name") self.parent_id = kwargs.get("ParentId") self._arn_format = utils.OU_ARN_FORMAT self.attached_policies: List[FakePolicy] = [] self.tags = {tag["Key"]: tag["Value"] for tag in kwargs.get("Tags", [])} @property def arn(self) -> str: return self._arn_format.format( self.master_account_id, self.organization_id, self.id ) def describe(self) -> Dict[str, Dict[str, Any]]: return { "OrganizationalUnit": {"Id": self.id, "Arn": self.arn, "Name": self.name} } class FakeRoot(FakeOrganizationalUnit): SUPPORTED_POLICY_TYPES = [ "AISERVICES_OPT_OUT_POLICY", "BACKUP_POLICY", "SERVICE_CONTROL_POLICY", "TAG_POLICY", ] def __init__(self, organization: FakeOrganization, **kwargs: Any): super().__init__(organization, **kwargs) self.type = "ROOT" self.id = organization.root_id self.name = "Root" self.policy_types: List[Dict[str, str]] = [] self._arn_format = utils.ROOT_ARN_FORMAT self.attached_policies = [] self.tags = {tag["Key"]: tag["Value"] for tag in kwargs.get("Tags", [])} def describe(self) -> Dict[str, Any]: return { "Id": self.id, "Arn": self.arn, "Name": self.name, "PolicyTypes": self.policy_types, } def add_policy_type(self, policy_type: str) -> None: if policy_type not in self.SUPPORTED_POLICY_TYPES: raise InvalidInputException("You specified an invalid value.") if any(type["Type"] == policy_type for type in self.policy_types): raise PolicyTypeAlreadyEnabledException self.policy_types.append({"Type": policy_type, "Status": "ENABLED"}) def remove_policy_type(self, policy_type: str) -> None: if not FakePolicy.supported_policy_type(policy_type): raise InvalidInputException("You specified an invalid value.") if all(type["Type"] != policy_type for type in self.policy_types): raise PolicyTypeNotEnabledException self.policy_types.remove({"Type": policy_type, "Status": "ENABLED"}) class FakePolicy(BaseModel): SUPPORTED_POLICY_TYPES = [ "AISERVICES_OPT_OUT_POLICY", "BACKUP_POLICY", "SERVICE_CONTROL_POLICY", "TAG_POLICY", ] def __init__(self, organization: FakeOrganization, **kwargs: Any): self.content = kwargs.get("Content") self.description = kwargs.get("Description") self.name = kwargs.get("Name") self.type = kwargs.get("Type", "") self.id = utils.make_random_policy_id() self.aws_managed = False self.organization_id = organization.id self.master_account_id = organization.master_account_id self.attachments: List[Any] = [] if not FakePolicy.supported_policy_type(self.type): raise InvalidInputException("You specified an invalid value.") elif self.type == "AISERVICES_OPT_OUT_POLICY": self._arn_format = utils.AI_POLICY_ARN_FORMAT elif self.type == "SERVICE_CONTROL_POLICY": self._arn_format = utils.SCP_ARN_FORMAT else: raise NotImplementedError( f"The {self.type} policy type has not been implemented" ) @property def arn(self) -> str: return self._arn_format.format( self.master_account_id, self.organization_id, self.id ) def describe(self) -> Dict[str, Any]: return { "Policy": { "PolicySummary": { "Id": self.id, "Arn": self.arn, "Name": self.name, "Description": self.description, "Type": self.type, "AwsManaged": self.aws_managed, }, "Content": self.content, } } @staticmethod def supported_policy_type(policy_type: str) -> bool: return policy_type in FakePolicy.SUPPORTED_POLICY_TYPES class FakeServiceAccess(BaseModel): # List of trusted services, which support trusted access with Organizations # https://docs.aws.amazon.com/organizations/latest/userguide/orgs_integrated-services-list.html TRUSTED_SERVICES = [ "aws-artifact-account-sync.amazonaws.com", "backup.amazonaws.com", "member.org.stacksets.cloudformation.amazonaws.com", "cloudtrail.amazonaws.com", "compute-optimizer.amazonaws.com", "config.amazonaws.com", "config-multiaccountsetup.amazonaws.com", "controltower.amazonaws.com", "ds.amazonaws.com", "fms.amazonaws.com", "guardduty.amazonaws.com", "access-analyzer.amazonaws.com", "license-manager.amazonaws.com", "license-manager.member-account.amazonaws.com.", "macie.amazonaws.com", "ram.amazonaws.com", "servicecatalog.amazonaws.com", "servicequotas.amazonaws.com", "sso.amazonaws.com", "ssm.amazonaws.com", "tagpolicies.tag.amazonaws.com", ] def __init__(self, **kwargs: Any): if not self.trusted_service(kwargs["ServicePrincipal"]): raise InvalidInputException( "You specified an unrecognized service principal." ) self.service_principal = kwargs["ServicePrincipal"] self.date_enabled = datetime.datetime.utcnow() def describe(self) -> Dict[str, Any]: return { "ServicePrincipal": self.service_principal, "DateEnabled": unix_time(self.date_enabled), } @staticmethod def trusted_service(service_principal: str) -> bool: return service_principal in FakeServiceAccess.TRUSTED_SERVICES class FakeDelegatedAdministrator(BaseModel): # List of services, which support a different Account to ba a delegated administrator # https://docs.aws.amazon.com/organizations/latest/userguide/orgs_integrated-services-list.html SUPPORTED_SERVICES = [ "config-multiaccountsetup.amazonaws.com", "guardduty.amazonaws.com", "access-analyzer.amazonaws.com", "macie.amazonaws.com", "servicecatalog.amazonaws.com", "ssm.amazonaws.com", ] def __init__(self, account: FakeAccount): self.account = account self.enabled_date = datetime.datetime.utcnow() self.services: Dict[str, Any] = {} def add_service_principal(self, service_principal: str) -> None: if service_principal in self.services: raise AccountAlreadyRegisteredException if not self.supported_service(service_principal): raise InvalidInputException( "You specified an unrecognized service principal." ) self.services[service_principal] = { "ServicePrincipal": service_principal, "DelegationEnabledDate": unix_time(datetime.datetime.utcnow()), } def remove_service_principal(self, service_principal: str) -> None: if service_principal not in self.services: raise InvalidInputException( "You specified an unrecognized service principal." ) self.services.pop(service_principal) def describe(self) -> Dict[str, Any]: admin = self.account.describe() admin["DelegationEnabledDate"] = unix_time(self.enabled_date) return admin @staticmethod def supported_service(service_principal: str) -> bool: return service_principal in FakeDelegatedAdministrator.SUPPORTED_SERVICES class OrganizationsBackend(BaseBackend): def __init__(self, region_name: str, account_id: str): super().__init__(region_name, account_id) self._reset() def _reset(self) -> None: self.org: Optional[FakeOrganization] = None self.accounts: List[FakeAccount] = [] self.ou: List[FakeOrganizationalUnit] = [] self.policies: List[FakePolicy] = [] self.services: List[Dict[str, Any]] = [] self.admins: List[FakeDelegatedAdministrator] = [] def _get_root_by_id(self, root_id: str) -> FakeRoot: root = next((ou for ou in self.ou if ou.id == root_id), None) if not root: raise RootNotFoundException return root # type: ignore[return-value] def create_organization(self, **kwargs: Any) -> Dict[str, Any]: self.org = FakeOrganization(self.account_id, kwargs.get("FeatureSet") or "ALL") root_ou = FakeRoot(self.org) self.ou.append(root_ou) master_account = FakeAccount( self.org, AccountName="master", Email=self.org.master_account_email ) master_account.id = self.org.master_account_id self.accounts.append(master_account) default_policy = FakePolicy( self.org, Name="FullAWSAccess", Description="Allows access to every operation", Type="SERVICE_CONTROL_POLICY", Content=json.dumps( { "Version": "2012-10-17", "Statement": [{"Effect": "Allow", "Action": "*", "Resource": "*"}], } ), ) default_policy.id = utils.DEFAULT_POLICY_ID default_policy.aws_managed = True self.policies.append(default_policy) self.attach_policy(PolicyId=default_policy.id, TargetId=root_ou.id) self.attach_policy(PolicyId=default_policy.id, TargetId=master_account.id) return self.org.describe() def describe_organization(self) -> Dict[str, Any]: if not self.org: raise AWSOrganizationsNotInUseException return self.org.describe() def delete_organization(self) -> None: if [account for account in self.accounts if account.name != "master"]: raise RESTError( "OrganizationNotEmptyException", "To delete an organization you must first remove all member accounts (except the master).", ) self._reset() def list_roots(self) -> Dict[str, Any]: return dict(Roots=[ou.describe() for ou in self.ou if isinstance(ou, FakeRoot)]) def create_organizational_unit(self, **kwargs: Any) -> Dict[str, Any]: new_ou = FakeOrganizationalUnit(self.org, **kwargs) # type: ignore self.ou.append(new_ou) self.attach_policy(PolicyId=utils.DEFAULT_POLICY_ID, TargetId=new_ou.id) return new_ou.describe() def delete_organizational_unit(self, **kwargs: Any) -> None: ou_to_delete = self.get_organizational_unit_by_id( kwargs["OrganizationalUnitId"] ) self.ou.remove(ou_to_delete) def update_organizational_unit(self, **kwargs: Any) -> Dict[str, Any]: for ou in self.ou: if ou.name == kwargs["Name"]: raise DuplicateOrganizationalUnitException ou = self.get_organizational_unit_by_id(kwargs["OrganizationalUnitId"]) ou.name = kwargs["Name"] return ou.describe() def get_organizational_unit_by_id(self, ou_id: str) -> FakeOrganizationalUnit: ou = next((ou for ou in self.ou if ou.id == ou_id), None) if ou is None: raise RESTError( "OrganizationalUnitNotFoundException", "You specified an organizational unit that doesn't exist.", ) return ou def validate_parent_id(self, parent_id: str) -> str: try: self.get_organizational_unit_by_id(parent_id) except RESTError: raise RESTError( "ParentNotFoundException", "You specified parent that doesn't exist." ) return parent_id def describe_organizational_unit(self, **kwargs: Any) -> Dict[str, Any]: ou = self.get_organizational_unit_by_id(kwargs["OrganizationalUnitId"]) return ou.describe() @paginate(pagination_model=PAGINATION_MODEL) def list_organizational_units_for_parent(self, **kwargs: Any) -> List[Dict[str, Any]]: # type: ignore parent_id = self.validate_parent_id(kwargs["parent_id"]) return [ {"Id": ou.id, "Arn": ou.arn, "Name": ou.name} for ou in self.ou if ou.parent_id == parent_id ] def create_account(self, **kwargs: Any) -> Dict[str, Any]: new_account = FakeAccount(self.org, **kwargs) # type: ignore self.accounts.append(new_account) self.attach_policy(PolicyId=utils.DEFAULT_POLICY_ID, TargetId=new_account.id) return new_account.create_account_status def close_account(self, **kwargs: Any) -> None: for account in self.accounts: if account.id == kwargs["AccountId"]: account.close() return raise AccountNotFoundException def get_account_by_id(self, account_id: str) -> FakeAccount: account = next( (account for account in self.accounts if account.id == account_id), None ) if account is None: raise AccountNotFoundException return account def get_account_by_attr(self, attr: str, value: Any) -> FakeAccount: account = next( ( account for account in self.accounts if hasattr(account, attr) and getattr(account, attr) == value ), None, ) if account is None: raise AccountNotFoundException return account def describe_account(self, **kwargs: Any) -> Dict[str, Any]: account = self.get_account_by_id(kwargs["AccountId"]) return dict(Account=account.describe()) def describe_create_account_status(self, **kwargs: Any) -> Dict[str, Any]: account = self.get_account_by_attr( "create_account_status_id", kwargs["CreateAccountRequestId"] ) return account.create_account_status def list_create_account_status(self, **kwargs: Any) -> Dict[str, Any]: requested_states = kwargs.get("States") if not requested_states: requested_states = ["IN_PROGRESS", "SUCCEEDED", "FAILED"] accountStatuses = [] for account in self.accounts: create_account_status = account.create_account_status["CreateAccountStatus"] if create_account_status["State"] in requested_states: accountStatuses.append(create_account_status) token = kwargs.get("NextToken") if token: start = int(token) else: start = 0 max_results = int(kwargs.get("MaxResults", 123)) accounts_resp = accountStatuses[start : start + max_results] next_token = None if max_results and len(accountStatuses) > (start + max_results): next_token = str(len(accounts_resp)) return dict(CreateAccountStatuses=accounts_resp, NextToken=next_token) @paginate(pagination_model=PAGINATION_MODEL) def list_accounts(self) -> List[FakeAccount]: # type: ignore accounts = [account.describe() for account in self.accounts] return sorted(accounts, key=lambda x: x["JoinedTimestamp"]) # type: ignore @paginate(pagination_model=PAGINATION_MODEL) def list_accounts_for_parent(self, **kwargs: Any) -> Any: # type: ignore parent_id = self.validate_parent_id(kwargs["parent_id"]) accounts = [ account.describe() for account in self.accounts if account.parent_id == parent_id ] return sorted(accounts, key=lambda x: x["JoinedTimestamp"]) def move_account(self, **kwargs: Any) -> None: new_parent_id = self.validate_parent_id(kwargs["DestinationParentId"]) self.validate_parent_id(kwargs["SourceParentId"]) account = self.get_account_by_id(kwargs["AccountId"]) index = self.accounts.index(account) self.accounts[index].parent_id = new_parent_id def list_parents(self, **kwargs: Any) -> Dict[str, Any]: if re.compile(r"[0-9]{12}").match(kwargs["ChildId"]): child_object: Any = self.get_account_by_id(kwargs["ChildId"]) else: child_object = self.get_organizational_unit_by_id(kwargs["ChildId"]) return dict( Parents=[ {"Id": ou.id, "Type": ou.type} for ou in self.ou if ou.id == child_object.parent_id ] ) def list_children(self, **kwargs: Any) -> Dict[str, Any]: parent_id = self.validate_parent_id(kwargs["ParentId"]) if kwargs["ChildType"] == "ACCOUNT": obj_list: List[Any] = self.accounts elif kwargs["ChildType"] == "ORGANIZATIONAL_UNIT": obj_list = self.ou else: raise InvalidInputException("You specified an invalid value.") return dict( Children=[ {"Id": obj.id, "Type": kwargs["ChildType"]} for obj in obj_list if obj.parent_id == parent_id ] ) def create_policy(self, **kwargs: Any) -> Dict[str, Any]: new_policy = FakePolicy(self.org, **kwargs) # type: ignore for policy in self.policies: if kwargs["Name"] == policy.name: raise DuplicatePolicyException self.policies.append(new_policy) return new_policy.describe() def describe_policy(self, **kwargs: Any) -> Dict[str, Any]: if re.compile(utils.POLICY_ID_REGEX).match(kwargs["PolicyId"]): policy = next( (p for p in self.policies if p.id == kwargs["PolicyId"]), None ) if policy is None: raise RESTError( "PolicyNotFoundException", "You specified a policy that doesn't exist.", ) else: raise InvalidInputException("You specified an invalid value.") return policy.describe() def get_policy_by_id(self, policy_id: str) -> FakePolicy: policy = next( (policy for policy in self.policies if policy.id == policy_id), None ) if policy is None: raise RESTError( "PolicyNotFoundException", "We can't find a policy with the PolicyId that you specified.", ) return policy def update_policy(self, **kwargs: Any) -> Dict[str, Any]: policy = self.get_policy_by_id(kwargs["PolicyId"]) policy.name = kwargs.get("Name", policy.name) policy.description = kwargs.get("Description", policy.description) policy.content = kwargs.get("Content", policy.content) return policy.describe() def attach_policy(self, **kwargs: Any) -> None: policy = self.get_policy_by_id(kwargs["PolicyId"]) if re.compile(utils.ROOT_ID_REGEX).match(kwargs["TargetId"]) or re.compile( utils.OU_ID_REGEX ).match(kwargs["TargetId"]): ou = next((ou for ou in self.ou if ou.id == kwargs["TargetId"]), None) if ou is not None: if policy not in ou.attached_policies: ou.attached_policies.append(policy) policy.attachments.append(ou) else: raise RESTError( "OrganizationalUnitNotFoundException", "You specified an organizational unit that doesn't exist.", ) elif re.compile(utils.ACCOUNT_ID_REGEX).match(kwargs["TargetId"]): account = next( (a for a in self.accounts if a.id == kwargs["TargetId"]), None ) if account is not None: if policy not in account.attached_policies: account.attached_policies.append(policy) policy.attachments.append(account) else: raise AccountNotFoundException else: raise InvalidInputException("You specified an invalid value.") def list_policies(self) -> Dict[str, Any]: return dict( Policies=[p.describe()["Policy"]["PolicySummary"] for p in self.policies] ) def delete_policy(self, **kwargs: Any) -> None: for idx, policy in enumerate(self.policies): if policy.id == kwargs["PolicyId"]: if self.list_targets_for_policy(PolicyId=policy.id)["Targets"]: raise RESTError( "PolicyInUseException", "The policy is attached to one or more entities. You must detach it from all roots, OUs, and accounts before performing this operation.", ) del self.policies[idx] return raise RESTError( "PolicyNotFoundException", "We can't find a policy with the PolicyId that you specified.", ) def list_policies_for_target(self, **kwargs: Any) -> Dict[str, Any]: _filter = kwargs["Filter"] if re.match(utils.ROOT_ID_REGEX, kwargs["TargetId"]): obj: Any = next((ou for ou in self.ou if ou.id == kwargs["TargetId"]), None) if obj is None: raise TargetNotFoundException elif re.compile(utils.OU_ID_REGEX).match(kwargs["TargetId"]): obj = next((ou for ou in self.ou if ou.id == kwargs["TargetId"]), None) if obj is None: raise RESTError( "OrganizationalUnitNotFoundException", "You specified an organizational unit that doesn't exist.", ) elif re.compile(utils.ACCOUNT_ID_REGEX).match(kwargs["TargetId"]): obj = next((a for a in self.accounts if a.id == kwargs["TargetId"]), None) if obj is None: raise AccountNotFoundException else: raise InvalidInputException("You specified an invalid value.") if not FakePolicy.supported_policy_type(_filter): raise InvalidInputException("You specified an invalid value.") if _filter not in ["AISERVICES_OPT_OUT_POLICY", "SERVICE_CONTROL_POLICY"]: raise NotImplementedError( f"The {_filter} policy type has not been implemented" ) return dict( Policies=[ p.describe()["Policy"]["PolicySummary"] for p in obj.attached_policies if p.type == _filter ] ) def _get_resource_for_tagging(self, resource_id: str) -> Any: if utils.fullmatch( re.compile(utils.OU_ID_REGEX), resource_id ) or utils.fullmatch(utils.ROOT_ID_REGEX, resource_id): resource: Any = next((a for a in self.ou if a.id == resource_id), None) elif utils.fullmatch(re.compile(utils.ACCOUNT_ID_REGEX), resource_id): resource = next((a for a in self.accounts if a.id == resource_id), None) elif utils.fullmatch(re.compile(utils.POLICY_ID_REGEX), resource_id): resource = next((a for a in self.policies if a.id == resource_id), None) else: raise InvalidInputException( "You provided a value that does not match the required pattern." ) if resource is None: raise TargetNotFoundException return resource def list_targets_for_policy(self, **kwargs: Any) -> Dict[str, Any]: if re.compile(utils.POLICY_ID_REGEX).match(kwargs["PolicyId"]): policy = next( (p for p in self.policies if p.id == kwargs["PolicyId"]), None ) if policy is None: raise RESTError( "PolicyNotFoundException", "You specified a policy that doesn't exist.", ) else: raise InvalidInputException("You specified an invalid value.") objects = [ {"TargetId": obj.id, "Arn": obj.arn, "Name": obj.name, "Type": obj.type} for obj in policy.attachments ] return dict(Targets=objects) def tag_resource(self, **kwargs: Any) -> None: resource = self._get_resource_for_tagging(kwargs["ResourceId"]) new_tags = {tag["Key"]: tag["Value"] for tag in kwargs["Tags"]} resource.tags.update(new_tags) def list_tags_for_resource(self, **kwargs: str) -> Dict[str, Any]: resource = self._get_resource_for_tagging(kwargs["ResourceId"]) tags = [{"Key": key, "Value": value} for key, value in resource.tags.items()] return dict(Tags=tags) def untag_resource(self, **kwargs: Any) -> None: resource = self._get_resource_for_tagging(kwargs["ResourceId"]) for key in kwargs["TagKeys"]: resource.tags.pop(key, None) def enable_aws_service_access(self, **kwargs: str) -> None: service = FakeServiceAccess(**kwargs) # enabling an existing service results in no changes if any( service["ServicePrincipal"] == kwargs["ServicePrincipal"] for service in self.services ): return self.services.append(service.describe()) def list_aws_service_access_for_organization(self) -> Dict[str, Any]: return dict(EnabledServicePrincipals=self.services) def disable_aws_service_access(self, **kwargs: str) -> None: if not FakeServiceAccess.trusted_service(kwargs["ServicePrincipal"]): raise InvalidInputException( "You specified an unrecognized service principal." ) service_principal = next( ( service for service in self.services if service["ServicePrincipal"] == kwargs["ServicePrincipal"] ), None, ) if service_principal: self.services.remove(service_principal) def register_delegated_administrator(self, **kwargs: str) -> None: account_id = kwargs["AccountId"] if account_id == self.account_id: raise ConstraintViolationException( "You cannot register master account/yourself as delegated administrator for your organization." ) account = self.get_account_by_id(account_id) admin = next( (admin for admin in self.admins if admin.account.id == account_id), None ) if admin is None: admin = FakeDelegatedAdministrator(account) self.admins.append(admin) admin.add_service_principal(kwargs["ServicePrincipal"]) def list_delegated_administrators(self, **kwargs: str) -> Dict[str, Any]: admins = self.admins service = kwargs.get("ServicePrincipal") if service: if not FakeDelegatedAdministrator.supported_service(service): raise InvalidInputException( "You specified an unrecognized service principal." ) admins = [admin for admin in admins if service in admin.services] delegated_admins = [admin.describe() for admin in admins] return dict(DelegatedAdministrators=delegated_admins) def list_delegated_services_for_account(self, **kwargs: str) -> Dict[str, Any]: admin = next( (admin for admin in self.admins if admin.account.id == kwargs["AccountId"]), None, ) if admin is None: account = next( ( account for account in self.accounts if account.id == kwargs["AccountId"] ), None, ) if account: raise AccountNotRegisteredException raise AWSOrganizationsNotInUseException services = [service for service in admin.services.values()] return dict(DelegatedServices=services) def deregister_delegated_administrator(self, **kwargs: str) -> None: account_id = kwargs["AccountId"] service = kwargs["ServicePrincipal"] if account_id == self.account_id: raise ConstraintViolationException( "You cannot register master account/yourself as delegated administrator for your organization." ) admin = next( (admin for admin in self.admins if admin.account.id == account_id), None ) if admin is None: account = next( ( account for account in self.accounts if account.id == kwargs["AccountId"] ), None, ) if account: raise AccountNotRegisteredException raise AccountNotFoundException admin.remove_service_principal(service) # remove account, when no services attached if not admin.services: self.admins.remove(admin) def enable_policy_type(self, **kwargs: str) -> Dict[str, Any]: root = self._get_root_by_id(kwargs["RootId"]) root.add_policy_type(kwargs["PolicyType"]) return dict(Root=root.describe()) def disable_policy_type(self, **kwargs: str) -> Dict[str, Any]: root = self._get_root_by_id(kwargs["RootId"]) root.remove_policy_type(kwargs["PolicyType"]) return dict(Root=root.describe()) def detach_policy(self, **kwargs: str) -> None: 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 policy 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 policy in account.attached_policies: account.attached_policies.remove(policy) policy.attachments.remove(account) else: raise AccountNotFoundException else: raise InvalidInputException("You specified an invalid value.") def remove_account_from_organization(self, **kwargs: str) -> None: account = self.get_account_by_id(kwargs["AccountId"]) for policy in account.attached_policies: policy.attachments.remove(account) self.accounts.remove(account) organizations_backends = BackendDict( OrganizationsBackend, "organizations", use_boto3_regions=False, additional_regions=["global"], )