Organizations - implement Delegated Administrator functionality (#3200)
* Add organizations.register_delegated_administrator * Add organizations.list_delegated_administrators * Add organizations.list_delegated_services_for_account * Add organizations.deregister_delegated_administrator * Fix Python2 incompatibility
This commit is contained in:
parent
943ecb7ea7
commit
8162947ebb
@ -2,6 +2,54 @@ from __future__ import unicode_literals
|
|||||||
from moto.core.exceptions import JsonRESTError
|
from moto.core.exceptions import JsonRESTError
|
||||||
|
|
||||||
|
|
||||||
|
class AccountAlreadyRegisteredException(JsonRESTError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(AccountAlreadyRegisteredException, self).__init__(
|
||||||
|
"AccountAlreadyRegisteredException",
|
||||||
|
"The provided account is already a delegated administrator for your organization.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AccountNotRegisteredException(JsonRESTError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(AccountNotRegisteredException, self).__init__(
|
||||||
|
"AccountNotRegisteredException",
|
||||||
|
"The provided account is not a registered delegated administrator for your organization.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AccountNotFoundException(JsonRESTError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(AccountNotFoundException, self).__init__(
|
||||||
|
"AccountNotFoundException", "You specified an account that doesn't exist."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AWSOrganizationsNotInUseException(JsonRESTError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(AWSOrganizationsNotInUseException, self).__init__(
|
||||||
|
"AWSOrganizationsNotInUseException",
|
||||||
|
"Your account is not a member of an organization.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ConstraintViolationException(JsonRESTError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self, message):
|
||||||
|
super(ConstraintViolationException, self).__init__(
|
||||||
|
"ConstraintViolationException", message
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class InvalidInputException(JsonRESTError):
|
class InvalidInputException(JsonRESTError):
|
||||||
code = 400
|
code = 400
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import datetime
|
|||||||
import re
|
import re
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from moto.core import BaseBackend, BaseModel
|
from moto.core import BaseBackend, BaseModel, ACCOUNT_ID
|
||||||
from moto.core.exceptions import RESTError
|
from moto.core.exceptions import RESTError
|
||||||
from moto.core.utils import unix_time
|
from moto.core.utils import unix_time
|
||||||
from moto.organizations import utils
|
from moto.organizations import utils
|
||||||
@ -12,6 +12,11 @@ from moto.organizations.exceptions import (
|
|||||||
InvalidInputException,
|
InvalidInputException,
|
||||||
DuplicateOrganizationalUnitException,
|
DuplicateOrganizationalUnitException,
|
||||||
DuplicatePolicyException,
|
DuplicatePolicyException,
|
||||||
|
AccountNotFoundException,
|
||||||
|
ConstraintViolationException,
|
||||||
|
AccountAlreadyRegisteredException,
|
||||||
|
AWSOrganizationsNotInUseException,
|
||||||
|
AccountNotRegisteredException,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -85,15 +90,13 @@ class FakeAccount(BaseModel):
|
|||||||
|
|
||||||
def describe(self):
|
def describe(self):
|
||||||
return {
|
return {
|
||||||
"Account": {
|
"Id": self.id,
|
||||||
"Id": self.id,
|
"Arn": self.arn,
|
||||||
"Arn": self.arn,
|
"Email": self.email,
|
||||||
"Email": self.email,
|
"Name": self.name,
|
||||||
"Name": self.name,
|
"Status": self.status,
|
||||||
"Status": self.status,
|
"JoinedMethod": self.joined_method,
|
||||||
"JoinedMethod": self.joined_method,
|
"JoinedTimestamp": unix_time(self.create_time),
|
||||||
"JoinedTimestamp": unix_time(self.create_time),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -221,6 +224,56 @@ class FakeServiceAccess(BaseModel):
|
|||||||
return service_principal in FakeServiceAccess.TRUSTED_SERVICES
|
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):
|
||||||
|
self.account = account
|
||||||
|
self.enabled_date = datetime.datetime.utcnow()
|
||||||
|
self.services = {}
|
||||||
|
|
||||||
|
def add_service_principal(self, service_principal):
|
||||||
|
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):
|
||||||
|
if service_principal not in self.services:
|
||||||
|
raise InvalidInputException(
|
||||||
|
"You specified an unrecognized service principal."
|
||||||
|
)
|
||||||
|
|
||||||
|
self.services.pop(service_principal)
|
||||||
|
|
||||||
|
def describe(self):
|
||||||
|
admin = self.account.describe()
|
||||||
|
admin["DelegationEnabledDate"] = unix_time(self.enabled_date)
|
||||||
|
|
||||||
|
return admin
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def supported_service(service_principal):
|
||||||
|
return service_principal in FakeDelegatedAdministrator.SUPPORTED_SERVICES
|
||||||
|
|
||||||
|
|
||||||
class OrganizationsBackend(BaseBackend):
|
class OrganizationsBackend(BaseBackend):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.org = None
|
self.org = None
|
||||||
@ -228,6 +281,7 @@ class OrganizationsBackend(BaseBackend):
|
|||||||
self.ou = []
|
self.ou = []
|
||||||
self.policies = []
|
self.policies = []
|
||||||
self.services = []
|
self.services = []
|
||||||
|
self.admins = []
|
||||||
|
|
||||||
def create_organization(self, **kwargs):
|
def create_organization(self, **kwargs):
|
||||||
self.org = FakeOrganization(kwargs["FeatureSet"])
|
self.org = FakeOrganization(kwargs["FeatureSet"])
|
||||||
@ -259,10 +313,7 @@ class OrganizationsBackend(BaseBackend):
|
|||||||
|
|
||||||
def describe_organization(self):
|
def describe_organization(self):
|
||||||
if not self.org:
|
if not self.org:
|
||||||
raise RESTError(
|
raise AWSOrganizationsNotInUseException
|
||||||
"AWSOrganizationsNotInUseException",
|
|
||||||
"Your account is not a member of an organization.",
|
|
||||||
)
|
|
||||||
return self.org.describe()
|
return self.org.describe()
|
||||||
|
|
||||||
def list_roots(self):
|
def list_roots(self):
|
||||||
@ -325,10 +376,7 @@ class OrganizationsBackend(BaseBackend):
|
|||||||
(account for account in self.accounts if account.id == account_id), None
|
(account for account in self.accounts if account.id == account_id), None
|
||||||
)
|
)
|
||||||
if account is None:
|
if account is None:
|
||||||
raise RESTError(
|
raise AccountNotFoundException
|
||||||
"AccountNotFoundException",
|
|
||||||
"You specified an account that doesn't exist.",
|
|
||||||
)
|
|
||||||
return account
|
return account
|
||||||
|
|
||||||
def get_account_by_attr(self, attr, value):
|
def get_account_by_attr(self, attr, value):
|
||||||
@ -341,15 +389,12 @@ class OrganizationsBackend(BaseBackend):
|
|||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
if account is None:
|
if account is None:
|
||||||
raise RESTError(
|
raise AccountNotFoundException
|
||||||
"AccountNotFoundException",
|
|
||||||
"You specified an account that doesn't exist.",
|
|
||||||
)
|
|
||||||
return account
|
return account
|
||||||
|
|
||||||
def describe_account(self, **kwargs):
|
def describe_account(self, **kwargs):
|
||||||
account = self.get_account_by_id(kwargs["AccountId"])
|
account = self.get_account_by_id(kwargs["AccountId"])
|
||||||
return account.describe()
|
return dict(Account=account.describe())
|
||||||
|
|
||||||
def describe_create_account_status(self, **kwargs):
|
def describe_create_account_status(self, **kwargs):
|
||||||
account = self.get_account_by_attr(
|
account = self.get_account_by_attr(
|
||||||
@ -358,15 +403,13 @@ class OrganizationsBackend(BaseBackend):
|
|||||||
return account.create_account_status
|
return account.create_account_status
|
||||||
|
|
||||||
def list_accounts(self):
|
def list_accounts(self):
|
||||||
return dict(
|
return dict(Accounts=[account.describe() for account in self.accounts])
|
||||||
Accounts=[account.describe()["Account"] for account in self.accounts]
|
|
||||||
)
|
|
||||||
|
|
||||||
def list_accounts_for_parent(self, **kwargs):
|
def list_accounts_for_parent(self, **kwargs):
|
||||||
parent_id = self.validate_parent_id(kwargs["ParentId"])
|
parent_id = self.validate_parent_id(kwargs["ParentId"])
|
||||||
return dict(
|
return dict(
|
||||||
Accounts=[
|
Accounts=[
|
||||||
account.describe()["Account"]
|
account.describe()
|
||||||
for account in self.accounts
|
for account in self.accounts
|
||||||
if account.parent_id == parent_id
|
if account.parent_id == parent_id
|
||||||
]
|
]
|
||||||
@ -399,7 +442,7 @@ class OrganizationsBackend(BaseBackend):
|
|||||||
elif kwargs["ChildType"] == "ORGANIZATIONAL_UNIT":
|
elif kwargs["ChildType"] == "ORGANIZATIONAL_UNIT":
|
||||||
obj_list = self.ou
|
obj_list = self.ou
|
||||||
else:
|
else:
|
||||||
raise RESTError("InvalidInputException", "You specified an invalid value.")
|
raise InvalidInputException("You specified an invalid value.")
|
||||||
return dict(
|
return dict(
|
||||||
Children=[
|
Children=[
|
||||||
{"Id": obj.id, "Type": kwargs["ChildType"]}
|
{"Id": obj.id, "Type": kwargs["ChildType"]}
|
||||||
@ -427,7 +470,7 @@ class OrganizationsBackend(BaseBackend):
|
|||||||
"You specified a policy that doesn't exist.",
|
"You specified a policy that doesn't exist.",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise RESTError("InvalidInputException", "You specified an invalid value.")
|
raise InvalidInputException("You specified an invalid value.")
|
||||||
return policy.describe()
|
return policy.describe()
|
||||||
|
|
||||||
def get_policy_by_id(self, policy_id):
|
def get_policy_by_id(self, policy_id):
|
||||||
@ -472,12 +515,9 @@ class OrganizationsBackend(BaseBackend):
|
|||||||
account.attached_policies.append(policy)
|
account.attached_policies.append(policy)
|
||||||
policy.attachments.append(account)
|
policy.attachments.append(account)
|
||||||
else:
|
else:
|
||||||
raise RESTError(
|
raise AccountNotFoundException
|
||||||
"AccountNotFoundException",
|
|
||||||
"You specified an account that doesn't exist.",
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
raise RESTError("InvalidInputException", "You specified an invalid value.")
|
raise InvalidInputException("You specified an invalid value.")
|
||||||
|
|
||||||
def list_policies(self, **kwargs):
|
def list_policies(self, **kwargs):
|
||||||
return dict(
|
return dict(
|
||||||
@ -510,12 +550,9 @@ class OrganizationsBackend(BaseBackend):
|
|||||||
elif re.compile(utils.ACCOUNT_ID_REGEX).match(kwargs["TargetId"]):
|
elif re.compile(utils.ACCOUNT_ID_REGEX).match(kwargs["TargetId"]):
|
||||||
obj = next((a for a in self.accounts if a.id == kwargs["TargetId"]), None)
|
obj = next((a for a in self.accounts if a.id == kwargs["TargetId"]), None)
|
||||||
if obj is None:
|
if obj is None:
|
||||||
raise RESTError(
|
raise AccountNotFoundException
|
||||||
"AccountNotFoundException",
|
|
||||||
"You specified an account that doesn't exist.",
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
raise RESTError("InvalidInputException", "You specified an invalid value.")
|
raise InvalidInputException("You specified an invalid value.")
|
||||||
return dict(
|
return dict(
|
||||||
Policies=[
|
Policies=[
|
||||||
p.describe()["Policy"]["PolicySummary"] for p in obj.attached_policies
|
p.describe()["Policy"]["PolicySummary"] for p in obj.attached_policies
|
||||||
@ -533,7 +570,7 @@ class OrganizationsBackend(BaseBackend):
|
|||||||
"You specified a policy that doesn't exist.",
|
"You specified a policy that doesn't exist.",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise RESTError("InvalidInputException", "You specified an invalid value.")
|
raise InvalidInputException("You specified an invalid value.")
|
||||||
objects = [
|
objects = [
|
||||||
{"TargetId": obj.id, "Arn": obj.arn, "Name": obj.name, "Type": obj.type}
|
{"TargetId": obj.id, "Arn": obj.arn, "Name": obj.name, "Type": obj.type}
|
||||||
for obj in policy.attachments
|
for obj in policy.attachments
|
||||||
@ -606,5 +643,95 @@ class OrganizationsBackend(BaseBackend):
|
|||||||
if service_principal:
|
if service_principal:
|
||||||
self.services.remove(service_principal)
|
self.services.remove(service_principal)
|
||||||
|
|
||||||
|
def register_delegated_administrator(self, **kwargs):
|
||||||
|
account_id = kwargs["AccountId"]
|
||||||
|
|
||||||
|
if account_id == 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):
|
||||||
|
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):
|
||||||
|
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):
|
||||||
|
account_id = kwargs["AccountId"]
|
||||||
|
service = kwargs["ServicePrincipal"]
|
||||||
|
|
||||||
|
if account_id == 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)
|
||||||
|
|
||||||
|
|
||||||
organizations_backend = OrganizationsBackend()
|
organizations_backend = OrganizationsBackend()
|
||||||
|
@ -163,3 +163,31 @@ class OrganizationsResponse(BaseResponse):
|
|||||||
return json.dumps(
|
return json.dumps(
|
||||||
self.organizations_backend.disable_aws_service_access(**self.request_params)
|
self.organizations_backend.disable_aws_service_access(**self.request_params)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def register_delegated_administrator(self):
|
||||||
|
return json.dumps(
|
||||||
|
self.organizations_backend.register_delegated_administrator(
|
||||||
|
**self.request_params
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def list_delegated_administrators(self):
|
||||||
|
return json.dumps(
|
||||||
|
self.organizations_backend.list_delegated_administrators(
|
||||||
|
**self.request_params
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def list_delegated_services_for_account(self):
|
||||||
|
return json.dumps(
|
||||||
|
self.organizations_backend.list_delegated_services_for_account(
|
||||||
|
**self.request_params
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def deregister_delegated_administrator(self):
|
||||||
|
return json.dumps(
|
||||||
|
self.organizations_backend.deregister_delegated_administrator(
|
||||||
|
**self.request_params
|
||||||
|
)
|
||||||
|
)
|
||||||
|
@ -10,6 +10,7 @@ from botocore.exceptions import ClientError
|
|||||||
from nose.tools import assert_raises
|
from nose.tools import assert_raises
|
||||||
|
|
||||||
from moto import mock_organizations
|
from moto import mock_organizations
|
||||||
|
from moto.core import ACCOUNT_ID
|
||||||
from moto.organizations import utils
|
from moto.organizations import utils
|
||||||
from .organizations_test_utils import (
|
from .organizations_test_utils import (
|
||||||
validate_organization,
|
validate_organization,
|
||||||
@ -64,8 +65,11 @@ def test_describe_organization_exception():
|
|||||||
response = client.describe_organization()
|
response = client.describe_organization()
|
||||||
ex = e.exception
|
ex = e.exception
|
||||||
ex.operation_name.should.equal("DescribeOrganization")
|
ex.operation_name.should.equal("DescribeOrganization")
|
||||||
ex.response["Error"]["Code"].should.equal("400")
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
ex.response["Error"]["Message"].should.contain("AWSOrganizationsNotInUseException")
|
ex.response["Error"]["Code"].should.contain("AWSOrganizationsNotInUseException")
|
||||||
|
ex.response["Error"]["Message"].should.equal(
|
||||||
|
"Your account is not a member of an organization."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Organizational Units
|
# Organizational Units
|
||||||
@ -193,8 +197,11 @@ def test_describe_account_exception():
|
|||||||
response = client.describe_account(AccountId=utils.make_random_account_id())
|
response = client.describe_account(AccountId=utils.make_random_account_id())
|
||||||
ex = e.exception
|
ex = e.exception
|
||||||
ex.operation_name.should.equal("DescribeAccount")
|
ex.operation_name.should.equal("DescribeAccount")
|
||||||
ex.response["Error"]["Code"].should.equal("400")
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
ex.response["Error"]["Message"].should.contain("AccountNotFoundException")
|
ex.response["Error"]["Code"].should.contain("AccountNotFoundException")
|
||||||
|
ex.response["Error"]["Message"].should.equal(
|
||||||
|
"You specified an account that doesn't exist."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@mock_organizations
|
@mock_organizations
|
||||||
@ -340,8 +347,9 @@ def test_list_children_exception():
|
|||||||
response = client.list_children(ParentId=root_id, ChildType="BLEE")
|
response = client.list_children(ParentId=root_id, ChildType="BLEE")
|
||||||
ex = e.exception
|
ex = e.exception
|
||||||
ex.operation_name.should.equal("ListChildren")
|
ex.operation_name.should.equal("ListChildren")
|
||||||
ex.response["Error"]["Code"].should.equal("400")
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
ex.response["Error"]["Message"].should.contain("InvalidInputException")
|
ex.response["Error"]["Code"].should.contain("InvalidInputException")
|
||||||
|
ex.response["Error"]["Message"].should.equal("You specified an invalid value.")
|
||||||
|
|
||||||
|
|
||||||
# Service Control Policies
|
# Service Control Policies
|
||||||
@ -405,8 +413,9 @@ def test_describe_policy_exception():
|
|||||||
response = client.describe_policy(PolicyId="meaninglessstring")
|
response = client.describe_policy(PolicyId="meaninglessstring")
|
||||||
ex = e.exception
|
ex = e.exception
|
||||||
ex.operation_name.should.equal("DescribePolicy")
|
ex.operation_name.should.equal("DescribePolicy")
|
||||||
ex.response["Error"]["Code"].should.equal("400")
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
ex.response["Error"]["Message"].should.contain("InvalidInputException")
|
ex.response["Error"]["Code"].should.contain("InvalidInputException")
|
||||||
|
ex.response["Error"]["Message"].should.equal("You specified an invalid value.")
|
||||||
|
|
||||||
|
|
||||||
@mock_organizations
|
@mock_organizations
|
||||||
@ -517,16 +526,20 @@ def test_attach_policy_exception():
|
|||||||
response = client.attach_policy(PolicyId=policy_id, TargetId=account_id)
|
response = client.attach_policy(PolicyId=policy_id, TargetId=account_id)
|
||||||
ex = e.exception
|
ex = e.exception
|
||||||
ex.operation_name.should.equal("AttachPolicy")
|
ex.operation_name.should.equal("AttachPolicy")
|
||||||
ex.response["Error"]["Code"].should.equal("400")
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
ex.response["Error"]["Message"].should.contain("AccountNotFoundException")
|
ex.response["Error"]["Code"].should.contain("AccountNotFoundException")
|
||||||
|
ex.response["Error"]["Message"].should.equal(
|
||||||
|
"You specified an account that doesn't exist."
|
||||||
|
)
|
||||||
with assert_raises(ClientError) as e:
|
with assert_raises(ClientError) as e:
|
||||||
response = client.attach_policy(
|
response = client.attach_policy(
|
||||||
PolicyId=policy_id, TargetId="meaninglessstring"
|
PolicyId=policy_id, TargetId="meaninglessstring"
|
||||||
)
|
)
|
||||||
ex = e.exception
|
ex = e.exception
|
||||||
ex.operation_name.should.equal("AttachPolicy")
|
ex.operation_name.should.equal("AttachPolicy")
|
||||||
ex.response["Error"]["Code"].should.equal("400")
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
ex.response["Error"]["Message"].should.contain("InvalidInputException")
|
ex.response["Error"]["Code"].should.contain("InvalidInputException")
|
||||||
|
ex.response["Error"]["Message"].should.equal("You specified an invalid value.")
|
||||||
|
|
||||||
|
|
||||||
@mock_organizations
|
@mock_organizations
|
||||||
@ -636,16 +649,20 @@ def test_list_policies_for_target_exception():
|
|||||||
)
|
)
|
||||||
ex = e.exception
|
ex = e.exception
|
||||||
ex.operation_name.should.equal("ListPoliciesForTarget")
|
ex.operation_name.should.equal("ListPoliciesForTarget")
|
||||||
ex.response["Error"]["Code"].should.equal("400")
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
ex.response["Error"]["Message"].should.contain("AccountNotFoundException")
|
ex.response["Error"]["Code"].should.contain("AccountNotFoundException")
|
||||||
|
ex.response["Error"]["Message"].should.equal(
|
||||||
|
"You specified an account that doesn't exist."
|
||||||
|
)
|
||||||
with assert_raises(ClientError) as e:
|
with assert_raises(ClientError) as e:
|
||||||
response = client.list_policies_for_target(
|
response = client.list_policies_for_target(
|
||||||
TargetId="meaninglessstring", Filter="SERVICE_CONTROL_POLICY"
|
TargetId="meaninglessstring", Filter="SERVICE_CONTROL_POLICY"
|
||||||
)
|
)
|
||||||
ex = e.exception
|
ex = e.exception
|
||||||
ex.operation_name.should.equal("ListPoliciesForTarget")
|
ex.operation_name.should.equal("ListPoliciesForTarget")
|
||||||
ex.response["Error"]["Code"].should.equal("400")
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
ex.response["Error"]["Message"].should.contain("InvalidInputException")
|
ex.response["Error"]["Code"].should.contain("InvalidInputException")
|
||||||
|
ex.response["Error"]["Message"].should.equal("You specified an invalid value.")
|
||||||
|
|
||||||
|
|
||||||
@mock_organizations
|
@mock_organizations
|
||||||
@ -694,8 +711,9 @@ def test_list_targets_for_policy_exception():
|
|||||||
response = client.list_targets_for_policy(PolicyId="meaninglessstring")
|
response = client.list_targets_for_policy(PolicyId="meaninglessstring")
|
||||||
ex = e.exception
|
ex = e.exception
|
||||||
ex.operation_name.should.equal("ListTargetsForPolicy")
|
ex.operation_name.should.equal("ListTargetsForPolicy")
|
||||||
ex.response["Error"]["Code"].should.equal("400")
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
ex.response["Error"]["Message"].should.contain("InvalidInputException")
|
ex.response["Error"]["Code"].should.contain("InvalidInputException")
|
||||||
|
ex.response["Error"]["Message"].should.equal("You specified an invalid value.")
|
||||||
|
|
||||||
|
|
||||||
@mock_organizations
|
@mock_organizations
|
||||||
@ -947,3 +965,343 @@ def test_disable_aws_service_access_errors():
|
|||||||
ex.response["Error"]["Message"].should.equal(
|
ex.response["Error"]["Message"].should.equal(
|
||||||
"You specified an unrecognized service principal."
|
"You specified an unrecognized service principal."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_organizations
|
||||||
|
def test_register_delegated_administrator():
|
||||||
|
# given
|
||||||
|
client = boto3.client("organizations", region_name="us-east-1")
|
||||||
|
org_id = client.create_organization(FeatureSet="ALL")["Organization"]["Id"]
|
||||||
|
account_id = client.create_account(AccountName=mockname, Email=mockemail)[
|
||||||
|
"CreateAccountStatus"
|
||||||
|
]["AccountId"]
|
||||||
|
|
||||||
|
# when
|
||||||
|
client.register_delegated_administrator(
|
||||||
|
AccountId=account_id, ServicePrincipal="ssm.amazonaws.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
# then
|
||||||
|
response = client.list_delegated_administrators()
|
||||||
|
response["DelegatedAdministrators"].should.have.length_of(1)
|
||||||
|
admin = response["DelegatedAdministrators"][0]
|
||||||
|
admin["Id"].should.equal(account_id)
|
||||||
|
admin["Arn"].should.equal(
|
||||||
|
"arn:aws:organizations::{0}:account/{1}/{2}".format(
|
||||||
|
ACCOUNT_ID, org_id, account_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
admin["Email"].should.equal(mockemail)
|
||||||
|
admin["Name"].should.equal(mockname)
|
||||||
|
admin["Status"].should.equal("ACTIVE")
|
||||||
|
admin["JoinedMethod"].should.equal("CREATED")
|
||||||
|
admin["JoinedTimestamp"].should.be.a(datetime)
|
||||||
|
admin["DelegationEnabledDate"].should.be.a(datetime)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_organizations
|
||||||
|
def test_register_delegated_administrator_errors():
|
||||||
|
# given
|
||||||
|
client = boto3.client("organizations", region_name="us-east-1")
|
||||||
|
client.create_organization(FeatureSet="ALL")
|
||||||
|
account_id = client.create_account(AccountName=mockname, Email=mockemail)[
|
||||||
|
"CreateAccountStatus"
|
||||||
|
]["AccountId"]
|
||||||
|
client.register_delegated_administrator(
|
||||||
|
AccountId=account_id, ServicePrincipal="ssm.amazonaws.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
# register master Account
|
||||||
|
# when
|
||||||
|
with assert_raises(ClientError) as e:
|
||||||
|
client.register_delegated_administrator(
|
||||||
|
AccountId=ACCOUNT_ID, ServicePrincipal="ssm.amazonaws.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
# then
|
||||||
|
ex = e.exception
|
||||||
|
ex.operation_name.should.equal("RegisterDelegatedAdministrator")
|
||||||
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
ex.response["Error"]["Code"].should.contain("ConstraintViolationException")
|
||||||
|
ex.response["Error"]["Message"].should.equal(
|
||||||
|
"You cannot register master account/yourself as delegated administrator for your organization."
|
||||||
|
)
|
||||||
|
|
||||||
|
# register not existing Account
|
||||||
|
# when
|
||||||
|
with assert_raises(ClientError) as e:
|
||||||
|
client.register_delegated_administrator(
|
||||||
|
AccountId="000000000000", ServicePrincipal="ssm.amazonaws.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
# then
|
||||||
|
ex = e.exception
|
||||||
|
ex.operation_name.should.equal("RegisterDelegatedAdministrator")
|
||||||
|
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."
|
||||||
|
)
|
||||||
|
|
||||||
|
# register not supported service
|
||||||
|
# when
|
||||||
|
with assert_raises(ClientError) as e:
|
||||||
|
client.register_delegated_administrator(
|
||||||
|
AccountId=account_id, ServicePrincipal="moto.amazonaws.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
# then
|
||||||
|
ex = e.exception
|
||||||
|
ex.operation_name.should.equal("RegisterDelegatedAdministrator")
|
||||||
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
ex.response["Error"]["Code"].should.contain("InvalidInputException")
|
||||||
|
ex.response["Error"]["Message"].should.equal(
|
||||||
|
"You specified an unrecognized service principal."
|
||||||
|
)
|
||||||
|
|
||||||
|
# register service again
|
||||||
|
# when
|
||||||
|
with assert_raises(ClientError) as e:
|
||||||
|
client.register_delegated_administrator(
|
||||||
|
AccountId=account_id, ServicePrincipal="ssm.amazonaws.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
# then
|
||||||
|
ex = e.exception
|
||||||
|
ex.operation_name.should.equal("RegisterDelegatedAdministrator")
|
||||||
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
ex.response["Error"]["Code"].should.contain("AccountAlreadyRegisteredException")
|
||||||
|
ex.response["Error"]["Message"].should.equal(
|
||||||
|
"The provided account is already a delegated administrator for your organization."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_organizations
|
||||||
|
def test_list_delegated_administrators():
|
||||||
|
# given
|
||||||
|
client = boto3.client("organizations", region_name="us-east-1")
|
||||||
|
org_id = client.create_organization(FeatureSet="ALL")["Organization"]["Id"]
|
||||||
|
account_id_1 = client.create_account(AccountName=mockname, Email=mockemail)[
|
||||||
|
"CreateAccountStatus"
|
||||||
|
]["AccountId"]
|
||||||
|
account_id_2 = client.create_account(AccountName=mockname, Email=mockemail)[
|
||||||
|
"CreateAccountStatus"
|
||||||
|
]["AccountId"]
|
||||||
|
client.register_delegated_administrator(
|
||||||
|
AccountId=account_id_1, ServicePrincipal="ssm.amazonaws.com"
|
||||||
|
)
|
||||||
|
client.register_delegated_administrator(
|
||||||
|
AccountId=account_id_2, ServicePrincipal="guardduty.amazonaws.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
# when
|
||||||
|
response = client.list_delegated_administrators()
|
||||||
|
|
||||||
|
# then
|
||||||
|
response["DelegatedAdministrators"].should.have.length_of(2)
|
||||||
|
sorted([admin["Id"] for admin in response["DelegatedAdministrators"]]).should.equal(
|
||||||
|
sorted([account_id_1, account_id_2])
|
||||||
|
)
|
||||||
|
|
||||||
|
# when
|
||||||
|
response = client.list_delegated_administrators(
|
||||||
|
ServicePrincipal="ssm.amazonaws.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
# then
|
||||||
|
response["DelegatedAdministrators"].should.have.length_of(1)
|
||||||
|
admin = response["DelegatedAdministrators"][0]
|
||||||
|
admin["Id"].should.equal(account_id_1)
|
||||||
|
admin["Arn"].should.equal(
|
||||||
|
"arn:aws:organizations::{0}:account/{1}/{2}".format(
|
||||||
|
ACCOUNT_ID, org_id, account_id_1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
admin["Email"].should.equal(mockemail)
|
||||||
|
admin["Name"].should.equal(mockname)
|
||||||
|
admin["Status"].should.equal("ACTIVE")
|
||||||
|
admin["JoinedMethod"].should.equal("CREATED")
|
||||||
|
admin["JoinedTimestamp"].should.be.a(datetime)
|
||||||
|
admin["DelegationEnabledDate"].should.be.a(datetime)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_organizations
|
||||||
|
def test_list_delegated_administrators_erros():
|
||||||
|
# given
|
||||||
|
client = boto3.client("organizations", region_name="us-east-1")
|
||||||
|
client.create_organization(FeatureSet="ALL")
|
||||||
|
|
||||||
|
# list not supported service
|
||||||
|
# when
|
||||||
|
with assert_raises(ClientError) as e:
|
||||||
|
client.list_delegated_administrators(ServicePrincipal="moto.amazonaws.com")
|
||||||
|
|
||||||
|
# then
|
||||||
|
ex = e.exception
|
||||||
|
ex.operation_name.should.equal("ListDelegatedAdministrators")
|
||||||
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
ex.response["Error"]["Code"].should.contain("InvalidInputException")
|
||||||
|
ex.response["Error"]["Message"].should.equal(
|
||||||
|
"You specified an unrecognized service principal."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_organizations
|
||||||
|
def test_list_delegated_services_for_account():
|
||||||
|
# given
|
||||||
|
client = boto3.client("organizations", region_name="us-east-1")
|
||||||
|
client.create_organization(FeatureSet="ALL")
|
||||||
|
account_id = client.create_account(AccountName=mockname, Email=mockemail)[
|
||||||
|
"CreateAccountStatus"
|
||||||
|
]["AccountId"]
|
||||||
|
client.register_delegated_administrator(
|
||||||
|
AccountId=account_id, ServicePrincipal="ssm.amazonaws.com"
|
||||||
|
)
|
||||||
|
client.register_delegated_administrator(
|
||||||
|
AccountId=account_id, ServicePrincipal="guardduty.amazonaws.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
# when
|
||||||
|
response = client.list_delegated_services_for_account(AccountId=account_id)
|
||||||
|
|
||||||
|
# then
|
||||||
|
response["DelegatedServices"].should.have.length_of(2)
|
||||||
|
sorted(
|
||||||
|
[service["ServicePrincipal"] for service in response["DelegatedServices"]]
|
||||||
|
).should.equal(["guardduty.amazonaws.com", "ssm.amazonaws.com"])
|
||||||
|
|
||||||
|
|
||||||
|
@mock_organizations
|
||||||
|
def test_list_delegated_services_for_account_erros():
|
||||||
|
# given
|
||||||
|
client = boto3.client("organizations", region_name="us-east-1")
|
||||||
|
client.create_organization(FeatureSet="ALL")
|
||||||
|
|
||||||
|
# list services for not existing Account
|
||||||
|
# when
|
||||||
|
with assert_raises(ClientError) as e:
|
||||||
|
client.list_delegated_services_for_account(AccountId="000000000000")
|
||||||
|
|
||||||
|
# then
|
||||||
|
ex = e.exception
|
||||||
|
ex.operation_name.should.equal("ListDelegatedServicesForAccount")
|
||||||
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
ex.response["Error"]["Code"].should.contain("AWSOrganizationsNotInUseException")
|
||||||
|
ex.response["Error"]["Message"].should.equal(
|
||||||
|
"Your account is not a member of an organization."
|
||||||
|
)
|
||||||
|
|
||||||
|
# list services for not registered Account
|
||||||
|
# when
|
||||||
|
with assert_raises(ClientError) as e:
|
||||||
|
client.list_delegated_services_for_account(AccountId=ACCOUNT_ID)
|
||||||
|
|
||||||
|
# then
|
||||||
|
ex = e.exception
|
||||||
|
ex.operation_name.should.equal("ListDelegatedServicesForAccount")
|
||||||
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
ex.response["Error"]["Code"].should.contain("AccountNotRegisteredException")
|
||||||
|
ex.response["Error"]["Message"].should.equal(
|
||||||
|
"The provided account is not a registered delegated administrator for your organization."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_organizations
|
||||||
|
def test_deregister_delegated_administrator():
|
||||||
|
# given
|
||||||
|
client = boto3.client("organizations", region_name="us-east-1")
|
||||||
|
client.create_organization(FeatureSet="ALL")
|
||||||
|
account_id = client.create_account(AccountName=mockname, Email=mockemail)[
|
||||||
|
"CreateAccountStatus"
|
||||||
|
]["AccountId"]
|
||||||
|
client.register_delegated_administrator(
|
||||||
|
AccountId=account_id, ServicePrincipal="ssm.amazonaws.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
# when
|
||||||
|
client.deregister_delegated_administrator(
|
||||||
|
AccountId=account_id, ServicePrincipal="ssm.amazonaws.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
# then
|
||||||
|
response = client.list_delegated_administrators()
|
||||||
|
response["DelegatedAdministrators"].should.have.length_of(0)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_organizations
|
||||||
|
def test_deregister_delegated_administrator_erros():
|
||||||
|
# given
|
||||||
|
client = boto3.client("organizations", region_name="us-east-1")
|
||||||
|
client.create_organization(FeatureSet="ALL")
|
||||||
|
account_id = client.create_account(AccountName=mockname, Email=mockemail)[
|
||||||
|
"CreateAccountStatus"
|
||||||
|
]["AccountId"]
|
||||||
|
|
||||||
|
# deregister master Account
|
||||||
|
# when
|
||||||
|
with assert_raises(ClientError) as e:
|
||||||
|
client.deregister_delegated_administrator(
|
||||||
|
AccountId=ACCOUNT_ID, ServicePrincipal="ssm.amazonaws.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
# then
|
||||||
|
ex = e.exception
|
||||||
|
ex.operation_name.should.equal("DeregisterDelegatedAdministrator")
|
||||||
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
ex.response["Error"]["Code"].should.contain("ConstraintViolationException")
|
||||||
|
ex.response["Error"]["Message"].should.equal(
|
||||||
|
"You cannot register master account/yourself as delegated administrator for your organization."
|
||||||
|
)
|
||||||
|
|
||||||
|
# deregister not existing Account
|
||||||
|
# when
|
||||||
|
with assert_raises(ClientError) as e:
|
||||||
|
client.deregister_delegated_administrator(
|
||||||
|
AccountId="000000000000", ServicePrincipal="ssm.amazonaws.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
# then
|
||||||
|
ex = e.exception
|
||||||
|
ex.operation_name.should.equal("DeregisterDelegatedAdministrator")
|
||||||
|
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."
|
||||||
|
)
|
||||||
|
|
||||||
|
# deregister not registered Account
|
||||||
|
# when
|
||||||
|
with assert_raises(ClientError) as e:
|
||||||
|
client.deregister_delegated_administrator(
|
||||||
|
AccountId=account_id, ServicePrincipal="ssm.amazonaws.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
# then
|
||||||
|
ex = e.exception
|
||||||
|
ex.operation_name.should.equal("DeregisterDelegatedAdministrator")
|
||||||
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
ex.response["Error"]["Code"].should.contain("AccountNotRegisteredException")
|
||||||
|
ex.response["Error"]["Message"].should.equal(
|
||||||
|
"The provided account is not a registered delegated administrator for your organization."
|
||||||
|
)
|
||||||
|
|
||||||
|
# given
|
||||||
|
client.register_delegated_administrator(
|
||||||
|
AccountId=account_id, ServicePrincipal="ssm.amazonaws.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
# deregister not registered service
|
||||||
|
# when
|
||||||
|
with assert_raises(ClientError) as e:
|
||||||
|
client.deregister_delegated_administrator(
|
||||||
|
AccountId=account_id, ServicePrincipal="guardduty.amazonaws.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
# then
|
||||||
|
ex = e.exception
|
||||||
|
ex.operation_name.should.equal("DeregisterDelegatedAdministrator")
|
||||||
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
ex.response["Error"]["Code"].should.contain("InvalidInputException")
|
||||||
|
ex.response["Error"]["Message"].should.equal(
|
||||||
|
"You specified an unrecognized service principal."
|
||||||
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user