Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SSO-Admin instances #7727

Merged
merged 1 commit into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions IMPLEMENTATION_COVERAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7891,7 +7891,7 @@

## sso-admin
<details>
<summary>27% implemented</summary>
<summary>34% implemented</summary>

- [X] attach_customer_managed_policy_reference_to_permission_set
- [X] attach_managed_policy_to_permission_set
Expand Down Expand Up @@ -7936,7 +7936,7 @@
- [ ] list_account_assignment_deletion_status
- [X] list_account_assignments
- [X] list_account_assignments_for_principal
- [ ] list_accounts_for_provisioned_permission_set
- [X] list_accounts_for_provisioned_permission_set
- [ ] list_application_access_scopes
- [ ] list_application_assignments
- [ ] list_application_assignments_for_principal
Expand All @@ -7945,14 +7945,14 @@
- [ ] list_application_providers
- [ ] list_applications
- [X] list_customer_managed_policy_references_in_permission_set
- [ ] list_instances
- [X] list_instances
- [X] list_managed_policies_in_permission_set
- [ ] list_permission_set_provisioning_status
- [X] list_permission_sets
- [ ] list_permission_sets_provisioned_to_account
- [X] list_permission_sets_provisioned_to_account
- [ ] list_tags_for_resource
- [ ] list_trusted_token_issuers
- [ ] provision_permission_set
- [X] provision_permission_set
- [ ] put_application_access_scope
- [ ] put_application_assignment_configuration
- [ ] put_application_authentication_method
Expand All @@ -7962,7 +7962,7 @@
- [ ] tag_resource
- [ ] untag_resource
- [ ] update_application
- [ ] update_instance
- [X] update_instance
- [ ] update_instance_access_control_attribute_configuration
- [X] update_permission_set
- [ ] update_trusted_token_issuer
Expand Down
22 changes: 17 additions & 5 deletions docs/docs/services/sso-admin.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@ sso-admin
- [ ] list_account_assignment_deletion_status
- [X] list_account_assignments
- [X] list_account_assignments_for_principal
- [ ] list_accounts_for_provisioned_permission_set
- [X] list_accounts_for_provisioned_permission_set

The following parameters are not yet implemented: MaxResults, NextToken, ProvisioningStatus


- [ ] list_application_access_scopes
- [ ] list_application_assignments
- [ ] list_application_assignments_for_principal
Expand All @@ -68,14 +72,22 @@ sso-admin
- [ ] list_application_providers
- [ ] list_applications
- [X] list_customer_managed_policy_references_in_permission_set
- [ ] list_instances
- [X] list_instances
- [X] list_managed_policies_in_permission_set
- [ ] list_permission_set_provisioning_status
- [X] list_permission_sets
- [ ] list_permission_sets_provisioned_to_account
- [X] list_permission_sets_provisioned_to_account

The following parameters are not yet implemented: AccountId, ProvisioningStatus, MaxResults, NextToken


- [ ] list_tags_for_resource
- [ ] list_trusted_token_issuers
- [ ] provision_permission_set
- [X] provision_permission_set

The TargetType/TargetId parameters are currently ignored - PermissionSets are simply provisioned to the caller's account


- [ ] put_application_access_scope
- [ ] put_application_assignment_configuration
- [ ] put_application_authentication_method
Expand All @@ -85,7 +97,7 @@ sso-admin
- [ ] tag_resource
- [ ] untag_resource
- [ ] update_application
- [ ] update_instance
- [X] update_instance
- [ ] update_instance_access_control_attribute_configuration
- [X] update_permission_set
- [ ] update_trusted_token_issuer
Expand Down
84 changes: 78 additions & 6 deletions moto/ssoadmin/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from moto.iam.aws_managed_policies import aws_managed_policies_data
from moto.moto_api._internal import mock_random as random
from moto.utilities.paginator import paginate
from moto.utilities.utils import get_partition

from .exceptions import (
ConflictException,
Expand Down Expand Up @@ -94,12 +95,7 @@

@staticmethod
def generate_id(instance_arn: str) -> str:
chars = list(range(10)) + ["a", "b", "c", "d", "e", "f"]
return (
instance_arn
+ "/ps-"
+ "".join(str(random.choice(chars)) for _ in range(16))
)
return instance_arn + "/ps-" + random.get_random_string(length=16).lower()


class ManagedPolicy(BaseModel):
Expand All @@ -124,6 +120,30 @@
return f"{self.path}{self.name}" == f"{other.path}{other.name}"


class Instance:
def __init__(self, account_id: str, region: str):
self.created_date = unix_time()
self.identity_store_id = (
f"d-{random.get_random_string(length=10, lower_case=True)}"
)
self.instance_arn = f"arn:{get_partition(region)}:sso:::instance/ssoins-{random.get_random_string(length=16, lower_case=True)}"
self.account_id = account_id
self.status = "ACTIVE"
self.name: Optional[str] = None

self.provisioned_permission_sets: List[PermissionSet] = []

def to_json(self) -> Dict[str, Any]:
return {
"CreatedDate": self.created_date,
"IdentityStoreId": self.identity_store_id,
"InstanceArn": self.instance_arn,
"Name": self.name,
"OwnerAccountId": self.account_id,
"Status": self.status,
}


class SSOAdminBackend(BaseBackend):
"""Implementation of SSOAdmin APIs."""

Expand All @@ -133,6 +153,9 @@
self.deleted_account_assignments: List[AccountAssignment] = list()
self.permission_sets: List[PermissionSet] = list()
self.aws_managed_policies: Optional[Dict[str, Any]] = None
self.instances: List[Instance] = []

self.instances.append(Instance(self.account_id, self.region_name))

def create_account_assignment(
self,
Expand Down Expand Up @@ -327,6 +350,13 @@
permission_set_arn,
)
self.permission_sets.remove(permission_set)

for instance in self.instances:
try:
instance.provisioned_permission_sets.remove(permission_set)
except ValueError:
pass

return permission_set.to_json(include_creation_date=True)

def _find_permission_set(
Expand Down Expand Up @@ -542,5 +572,47 @@

raise ResourceNotFoundException

def list_instances(self) -> List[Instance]:
return self.instances

def update_instance(self, instance_arn: str, name: str) -> None:
for instance in self.instances:
if instance.instance_arn == instance_arn:
instance.name = name

def provision_permission_set(
self, instance_arn: str, permission_set_arn: str
) -> None:
"""
The TargetType/TargetId parameters are currently ignored - PermissionSets are simply provisioned to the caller's account
"""
permission_set = self._find_permission_set(instance_arn, permission_set_arn)
instance = [i for i in self.instances if i.instance_arn == instance_arn][0]
instance.provisioned_permission_sets.append(permission_set)

def list_permission_sets_provisioned_to_account(
self, instance_arn: str
) -> List[PermissionSet]:
"""
The following parameters are not yet implemented: AccountId, ProvisioningStatus, MaxResults, NextToken
"""
for instance in self.instances:
if instance.instance_arn == instance_arn:
return instance.provisioned_permission_sets
return []

Check warning on line 602 in moto/ssoadmin/models.py

View check run for this annotation

Codecov / codecov/patch

moto/ssoadmin/models.py#L602

Added line #L602 was not covered by tests

def list_accounts_for_provisioned_permission_set(
self, instance_arn: str, permission_set_arn: str
) -> List[str]:
"""
The following parameters are not yet implemented: MaxResults, NextToken, ProvisioningStatus
"""
for instance in self.instances:
if instance.instance_arn == instance_arn:
for ps in instance.provisioned_permission_sets:
if ps.permission_set_arn == permission_set_arn:
return [self.account_id]
return []


ssoadmin_backends = BackendDict(SSOAdminBackend, "sso-admin")
57 changes: 57 additions & 0 deletions moto/ssoadmin/responses.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import json
from uuid import uuid4

from moto.core.responses import BaseResponse
from moto.core.utils import unix_time

from .models import SSOAdminBackend, ssoadmin_backends

Expand Down Expand Up @@ -325,3 +327,58 @@ def describe_account_assignment_deletion_status(self) -> str:
return json.dumps(
dict(AccountAssignmentDeletionStatus=account_assignment_deletion_status)
)

def list_instances(self) -> str:
instances = self.ssoadmin_backend.list_instances()

return json.dumps({"Instances": [i.to_json() for i in instances]})

def update_instance(self) -> str:
instance_arn = self._get_param("InstanceArn")
name = self._get_param("Name")

self.ssoadmin_backend.update_instance(instance_arn=instance_arn, name=name)

return "{}"

def provision_permission_set(self) -> str:
instance_arn = self._get_param("InstanceArn")
permission_set_arn = self._get_param("PermissionSetArn")

self.ssoadmin_backend.provision_permission_set(
instance_arn=instance_arn,
permission_set_arn=permission_set_arn,
)
return json.dumps(
{
"PermissionSetProvisioningStatus": {
"AccountId": self.current_account,
"CreatedDate": unix_time(),
"PermissionSetArn": permission_set_arn,
"RequestId": str(uuid4()),
"Status": "SUCCEEDED",
}
}
)

def list_permission_sets_provisioned_to_account(self) -> str:
instance_arn = self._get_param("InstanceArn")

permission_sets = (
self.ssoadmin_backend.list_permission_sets_provisioned_to_account(
instance_arn
)
)
arns = [p.permission_set_arn for p in permission_sets]
return json.dumps({"PermissionSets": arns})

def list_accounts_for_provisioned_permission_set(self) -> str:
instance_arn = self._get_param("InstanceArn")
permission_set_arn = self._get_param("PermissionSetArn")

account_ids = (
self.ssoadmin_backend.list_accounts_for_provisioned_permission_set(
instance_arn=instance_arn, permission_set_arn=permission_set_arn
)
)
return json.dumps({"AccountIds": account_ids})
38 changes: 38 additions & 0 deletions tests/test_ssoadmin/test_ssoadmin_instances.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import boto3

from moto import mock_aws
from tests import DEFAULT_ACCOUNT_ID


@mock_aws
def test_list_instances():
ssoadmin = boto3.client("sso-admin", "us-east-1")

# We automatically create an instance on startup
# In AWS, this would involve some manual steps on the dashboard
instances = ssoadmin.list_instances()["Instances"]
assert len(instances) == 1

assert instances[0]["CreatedDate"]
assert instances[0]["IdentityStoreId"].startswith("d-")
assert instances[0]["InstanceArn"].startswith("arn:aws:sso:::instance/ssoins-")
assert instances[0]["OwnerAccountId"] == DEFAULT_ACCOUNT_ID
assert instances[0]["Status"] == "ACTIVE"

assert "Name" not in instances[0]


@mock_aws
def test_update_instance():
ssoadmin = boto3.client("sso-admin", "us-east-1")

# We automatically create an instance on startup
# In AWS, this would involve some manual steps on the dashboard
initial = ssoadmin.list_instances()["Instances"][0]

ssoadmin.update_instance(InstanceArn=initial["InstanceArn"], Name="instancename")

updated = ssoadmin.list_instances()["Instances"][0]
assert updated["Name"] == "instancename"
assert initial["IdentityStoreId"] == updated["IdentityStoreId"]
assert initial["InstanceArn"] == updated["InstanceArn"]
63 changes: 63 additions & 0 deletions tests/test_ssoadmin/test_ssoadmin_permission_sets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import boto3

from moto import mock_aws
from tests import DEFAULT_ACCOUNT_ID


@mock_aws
def test_provision_permission_set():
ssoadmin = boto3.client("sso-admin", "us-east-1")

instance_arn = ssoadmin.list_instances()["Instances"][0]["InstanceArn"]

p_set_arn = ssoadmin.create_permission_set(InstanceArn=instance_arn, Name="pset1")[
"PermissionSet"
]["PermissionSetArn"]

status = ssoadmin.provision_permission_set(
InstanceArn=instance_arn,
PermissionSetArn=p_set_arn,
TargetType="AWS_ACCOUNT",
)["PermissionSetProvisioningStatus"]

assert status["AccountId"] == DEFAULT_ACCOUNT_ID
assert status["CreatedDate"]
assert status["PermissionSetArn"] == p_set_arn
assert status["Status"] == "SUCCEEDED"


@mock_aws
def test_list_permission_sets_provisioned_to_account():
ssoadmin = boto3.client("sso-admin", "us-east-1")

instance_arn = ssoadmin.list_instances()["Instances"][0]["InstanceArn"]

p_set_arn = ssoadmin.create_permission_set(InstanceArn=instance_arn, Name="pset1")[
"PermissionSet"
]["PermissionSetArn"]

provisioned = ssoadmin.list_permission_sets_provisioned_to_account(
AccountId=DEFAULT_ACCOUNT_ID, InstanceArn=instance_arn
)["PermissionSets"]
assert len(provisioned) == 0

accounts = ssoadmin.list_accounts_for_provisioned_permission_set(
InstanceArn=instance_arn, PermissionSetArn=p_set_arn
)["AccountIds"]
assert accounts == []

ssoadmin.provision_permission_set(
InstanceArn=instance_arn,
PermissionSetArn=p_set_arn,
TargetType="AWS_ACCOUNT",
)

provisioned = ssoadmin.list_permission_sets_provisioned_to_account(
AccountId=DEFAULT_ACCOUNT_ID, InstanceArn=instance_arn
)["PermissionSets"]
assert provisioned == [p_set_arn]

accounts = ssoadmin.list_accounts_for_provisioned_permission_set(
InstanceArn=instance_arn, PermissionSetArn=p_set_arn
)["AccountIds"]
assert accounts == [DEFAULT_ACCOUNT_ID]