Repository: getmoto/moto
Pull request #8949 (2025-06-05 14:13:51)

Stable cognito user pool client
Same as #5194 , but for user pool client.

My use case is: 
I have a frontend which uses cognito for login and i want to connect to moto server for testing and developing. Every time I run the compose file, it creates a new user pool client with a different id, and the frontend breaks.



Patch:
diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md
index 3d35c5c87..0a39e42aa 100644
--- a/IMPLEMENTATION_COVERAGE.md
+++ b/IMPLEMENTATION_COVERAGE.md
@@ -1012,7 +1012,7 @@
 <summary>10% implemented</summary>
 
 - [ ] add_facet_to_object
-- [ ] apply_schema
+- [X] apply_schema
 - [ ] attach_object
 - [ ] attach_policy
 - [ ] attach_to_index
@@ -1023,12 +1023,12 @@
 - [ ] create_facet
 - [ ] create_index
 - [ ] create_object
-- [ ] create_schema
+- [X] create_schema
 - [ ] create_typed_link_facet
 - [X] delete_directory
 - [ ] delete_facet
 - [ ] delete_object
-- [ ] delete_schema
+- [X] delete_schema
 - [ ] delete_typed_link_facet
 - [ ] detach_from_index
 - [ ] detach_object
@@ -1046,7 +1046,7 @@
 - [ ] get_typed_link_facet_information
 - [ ] list_applied_schema_arns
 - [ ] list_attached_indices
-- [ ] list_development_schema_arns
+- [X] list_development_schema_arns
 - [X] list_directories
 - [ ] list_facet_attributes
 - [ ] list_facet_names
@@ -1060,12 +1060,12 @@
 - [ ] list_object_policies
 - [ ] list_outgoing_typed_links
 - [ ] list_policy_attachments
-- [ ] list_published_schema_arns
+- [X] list_published_schema_arns
 - [X] list_tags_for_resource
 - [ ] list_typed_link_facet_attributes
 - [ ] list_typed_link_facet_names
 - [ ] lookup_policy
-- [ ] publish_schema
+- [X] publish_schema
 - [ ] put_schema_from_json
 - [ ] remove_facet_from_object
 - [X] tag_resource
@@ -4262,7 +4262,7 @@
 - [X] delete_partition
 - [ ] delete_partition_index
 - [X] delete_registry
-- [ ] delete_resource_policy
+- [X] delete_resource_policy
 - [X] delete_schema
 - [ ] delete_schema_versions
 - [ ] delete_security_configuration
@@ -8706,7 +8706,7 @@
 
 ## securityhub
 <details>
-<summary>2% implemented</summary>
+<summary>7% implemented</summary>
 
 - [ ] accept_administrator_invitation
 - [ ] accept_invitation
@@ -8736,7 +8736,7 @@
 - [ ] delete_members
 - [ ] describe_action_targets
 - [ ] describe_hub
-- [ ] describe_organization_configuration
+- [X] describe_organization_configuration
 - [ ] describe_products
 - [ ] describe_standards
 - [ ] describe_standards_controls
@@ -8747,9 +8747,9 @@
 - [ ] disassociate_from_master_account
 - [ ] disassociate_members
 - [ ] enable_import_findings_for_product
-- [ ] enable_organization_admin_account
+- [X] enable_organization_admin_account
 - [ ] enable_security_hub
-- [ ] get_administrator_account
+- [X] get_administrator_account
 - [ ] get_configuration_policy
 - [ ] get_configuration_policy_association
 - [ ] get_enabled_standards
@@ -8783,7 +8783,7 @@
 - [ ] update_finding_aggregator
 - [ ] update_findings
 - [ ] update_insight
-- [ ] update_organization_configuration
+- [X] update_organization_configuration
 - [ ] update_security_control
 - [ ] update_security_hub_configuration
 - [ ] update_standards_control
diff --git a/docs/docs/services/securityhub.rst b/docs/docs/services/securityhub.rst
index a395350a1..d9987f19e 100644
--- a/docs/docs/services/securityhub.rst
+++ b/docs/docs/services/securityhub.rst
@@ -54,7 +54,7 @@ securityhub
 - [ ] delete_members
 - [ ] describe_action_targets
 - [ ] describe_hub
-- [ ] describe_organization_configuration
+- [X] describe_organization_configuration
 - [ ] describe_products
 - [ ] describe_standards
 - [ ] describe_standards_controls
@@ -65,9 +65,9 @@ securityhub
 - [ ] disassociate_from_master_account
 - [ ] disassociate_members
 - [ ] enable_import_findings_for_product
-- [ ] enable_organization_admin_account
+- [X] enable_organization_admin_account
 - [ ] enable_security_hub
-- [ ] get_administrator_account
+- [X] get_administrator_account
 - [ ] get_configuration_policy
 - [ ] get_configuration_policy_association
 - [ ] get_enabled_standards
@@ -105,7 +105,7 @@ securityhub
 - [ ] update_finding_aggregator
 - [ ] update_findings
 - [ ] update_insight
-- [ ] update_organization_configuration
+- [X] update_organization_configuration
 - [ ] update_security_control
 - [ ] update_security_hub_configuration
 - [ ] update_standards_control
diff --git a/moto/clouddirectory/exceptions.py b/moto/clouddirectory/exceptions.py
index 7b2d582ec..8e3892849 100644
--- a/moto/clouddirectory/exceptions.py
+++ b/moto/clouddirectory/exceptions.py
@@ -18,3 +18,33 @@ class InvalidArnException(JsonRESTError):
             "Message": "Invalid Arn",
         }
         self.description = json.dumps(body)
+
+
+class ResourceNotFoundException(JsonRESTError):
+    def __init__(self, resource_id: str):
+        super().__init__("ResourceNotFoundException", "Resource not found")
+        body = {
+            "ResourceId": resource_id,
+            "Message": "Resource not found",
+        }
+        self.description = json.dumps(body)
+
+
+class SchemaAlreadyPublishedException(JsonRESTError):
+    def __init__(self, schema_arn: str):
+        super().__init__("SchemaAlreadyPublishedException", "Schema already published")
+        body = {
+            "SchemaArn": schema_arn,
+            "Message": "Schema already published",
+        }
+        self.description = json.dumps(body)
+
+
+class SchemaAlreadyExistsException(JsonRESTError):
+    def __init__(self, schema_arn: str):
+        super().__init__("SchemaAlreadyExistsException", "Schema already exists")
+        body = {
+            "SchemaArn": schema_arn,
+            "Message": "Schema already exists",
+        }
+        self.description = json.dumps(body)
diff --git a/moto/clouddirectory/models.py b/moto/clouddirectory/models.py
index 40142e42b..f75ef4282 100644
--- a/moto/clouddirectory/models.py
+++ b/moto/clouddirectory/models.py
@@ -8,7 +8,11 @@ from moto.core.common_models import BaseModel
 from moto.utilities.paginator import paginate
 from moto.utilities.tagging_service import TaggingService
 
-from .exceptions import InvalidArnException
+from .exceptions import (
+    InvalidArnException,
+    ResourceNotFoundException,
+    SchemaAlreadyPublishedException,
+)
 
 PAGINATION_MODEL = {
     "list_directories": {
@@ -16,7 +20,19 @@ PAGINATION_MODEL = {
         "limit_key": "max_results",
         "limit_default": 100,
         "unique_attribute": "directory_arn",
-    }
+    },
+    "list_development_schema_arns": {
+        "input_token": "next_token",
+        "limit_key": "max_results",
+        "limit_default": 100,
+        "unique_attribute": "schema_arn",
+    },
+    "list_published_schema_arns": {
+        "input_token": "next_token",
+        "limit_key": "max_results",
+        "limit_default": 100,
+        "unique_attribute": "schema_arn",
+    },
 }
 
 
@@ -50,13 +66,62 @@ class CloudDirectoryBackend(BaseBackend):
     def __init__(self, region_name: str, account_id: str) -> None:
         super().__init__(region_name, account_id)
         self.directories: Dict[str, Directory] = {}
+        self.schemas_states: Dict[str, List[str]] = {
+            "development": [],
+            "published": [],
+            "applied": [],
+        }
         self.tagger = TaggingService()
 
+    def apply_schema(self, directory_arn: str, published_schema_arn: str) -> None:
+        directory = self.directories.get(directory_arn)
+        if not directory:
+            raise ResourceNotFoundException(directory_arn)
+        if published_schema_arn not in self.schemas_states["published"]:
+            raise ResourceNotFoundException(published_schema_arn)
+        directory.schema_arn = published_schema_arn
+        return
+
+    def publish_schema(
+        self, name: str, version: str, development_schema_arn: str, minor_version: str
+    ) -> str:
+        schema_arn = f"arn:aws:clouddirectory:{self.region_name}:{self.account_id}:schema/published/{name}/{version}/{minor_version}"
+        if development_schema_arn in self.schemas_states["published"]:
+            raise SchemaAlreadyPublishedException(development_schema_arn)
+        if development_schema_arn in self.schemas_states["development"]:
+            self.schemas_states["development"].remove(development_schema_arn)
+            self.schemas_states["published"].append(schema_arn)
+        else:
+            raise ResourceNotFoundException(development_schema_arn)
+        return schema_arn
+
     def create_directory(self, name: str, schema_arn: str) -> Directory:
         directory = Directory(self.account_id, self.region_name, name, schema_arn)
         self.directories[directory.directory_arn] = directory
         return directory
 
+    def create_schema(self, name: str) -> str:
+        self.schema_arn = f"arn:aws:clouddirectory:{self.region_name}:{self.account_id}:schema/development/{name}"
+        self.schemas_states["development"].append(self.schema_arn)
+        return self.schema_arn
+
+    def delete_schema(self, schema_arn: str) -> None:
+        if schema_arn in self.schemas_states["development"]:
+            self.schemas_states["development"].remove(schema_arn)
+        elif schema_arn in self.schemas_states["published"]:
+            self.schemas_states["published"].remove(schema_arn)
+        else:
+            raise ResourceNotFoundException(schema_arn)
+        return
+
+    @paginate(pagination_model=PAGINATION_MODEL)
+    def list_development_schema_arns(self) -> List[str]:
+        return self.schemas_states["development"]
+
+    @paginate(pagination_model=PAGINATION_MODEL)
+    def list_published_schema_arns(self) -> List[str]:
+        return self.schemas_states["published"]
+
     @paginate(pagination_model=PAGINATION_MODEL)
     def list_directories(self, state: str) -> List[Directory]:
         directories = list(self.directories.values())
diff --git a/moto/clouddirectory/responses.py b/moto/clouddirectory/responses.py
index c967791de..3038ee26c 100644
--- a/moto/clouddirectory/responses.py
+++ b/moto/clouddirectory/responses.py
@@ -18,9 +18,36 @@ class CloudDirectoryResponse(BaseResponse):
         """Return backend instance specific for this region."""
         return clouddirectory_backends[self.current_account][self.region]
 
+    def apply_schema(self) -> str:
+        directory_arn = self.headers.get("x-amz-data-partition")
+        published_schema_arn = self._get_param("PublishedSchemaArn")
+        self.clouddirectory_backend.apply_schema(
+            directory_arn=directory_arn,
+            published_schema_arn=published_schema_arn,
+        )
+        return json.dumps(
+            {
+                "AppliedSchemaArn": published_schema_arn,
+                "DirectoryArn": directory_arn,
+            }
+        )
+
+    def publish_schema(self) -> str:
+        development_schema_arn = self.headers.get("x-amz-data-partition")
+        version = self._get_param("Version")
+        minor_version = self._get_param("MinorVersion")
+        name = self._get_param("Name")
+        schema = self.clouddirectory_backend.publish_schema(
+            name=name,
+            version=version,
+            minor_version=minor_version,
+            development_schema_arn=development_schema_arn,
+        )
+        return json.dumps({"PublishedSchemaArn": schema})
+
     def create_directory(self) -> str:
         name = self._get_param("Name")
-        schema_arn = self._get_param("SchemaArn")
+        schema_arn = self.headers.get("x-amz-data-partition")
         directory = self.clouddirectory_backend.create_directory(
             name=name,
             schema_arn=schema_arn,
@@ -35,6 +62,13 @@ class CloudDirectoryResponse(BaseResponse):
             )
         )
 
+    def create_schema(self) -> str:
+        name = self._get_param("Name")
+        schema = self.clouddirectory_backend.create_schema(
+            name=name,
+        )
+        return json.dumps(dict(SchemaArn=schema))
+
     def list_directories(self) -> str:
         next_token = self._get_param("NextToken")
         max_results = self._get_param("MaxResults")
@@ -72,6 +106,33 @@ class CloudDirectoryResponse(BaseResponse):
         )
         return json.dumps(dict(DirectoryArn=directory_arn))
 
+    def delete_schema(self) -> str:
+        # Retrieve arn from headers
+        # https://docs.aws.amazon.com/clouddirectory/latest/APIReference/API_DeleteSchema.html
+        arn = self.headers.get("x-amz-data-partition")
+        self.clouddirectory_backend.delete_schema(
+            schema_arn=arn,
+        )
+        return json.dumps(dict(SchemaArn=arn))
+
+    def list_development_schema_arns(self) -> str:
+        next_token = self._get_param("NextToken")
+        max_results = self._get_param("MaxResults")
+        schemas, next_token = self.clouddirectory_backend.list_development_schema_arns(
+            next_token=next_token,
+            max_results=max_results,
+        )
+        return json.dumps(dict(SchemaArns=schemas, NextToken=next_token))
+
+    def list_published_schema_arns(self) -> str:
+        next_token = self._get_param("NextToken")
+        max_results = self._get_param("MaxResults")
+        schemas, next_token = self.clouddirectory_backend.list_published_schema_arns(
+            next_token=next_token,
+            max_results=max_results,
+        )
+        return json.dumps(dict(SchemaArns=schemas, NextToken=next_token))
+
     def get_directory(self) -> str:
         # Retrieve arn from headers
         # https://docs.aws.amazon.com/clouddirectory/latest/APIReference/API_GetDirectory.html
diff --git a/moto/clouddirectory/urls.py b/moto/clouddirectory/urls.py
index e82051ba4..ef70b8a98 100644
--- a/moto/clouddirectory/urls.py
+++ b/moto/clouddirectory/urls.py
@@ -8,10 +8,16 @@ url_bases = [
 
 url_paths = {
     "{0}/amazonclouddirectory/2017-01-11/directory/create$": CloudDirectoryResponse.dispatch,
+    "{0}/amazonclouddirectory/2017-01-11/schema/create$": CloudDirectoryResponse.dispatch,
     "{0}/amazonclouddirectory/2017-01-11/directory/list$": CloudDirectoryResponse.dispatch,
     "{0}/amazonclouddirectory/2017-01-11/tags/add$": CloudDirectoryResponse.dispatch,
     "{0}/amazonclouddirectory/2017-01-11/tags/remove$": CloudDirectoryResponse.dispatch,
     "{0}/amazonclouddirectory/2017-01-11/directory$": CloudDirectoryResponse.dispatch,
     "{0}/amazonclouddirectory/2017-01-11/directory/get$": CloudDirectoryResponse.dispatch,
     "{0}/amazonclouddirectory/2017-01-11/tags$": CloudDirectoryResponse.dispatch,
+    "{0}/amazonclouddirectory/2017-01-11/schema$": CloudDirectoryResponse.dispatch,
+    "{0}/amazonclouddirectory/2017-01-11/schema/development$": CloudDirectoryResponse.dispatch,
+    "{0}/amazonclouddirectory/2017-01-11/schema/published$": CloudDirectoryResponse.dispatch,
+    "{0}/amazonclouddirectory/2017-01-11/schema/apply$": CloudDirectoryResponse.dispatch,
+    "{0}/amazonclouddirectory/2017-01-11/schema/publish$": CloudDirectoryResponse.dispatch,
 }
diff --git a/moto/cognitoidp/models.py b/moto/cognitoidp/models.py
index 0783bee9c..d1e7cfc05 100644
--- a/moto/cognitoidp/models.py
+++ b/moto/cognitoidp/models.py
@@ -15,7 +15,10 @@ from moto.moto_api._internal import mock_random as random
 from moto.utilities.paginator import paginate
 from moto.utilities.utils import get_partition, load_resource, md5_hash
 
-from ..settings import get_cognito_idp_user_pool_id_strategy
+from ..settings import (
+    get_cognito_idp_user_pool_client_id_strategy,
+    get_cognito_idp_user_pool_id_strategy,
+)
 from .exceptions import (
     AliasExistsException,
     ExpiredCodeException,
@@ -31,7 +34,6 @@ from .exceptions import (
 from .utils import (
     PAGINATION_MODEL,
     check_secret_hash,
-    create_id,
     expand_attrs,
     flatten_attrs,
     generate_id,
@@ -705,6 +707,8 @@ class CognitoIdpUserPoolDomain(BaseModel):
 
 
 class CognitoIdpUserPoolClient(BaseModel):
+    MAX_ID_LENGTH = 26
+
     def __init__(
         self,
         user_pool_id: str,
@@ -712,7 +716,12 @@ class CognitoIdpUserPoolClient(BaseModel):
         extended_config: Optional[Dict[str, Any]],
     ):
         self.user_pool_id = user_pool_id
-        self.id = create_id()
+        self.id = generate_id(
+            get_cognito_idp_user_pool_client_id_strategy(),
+            user_pool_id,
+            generate_secret,
+            extended_config,
+        )[: self.MAX_ID_LENGTH]
         self.secret = str(random.uuid4())
         self.generate_secret = generate_secret or False
         # Some default values - may be overridden by the user
@@ -961,6 +970,7 @@ class CognitoIdpBackend(BaseBackend):
     For example, a single initialization before the start of integration tests.
 
     This behavior can be enabled by passing the environment variable: MOTO_COGNITO_IDP_USER_POOL_ID_STRATEGY=HASH.
+    Passing MOTO_COGNITO_IDP_USER_POOL_CLIENT_ID_STRATEGY=HASH enables the same logic for user pool clients.
     """
 
     def __init__(self, region_name: str, account_id: str):
diff --git a/moto/glue/models.py b/moto/glue/models.py
index a81bafc42..af3a9e621 100644
--- a/moto/glue/models.py
+++ b/moto/glue/models.py
@@ -1360,6 +1360,32 @@ class GlueBackend(BaseBackend):
 
         return response
 
+    def delete_resource_policy(
+        self,
+        resource_arn: Optional[str] = None,
+        policy_hash_condition: Optional[str] = None,
+    ) -> Dict[str, str]:
+        if resource_arn is None:
+            resource_arn = self.default_catalog_arn
+
+        if resource_arn not in self.resource_policies:
+            raise EntityNotFoundException(
+                "Policy does not exist",
+            )
+        policy_dict = self.resource_policies.get(resource_arn, {})
+
+        if (
+            policy_hash_condition
+            and policy_dict
+            and policy_hash_condition != policy_dict.get("PolicyHash")
+        ):
+            raise JsonRESTError(
+                "ConcurrentModificationException", "Policy hash condition not met"
+            )
+
+        del self.resource_policies[resource_arn]
+        return {}
+
 
 class FakeDatabase(BaseModel):
     def __init__(
diff --git a/moto/glue/responses.py b/moto/glue/responses.py
index 6effc42b3..8ff93753d 100644
--- a/moto/glue/responses.py
+++ b/moto/glue/responses.py
@@ -910,3 +910,14 @@ class GlueResponse(BaseResponse):
         )
 
         return json.dumps(response, cls=DateTimeEncoder)
+
+    def delete_resource_policy(self) -> str:
+        params = json.loads(self.body)
+        policy_hash_condition = params.get("PolicyHashCondition")
+        resource_arn = params.get("ResourceArn")
+        self.glue_backend.delete_resource_policy(
+            resource_arn=resource_arn,
+            policy_hash_condition=policy_hash_condition,
+        )
+
+        return "{}"
diff --git a/moto/networkfirewall/models.py b/moto/networkfirewall/models.py
index 7e8ed45e8..7ae859aa6 100644
--- a/moto/networkfirewall/models.py
+++ b/moto/networkfirewall/models.py
@@ -57,6 +57,7 @@ class NetworkFirewallModel(BaseModel):
 
         self.arn = f"arn:aws:network-firewall:{region_name}:{account_id}:firewall/{self.firewall_name}"
 
+        self.update_token = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
         self.firewall_status = {
             "Status": "READY",
             "ConfigurationSyncStateSummary": "IN_SYNC",
diff --git a/moto/networkfirewall/responses.py b/moto/networkfirewall/responses.py
index 4f7d30391..d3175a996 100644
--- a/moto/networkfirewall/responses.py
+++ b/moto/networkfirewall/responses.py
@@ -102,6 +102,7 @@ class NetworkFirewallResponse(BaseResponse):
         )
         return json.dumps(
             dict(
+                UpdateToken=firewall.update_token,
                 Firewall=firewall.to_dict(),
                 FirewallStatus=firewall.firewall_status,
             )
diff --git a/moto/securityhub/models.py b/moto/securityhub/models.py
index afcd6fcae..a25c93219 100644
--- a/moto/securityhub/models.py
+++ b/moto/securityhub/models.py
@@ -1,9 +1,13 @@
 """SecurityHubBackend class with methods for supported APIs."""
 
+import datetime
 from typing import Any, Dict, List, Optional, Tuple
 
 from moto.core.base_backend import BackendDict, BaseBackend
 from moto.core.common_models import BaseModel
+from moto.core.exceptions import RESTError
+from moto.organizations.exceptions import AWSOrganizationsNotInUseException
+from moto.organizations.models import organizations_backends
 from moto.securityhub.exceptions import InvalidInputException
 from moto.utilities.paginator import paginate
 
@@ -30,9 +34,34 @@ class SecurityHubBackend(BaseBackend):
         }
     }
 
+    _org_configs: Dict[str, Dict[str, Any]] = {}
+
     def __init__(self, region_name: str, account_id: str):
         super().__init__(region_name, account_id)
         self.findings: List[Finding] = []
+        self.region_name = region_name
+        self.org_backend = organizations_backends[self.account_id]["aws"]
+
+    def _get_org_config(self) -> Dict[str, Any]:
+        """Get organization config for the current account."""
+        try:
+            org = self.org_backend.describe_organization()
+            org_id = org["Organization"]["Id"]
+        except RESTError:
+            raise AWSOrganizationsNotInUseException()
+
+        if org_id not in SecurityHubBackend._org_configs:
+            SecurityHubBackend._org_configs[org_id] = {
+                "admin_account_id": None,
+                "auto_enable": False,
+                "auto_enable_standards": "DEFAULT",
+                "configuration": {
+                    "ConfigurationType": "LOCAL",
+                    "Status": "ENABLED",
+                    "StatusMessage": "",
+                },
+            }
+        return SecurityHubBackend._org_configs[org_id]
 
     @paginate(pagination_model=PAGINATION_MODEL)
     def get_findings(
@@ -118,5 +147,163 @@ class SecurityHubBackend(BaseBackend):
 
         return failed_count, success_count, failed_findings
 
+    def enable_organization_admin_account(self, admin_account_id: str) -> None:
+        try:
+            org = self.org_backend.describe_organization()
+            org_id = org["Organization"]["Id"]
+        except RESTError:
+            raise AWSOrganizationsNotInUseException()
+
+        if self.account_id != org["Organization"]["MasterAccountId"]:
+            raise RESTError(
+                "AccessDeniedException",
+                "The request was rejected because you don't have sufficient permissions "
+                "to perform this operation. The security token included in the request "
+                "is for an account that isn't authorized to perform this operation.",
+            )
+
+        try:
+            self.org_backend.get_account_by_id(admin_account_id)
+        except RESTError:
+            raise RESTError(
+                "InvalidInputException",
+                f"The request was rejected because the account {admin_account_id} is not "
+                f"a member of organization {org_id}.",
+            )
+
+        org_config = self._get_org_config()
+        org_config["admin_account_id"] = admin_account_id
+
+    def update_organization_configuration(
+        self,
+        auto_enable: bool,
+        auto_enable_standards: Optional[str] = None,
+        organization_configuration: Optional[Dict[str, Any]] = None,
+    ) -> None:
+        try:
+            self.org_backend.describe_organization()
+        except RESTError:
+            raise RESTError(
+                "ResourceNotFoundException",
+                "The request was rejected because AWS Organizations is not in use or not "
+                "configured for this account.",
+            )
+
+        org_config = self._get_org_config()
+        if not org_config["admin_account_id"]:
+            raise RESTError(
+                "ResourceNotFoundException",
+                "The request was rejected because no administrator account has been designated.",
+            )
+
+        if self.account_id != org_config["admin_account_id"]:
+            raise RESTError(
+                "AccessDeniedException",
+                "The request was rejected because you don't have permission to perform "
+                "this action. Only the designated administrator account can update the "
+                "organization configuration.",
+            )
+
+        if organization_configuration:
+            config_type = organization_configuration.get("ConfigurationType")
+            if config_type not in ["CENTRAL", "LOCAL"]:
+                raise RESTError(
+                    "InvalidInputException",
+                    "The request was rejected because the ConfigurationType value must be "
+                    "either CENTRAL or LOCAL.",
+                )
+
+            status = organization_configuration.get("Status")
+            if status not in ["PENDING", "ENABLED", "FAILED"]:
+                raise RESTError(
+                    "InvalidInputException",
+                    "The request was rejected because the Status value must be one of "
+                    "PENDING, ENABLED, or FAILED.",
+                )
+
+            if config_type == "CENTRAL":
+                if auto_enable:
+                    raise RESTError(
+                        "ValidationException",
+                        "The request was rejected because AutoEnable must be false when "
+                        "ConfigurationType is CENTRAL.",
+                    )
+                if auto_enable_standards != "NONE":
+                    raise RESTError(
+                        "ValidationException",
+                        "The request was rejected because AutoEnableStandards must be NONE "
+                        "when ConfigurationType is CENTRAL.",
+                    )
+
+            org_config["configuration"] = organization_configuration
+
+        org_config["auto_enable"] = auto_enable
+
+        if auto_enable_standards is not None:
+            if auto_enable_standards not in ["NONE", "DEFAULT"]:
+                raise RESTError(
+                    "InvalidInputException",
+                    "The request was rejected because AutoEnableStandards must be either "
+                    "NONE or DEFAULT.",
+                )
+            org_config["auto_enable_standards"] = auto_enable_standards
+
+    def get_administrator_account(self) -> Dict[str, Any]:
+        try:
+            org = self.org_backend.describe_organization()
+            management_account_id = org["Organization"]["MasterAccountId"]
+        except RESTError:
+            return {}
+
+        org_config = self._get_org_config()
+        admin_account_id = org_config["admin_account_id"]
+
+        if not admin_account_id:
+            return {}
+
+        if (
+            self.account_id == management_account_id
+            or self.account_id == admin_account_id
+        ):
+            return {}
+
+        return {
+            "Administrator": {
+                "AccountId": admin_account_id,
+                "MemberStatus": "ENABLED",
+                "InvitationId": f"invitation-{admin_account_id}",
+                "InvitedAt": datetime.datetime.now().isoformat(),
+            }
+        }
+
+    def describe_organization_configuration(self) -> Dict[str, Any]:
+        try:
+            self.org_backend.describe_organization()
+        except RESTError:
+            raise RESTError(
+                "AccessDeniedException",
+                "You do not have sufficient access to perform this action.",
+            )
+
+        org_config = self._get_org_config()
+        if not org_config["admin_account_id"]:
+            raise RESTError(
+                "AccessDeniedException",
+                "You do not have sufficient access to perform this action.",
+            )
+
+        if self.account_id != org_config["admin_account_id"]:
+            raise RESTError(
+                "AccessDeniedException",
+                "You do not have sufficient access to perform this action.",
+            )
+
+        return {
+            "AutoEnable": org_config["auto_enable"],
+            "MemberAccountLimitReached": False,
+            "AutoEnableStandards": org_config["auto_enable_standards"],
+            "OrganizationConfiguration": org_config["configuration"],
+        }
+
 
 securityhub_backends = BackendDict(SecurityHubBackend, "securityhub")
diff --git a/moto/securityhub/responses.py b/moto/securityhub/responses.py
index 8ce0ce1e2..047bf1ac8 100644
--- a/moto/securityhub/responses.py
+++ b/moto/securityhub/responses.py
@@ -59,3 +59,32 @@ class SecurityHubResponse(BaseResponse):
                 "SuccessCount": success_count,
             }
         )
+
+    def enable_organization_admin_account(self) -> str:
+        params = json.loads(self.body)
+        admin_account_id = params.get("AdminAccountId")
+        self.securityhub_backend.enable_organization_admin_account(
+            admin_account_id=admin_account_id,
+        )
+        return json.dumps(dict())
+
+    def update_organization_configuration(self) -> str:
+        params = json.loads(self.body)
+        auto_enable = params.get("AutoEnable")
+        auto_enable_standards = params.get("AutoEnableStandards")
+        organization_configuration = params.get("OrganizationConfiguration")
+        self.securityhub_backend.update_organization_configuration(
+            auto_enable=auto_enable,
+            auto_enable_standards=auto_enable_standards,
+            organization_configuration=organization_configuration,
+        )
+        return json.dumps(dict())
+
+    def get_administrator_account(self) -> str:
+        administrator = self.securityhub_backend.get_administrator_account()
+
+        return json.dumps(administrator)
+
+    def describe_organization_configuration(self) -> str:
+        response = self.securityhub_backend.describe_organization_configuration()
+        return json.dumps(dict(response))
diff --git a/moto/securityhub/urls.py b/moto/securityhub/urls.py
index 162e66d8a..d021d436f 100644
--- a/moto/securityhub/urls.py
+++ b/moto/securityhub/urls.py
@@ -9,4 +9,7 @@ url_bases = [
 url_paths = {
     "{0}/findings$": SecurityHubResponse.dispatch,
     "{0}/findings/import$": SecurityHubResponse.dispatch,
+    "{0}/organization/admin/enable$": SecurityHubResponse.dispatch,
+    "{0}/organization/configuration$": SecurityHubResponse.dispatch,
+    "{0}/administrator$": SecurityHubResponse.dispatch,
 }
diff --git a/moto/settings.py b/moto/settings.py
index 410e7a7f6..b42454ab3 100644
--- a/moto/settings.py
+++ b/moto/settings.py
@@ -183,6 +183,10 @@ def get_cognito_idp_user_pool_id_strategy() -> Optional[str]:
     return os.environ.get("MOTO_COGNITO_IDP_USER_POOL_ID_STRATEGY")
 
 
+def get_cognito_idp_user_pool_client_id_strategy() -> Optional[str]:
+    return os.environ.get("MOTO_COGNITO_IDP_USER_POOL_CLIENT_ID_STRATEGY")
+
+
 def enable_iso_regions() -> bool:
     return os.environ.get("MOTO_ENABLE_ISO_REGIONS", "false").lower() == "true"
 


Test patch:
diff --git a/tests/test_clouddirectory/test_clouddirectory.py b/tests/test_clouddirectory/test_clouddirectory.py
index 9aa08fcc8..36cbe59dc 100644
--- a/tests/test_clouddirectory/test_clouddirectory.py
+++ b/tests/test_clouddirectory/test_clouddirectory.py
@@ -1,6 +1,7 @@
 """Unit tests for clouddirectory-supported APIs."""
 
 import boto3
+import pytest
 
 from moto import mock_aws
 from tests import DEFAULT_ACCOUNT_ID
@@ -10,9 +11,8 @@ from tests import DEFAULT_ACCOUNT_ID
 def test_create_directory():
     region = "us-west-2"
     client = boto3.client("clouddirectory", region_name=region)
-    schema_arn = (
-        f"arn:aws:clouddirectory:{region}:{DEFAULT_ACCOUNT_ID}:directory/test-schema/1"
-    )
+    schema = client.create_schema(Name="test-schema")
+    schema_arn = schema["SchemaArn"]
     resp = client.create_directory(SchemaArn=schema_arn, Name="test-directory")
     assert (
         resp["DirectoryArn"]
@@ -20,6 +20,109 @@ def test_create_directory():
     )
     assert resp["Name"] == "test-directory"
     assert "ObjectIdentifier" in resp
+    assert resp["AppliedSchemaArn"] == schema_arn
+
+
+@mock_aws
+def test_create_schema():
+    region = "us-west-2"
+    client = boto3.client("clouddirectory", region_name=region)
+    resp = client.create_schema(Name="test-schema")
+    assert (
+        resp["SchemaArn"]
+        == f"arn:aws:clouddirectory:{region}:{DEFAULT_ACCOUNT_ID}:schema/development/test-schema"
+    )
+
+
+@mock_aws
+def test_apply_schema():
+    region = "us-west-2"
+    client = boto3.client("clouddirectory", region_name=region)
+    schema = client.create_schema(Name="test-schema")
+    schema_arn = schema["SchemaArn"]
+    pub_schema = client.publish_schema(
+        DevelopmentSchemaArn=schema_arn,
+        Name="test-schema",
+        Version="1",
+        MinorVersion="0",
+    )
+    pub_schema_arn = pub_schema["PublishedSchemaArn"]
+    directory = client.create_directory(SchemaArn=pub_schema_arn, Name="test-directory")
+    directory_arn = directory["DirectoryArn"]
+    resp = client.apply_schema(
+        PublishedSchemaArn=pub_schema_arn, DirectoryArn=directory_arn
+    )
+    assert resp["AppliedSchemaArn"] == pub_schema_arn
+    assert resp["DirectoryArn"] == directory_arn
+
+
+@mock_aws
+def test_publish_schema():
+    region = "us-west-2"
+    client = boto3.client("clouddirectory", region_name=region)
+    schema = client.create_schema(Name="test-schema")
+    schema_arn = schema["SchemaArn"]
+    resp = client.publish_schema(
+        DevelopmentSchemaArn=schema_arn,
+        Name="test-schema",
+        Version="1",
+        MinorVersion="0",
+    )
+    assert (
+        resp["PublishedSchemaArn"]
+        == f"arn:aws:clouddirectory:{region}:{DEFAULT_ACCOUNT_ID}:schema/published/test-schema/1/0"
+    )
+
+
+@mock_aws
+def test_list_development_schema_arns():
+    region = "us-west-2"
+    client = boto3.client("clouddirectory", region_name=region)
+    schema_arn1 = client.create_schema(Name="test-schema1")["SchemaArn"]
+    schema_arn2 = client.create_schema(Name="test-schema2")["SchemaArn"]
+
+    resp = client.list_development_schema_arns()
+    assert len(resp["SchemaArns"]) == 2
+    assert schema_arn1 in resp["SchemaArns"]
+    assert schema_arn2 in resp["SchemaArns"]
+
+
+@mock_aws
+def test_list_published_schema_arns():
+    region = "us-west-2"
+    client = boto3.client("clouddirectory", region_name=region)
+    schema_arn1 = client.create_schema(Name="test-schema1")["SchemaArn"]
+    schema_arn2 = client.create_schema(Name="test-schema2")["SchemaArn"]
+
+    published_arn1 = client.publish_schema(
+        DevelopmentSchemaArn=schema_arn1,
+        Name="test-schema1",
+        Version="1",
+        MinorVersion="0",
+    )["PublishedSchemaArn"]
+
+    published_arn2 = client.publish_schema(
+        DevelopmentSchemaArn=schema_arn2,
+        Name="test-schema2",
+        Version="1",
+        MinorVersion="0",
+    )["PublishedSchemaArn"]
+
+    resp = client.list_published_schema_arns()
+    assert len(resp["SchemaArns"]) == 2
+    assert published_arn1 in resp["SchemaArns"]
+    assert published_arn2 in resp["SchemaArns"]
+
+
+@mock_aws
+def test_delete_schema():
+    region = "us-west-2"
+    client = boto3.client("clouddirectory", region_name=region)
+    schema_arn = client.create_schema(Name="test-schema")["SchemaArn"]
+    client.create_directory(SchemaArn=schema_arn, Name="test-directory")
+    client.delete_schema(SchemaArn=schema_arn)
+    resp = client.list_development_schema_arns()
+    assert len(resp["SchemaArns"]) == 0
 
 
 @mock_aws
@@ -152,3 +255,139 @@ def test_get_directory():
     assert directory["DirectoryArn"] == directory_arns[0]
     assert directory["State"] == "ENABLED"
     assert "CreationDateTime" in directory
+
+
+@mock_aws
+def test_apply_schema_with_nonexistent_schema():
+    region = "us-west-2"
+    client = boto3.client("clouddirectory", region_name=region)
+
+    schema = client.create_schema(Name="test-schema")
+    schema_arn = schema["SchemaArn"]
+    pub_schema = client.publish_schema(
+        DevelopmentSchemaArn=schema_arn,
+        Name="test-schema",
+        Version="1",
+        MinorVersion="0",
+    )
+    pub_schema_arn = pub_schema["PublishedSchemaArn"]
+    directory = client.create_directory(SchemaArn=pub_schema_arn, Name="test-directory")
+    directory_arn = directory["DirectoryArn"]
+
+    non_existent_schema_arn = (
+        f"arn:aws:clouddirectory:{region}:123456789012:schema/published/nonexistent/1/0"
+    )
+
+    with pytest.raises(client.exceptions.ResourceNotFoundException):
+        client.apply_schema(
+            PublishedSchemaArn=non_existent_schema_arn, DirectoryArn=directory_arn
+        )
+
+
+@mock_aws
+def test_apply_schema_updates_directory_schema():
+    region = "us-west-2"
+    client = boto3.client("clouddirectory", region_name=region)
+
+    schema1 = client.create_schema(Name="test-schema1")
+    schema1_arn = schema1["SchemaArn"]
+    pub_schema1 = client.publish_schema(
+        DevelopmentSchemaArn=schema1_arn,
+        Name="test-schema1",
+        Version="1",
+        MinorVersion="0",
+    )
+    pub_schema1_arn = pub_schema1["PublishedSchemaArn"]
+
+    directory = client.create_directory(
+        SchemaArn=pub_schema1_arn, Name="test-directory"
+    )
+    directory_arn = directory["DirectoryArn"]
+
+    schema2 = client.create_schema(Name="test-schema2")
+    schema2_arn = schema2["SchemaArn"]
+    pub_schema2 = client.publish_schema(
+        DevelopmentSchemaArn=schema2_arn,
+        Name="test-schema2",
+        Version="1",
+        MinorVersion="0",
+    )
+    pub_schema2_arn = pub_schema2["PublishedSchemaArn"]
+
+    # Apply second schema to directory
+    resp = client.apply_schema(
+        PublishedSchemaArn=pub_schema2_arn, DirectoryArn=directory_arn
+    )
+
+    # Verify schema was updated
+    assert resp["AppliedSchemaArn"] == pub_schema2_arn
+
+
+@mock_aws
+def test_publish_nonexistent_schema():
+    region = "us-west-2"
+    client = boto3.client("clouddirectory", region_name=region)
+
+    non_existent_schema_arn = f"arn:aws:clouddirectory:{region}:{DEFAULT_ACCOUNT_ID}:schema/development/nonexistent"
+
+    with pytest.raises(client.exceptions.ResourceNotFoundException):
+        client.publish_schema(
+            DevelopmentSchemaArn=non_existent_schema_arn,
+            Name="nonexistent",
+            Version="1",
+            MinorVersion="0",
+        )
+
+
+@mock_aws
+def test_publish_schema_state_transition():
+    region = "us-west-2"
+    client = boto3.client("clouddirectory", region_name=region)
+
+    dev_schema = client.create_schema(Name="test-schema")
+    dev_schema_arn = dev_schema["SchemaArn"]
+
+    dev_schemas = client.list_development_schema_arns()
+    assert dev_schema_arn in dev_schemas["SchemaArns"]
+
+    published = client.publish_schema(
+        DevelopmentSchemaArn=dev_schema_arn,
+        Name="test-schema",
+        Version="1",
+        MinorVersion="0",
+    )
+    published_schema_arn = published["PublishedSchemaArn"]
+
+    dev_schemas = client.list_development_schema_arns()
+    pub_schemas = client.list_published_schema_arns()
+
+    assert dev_schema_arn not in dev_schemas["SchemaArns"]
+    assert published_schema_arn in pub_schemas["SchemaArns"]
+    assert (
+        published_schema_arn
+        == f"arn:aws:clouddirectory:{region}:{DEFAULT_ACCOUNT_ID}:schema/published/test-schema/1/0"
+    )
+
+
+@mock_aws
+def test_publish_already_published_schema():
+    region = "us-west-2"
+    client = boto3.client("clouddirectory", region_name=region)
+
+    schema = client.create_schema(Name="test-schema")
+    schema_arn = schema["SchemaArn"]
+    new_schema_arn = client.publish_schema(
+        DevelopmentSchemaArn=schema_arn,
+        Name="test-schema",
+        Version="1",
+        MinorVersion="0",
+    )
+
+    # Try to publish again with same development schema
+    with pytest.raises(client.exceptions.SchemaAlreadyPublishedException):
+        client.publish_schema(
+            DevelopmentSchemaArn=new_schema_arn["PublishedSchemaArn"],
+            Name="test-schema",
+            Version="1",
+            MinorVersion="0",
+        )
diff --git a/tests/test_cognitoidp/test_cognitoidp.py b/tests/test_cognitoidp/test_cognitoidp.py
index b4fb03f8b..b093394af 100644
--- a/tests/test_cognitoidp/test_cognitoidp.py
+++ b/tests/test_cognitoidp/test_cognitoidp.py
@@ -980,7 +980,7 @@ def test_update_user_pool_domain():
 
 
 @mock_aws
-def test_create_user_pool_client():
+def test_create_user_pool_client_default_id_strategy():
     conn = boto3.client("cognito-idp", "us-west-2")
 
     client_name = str(uuid.uuid4())
@@ -998,6 +998,50 @@ def test_create_user_pool_client():
     assert result["UserPoolClient"]["CallbackURLs"][0] == value
 
 
+@mock_aws
+@mock.patch.dict(os.environ, {"MOTO_COGNITO_IDP_USER_POOL_CLIENT_ID_STRATEGY": "HASH"})
+def test_create_user_pool_client_hash_id_strategy_with_equal_args():
+    if settings.TEST_SERVER_MODE:
+        raise SkipTest("Cannot set environemnt variables in ServerMode")
+
+    conn = boto3.client("cognito-idp", "us-west-2")
+
+    client_name = str(uuid.uuid4())
+    value = str(uuid.uuid4())
+    user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"]
+
+    first = conn.create_user_pool_client(
+        UserPoolId=user_pool_id, ClientName=client_name, CallbackURLs=[value]
+    )
+    second = conn.create_user_pool_client(
+        UserPoolId=user_pool_id, ClientName=client_name, CallbackURLs=[value]
+    )
+
+    assert first["UserPoolClient"]["ClientId"] == second["UserPoolClient"]["ClientId"]
+
+
+@mock_aws
+@mock.patch.dict(os.environ, {"MOTO_COGNITO_IDP_USER_POOL_CLIENT_ID_STRATEGY": "HASH"})
+def test_create_user_pool_client_hash_id_strategy_with_different_args():
+    if settings.TEST_SERVER_MODE:
+        raise SkipTest("Cannot set environemnt variables in ServerMode")
+
+    conn = boto3.client("cognito-idp", "us-west-2")
+
+    first = conn.create_user_pool_client(
+        UserPoolId=conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"],
+        ClientName=str(uuid.uuid4()),
+        CallbackURLs=[str(uuid.uuid4())],
+    )
+    second = conn.create_user_pool_client(
+        UserPoolId=conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"],
+        ClientName=str(uuid.uuid4()),
+        CallbackURLs=[str(uuid.uuid4())],
+    )
+
+    assert first["UserPoolClient"]["ClientId"] != second["UserPoolClient"]["ClientId"]
+
+
 @mock_aws
 def test_create_user_pool_client_returns_secret():
     conn = boto3.client("cognito-idp", "us-west-2")
diff --git a/tests/test_glue/test_glue.py b/tests/test_glue/test_glue.py
index 01bbedd85..4ffb30a14 100644
--- a/tests/test_glue/test_glue.py
+++ b/tests/test_glue/test_glue.py
@@ -1287,3 +1287,74 @@ def test_put_resource_policy_error_conditions():
             PolicyExistsCondition="MUST_EXIST",
         )
     assert "ResourceNumberLimitExceededException" in str(excinfo.value)
+
+
+@mock_aws
+def test_delete_resource_policy():
+    client = boto3.client("glue", region_name="us-east-1")
+
+    policy_json = json.dumps(
+        {
+            "Version": "2012-10-17",
+            "Statement": [
+                {
+                    "Effect": "Allow",
+                    "Principal": {"Service": "glue.amazonaws.com"},
+                    "Action": ["glue:*"],
+                    "Resource": ["*"],
+                }
+            ],
+        }
+    )
+    catalog_arn = "arn:aws:glue:us-east-1:123456789012:catalog"
+    client.put_resource_policy(PolicyInJson=policy_json, ResourceArn=catalog_arn)
+    catalog_response = client.get_resource_policy(ResourceArn=catalog_arn)
+
+    assert "PolicyInJson" in catalog_response
+    assert "PolicyHash" in catalog_response
+
+    # Delete resource policy
+    client.delete_resource_policy(ResourceArn=catalog_arn)
+
+    catalog_response = client.get_resource_policy(ResourceArn=catalog_arn)
+    assert "PolicyInJson" not in catalog_response
+    assert "PolicyHash" not in catalog_response
+
+
+@mock_aws
+def test_delete_resource_policy_error_conditions():
+    client = boto3.client("glue", region_name="us-east-1")
+
+    policy_json = json.dumps(
+        {
+            "Version": "2012-10-17",
+            "Statement": [
+                {
+                    "Effect": "Allow",
+                    "Principal": {"Service": "glue.amazonaws.com"},
+                    "Action": ["glue:*"],
+                    "Resource": ["*"],
+                }
+            ],
+        }
+    )
+    catalog_arn = "arn:aws:glue:us-east-1:123456789012:catalog"
+
+    # Delete resource policy that does not exist.
+    with pytest.raises(ClientError) as error_info:
+        client.delete_resource_policy(ResourceArn=catalog_arn)
+
+    assert error_info.value.response["Error"]["Code"] == "EntityNotFoundException"
+
+    # Create resource policy
+    catalog_arn = "arn:aws:glue:us-east-1:123456789012:catalog"
+    client.put_resource_policy(PolicyInJson=policy_json, ResourceArn=catalog_arn)
+
+    with pytest.raises(ClientError) as error_info:
+        client.delete_resource_policy(
+            ResourceArn=catalog_arn,
+            PolicyHashCondition="incorrect-hash",
+        )
+    assert (
+        error_info.value.response["Error"]["Code"] == "ConcurrentModificationException"
+    )
diff --git a/tests/test_networkfirewall/test_networkfirewall.py b/tests/test_networkfirewall/test_networkfirewall.py
index acebcdcc3..5b6789131 100644
--- a/tests/test_networkfirewall/test_networkfirewall.py
+++ b/tests/test_networkfirewall/test_networkfirewall.py
@@ -141,6 +141,7 @@ def test_describe_firewall():
     assert resp["Firewall"]["FirewallPolicyChangeProtection"] is False
     assert resp["Firewall"]["Description"] == "Test firewall"
     assert resp["Firewall"]["Tags"] == [{"Key": "Name", "Value": "test-firewall"}]
+    assert resp["UpdateToken"] == "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
 
     # Describe the firewall using the name
     resp_name = client.describe_firewall(FirewallName="test-firewall")
diff --git a/tests/test_securityhub/test_securityhub.py b/tests/test_securityhub/test_securityhub.py
index 3a95c07a4..49183c371 100644
--- a/tests/test_securityhub/test_securityhub.py
+++ b/tests/test_securityhub/test_securityhub.py
@@ -1,10 +1,13 @@
 """Unit tests for securityhub-supported APIs."""
 
+import os
+from unittest import mock
+
 import boto3
 import pytest
 from botocore.exceptions import ClientError
 
-from moto import mock_aws
+from moto import mock_aws, settings
 from moto.core import DEFAULT_ACCOUNT_ID
 
 
@@ -154,3 +157,553 @@ def test_get_findings_max_results():
     assert isinstance(get_response["Findings"], list)
     assert len(get_response["Findings"]) == 1
     assert "NextToken" in get_response
+
+
+@mock_aws
+def test_enable_organization_admin_account():
+    if not settings.TEST_DECORATOR_MODE:
+        # Can't set env variables in ServerMode
+        return
+
+    org_client = boto3.client("organizations", region_name="us-east-1")
+    org_client.create_organization(FeatureSet="ALL")
+
+    org_client.create_account(
+        AccountName="SecurityHubAdmin",
+        Email="securityhub.admin@example.com",
+    )
+
+    accounts = org_client.list_accounts()["Accounts"]
+    admin_account = next(acc for acc in accounts if acc["Name"] == "SecurityHubAdmin")
+    admin_account_id = admin_account["Id"]
+
+    client = boto3.client("securityhub", region_name="us-east-1")
+
+    with pytest.raises(ClientError) as exc:
+        client.enable_organization_admin_account(AdminAccountId="111111111111")
+    err = exc.value.response["Error"]
+    assert err["Code"] == "InvalidInputException"
+    assert "is not a member of organization" in err["Message"]
+
+    with mock.patch.dict(os.environ, {"MOTO_ACCOUNT_ID": admin_account_id}):
+        non_management_client = boto3.client("securityhub", region_name="us-east-1")
+        with pytest.raises(ClientError) as exc:
+            non_management_client.enable_organization_admin_account(
+                AdminAccountId=admin_account_id
+            )
+        err = exc.value.response["Error"]
+        assert err["Code"] == "AccessDeniedException"
+
+    client.enable_organization_admin_account(AdminAccountId=admin_account_id)
+
+    get_resp = client.get_administrator_account()
+    assert "Administrator" not in get_resp
+
+
+@mock_aws
+def test_enable_organization_admin_account_without_org():
+    client = boto3.client("securityhub", region_name="us-east-1")
+
+    with pytest.raises(ClientError) as exc:
+        client.enable_organization_admin_account(AdminAccountId="123456789012")
+
+    err = exc.value.response["Error"]
+    assert err["Code"] == "AWSOrganizationsNotInUseException"
+
+
+@mock_aws
+def test_enable_organization_admin_account_non_management():
+    if not settings.TEST_DECORATOR_MODE:
+        # Can't set env variables in ServerMode
+        return
+
+    org_client = boto3.client("organizations", region_name="us-east-1")
+    org_client.create_organization(FeatureSet="ALL")
+
+    admin_account_id = "123456789012"
+    org_client.create_account(
+        AccountName="SecurityHubAdmin",
+        Email="securityhub.admin@example.com",
+    )
+
+    accounts = org_client.list_accounts()["Accounts"]
+    admin_account = next(acc for acc in accounts if acc["Name"] == "SecurityHubAdmin")
+    admin_account_id = admin_account["Id"]
+
+    # Enable SecurityHub admin account from non-management account
+    with mock.patch.dict(os.environ, {"MOTO_ACCOUNT_ID": admin_account_id}):
+        client = boto3.client("securityhub", region_name="us-east-1")
+        with pytest.raises(ClientError) as exc:
+            client.enable_organization_admin_account(AdminAccountId=admin_account_id)
+
+        err = exc.value.response["Error"]
+        assert err["Code"] == "AccessDeniedException"
+
+
+@mock_aws
+def test_update_organization_configuration():
+    if not settings.TEST_DECORATOR_MODE:
+        return
+
+    client = boto3.client("securityhub", region_name="us-east-1")
+    with pytest.raises(ClientError) as exc:
+        client.update_organization_configuration(
+            AutoEnable=True, AutoEnableStandards="DEFAULT"
+        )
+    err = exc.value.response["Error"]
+    assert err["Code"] == "ResourceNotFoundException"
+    assert (
+        "AWS Organizations is not in use or not configured for this account"
+        in err["Message"]
+    )
+
+    org_client = boto3.client("organizations", region_name="us-east-1")
+    org_client.create_organization(FeatureSet="ALL")
+
+    org_client.create_account(
+        AccountName="SecurityHubAdmin",
+        Email="securityhub.admin@example.com",
+    )
+
+    accounts = org_client.list_accounts()["Accounts"]
+    admin_account = next(acc for acc in accounts if acc["Name"] == "SecurityHubAdmin")
+    admin_account_id = admin_account["Id"]
+
+    client = boto3.client("securityhub", region_name="us-east-1")
+
+    # Test ResourceNotFoundException when no admin account is designated
+    with pytest.raises(ClientError) as exc:
+        client.update_organization_configuration(
+            AutoEnable=True, AutoEnableStandards="DEFAULT"
+        )
+    err = exc.value.response["Error"]
+    assert err["Code"] == "ResourceNotFoundException"
+    assert "no administrator account has been designated" in err["Message"]
+
+    client.enable_organization_admin_account(AdminAccountId=admin_account_id)
+
+    with mock.patch.dict(os.environ, {"MOTO_ACCOUNT_ID": admin_account_id}):
+        admin_client = boto3.client("securityhub", region_name="us-east-1")
+        with pytest.raises(ClientError) as exc:
+            admin_client.update_organization_configuration(
+                AutoEnable=True, AutoEnableStandards="INVALID_VALUE"
+            )
+        err = exc.value.response["Error"]
+        assert err["Code"] == "InvalidInputException"
+        assert "AutoEnableStandards must be either NONE or DEFAULT" in err["Message"]
+
+        with pytest.raises(ClientError) as exc:
+            admin_client.update_organization_configuration(
+                AutoEnable=True,
+                AutoEnableStandards="NONE",
+                OrganizationConfiguration={
+                    "ConfigurationType": "CENTRAL",
+                    "Status": "ENABLED",
+                    "StatusMessage": "Configuration centralized",
+                },
+            )
+        err = exc.value.response["Error"]
+        assert err["Code"] == "ValidationException"
+        assert (
+            "AutoEnable must be false when ConfigurationType is CENTRAL"
+            in err["Message"]
+        )
+
+        with pytest.raises(ClientError) as exc:
+            admin_client.update_organization_configuration(
+                AutoEnable=False,
+                AutoEnableStandards="DEFAULT",
+                OrganizationConfiguration={
+                    "ConfigurationType": "CENTRAL",
+                    "Status": "ENABLED",
+                    "StatusMessage": "Configuration centralized",
+                },
+            )
+        err = exc.value.response["Error"]
+        assert err["Code"] == "ValidationException"
+        assert (
+            "AutoEnableStandards must be NONE when ConfigurationType is CENTRAL"
+            in err["Message"]
+        )
+
+        admin_client.update_organization_configuration(
+            AutoEnable=False,
+            AutoEnableStandards="NONE",
+            OrganizationConfiguration={
+                "ConfigurationType": "CENTRAL",
+                "Status": "ENABLED",
+                "StatusMessage": "Configuration centralized",
+            },
+        )
+
+        describe_resp = admin_client.describe_organization_configuration()
+        assert describe_resp["AutoEnable"] is False
+        assert describe_resp["AutoEnableStandards"] == "NONE"
+        assert (
+            describe_resp["OrganizationConfiguration"]["ConfigurationType"] == "CENTRAL"
+        )
+        assert describe_resp["OrganizationConfiguration"]["Status"] == "ENABLED"
+        assert (
+            describe_resp["OrganizationConfiguration"]["StatusMessage"]
+            == "Configuration centralized"
+        )
+
+    # Test non-admin account access
+    with mock.patch.dict(os.environ, {"MOTO_ACCOUNT_ID": DEFAULT_ACCOUNT_ID}):
+        non_admin_client = boto3.client("securityhub", region_name="us-east-1")
+        with pytest.raises(ClientError) as exc:
+            non_admin_client.update_organization_configuration(
+                AutoEnable=True,
+                AutoEnableStandards="DEFAULT",
+            )
+        assert exc.value.response["Error"]["Code"] == "AccessDeniedException"
+
+
+@mock_aws
+def test_multiple_accounts_in_organization():
+    if not settings.TEST_DECORATOR_MODE:
+        # Can't set env variables in ServerMode
+        return
+
+    org_client = boto3.client("organizations", region_name="us-east-1")
+    org_client.create_organization(FeatureSet="ALL")
+
+    org_client.create_account(
+        AccountName="SecurityHubAdmin",
+        Email="securityhub.admin@example.com",
+    )
+    org_client.create_account(
+        AccountName="Member1",
+        Email="member1@example.com",
+    )
+    org_client.create_account(
+        AccountName="Member2",
+        Email="member2@example.com",
+    )
+
+    accounts = org_client.list_accounts()["Accounts"]
+    admin_account = next(acc for acc in accounts if acc["Name"] == "SecurityHubAdmin")
+    member1_account = next(acc for acc in accounts if acc["Name"] == "Member1")
+    member2_account = next(acc for acc in accounts if acc["Name"] == "Member2")
+
+    admin_account_id = admin_account["Id"]
+    member_account_id_1 = member1_account["Id"]
+    member_account_id_2 = member2_account["Id"]
+
+    client = boto3.client("securityhub", region_name="us-east-1")
+    client.enable_organization_admin_account(AdminAccountId=admin_account_id)
+
+    with mock.patch.dict(os.environ, {"MOTO_ACCOUNT_ID": admin_account_id}):
+        admin_client = boto3.client("securityhub", region_name="us-east-1")
+        admin_client.update_organization_configuration(
+            AutoEnable=True,
+            AutoEnableStandards="NONE",
+        )
+
+    admin_response = client.get_administrator_account()
+    assert "Administrator" not in admin_response
+
+    with mock.patch.dict(os.environ, {"MOTO_ACCOUNT_ID": admin_account_id}):
+        admin_client = boto3.client("securityhub", region_name="us-east-1")
+        admin_response = admin_client.get_administrator_account()
+        assert "Administrator" not in admin_response
+
+    # Check from member account 1 - should see admin details since auto_enable is True
+    with mock.patch.dict(os.environ, {"MOTO_ACCOUNT_ID": member_account_id_1}):
+        member_client_1 = boto3.client("securityhub", region_name="us-east-1")
+        admin_response_1 = member_client_1.get_administrator_account()
+
+        assert "Administrator" in admin_response_1
+        assert admin_response_1["Administrator"]["AccountId"] == admin_account_id
+        assert admin_response_1["Administrator"]["MemberStatus"] == "ENABLED"
+
+    # Check from member account 2 - should see admin details since auto_enable is True
+    with mock.patch.dict(os.environ, {"MOTO_ACCOUNT_ID": member_account_id_2}):
+        member_client_2 = boto3.client("securityhub", region_name="us-east-1")
+        admin_response_2 = member_client_2.get_administrator_account()
+
+        assert "Administrator" in admin_response_2
+        assert admin_response_2["Administrator"]["AccountId"] == admin_account_id
+        assert admin_response_2["Administrator"]["MemberStatus"] == "ENABLED"
+
+
+@mock_aws
+def test_organization_auto_enable_disabled():
+    if not settings.TEST_DECORATOR_MODE:
+        # Can't set env variables in ServerMode
+        return
+
+    org_client = boto3.client("organizations", region_name="us-east-1")
+    org_client.create_organization(FeatureSet="ALL")
+
+    org_client.create_account(
+        AccountName="SecurityHubAdmin",
+        Email="securityhub.admin@example.com",
+    )
+    org_client.create_account(
+        AccountName="Member1",
+        Email="member1@example.com",
+    )
+
+    accounts = org_client.list_accounts()["Accounts"]
+    admin_account = next(acc for acc in accounts if acc["Name"] == "SecurityHubAdmin")
+    member1_account = next(acc for acc in accounts if acc["Name"] == "Member1")
+
+    admin_account_id = admin_account["Id"]
+    member_account_id = member1_account["Id"]
+
+    client = boto3.client("securityhub", region_name="us-east-1")
+    client.enable_organization_admin_account(AdminAccountId=admin_account_id)
+
+    with mock.patch.dict(os.environ, {"MOTO_ACCOUNT_ID": admin_account_id}):
+        admin_client = boto3.client("securityhub", region_name="us-east-1")
+        admin_client.update_organization_configuration(
+            AutoEnable=False,
+            AutoEnableStandards="NONE",
+        )
+
+    admin_response = client.get_administrator_account()
+    assert "Administrator" not in admin_response
+
+    with mock.patch.dict(os.environ, {"MOTO_ACCOUNT_ID": admin_account_id}):
+        admin_client = boto3.client("securityhub", region_name="us-east-1")
+        admin_response = admin_client.get_administrator_account()
+        assert "Administrator" not in admin_response
+
+    with mock.patch.dict(os.environ, {"MOTO_ACCOUNT_ID": member_account_id}):
+        member_client = boto3.client("securityhub", region_name="us-east-1")
+        member_response = member_client.get_administrator_account()
+        assert "Administrator" in member_response
+        assert member_response["Administrator"]["AccountId"] == admin_account_id
+        assert member_response["Administrator"]["MemberStatus"] == "ENABLED"
+
+
+@mock_aws
+def test_describe_organization_configuration_access_control():
+    if not settings.TEST_DECORATOR_MODE:
+        # Can't set env variables in ServerMode
+        return
+
+    org_client = boto3.client("organizations", region_name="us-east-1")
+    org_client.create_organization(FeatureSet="ALL")
+
+    org_client.create_account(
+        AccountName="SecurityHubAdmin",
+        Email="securityhub.admin@example.com",
+    )
+
+    accounts = org_client.list_accounts()["Accounts"]
+    admin_account = next(acc for acc in accounts if acc["Name"] == "SecurityHubAdmin")
+    admin_account_id = admin_account["Id"]
+
+    client = boto3.client("securityhub", region_name="us-east-1")
+    with pytest.raises(ClientError) as exc:
+        client.describe_organization_configuration()
+    err = exc.value.response["Error"]
+    assert err["Code"] == "AccessDeniedException"
+    assert "You do not have sufficient access to perform this action" in err["Message"]
+
+    client.enable_organization_admin_account(AdminAccountId=admin_account_id)
+
+    with pytest.raises(ClientError) as exc:
+        client.describe_organization_configuration()
+    err = exc.value.response["Error"]
+    assert err["Code"] == "AccessDeniedException"
+    assert "You do not have sufficient access to perform this action" in err["Message"]
+
+    with mock.patch.dict(os.environ, {"MOTO_ACCOUNT_ID": admin_account_id}):
+        admin_client = boto3.client("securityhub", region_name="us-east-1")
+        response = admin_client.describe_organization_configuration()
+        assert "AutoEnable" in response
+        assert "MemberAccountLimitReached" in response
+        assert "AutoEnableStandards" in response
+        assert "OrganizationConfiguration" in response
+
+    org_client.create_account(
+        AccountName="Member1",
+        Email="member1@example.com",
+    )
+
+    accounts = org_client.list_accounts()["Accounts"]
+    member_account = next(acc for acc in accounts if acc["Name"] == "Member1")
+    member_account_id = member_account["Id"]
+    with mock.patch.dict(os.environ, {"MOTO_ACCOUNT_ID": member_account_id}):
+        member_client = boto3.client("securityhub", region_name="us-east-1")
+        with pytest.raises(ClientError) as exc:
+            member_client.describe_organization_configuration()
+        err = exc.value.response["Error"]
+        assert err["Code"] == "AccessDeniedException"
+        assert (
+            "You do not have sufficient access to perform this action" in err["Message"]
+        )
+
+
+@mock_aws
+def test_multiple_admin_member_relationships():
+    if not settings.TEST_DECORATOR_MODE:
+        # Can't set env variables in ServerMode
+        return
+
+    """Different accounts can be admins of different member accounts.
+    - Account A is admin of Account B
+    - Account C is admin of Account D
+    """
+    org_client_1 = boto3.client("organizations", region_name="us-east-1")
+    org_client_1.create_organization(FeatureSet="ALL")
+
+    org_client_1.create_account(
+        AccountName="AdminA",
+        Email="admin.a@bestcompany.com",
+    )
+    org_client_1.create_account(
+        AccountName="MemberB",
+        Email="member.b@bestcompany.com",
+    )
+
+    accounts_1 = org_client_1.list_accounts()["Accounts"]
+    admin_a = next(acc for acc in accounts_1 if acc["Name"] == "AdminA")
+    member_b = next(acc for acc in accounts_1 if acc["Name"] == "MemberB")
+
+    with mock.patch.dict(os.environ, {"MOTO_ACCOUNT_ID": "999999999999"}):
+        org_client_2 = boto3.client("organizations", region_name="us-east-1")
+        org_client_2.create_organization(FeatureSet="ALL")
+
+        # Create accounts C and D
+        org_client_2.create_account(
+            AccountName="AdminC",
+            Email="admin.c@bestcompany.com",
+        )
+        org_client_2.create_account(
+            AccountName="MemberD",
+            Email="member.d@bestcompany.com",
+        )
+
+        accounts_2 = org_client_2.list_accounts()["Accounts"]
+        admin_c = next(acc for acc in accounts_2 if acc["Name"] == "AdminC")
+        member_d = next(acc for acc in accounts_2 if acc["Name"] == "MemberD")
+
+    # Setup A as admin of B
+    client_1 = boto3.client("securityhub", region_name="us-east-1")
+    client_1.enable_organization_admin_account(AdminAccountId=admin_a["Id"])
+
+    # Setup C as admin of D
+    with mock.patch.dict(os.environ, {"MOTO_ACCOUNT_ID": "999999999999"}):
+        client_2 = boto3.client("securityhub", region_name="us-east-1")
+        client_2.enable_organization_admin_account(AdminAccountId=admin_c["Id"])
+
+    # Verify B sees A as its admin
+    with mock.patch.dict(os.environ, {"MOTO_ACCOUNT_ID": member_b["Id"]}):
+        member_b_client = boto3.client("securityhub", region_name="us-east-1")
+        admin_response_b = member_b_client.get_administrator_account()
+        assert "Administrator" in admin_response_b
+        assert admin_response_b["Administrator"]["AccountId"] == admin_a["Id"]
+
+    # Verify D sees C as its admin
+    with mock.patch.dict(os.environ, {"MOTO_ACCOUNT_ID": member_d["Id"]}):
+        member_d_client = boto3.client("securityhub", region_name="us-east-1")
+        admin_response_d = member_d_client.get_administrator_account()
+        assert "Administrator" in admin_response_d
+        assert admin_response_d["Administrator"]["AccountId"] == admin_c["Id"]
+
+    # Verify B doesn't see C as its admin
+    with mock.patch.dict(os.environ, {"MOTO_ACCOUNT_ID": member_b["Id"]}):
+        member_b_client = boto3.client("securityhub", region_name="us-east-1")
+        admin_response_b = member_b_client.get_administrator_account()
+        assert "Administrator" in admin_response_b
+        assert admin_response_b["Administrator"]["AccountId"] != admin_c["Id"]
+
+    # Verify D doesn't see A as its admin
+    with mock.patch.dict(os.environ, {"MOTO_ACCOUNT_ID": member_d["Id"]}):
+        member_d_client = boto3.client("securityhub", region_name="us-east-1")
+        admin_response_d = member_d_client.get_administrator_account()
+        assert "Administrator" in admin_response_d
+        assert admin_response_d["Administrator"]["AccountId"] != admin_a["Id"]
+
+
+@mock_aws
+def test_describe_organization_configuration():
+    if not settings.TEST_DECORATOR_MODE:
+        # Can't set env variables in ServerMode
+        return
+
+    org_client = boto3.client("organizations", region_name="us-east-1")
+    org_client.create_organization(FeatureSet="ALL")
+
+    org_client.create_account(
+        AccountName="SecurityHubAdmin",
+        Email="securityhub.admin@example.com",
+    )
+
+    accounts = org_client.list_accounts()["Accounts"]
+    admin_account = next(acc for acc in accounts if acc["Name"] == "SecurityHubAdmin")
+    admin_account_id = admin_account["Id"]
+
+    client = boto3.client("securityhub", region_name="us-east-1")
+
+    with pytest.raises(ClientError) as exc:
+        client.describe_organization_configuration()
+    err = exc.value.response["Error"]
+    assert err["Code"] == "AccessDeniedException"
+    assert "You do not have sufficient access to perform this action" in err["Message"]
+
+    client.enable_organization_admin_account(AdminAccountId=admin_account_id)
+
+    with pytest.raises(ClientError) as exc:
+        client.describe_organization_configuration()
+    err = exc.value.response["Error"]
+    assert err["Code"] == "AccessDeniedException"
+    assert "You do not have sufficient access to perform this action" in err["Message"]
+
+    with mock.patch.dict(os.environ, {"MOTO_ACCOUNT_ID": admin_account_id}):
+        admin_client = boto3.client("securityhub", region_name="us-east-1")
+        response = admin_client.describe_organization_configuration()
+        assert "AutoEnable" in response
+        assert "MemberAccountLimitReached" in response
+        assert "AutoEnableStandards" in response
+        assert "OrganizationConfiguration" in response
+
+
+@mock_aws
+def test_get_administrator_account():
+    if not settings.TEST_DECORATOR_MODE:
+        return
+
+    org_client = boto3.client("organizations", region_name="us-east-1")
+    org_client.create_organization(FeatureSet="ALL")
+
+    org_client.create_account(
+        AccountName="SecurityHubAdmin",
+        Email="securityhub.admin@example.com",
+    )
+    org_client.create_account(
+        AccountName="Member",
+        Email="member@example.com",
+    )
+
+    accounts = org_client.list_accounts()["Accounts"]
+    admin_account = next(acc for acc in accounts if acc["Name"] == "SecurityHubAdmin")
+    member_account = next(acc for acc in accounts if acc["Name"] == "Member")
+    admin_account_id = admin_account["Id"]
+    member_account_id = member_account["Id"]
+
+    client = boto3.client("securityhub", region_name="us-east-1")
+
+    response = client.get_administrator_account()
+    assert "Administrator" not in response
+
+    client.enable_organization_admin_account(AdminAccountId=admin_account_id)
+
+    response = client.get_administrator_account()
+    assert "Administrator" not in response
+
+    with mock.patch.dict(os.environ, {"MOTO_ACCOUNT_ID": admin_account_id}):
+        admin_client = boto3.client("securityhub", region_name="us-east-1")
+        response = admin_client.get_administrator_account()
+        assert "Administrator" not in response
+
+    with mock.patch.dict(os.environ, {"MOTO_ACCOUNT_ID": member_account_id}):
+        member_client = boto3.client("securityhub", region_name="us-east-1")
+        response = member_client.get_administrator_account()
+        assert "Administrator" in response
+        assert response["Administrator"]["AccountId"] == admin_account_id
+        assert response["Administrator"]["MemberStatus"] == "ENABLED"