Identitystore - Partial implementation (#6411)
This commit is contained in:
		
							parent
							
								
									b11cc6a5fe
								
							
						
					
					
						commit
						5de95f4d02
					
				@ -1 +1,37 @@
 | 
			
		||||
"""Exceptions raised by the identitystore service."""
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
from moto.core.exceptions import AWSError
 | 
			
		||||
from typing import Any
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
request_id = "178936da-50ad-4d58-8871-22d9979e8658example"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class IdentityStoreError(AWSError):
 | 
			
		||||
    def __init__(self, **kwargs: Any):
 | 
			
		||||
        super(AWSError, self).__init__(error_type=self.TYPE, message=kwargs["message"])  # type: ignore
 | 
			
		||||
        self.description: str = json.dumps(
 | 
			
		||||
            {
 | 
			
		||||
                "__type": self.error_type,
 | 
			
		||||
                "RequestId": request_id,
 | 
			
		||||
                "Message": self.message,
 | 
			
		||||
                "ResourceType": kwargs.get("resource_type"),
 | 
			
		||||
                "Reason": kwargs.get("reason"),
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ResourceNotFoundException(IdentityStoreError):
 | 
			
		||||
    TYPE = "ResourceNotFoundException"
 | 
			
		||||
    code = 400
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ValidationException(IdentityStoreError):
 | 
			
		||||
    TYPE = "ValidationException"
 | 
			
		||||
    code = 400
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ConflictException(IdentityStoreError):
 | 
			
		||||
    TYPE = "ConflictException"
 | 
			
		||||
    code = 400
 | 
			
		||||
 | 
			
		||||
@ -1,19 +1,98 @@
 | 
			
		||||
from typing import Dict, Tuple
 | 
			
		||||
from typing import Dict, Tuple, List, Any, NamedTuple, Optional
 | 
			
		||||
from typing_extensions import Self
 | 
			
		||||
from moto.utilities.paginator import paginate
 | 
			
		||||
 | 
			
		||||
from botocore.exceptions import ParamValidationError
 | 
			
		||||
 | 
			
		||||
from moto.moto_api._internal import mock_random
 | 
			
		||||
from moto.core import BaseBackend, BackendDict
 | 
			
		||||
from .exceptions import (
 | 
			
		||||
    ResourceNotFoundException,
 | 
			
		||||
    ValidationException,
 | 
			
		||||
    ConflictException,
 | 
			
		||||
)
 | 
			
		||||
import warnings
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Name(NamedTuple):
 | 
			
		||||
    Formatted: Optional[str]
 | 
			
		||||
    FamilyName: Optional[str]
 | 
			
		||||
    GivenName: Optional[str]
 | 
			
		||||
    MiddleName: Optional[str]
 | 
			
		||||
    HonorificPrefix: Optional[str]
 | 
			
		||||
    HonorificSuffix: Optional[str]
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def from_dict(cls, name_dict: Dict[str, str]) -> Optional[Self]:
 | 
			
		||||
        if not name_dict:
 | 
			
		||||
            return None
 | 
			
		||||
        return cls(
 | 
			
		||||
            name_dict.get("Formatted"),
 | 
			
		||||
            name_dict.get("FamilyName"),
 | 
			
		||||
            name_dict.get("GivenName"),
 | 
			
		||||
            name_dict.get("MiddleName"),
 | 
			
		||||
            name_dict.get("HonorificPrefix"),
 | 
			
		||||
            name_dict.get("HonorificSuffix"),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class User(NamedTuple):
 | 
			
		||||
    UserId: str
 | 
			
		||||
    IdentityStoreId: str
 | 
			
		||||
    UserName: str
 | 
			
		||||
    Name: Optional[Name]
 | 
			
		||||
    DisplayName: str
 | 
			
		||||
    NickName: str
 | 
			
		||||
    ProfileUrl: str
 | 
			
		||||
    Emails: List[Dict[str, str]]
 | 
			
		||||
    Addresses: List[Dict[str, str]]
 | 
			
		||||
    PhoneNumbers: List[Dict[str, str]]
 | 
			
		||||
    UserType: str
 | 
			
		||||
    Title: str
 | 
			
		||||
    PreferredLanguage: str
 | 
			
		||||
    Locale: str
 | 
			
		||||
    Timezone: str
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class IdentityStoreData:
 | 
			
		||||
    def __init__(self) -> None:
 | 
			
		||||
        self.groups: Dict[str, Dict[str, str]] = {}
 | 
			
		||||
        self.users: Dict[str, User] = {}
 | 
			
		||||
        self.group_memberships: Dict[str, Any] = {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class IdentityStoreBackend(BaseBackend):
 | 
			
		||||
    """Implementation of IdentityStore APIs."""
 | 
			
		||||
 | 
			
		||||
    def __init__(self, region_name: str, account_id: str):
 | 
			
		||||
    PAGINATION_MODEL = {
 | 
			
		||||
        "list_group_memberships": {
 | 
			
		||||
            "input_token": "next_token",
 | 
			
		||||
            "limit_key": "max_results",
 | 
			
		||||
            "limit_default": 100,
 | 
			
		||||
            "unique_attribute": "MembershipId",
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    def __init__(self, region_name: str, account_id: str) -> None:
 | 
			
		||||
        super().__init__(region_name, account_id)
 | 
			
		||||
        self.groups: Dict[str, Dict[str, str]] = {}
 | 
			
		||||
        self.identity_stores: Dict[str, IdentityStoreData] = {}
 | 
			
		||||
 | 
			
		||||
    def create_group(
 | 
			
		||||
        self, identity_store_id: str, display_name: str, description: str
 | 
			
		||||
    ) -> Tuple[str, str]:
 | 
			
		||||
        identity_store = self.__get_identity_store(identity_store_id)
 | 
			
		||||
 | 
			
		||||
        matching = [
 | 
			
		||||
            g
 | 
			
		||||
            for g in identity_store.groups.values()
 | 
			
		||||
            if g["DisplayName"] == display_name
 | 
			
		||||
        ]
 | 
			
		||||
        if len(matching) > 0:
 | 
			
		||||
            raise ConflictException(
 | 
			
		||||
                message="Duplicate GroupDisplayName",
 | 
			
		||||
                reason="UNIQUENESS_CONSTRAINT_VIOLATION",
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        group_id = str(mock_random.uuid4())
 | 
			
		||||
        group_dict = {
 | 
			
		||||
            "GroupId": group_id,
 | 
			
		||||
@ -21,8 +100,180 @@ class IdentityStoreBackend(BaseBackend):
 | 
			
		||||
            "DisplayName": display_name,
 | 
			
		||||
            "Description": description,
 | 
			
		||||
        }
 | 
			
		||||
        self.groups[group_id] = group_dict
 | 
			
		||||
        identity_store.groups[group_id] = group_dict
 | 
			
		||||
        return group_id, identity_store_id
 | 
			
		||||
 | 
			
		||||
    def get_group_id(
 | 
			
		||||
        self, identity_store_id: str, alternate_identifier: Dict[str, Any]
 | 
			
		||||
    ) -> Tuple[str, str]:
 | 
			
		||||
        identity_store = self.__get_identity_store(identity_store_id)
 | 
			
		||||
        if "UniqueAttribute" in alternate_identifier:
 | 
			
		||||
            if (
 | 
			
		||||
                "AttributeValue" in alternate_identifier["UniqueAttribute"]
 | 
			
		||||
                and alternate_identifier["UniqueAttribute"]["AttributePath"].lower()
 | 
			
		||||
                == "displayname"
 | 
			
		||||
            ):
 | 
			
		||||
                for g in identity_store.groups.values():
 | 
			
		||||
                    if (
 | 
			
		||||
                        g["DisplayName"]
 | 
			
		||||
                        == alternate_identifier["UniqueAttribute"]["AttributeValue"]
 | 
			
		||||
                    ):
 | 
			
		||||
                        return g["GroupId"], identity_store_id
 | 
			
		||||
        elif "ExternalId" in alternate_identifier:
 | 
			
		||||
            warnings.warn("ExternalId has not been implemented.")
 | 
			
		||||
 | 
			
		||||
        raise ResourceNotFoundException(
 | 
			
		||||
            message="GROUP not found.", resource_type="GROUP"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def delete_group(self, identity_store_id: str, group_id: str) -> None:
 | 
			
		||||
        identity_store = self.__get_identity_store(identity_store_id)
 | 
			
		||||
        if group_id in identity_store.groups:
 | 
			
		||||
            del identity_store.groups[group_id]
 | 
			
		||||
 | 
			
		||||
    def create_user(
 | 
			
		||||
        self,
 | 
			
		||||
        identity_store_id: str,
 | 
			
		||||
        user_name: str,
 | 
			
		||||
        name: Dict[str, str],
 | 
			
		||||
        display_name: str,
 | 
			
		||||
        nick_name: str,
 | 
			
		||||
        profile_url: str,
 | 
			
		||||
        emails: List[Dict[str, Any]],
 | 
			
		||||
        addresses: List[Dict[str, Any]],
 | 
			
		||||
        phone_numbers: List[Dict[str, Any]],
 | 
			
		||||
        user_type: str,
 | 
			
		||||
        title: str,
 | 
			
		||||
        preferred_language: str,
 | 
			
		||||
        locale: str,
 | 
			
		||||
        timezone: str,
 | 
			
		||||
    ) -> Tuple[str, str]:
 | 
			
		||||
        identity_store = self.__get_identity_store(identity_store_id)
 | 
			
		||||
        user_id = str(mock_random.uuid4())
 | 
			
		||||
 | 
			
		||||
        new_user = User(
 | 
			
		||||
            user_id,
 | 
			
		||||
            identity_store_id,
 | 
			
		||||
            user_name,
 | 
			
		||||
            Name.from_dict(name),
 | 
			
		||||
            display_name,
 | 
			
		||||
            nick_name,
 | 
			
		||||
            profile_url,
 | 
			
		||||
            emails,
 | 
			
		||||
            addresses,
 | 
			
		||||
            phone_numbers,
 | 
			
		||||
            user_type,
 | 
			
		||||
            title,
 | 
			
		||||
            preferred_language,
 | 
			
		||||
            locale,
 | 
			
		||||
            timezone,
 | 
			
		||||
        )
 | 
			
		||||
        self.__validate_create_user(new_user, identity_store)
 | 
			
		||||
 | 
			
		||||
        identity_store.users[user_id] = new_user
 | 
			
		||||
 | 
			
		||||
        return user_id, identity_store_id
 | 
			
		||||
 | 
			
		||||
    def describe_user(self, identity_store_id: str, user_id: str) -> User:
 | 
			
		||||
        identity_store = self.__get_identity_store(identity_store_id)
 | 
			
		||||
 | 
			
		||||
        if user_id in identity_store.users:
 | 
			
		||||
            return identity_store.users[user_id]
 | 
			
		||||
 | 
			
		||||
        raise ResourceNotFoundException(message="USER not found.", resource_type="USER")
 | 
			
		||||
 | 
			
		||||
    def delete_user(self, identity_store_id: str, user_id: str) -> None:
 | 
			
		||||
        identity_store = self.__get_identity_store(identity_store_id)
 | 
			
		||||
 | 
			
		||||
        if user_id in identity_store.users:
 | 
			
		||||
            del identity_store.users[user_id]
 | 
			
		||||
 | 
			
		||||
    def create_group_membership(
 | 
			
		||||
        self, identity_store_id: str, group_id: str, member_id: Dict[str, str]
 | 
			
		||||
    ) -> Tuple[str, str]:
 | 
			
		||||
        identity_store = self.__get_identity_store(identity_store_id)
 | 
			
		||||
        user_id = member_id["UserId"]
 | 
			
		||||
        if user_id not in identity_store.users:
 | 
			
		||||
            raise ResourceNotFoundException(
 | 
			
		||||
                message="Member does not exist", resource_type="USER"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        if group_id not in identity_store.groups:
 | 
			
		||||
            raise ResourceNotFoundException(
 | 
			
		||||
                message="Group does not exist", resource_type="GROUP"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        membership_id = str(mock_random.uuid4())
 | 
			
		||||
        identity_store.group_memberships[membership_id] = {
 | 
			
		||||
            "IdentityStoreId": identity_store_id,
 | 
			
		||||
            "MembershipId": membership_id,
 | 
			
		||||
            "GroupId": group_id,
 | 
			
		||||
            "MemberId": {"UserId": user_id},
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return membership_id, identity_store_id
 | 
			
		||||
 | 
			
		||||
    @paginate(pagination_model=PAGINATION_MODEL)  # type: ignore
 | 
			
		||||
    def list_group_memberships(
 | 
			
		||||
        self,
 | 
			
		||||
        identity_store_id: str,
 | 
			
		||||
        group_id: str,
 | 
			
		||||
    ) -> List[Any]:  # type: ignore
 | 
			
		||||
        identity_store = self.__get_identity_store(identity_store_id)
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            m
 | 
			
		||||
            for m in identity_store.group_memberships.values()
 | 
			
		||||
            if m["GroupId"] == group_id
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
    def delete_group_membership(
 | 
			
		||||
        self, identity_store_id: str, membership_id: str
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        identity_store = self.__get_identity_store(identity_store_id)
 | 
			
		||||
        if membership_id in identity_store.group_memberships:
 | 
			
		||||
            del identity_store.group_memberships[membership_id]
 | 
			
		||||
 | 
			
		||||
    def __get_identity_store(self, store_id: str) -> IdentityStoreData:
 | 
			
		||||
        if len(store_id) < 1:
 | 
			
		||||
            raise ParamValidationError(
 | 
			
		||||
                msg="Invalid length for parameter IdentityStoreId, value: 0, valid min length: 1"
 | 
			
		||||
            )
 | 
			
		||||
        if store_id not in self.identity_stores:
 | 
			
		||||
            self.identity_stores[store_id] = IdentityStoreData()
 | 
			
		||||
        return self.identity_stores[store_id]
 | 
			
		||||
 | 
			
		||||
    def __validate_create_user(
 | 
			
		||||
        self, new_user: User, identity_store: IdentityStoreData
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        if not new_user.UserName:
 | 
			
		||||
            raise ValidationException(message="userName is a required attribute")
 | 
			
		||||
 | 
			
		||||
        missing = []
 | 
			
		||||
 | 
			
		||||
        if not new_user.DisplayName:
 | 
			
		||||
            missing.append("displayname")
 | 
			
		||||
        if not new_user.Name:
 | 
			
		||||
            missing.append("name")
 | 
			
		||||
        else:
 | 
			
		||||
            if not new_user.Name.GivenName:
 | 
			
		||||
                missing.append("givenname")
 | 
			
		||||
            if not new_user.Name.FamilyName:
 | 
			
		||||
                missing.append("familyname")
 | 
			
		||||
 | 
			
		||||
        if len(missing) > 0:
 | 
			
		||||
            message = ", ".join(
 | 
			
		||||
                [f"{att}: The attribute {att} is required" for att in missing]
 | 
			
		||||
            )
 | 
			
		||||
            raise ValidationException(message=message)
 | 
			
		||||
 | 
			
		||||
        matching = [
 | 
			
		||||
            u for u in identity_store.users.values() if u.UserName == new_user.UserName
 | 
			
		||||
        ]
 | 
			
		||||
        if len(matching) > 0:
 | 
			
		||||
            raise ConflictException(
 | 
			
		||||
                message="Duplicate UserName", reason="UNIQUENESS_CONSTRAINT_VIOLATION"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
identitystore_backends = BackendDict(IdentityStoreBackend, "identitystore")
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
"""Handles incoming identitystore requests, invokes methods, returns responses."""
 | 
			
		||||
import json
 | 
			
		||||
from typing import NamedTuple, Any, Dict, Optional
 | 
			
		||||
 | 
			
		||||
from moto.core.responses import BaseResponse
 | 
			
		||||
from .models import identitystore_backends, IdentityStoreBackend
 | 
			
		||||
@ -26,3 +27,145 @@ class IdentityStoreResponse(BaseResponse):
 | 
			
		||||
            description=description,
 | 
			
		||||
        )
 | 
			
		||||
        return json.dumps(dict(GroupId=group_id, IdentityStoreId=identity_store_id))
 | 
			
		||||
 | 
			
		||||
    def create_group_membership(self) -> str:
 | 
			
		||||
        identity_store_id = self._get_param("IdentityStoreId")
 | 
			
		||||
        group_id = self._get_param("GroupId")
 | 
			
		||||
        member_id = self._get_param("MemberId")
 | 
			
		||||
        (
 | 
			
		||||
            membership_id,
 | 
			
		||||
            identity_store_id,
 | 
			
		||||
        ) = self.identitystore_backend.create_group_membership(
 | 
			
		||||
            identity_store_id=identity_store_id,
 | 
			
		||||
            group_id=group_id,
 | 
			
		||||
            member_id=member_id,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        return json.dumps(
 | 
			
		||||
            dict(MembershipId=membership_id, IdentityStoreId=identity_store_id)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def create_user(self) -> str:
 | 
			
		||||
        user_id, identity_store_id = self.identitystore_backend.create_user(
 | 
			
		||||
            self._get_param("IdentityStoreId"),
 | 
			
		||||
            self._get_param("UserName"),
 | 
			
		||||
            self._get_param("Name"),
 | 
			
		||||
            self._get_param("DisplayName"),
 | 
			
		||||
            self._get_param("NickName"),
 | 
			
		||||
            self._get_param("ProfileUrl"),
 | 
			
		||||
            self._get_param("Emails"),
 | 
			
		||||
            self._get_param("Addresses"),
 | 
			
		||||
            self._get_param("PhoneNumbers"),
 | 
			
		||||
            self._get_param("UserType"),
 | 
			
		||||
            self._get_param("Title"),
 | 
			
		||||
            self._get_param("PreferredLanguage"),
 | 
			
		||||
            self._get_param("Locale"),
 | 
			
		||||
            self._get_param("Timezone"),
 | 
			
		||||
        )
 | 
			
		||||
        return json.dumps(dict(UserId=user_id, IdentityStoreId=identity_store_id))
 | 
			
		||||
 | 
			
		||||
    def get_group_id(self) -> str:
 | 
			
		||||
        identity_store_id = self._get_param("IdentityStoreId")
 | 
			
		||||
        alternate_identifier = self._get_param("AlternateIdentifier")
 | 
			
		||||
        group_id, identity_store_id = self.identitystore_backend.get_group_id(
 | 
			
		||||
            identity_store_id=identity_store_id,
 | 
			
		||||
            alternate_identifier=alternate_identifier,
 | 
			
		||||
        )
 | 
			
		||||
        return json.dumps(dict(GroupId=group_id, IdentityStoreId=identity_store_id))
 | 
			
		||||
 | 
			
		||||
    def describe_user(self) -> str:
 | 
			
		||||
        identity_store_id = self._get_param("IdentityStoreId")
 | 
			
		||||
        user_id = self._get_param("UserId")
 | 
			
		||||
        (
 | 
			
		||||
            user_id,
 | 
			
		||||
            identity_store_id,
 | 
			
		||||
            user_name,
 | 
			
		||||
            name,
 | 
			
		||||
            display_name,
 | 
			
		||||
            nick_name,
 | 
			
		||||
            profile_url,
 | 
			
		||||
            emails,
 | 
			
		||||
            addresses,
 | 
			
		||||
            phone_numbers,
 | 
			
		||||
            user_type,
 | 
			
		||||
            title,
 | 
			
		||||
            preferred_language,
 | 
			
		||||
            locale,
 | 
			
		||||
            timezone,
 | 
			
		||||
        ) = self.identitystore_backend.describe_user(
 | 
			
		||||
            identity_store_id=identity_store_id,
 | 
			
		||||
            user_id=user_id,
 | 
			
		||||
        )
 | 
			
		||||
        return json.dumps(
 | 
			
		||||
            dict(
 | 
			
		||||
                UserName=user_name,
 | 
			
		||||
                UserId=user_id,
 | 
			
		||||
                ExternalIds=None,
 | 
			
		||||
                Name=self.named_tuple_to_dict(name),
 | 
			
		||||
                DisplayName=display_name,
 | 
			
		||||
                NickName=nick_name,
 | 
			
		||||
                ProfileUrl=profile_url,
 | 
			
		||||
                Emails=emails,
 | 
			
		||||
                Addresses=addresses,
 | 
			
		||||
                PhoneNumbers=phone_numbers,
 | 
			
		||||
                UserType=user_type,
 | 
			
		||||
                Title=title,
 | 
			
		||||
                PreferredLanguage=preferred_language,
 | 
			
		||||
                Locale=locale,
 | 
			
		||||
                Timezone=timezone,
 | 
			
		||||
                IdentityStoreId=identity_store_id,
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def list_group_memberships(self) -> str:
 | 
			
		||||
        identity_store_id = self._get_param("IdentityStoreId")
 | 
			
		||||
        group_id = self._get_param("GroupId")
 | 
			
		||||
        max_results = self._get_param("MaxResults")
 | 
			
		||||
        next_token = self._get_param("NextToken")
 | 
			
		||||
        (
 | 
			
		||||
            group_memberships,
 | 
			
		||||
            next_token,
 | 
			
		||||
        ) = self.identitystore_backend.list_group_memberships(
 | 
			
		||||
            identity_store_id=identity_store_id,
 | 
			
		||||
            group_id=group_id,
 | 
			
		||||
            max_results=max_results,
 | 
			
		||||
            next_token=next_token,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        return json.dumps(
 | 
			
		||||
            dict(GroupMemberships=group_memberships, NextToken=next_token)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def delete_group(self) -> str:
 | 
			
		||||
        identity_store_id = self._get_param("IdentityStoreId")
 | 
			
		||||
        group_id = self._get_param("GroupId")
 | 
			
		||||
        self.identitystore_backend.delete_group(
 | 
			
		||||
            identity_store_id=identity_store_id,
 | 
			
		||||
            group_id=group_id,
 | 
			
		||||
        )
 | 
			
		||||
        return json.dumps(dict())
 | 
			
		||||
 | 
			
		||||
    def delete_group_membership(self) -> str:
 | 
			
		||||
        identity_store_id = self._get_param("IdentityStoreId")
 | 
			
		||||
        membership_id = self._get_param("MembershipId")
 | 
			
		||||
        self.identitystore_backend.delete_group_membership(
 | 
			
		||||
            identity_store_id=identity_store_id,
 | 
			
		||||
            membership_id=membership_id,
 | 
			
		||||
        )
 | 
			
		||||
        return json.dumps(dict())
 | 
			
		||||
 | 
			
		||||
    def delete_user(self) -> str:
 | 
			
		||||
        identity_store_id = self._get_param("IdentityStoreId")
 | 
			
		||||
        user_id = self._get_param("UserId")
 | 
			
		||||
        self.identitystore_backend.delete_user(
 | 
			
		||||
            identity_store_id=identity_store_id,
 | 
			
		||||
            user_id=user_id,
 | 
			
		||||
        )
 | 
			
		||||
        return json.dumps(dict())
 | 
			
		||||
 | 
			
		||||
    def named_tuple_to_dict(
 | 
			
		||||
        self, value: Optional[NamedTuple]
 | 
			
		||||
    ) -> Optional[Dict[str, Any]]:
 | 
			
		||||
        if value:
 | 
			
		||||
            return value._asdict()
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,5 @@
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from . import helpers  # noqa
 | 
			
		||||
 | 
			
		||||
# Disable extra logging for tests
 | 
			
		||||
logging.getLogger("boto").setLevel(logging.CRITICAL)
 | 
			
		||||
 | 
			
		||||
@ -22,6 +22,7 @@ from moto.cloudformation import cloudformation_backends
 | 
			
		||||
from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
 | 
			
		||||
 | 
			
		||||
from tests import EXAMPLE_AMI_ID
 | 
			
		||||
from tests.helpers import match_dict  # noqa # pylint: disable=unused-import
 | 
			
		||||
 | 
			
		||||
dummy_template = {
 | 
			
		||||
    "AWSTemplateFormatVersion": "2010-09-09",
 | 
			
		||||
 | 
			
		||||
@ -29,6 +29,9 @@ from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
 | 
			
		||||
from tests import EXAMPLE_AMI_ID, EXAMPLE_AMI_ID2
 | 
			
		||||
from tests.markers import requires_docker
 | 
			
		||||
from tests.test_cloudformation.fixtures import fn_join, single_instance_with_ebs_volume
 | 
			
		||||
from tests.helpers import (  # noqa # pylint: disable=unused-import
 | 
			
		||||
    containing_item_with_attributes,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@mock_cloudformation
 | 
			
		||||
 | 
			
		||||
@ -1,20 +1,28 @@
 | 
			
		||||
"""Unit tests for identitystore-supported APIs."""
 | 
			
		||||
from uuid import UUID
 | 
			
		||||
import random
 | 
			
		||||
import string
 | 
			
		||||
from uuid import UUID, uuid4
 | 
			
		||||
 | 
			
		||||
import boto3
 | 
			
		||||
import sure  # noqa # pylint: disable=unused-import
 | 
			
		||||
import pytest
 | 
			
		||||
from botocore.exceptions import ClientError
 | 
			
		||||
 | 
			
		||||
from moto import mock_identitystore
 | 
			
		||||
from moto.moto_api._internal import mock_random
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# See our Development Tips on writing tests for hints on how to write good tests:
 | 
			
		||||
# http://docs.getmoto.org/en/latest/docs/contributing/development_tips/tests.html
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_identity_store_id() -> str:
 | 
			
		||||
    return f"d-{random.choices(string.ascii_lowercase, k=10)}"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@mock_identitystore
 | 
			
		||||
def test_create_group():
 | 
			
		||||
    client = boto3.client("identitystore", region_name="ap-southeast-1")
 | 
			
		||||
    identity_store_id = "d-9067028cf5"
 | 
			
		||||
    client = boto3.client("identitystore", region_name="us-east-2")
 | 
			
		||||
    identity_store_id = get_identity_store_id()
 | 
			
		||||
    create_resp = client.create_group(
 | 
			
		||||
        IdentityStoreId=identity_store_id,
 | 
			
		||||
        DisplayName="test_group",
 | 
			
		||||
@ -22,3 +30,581 @@ def test_create_group():
 | 
			
		||||
    )
 | 
			
		||||
    assert create_resp["IdentityStoreId"] == identity_store_id
 | 
			
		||||
    assert UUID(create_resp["GroupId"])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@mock_identitystore
 | 
			
		||||
def test_create_group_duplicate_name():
 | 
			
		||||
    client = boto3.client("identitystore", region_name="us-east-2")
 | 
			
		||||
    identity_store_id = get_identity_store_id()
 | 
			
		||||
    create_resp = client.create_group(
 | 
			
		||||
        IdentityStoreId=identity_store_id,
 | 
			
		||||
        DisplayName="test_group",
 | 
			
		||||
        Description="description",
 | 
			
		||||
    )
 | 
			
		||||
    assert create_resp["IdentityStoreId"] == identity_store_id
 | 
			
		||||
    assert UUID(create_resp["GroupId"])
 | 
			
		||||
 | 
			
		||||
    with pytest.raises(ClientError) as exc:
 | 
			
		||||
        client.create_group(
 | 
			
		||||
            IdentityStoreId=identity_store_id,
 | 
			
		||||
            DisplayName="test_group",
 | 
			
		||||
            Description="description",
 | 
			
		||||
        )
 | 
			
		||||
    err = exc.value
 | 
			
		||||
    assert "ConflictException" in str(type(err))
 | 
			
		||||
    assert (
 | 
			
		||||
        str(err)
 | 
			
		||||
        == "An error occurred (ConflictException) when calling the CreateGroup operation: Duplicate GroupDisplayName"
 | 
			
		||||
    )
 | 
			
		||||
    assert err.operation_name == "CreateGroup"
 | 
			
		||||
    assert err.response["Error"]["Code"] == "ConflictException"
 | 
			
		||||
    assert err.response["Error"]["Message"] == "Duplicate GroupDisplayName"
 | 
			
		||||
    assert err.response["ResponseMetadata"]["HTTPStatusCode"] == 400
 | 
			
		||||
    assert err.response["Message"] == "Duplicate GroupDisplayName"
 | 
			
		||||
    assert err.response["Reason"] == "UNIQUENESS_CONSTRAINT_VIOLATION"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@mock_identitystore
 | 
			
		||||
def test_group_multiple_identity_stores():
 | 
			
		||||
    identity_store_id = get_identity_store_id()
 | 
			
		||||
    identity_store_id2 = get_identity_store_id()
 | 
			
		||||
    client = boto3.client("identitystore", region_name="us-east-2")
 | 
			
		||||
    group1 = __create_test_group(client, store_id=identity_store_id)
 | 
			
		||||
    group2 = __create_test_group(client, store_id=identity_store_id2)
 | 
			
		||||
 | 
			
		||||
    assert __group_exists(client, group1[0], store_id=identity_store_id)
 | 
			
		||||
    assert not __group_exists(client, group1[0], store_id=identity_store_id2)
 | 
			
		||||
 | 
			
		||||
    assert __group_exists(client, group2[0], store_id=identity_store_id2)
 | 
			
		||||
    assert not __group_exists(client, group2[0], store_id=identity_store_id)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@mock_identitystore
 | 
			
		||||
def test_create_group_membership():
 | 
			
		||||
    client = boto3.client("identitystore", region_name="us-east-2")
 | 
			
		||||
    identity_store_id = get_identity_store_id()
 | 
			
		||||
 | 
			
		||||
    group_id = client.create_group(
 | 
			
		||||
        IdentityStoreId=identity_store_id,
 | 
			
		||||
        DisplayName="test_group",
 | 
			
		||||
        Description="description",
 | 
			
		||||
    )["GroupId"]
 | 
			
		||||
 | 
			
		||||
    user_id = __create_and_verify_sparse_user(client, identity_store_id)
 | 
			
		||||
 | 
			
		||||
    create_response = client.create_group_membership(
 | 
			
		||||
        IdentityStoreId=identity_store_id,
 | 
			
		||||
        GroupId=group_id,
 | 
			
		||||
        MemberId={"UserId": user_id},
 | 
			
		||||
    )
 | 
			
		||||
    assert UUID(create_response["MembershipId"])
 | 
			
		||||
    assert create_response["IdentityStoreId"] == identity_store_id
 | 
			
		||||
 | 
			
		||||
    list_response = client.list_group_memberships(
 | 
			
		||||
        IdentityStoreId=identity_store_id, GroupId=group_id
 | 
			
		||||
    )
 | 
			
		||||
    assert len(list_response["GroupMemberships"]) == 1
 | 
			
		||||
    assert (
 | 
			
		||||
        list_response["GroupMemberships"][0]["MembershipId"]
 | 
			
		||||
        == create_response["MembershipId"]
 | 
			
		||||
    )
 | 
			
		||||
    assert list_response["GroupMemberships"][0]["MemberId"]["UserId"] == user_id
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@mock_identitystore
 | 
			
		||||
def test_create_duplicate_username():
 | 
			
		||||
    client = boto3.client("identitystore", region_name="us-east-2")
 | 
			
		||||
    identity_store_id = get_identity_store_id()
 | 
			
		||||
 | 
			
		||||
    # This should succeed
 | 
			
		||||
    client.create_user(
 | 
			
		||||
        IdentityStoreId=identity_store_id,
 | 
			
		||||
        UserName="deleteme_username",
 | 
			
		||||
        DisplayName="deleteme_displayname",
 | 
			
		||||
        Name={"GivenName": "Givenname", "FamilyName": "Familyname"},
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    with pytest.raises(ClientError) as exc:
 | 
			
		||||
        # This should fail
 | 
			
		||||
        client.create_user(
 | 
			
		||||
            IdentityStoreId=identity_store_id,
 | 
			
		||||
            UserName="deleteme_username",
 | 
			
		||||
            DisplayName="deleteme_displayname",
 | 
			
		||||
            Name={"GivenName": "Givenname", "FamilyName": "Familyname"},
 | 
			
		||||
        )
 | 
			
		||||
    err = exc.value
 | 
			
		||||
    assert "ConflictException" in str(type(err))
 | 
			
		||||
    assert (
 | 
			
		||||
        str(err)
 | 
			
		||||
        == "An error occurred (ConflictException) when calling the CreateUser operation: Duplicate UserName"
 | 
			
		||||
    )
 | 
			
		||||
    assert err.operation_name == "CreateUser"
 | 
			
		||||
    assert err.response["Error"]["Code"] == "ConflictException"
 | 
			
		||||
    assert err.response["Error"]["Message"] == "Duplicate UserName"
 | 
			
		||||
    assert err.response["ResponseMetadata"]["HTTPStatusCode"] == 400
 | 
			
		||||
    assert err.response["Message"] == "Duplicate UserName"
 | 
			
		||||
    assert err.response["Reason"] == "UNIQUENESS_CONSTRAINT_VIOLATION"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@mock_identitystore
 | 
			
		||||
def test_create_username_no_username():
 | 
			
		||||
    client = boto3.client("identitystore", region_name="us-east-2")
 | 
			
		||||
    identity_store_id = get_identity_store_id()
 | 
			
		||||
 | 
			
		||||
    with pytest.raises(ClientError) as exc:
 | 
			
		||||
        client.create_user(IdentityStoreId=identity_store_id)
 | 
			
		||||
    err = exc.value
 | 
			
		||||
    assert "ValidationException" in str(type(err))
 | 
			
		||||
    assert (
 | 
			
		||||
        str(err)
 | 
			
		||||
        == "An error occurred (ValidationException) when calling the CreateUser operation: userName is a required attribute"
 | 
			
		||||
    )
 | 
			
		||||
    assert err.operation_name == "CreateUser"
 | 
			
		||||
    assert err.response["Error"]["Code"] == "ValidationException"
 | 
			
		||||
    assert err.response["Error"]["Message"] == "userName is a required attribute"
 | 
			
		||||
    assert err.response["ResponseMetadata"]["HTTPStatusCode"] == 400
 | 
			
		||||
    assert err.response["Message"] == "userName is a required attribute"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@mock_identitystore
 | 
			
		||||
def test_create_username_missing_required_attributes():
 | 
			
		||||
    client = boto3.client("identitystore", region_name="us-east-2")
 | 
			
		||||
    identity_store_id = get_identity_store_id()
 | 
			
		||||
 | 
			
		||||
    with pytest.raises(ClientError) as exc:
 | 
			
		||||
        client.create_user(
 | 
			
		||||
            IdentityStoreId=identity_store_id, UserName="username", Name={}
 | 
			
		||||
        )
 | 
			
		||||
    err = exc.value
 | 
			
		||||
    assert "ValidationException" in str(type(err))
 | 
			
		||||
    assert (
 | 
			
		||||
        str(err)
 | 
			
		||||
        == "An error occurred (ValidationException) when calling the CreateUser operation: displayname: The attribute displayname is required, name: The attribute name is required"
 | 
			
		||||
    )
 | 
			
		||||
    assert err.operation_name == "CreateUser"
 | 
			
		||||
    assert err.response["Error"]["Code"] == "ValidationException"
 | 
			
		||||
    assert (
 | 
			
		||||
        err.response["Error"]["Message"]
 | 
			
		||||
        == "displayname: The attribute displayname is required, name: The attribute name is required"
 | 
			
		||||
    )
 | 
			
		||||
    assert err.response["ResponseMetadata"]["HTTPStatusCode"] == 400
 | 
			
		||||
    assert (
 | 
			
		||||
        err.response["Message"]
 | 
			
		||||
        == "displayname: The attribute displayname is required, name: The attribute name is required"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@mock_identitystore
 | 
			
		||||
@pytest.mark.parametrize(
 | 
			
		||||
    "field, missing", [("GivenName", "familyname"), ("FamilyName", "givenname")]
 | 
			
		||||
)
 | 
			
		||||
def test_create_username_missing_required_name_field(field, missing):
 | 
			
		||||
    client = boto3.client("identitystore", region_name="us-east-2")
 | 
			
		||||
    identity_store_id = get_identity_store_id()
 | 
			
		||||
 | 
			
		||||
    with pytest.raises(ClientError) as exc:
 | 
			
		||||
        client.create_user(
 | 
			
		||||
            IdentityStoreId=identity_store_id,
 | 
			
		||||
            UserName="username",
 | 
			
		||||
            DisplayName="displayName",
 | 
			
		||||
            Name={field: field},
 | 
			
		||||
        )
 | 
			
		||||
    err = exc.value
 | 
			
		||||
    assert "ValidationException" in str(type(err))
 | 
			
		||||
    assert (
 | 
			
		||||
        str(err)
 | 
			
		||||
        == f"An error occurred (ValidationException) when calling the CreateUser operation: {missing}: The attribute {missing} is required"
 | 
			
		||||
    )
 | 
			
		||||
    assert err.operation_name == "CreateUser"
 | 
			
		||||
    assert err.response["Error"]["Code"] == "ValidationException"
 | 
			
		||||
    assert (
 | 
			
		||||
        err.response["Error"]["Message"]
 | 
			
		||||
        == f"{missing}: The attribute {missing} is required"
 | 
			
		||||
    )
 | 
			
		||||
    assert err.response["ResponseMetadata"]["HTTPStatusCode"] == 400
 | 
			
		||||
    assert err.response["Message"] == f"{missing}: The attribute {missing} is required"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@mock_identitystore
 | 
			
		||||
def test_create_describe_sparse_user():
 | 
			
		||||
    client = boto3.client("identitystore", region_name="us-east-2")
 | 
			
		||||
    identity_store_id = get_identity_store_id()
 | 
			
		||||
    response = client.create_user(
 | 
			
		||||
        IdentityStoreId=identity_store_id,
 | 
			
		||||
        UserName="the_username",
 | 
			
		||||
        DisplayName="display_name",
 | 
			
		||||
        Name={"GivenName": "given_name", "FamilyName": "family_name"},
 | 
			
		||||
    )
 | 
			
		||||
    assert UUID(response["UserId"])
 | 
			
		||||
 | 
			
		||||
    user_resp = client.describe_user(
 | 
			
		||||
        IdentityStoreId=identity_store_id, UserId=response["UserId"]
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    assert user_resp["UserName"] == "the_username"
 | 
			
		||||
    assert user_resp["DisplayName"] == "display_name"
 | 
			
		||||
    assert "Name" in user_resp
 | 
			
		||||
    assert user_resp["Name"]["GivenName"] == "given_name"
 | 
			
		||||
    assert user_resp["Name"]["FamilyName"] == "family_name"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@mock_identitystore
 | 
			
		||||
def test_create_describe_full_user():
 | 
			
		||||
    client = boto3.client("identitystore", region_name="us-east-2")
 | 
			
		||||
    identity_store_id = get_identity_store_id()
 | 
			
		||||
    response = client.create_user(
 | 
			
		||||
        IdentityStoreId=identity_store_id,
 | 
			
		||||
        UserName="the_username",
 | 
			
		||||
        DisplayName="display_name",
 | 
			
		||||
        Name={
 | 
			
		||||
            "Formatted": "formatted_name",
 | 
			
		||||
            "GivenName": "given_name",
 | 
			
		||||
            "FamilyName": "family_name",
 | 
			
		||||
            "MiddleName": "middle_name",
 | 
			
		||||
            "HonorificPrefix": "The Honorable",
 | 
			
		||||
            "HonorificSuffix": "Wisest of us all",
 | 
			
		||||
        },
 | 
			
		||||
        NickName="nick_name",
 | 
			
		||||
        ProfileUrl="https://example.com",
 | 
			
		||||
        Emails=[
 | 
			
		||||
            {"Value": "email1@example.com", "Type": "Personal", "Primary": True},
 | 
			
		||||
            {"Value": "email2@example.com", "Type": "Work", "Primary": False},
 | 
			
		||||
        ],
 | 
			
		||||
        Addresses=[
 | 
			
		||||
            {
 | 
			
		||||
                "StreetAddress": "123 Address St.",
 | 
			
		||||
                "Locality": "locality",
 | 
			
		||||
                "Region": "region",
 | 
			
		||||
                "PostalCode": "123456",
 | 
			
		||||
                "Country": "USA",
 | 
			
		||||
                "Formatted": "123 Address St.\nlocality, region, 123456",
 | 
			
		||||
                "Type": "Home",
 | 
			
		||||
                "Primary": True,
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
        PhoneNumbers=[
 | 
			
		||||
            {"Value": "555-456-7890", "Type": "Home", "Primary": True},
 | 
			
		||||
        ],
 | 
			
		||||
        UserType="user_type",
 | 
			
		||||
        Title="title",
 | 
			
		||||
        PreferredLanguage="preferred_language",
 | 
			
		||||
        Locale="locale",
 | 
			
		||||
        Timezone="timezone",
 | 
			
		||||
    )
 | 
			
		||||
    assert UUID(response["UserId"])
 | 
			
		||||
 | 
			
		||||
    user_resp = client.describe_user(
 | 
			
		||||
        IdentityStoreId=identity_store_id, UserId=response["UserId"]
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    assert user_resp["UserName"] == "the_username"
 | 
			
		||||
    assert user_resp["DisplayName"] == "display_name"
 | 
			
		||||
    assert "Name" in user_resp
 | 
			
		||||
    assert user_resp["Name"]["Formatted"] == "formatted_name"
 | 
			
		||||
    assert user_resp["Name"]["GivenName"] == "given_name"
 | 
			
		||||
    assert user_resp["Name"]["FamilyName"] == "family_name"
 | 
			
		||||
    assert user_resp["Name"]["MiddleName"] == "middle_name"
 | 
			
		||||
    assert user_resp["Name"]["HonorificPrefix"] == "The Honorable"
 | 
			
		||||
    assert user_resp["Name"]["HonorificSuffix"] == "Wisest of us all"
 | 
			
		||||
    assert user_resp["NickName"] == "nick_name"
 | 
			
		||||
    assert user_resp["ProfileUrl"] == "https://example.com"
 | 
			
		||||
    assert "Emails" in user_resp
 | 
			
		||||
    assert len(user_resp["Emails"]) == 2
 | 
			
		||||
    email1 = user_resp["Emails"][0]
 | 
			
		||||
    assert email1["Value"] == "email1@example.com"
 | 
			
		||||
    assert email1["Type"] == "Personal"
 | 
			
		||||
    assert email1["Primary"] is True
 | 
			
		||||
    email2 = user_resp["Emails"][1]
 | 
			
		||||
    assert email2["Value"] == "email2@example.com"
 | 
			
		||||
    assert email2["Type"] == "Work"
 | 
			
		||||
    assert email2["Primary"] is False
 | 
			
		||||
    assert "Addresses" in user_resp
 | 
			
		||||
    assert len(user_resp["Addresses"]) == 1
 | 
			
		||||
    assert user_resp["Addresses"][0]["StreetAddress"] == "123 Address St."
 | 
			
		||||
    assert user_resp["Addresses"][0]["Locality"] == "locality"
 | 
			
		||||
    assert user_resp["Addresses"][0]["Region"] == "region"
 | 
			
		||||
    assert user_resp["Addresses"][0]["PostalCode"] == "123456"
 | 
			
		||||
    assert user_resp["Addresses"][0]["Country"] == "USA"
 | 
			
		||||
    assert (
 | 
			
		||||
        user_resp["Addresses"][0]["Formatted"]
 | 
			
		||||
        == "123 Address St.\nlocality, region, 123456"
 | 
			
		||||
    )
 | 
			
		||||
    assert user_resp["Addresses"][0]["Type"] == "Home"
 | 
			
		||||
    assert user_resp["Addresses"][0]["Primary"] is True
 | 
			
		||||
    assert "PhoneNumbers" in user_resp
 | 
			
		||||
    assert len(user_resp["PhoneNumbers"]) == 1
 | 
			
		||||
    assert user_resp["PhoneNumbers"][0]["Value"] == "555-456-7890"
 | 
			
		||||
    assert user_resp["PhoneNumbers"][0]["Type"] == "Home"
 | 
			
		||||
    assert user_resp["PhoneNumbers"][0]["Primary"] is True
 | 
			
		||||
    assert user_resp["UserType"] == "user_type"
 | 
			
		||||
    assert user_resp["Title"] == "title"
 | 
			
		||||
    assert user_resp["PreferredLanguage"] == "preferred_language"
 | 
			
		||||
    assert user_resp["Locale"] == "locale"
 | 
			
		||||
    assert user_resp["Timezone"] == "timezone"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@mock_identitystore
 | 
			
		||||
def test_describe_user_doesnt_exist():
 | 
			
		||||
    client = boto3.client("identitystore", region_name="us-east-2")
 | 
			
		||||
    identity_store_id = get_identity_store_id()
 | 
			
		||||
 | 
			
		||||
    with pytest.raises(ClientError) as exc:
 | 
			
		||||
        client.describe_user(
 | 
			
		||||
            IdentityStoreId=identity_store_id, UserId=str(mock_random.uuid4())
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    err = exc.value
 | 
			
		||||
    assert err.response["Error"]["Code"] == "ResourceNotFoundException"
 | 
			
		||||
    assert err.response["Error"]["Message"] == "USER not found."
 | 
			
		||||
    assert err.response["ResponseMetadata"]["HTTPStatusCode"] == 400
 | 
			
		||||
    assert err.response["ResourceType"] == "USER"
 | 
			
		||||
    assert err.response["Message"] == "USER not found."
 | 
			
		||||
    assert "RequestId" in err.response
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@mock_identitystore
 | 
			
		||||
def test_get_group_id():
 | 
			
		||||
    client = boto3.client("identitystore", region_name="us-east-2")
 | 
			
		||||
    identity_store_id = get_identity_store_id()
 | 
			
		||||
    groups = {}
 | 
			
		||||
 | 
			
		||||
    # Create a bunch of groups
 | 
			
		||||
    for _ in range(1, 10):
 | 
			
		||||
        group = __create_test_group(client, identity_store_id)
 | 
			
		||||
        groups[group[0]] = group[1]
 | 
			
		||||
 | 
			
		||||
    # Make sure we can get their ID
 | 
			
		||||
    for name, group_id in groups.items():
 | 
			
		||||
 | 
			
		||||
        response = client.get_group_id(
 | 
			
		||||
            IdentityStoreId=identity_store_id,
 | 
			
		||||
            AlternateIdentifier={
 | 
			
		||||
                "UniqueAttribute": {
 | 
			
		||||
                    "AttributePath": "displayName",
 | 
			
		||||
                    "AttributeValue": name,
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        assert response["IdentityStoreId"] == identity_store_id
 | 
			
		||||
        assert response["GroupId"] == group_id
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@mock_identitystore
 | 
			
		||||
def test_get_group_id_does_not_exist():
 | 
			
		||||
    client = boto3.client("identitystore", region_name="us-east-2")
 | 
			
		||||
    identity_store_id = get_identity_store_id()
 | 
			
		||||
    with pytest.raises(ClientError) as exc:
 | 
			
		||||
        client.get_group_id(
 | 
			
		||||
            IdentityStoreId=identity_store_id,
 | 
			
		||||
            AlternateIdentifier={
 | 
			
		||||
                "UniqueAttribute": {
 | 
			
		||||
                    "AttributePath": "displayName",
 | 
			
		||||
                    "AttributeValue": "does-not-exist",
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
    err = exc.value
 | 
			
		||||
    assert err.response["Error"]["Code"] == "ResourceNotFoundException"
 | 
			
		||||
    assert err.response["Error"]["Message"] == "GROUP not found."
 | 
			
		||||
    assert err.response["ResponseMetadata"]["HTTPStatusCode"] == 400
 | 
			
		||||
    assert err.response["ResourceType"] == "GROUP"
 | 
			
		||||
    assert err.response["Message"] == "GROUP not found."
 | 
			
		||||
    assert "RequestId" in err.response
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@mock_identitystore
 | 
			
		||||
def test_list_group_memberships():
 | 
			
		||||
    client = boto3.client("identitystore", region_name="us-east-2")
 | 
			
		||||
    identity_store_id = get_identity_store_id()
 | 
			
		||||
 | 
			
		||||
    start = 0
 | 
			
		||||
    end = 5000
 | 
			
		||||
    batch_size = 321
 | 
			
		||||
    next_token = None
 | 
			
		||||
    membership_ids = []
 | 
			
		||||
 | 
			
		||||
    group_id = client.create_group(
 | 
			
		||||
        IdentityStoreId=identity_store_id,
 | 
			
		||||
        DisplayName="test_group",
 | 
			
		||||
        Description="description",
 | 
			
		||||
    )["GroupId"]
 | 
			
		||||
 | 
			
		||||
    for _ in range(end):
 | 
			
		||||
        user_id = __create_and_verify_sparse_user(client, identity_store_id)
 | 
			
		||||
        create_response = client.create_group_membership(
 | 
			
		||||
            IdentityStoreId=identity_store_id,
 | 
			
		||||
            GroupId=group_id,
 | 
			
		||||
            MemberId={"UserId": user_id},
 | 
			
		||||
        )
 | 
			
		||||
        membership_ids.append((create_response["MembershipId"], user_id))
 | 
			
		||||
 | 
			
		||||
    for iteration in range(start, end, batch_size):
 | 
			
		||||
        last_iteration = end - iteration <= batch_size
 | 
			
		||||
        expected_size = batch_size if not last_iteration else end - iteration
 | 
			
		||||
        end_index = iteration + expected_size
 | 
			
		||||
 | 
			
		||||
        if next_token is not None:
 | 
			
		||||
            list_response = client.list_group_memberships(
 | 
			
		||||
                IdentityStoreId=identity_store_id,
 | 
			
		||||
                GroupId=group_id,
 | 
			
		||||
                MaxResults=batch_size,
 | 
			
		||||
                NextToken=next_token,
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            list_response = client.list_group_memberships(
 | 
			
		||||
                IdentityStoreId=identity_store_id,
 | 
			
		||||
                GroupId=group_id,
 | 
			
		||||
                MaxResults=batch_size,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        assert len(list_response["GroupMemberships"]) == expected_size
 | 
			
		||||
        __check_membership_list_values(
 | 
			
		||||
            list_response["GroupMemberships"], membership_ids[iteration:end_index]
 | 
			
		||||
        )
 | 
			
		||||
        if last_iteration:
 | 
			
		||||
            assert "NextToken" not in list_response
 | 
			
		||||
        else:
 | 
			
		||||
            assert "NextToken" in list_response
 | 
			
		||||
            next_token = list_response["NextToken"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def __check_membership_list_values(members, expected):
 | 
			
		||||
    assert len(members) == len(expected)
 | 
			
		||||
    for i in range(len(expected)):
 | 
			
		||||
        assert members[i]["MembershipId"] == expected[i][0]
 | 
			
		||||
        assert members[i]["MemberId"]["UserId"] == expected[i][1]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@mock_identitystore
 | 
			
		||||
def test_delete_group():
 | 
			
		||||
    client = boto3.client("identitystore", region_name="us-east-2")
 | 
			
		||||
    identity_store_id = get_identity_store_id()
 | 
			
		||||
 | 
			
		||||
    test_group = __create_test_group(client, identity_store_id)
 | 
			
		||||
    assert __group_exists(client, test_group[0], identity_store_id)
 | 
			
		||||
 | 
			
		||||
    resp = client.delete_group(IdentityStoreId=identity_store_id, GroupId=test_group[1])
 | 
			
		||||
    assert "ResponseMetadata" in resp
 | 
			
		||||
    assert resp["ResponseMetadata"]["HTTPStatusCode"] == 200
 | 
			
		||||
 | 
			
		||||
    assert not __group_exists(client, test_group[0], identity_store_id)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@mock_identitystore
 | 
			
		||||
def test_delete_group_doesnt_exist():
 | 
			
		||||
    client = boto3.client("identitystore", region_name="us-east-2")
 | 
			
		||||
    identity_store_id = get_identity_store_id()
 | 
			
		||||
 | 
			
		||||
    bogus_id = str(uuid4())
 | 
			
		||||
 | 
			
		||||
    resp = client.delete_group(IdentityStoreId=identity_store_id, GroupId=bogus_id)
 | 
			
		||||
    assert "ResponseMetadata" in resp
 | 
			
		||||
    assert resp["ResponseMetadata"]["HTTPStatusCode"] == 200
 | 
			
		||||
 | 
			
		||||
    assert not __group_exists(client, bogus_id, identity_store_id)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@mock_identitystore
 | 
			
		||||
def test_delete_group_membership():
 | 
			
		||||
    client = boto3.client("identitystore", region_name="eu-west-1")
 | 
			
		||||
    identity_store_id = get_identity_store_id()
 | 
			
		||||
    user_id = __create_and_verify_sparse_user(client, identity_store_id)
 | 
			
		||||
    _, group_id = __create_test_group(client, identity_store_id)
 | 
			
		||||
 | 
			
		||||
    membership = client.create_group_membership(
 | 
			
		||||
        IdentityStoreId=identity_store_id,
 | 
			
		||||
        GroupId=group_id,
 | 
			
		||||
        MemberId={"UserId": user_id},
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    # Verify the group membership
 | 
			
		||||
    response = client.list_group_memberships(
 | 
			
		||||
        IdentityStoreId=identity_store_id, GroupId=group_id
 | 
			
		||||
    )
 | 
			
		||||
    assert response["GroupMemberships"][0]["MemberId"]["UserId"] == user_id
 | 
			
		||||
 | 
			
		||||
    client.delete_group_membership(
 | 
			
		||||
        IdentityStoreId=identity_store_id, MembershipId=membership["MembershipId"]
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    # Verify the group membership has been removed
 | 
			
		||||
    response = client.list_group_memberships(
 | 
			
		||||
        IdentityStoreId=identity_store_id, GroupId=group_id
 | 
			
		||||
    )
 | 
			
		||||
    assert len(response["GroupMemberships"]) == 0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@mock_identitystore
 | 
			
		||||
def test_delete_user():
 | 
			
		||||
    client = boto3.client("identitystore", region_name="us-east-2")
 | 
			
		||||
    identity_store_id = get_identity_store_id()
 | 
			
		||||
    user_id = __create_and_verify_sparse_user(client, identity_store_id)
 | 
			
		||||
 | 
			
		||||
    client.delete_user(IdentityStoreId=identity_store_id, UserId=user_id)
 | 
			
		||||
 | 
			
		||||
    with pytest.raises(ClientError) as exc:
 | 
			
		||||
        client.describe_user(IdentityStoreId=identity_store_id, UserId=user_id)
 | 
			
		||||
    err = exc.value
 | 
			
		||||
    assert err.response["Error"]["Code"] == "ResourceNotFoundException"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@mock_identitystore
 | 
			
		||||
def test_delete_user_doesnt_exist():
 | 
			
		||||
    client = boto3.client("identitystore", region_name="us-east-2")
 | 
			
		||||
    identity_store_id = get_identity_store_id()
 | 
			
		||||
 | 
			
		||||
    # This test ensures that the delete_user call does not raise an error if the user ID does not exist
 | 
			
		||||
    client.delete_user(
 | 
			
		||||
        IdentityStoreId=identity_store_id, UserId=str(mock_random.uuid4())
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def __create_test_group(client, store_id: str):
 | 
			
		||||
    rand = "".join(random.choices(string.ascii_lowercase, k=8))
 | 
			
		||||
    group_name = f"test_group_{rand}"
 | 
			
		||||
 | 
			
		||||
    create_resp = client.create_group(
 | 
			
		||||
        IdentityStoreId=store_id,
 | 
			
		||||
        DisplayName=group_name,
 | 
			
		||||
        Description="description",
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    return group_name, create_resp["GroupId"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def __group_exists(client, group_name: str, store_id: str) -> bool:
 | 
			
		||||
    try:
 | 
			
		||||
        client.get_group_id(
 | 
			
		||||
            IdentityStoreId=store_id,
 | 
			
		||||
            AlternateIdentifier={
 | 
			
		||||
                "UniqueAttribute": {
 | 
			
		||||
                    "AttributePath": "displayName",
 | 
			
		||||
                    "AttributeValue": group_name,
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
        return True
 | 
			
		||||
    except ClientError as e:
 | 
			
		||||
        if "ResourceNotFoundException" in str(type(e)):
 | 
			
		||||
            return False
 | 
			
		||||
        raise e
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def __create_and_verify_sparse_user(client, store_id: str):
 | 
			
		||||
    rand = random.choices(string.ascii_lowercase, k=8)
 | 
			
		||||
    username = f"the_username_{rand}"
 | 
			
		||||
    response = client.create_user(
 | 
			
		||||
        IdentityStoreId=store_id,
 | 
			
		||||
        UserName=username,
 | 
			
		||||
        DisplayName=f"display_name_{rand}",
 | 
			
		||||
        Name={"GivenName": f"given_name_{rand}", "FamilyName": f"family_name_{rand}"},
 | 
			
		||||
    )
 | 
			
		||||
    assert UUID(response["UserId"])
 | 
			
		||||
 | 
			
		||||
    user_resp = client.describe_user(
 | 
			
		||||
        IdentityStoreId=store_id, UserId=response["UserId"]
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    assert user_resp["UserName"] == username
 | 
			
		||||
    return user_resp["UserId"]
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,7 @@ from tests.test_redshiftdata.test_redshiftdata_constants import (
 | 
			
		||||
    DEFAULT_ENCODING,
 | 
			
		||||
    HttpHeaders,
 | 
			
		||||
)
 | 
			
		||||
from tests.helpers import match_uuid4  # noqa # pylint: disable=unused-import
 | 
			
		||||
 | 
			
		||||
CLIENT_ENDPOINT = "/"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user