Repository: getmoto/moto
Pull request #6699 (2023-08-18 18:10:14)

RDS: Fix ClusterSnapshot template to include tags
ClusterSnapshot template is missing the TagList.
This means that describe_db_cluster_snapshots will make all the snapshots look like there are not tags.

This will solve #6695 


Patch:
diff --git a/moto/cognitoidp/models.py b/moto/cognitoidp/models.py
index 4772fd3eb..b9b6d53f6 100644
--- a/moto/cognitoidp/models.py
+++ b/moto/cognitoidp/models.py
@@ -1525,6 +1525,15 @@ class CognitoIdpBackend(BaseBackend):
                     "ChallengeParameters": {},
                 }
 
+            if user.status == UserStatus.FORCE_CHANGE_PASSWORD:
+                return {
+                    "ChallengeName": "NEW_PASSWORD_REQUIRED",
+                    "ChallengeParameters": {
+                        "USERNAME": username,
+                    },
+                    "Session": session,
+                }
+
             del self.sessions[session]
             return self._log_user_in(user_pool, client, username)
         elif challenge_name == "SOFTWARE_TOKEN_MFA":
diff --git a/moto/kms/models.py b/moto/kms/models.py
index 6c53dad06..a1971a05c 100644
--- a/moto/kms/models.py
+++ b/moto/kms/models.py
@@ -22,6 +22,7 @@ from .utils import (
     generate_key_id,
     generate_master_key,
     generate_private_key,
+    KeySpec,
 )
 
 
@@ -302,6 +303,8 @@ class KmsBackend(BaseBackend):
          - The resource is set to "*"
          - The Action matches `describe_key`
         """
+        if key_spec:
+            self.__ensure_valid_key_spec(key_spec)
         key = Key(
             policy,
             key_usage,
@@ -615,6 +618,16 @@ class KmsBackend(BaseBackend):
                 )
             )
 
+    def __ensure_valid_key_spec(self, key_spec: str) -> None:
+        if key_spec not in KeySpec.key_specs():
+            raise ValidationException(
+                (
+                    "1 validation error detected: Value '{key_spec}' at 'KeySpec' failed "
+                    "to satisfy constraint: Member must satisfy enum value set: "
+                    "{valid_key_specs}"
+                ).format(key_spec=key_spec, valid_key_specs=KeySpec.key_specs())
+            )
+
     def sign(
         self, key_id: str, message: bytes, signing_algorithm: str
     ) -> Tuple[str, bytes, str]:
diff --git a/moto/kms/utils.py b/moto/kms/utils.py
index 2639f5ca4..35fec1c0f 100644
--- a/moto/kms/utils.py
+++ b/moto/kms/utils.py
@@ -1,5 +1,6 @@
 from collections import namedtuple
-from typing import Any, Dict, Tuple
+from typing import Any, Dict, Tuple, List
+from enum import Enum
 import io
 import os
 import struct
@@ -48,6 +49,28 @@ RESERVED_ALIASE_TARGET_KEY_IDS = {
 RESERVED_ALIASES = list(RESERVED_ALIASE_TARGET_KEY_IDS.keys())
 
 
+class KeySpec(str, Enum):
+    # Asymmetric key specs
+    RSA_2048 = "RSA_2048"
+    RSA_3072 = "RSA_3072"
+    RSA_4096 = "RSA_4096"
+    ECC_NIST_P256 = "ECC_NIST_P256"
+    ECC_SECG_P256K1 = "ECC_SECG_P256K1"
+    ECC_NIST_P384 = "ECC_NIST_P384"
+    ECC_NIST_P512 = "ECC_NIST_P521"
+    SM2 = "SM2"  # China Regions only
+    # Symmetric key specs
+    SYMMETRIC_DEFAULT = "SYMMETRIC_DEFAULT"
+    HMAC_224 = "HMAC_224"
+    HMAC_256 = "HMAC_256"
+    HMAC_284 = "HMAC_384"
+    HMAC_512 = "HMAC_512"
+
+    @classmethod
+    def key_specs(self) -> List[str]:
+        return sorted([item.value for item in KeySpec])
+
+
 def generate_key_id(multi_region: bool = False) -> str:
     key = str(mock_random.uuid4())
     # https://docs.aws.amazon.com/kms/latest/developerguide/multi-region-keys-overview.html
diff --git a/moto/rds/models.py b/moto/rds/models.py
index 7ff975661..4cad78505 100644
--- a/moto/rds/models.py
+++ b/moto/rds/models.py
@@ -485,6 +485,11 @@ class ClusterSnapshot(BaseModel):
                 {% else %}
                 <StorageType>{{ cluster.storage_type }}</StorageType>
                 {% endif %}
+                <TagList>
+                {%- for tag in snapshot.tags -%}
+                    <Tag><Key>{{ tag['Key'] }}</Key><Value>{{ tag['Value'] }}</Value></Tag>
+                {%- endfor -%}
+                </TagList>
                 <Timezone></Timezone>
                 <LicenseModel>{{ cluster.license_model }}</LicenseModel>
             </DBClusterSnapshot>


Test patch:
diff --git a/tests/test_cognitoidp/test_cognitoidp.py b/tests/test_cognitoidp/test_cognitoidp.py
index 0d8d44951..10397884f 100644
--- a/tests/test_cognitoidp/test_cognitoidp.py
+++ b/tests/test_cognitoidp/test_cognitoidp.py
@@ -593,6 +593,66 @@ def test_list_user_pools():
     assert result["UserPools"][0]["Name"] == name
 
 
+@mock_cognitoidp
+def test_authorize_user_with_force_password_change_status():
+    conn = boto3.client("cognito-idp", "us-west-2")
+    pool_id = conn.create_user_pool(PoolName="TestUserPool")["UserPool"]["Id"]
+    client_id = conn.create_user_pool_client(
+        UserPoolId=pool_id, ClientName="TestAppClient"
+    )["UserPoolClient"]["ClientId"]
+
+    username = "test@example.com"
+    temp_password = "Tempor@ryPassword123"
+    new_password = "NewP@ssword456"
+    conn.admin_create_user(
+        UserPoolId=pool_id,
+        Username=username,
+        TemporaryPassword=temp_password,
+    )
+
+    # Initiate USER_SRP_AUTH flow
+    key = bytes(str(temp_password).encode("latin-1"))
+    msg = bytes(str(username + client_id).encode("latin-1"))
+    new_digest = hmac.new(key, msg, hashlib.sha256).digest()
+    secret_hash = base64.b64encode(new_digest).decode()
+    result = conn.initiate_auth(
+        ClientId=client_id,
+        AuthFlow="USER_SRP_AUTH",
+        AuthParameters={
+            "USERNAME": username,
+            "SRP_A": uuid.uuid4().hex,
+            "SECRET_HASH": secret_hash,
+        },
+    )
+
+    # Try to log in with user in status FORCE_CHANGE_PASSWORD
+    result = conn.respond_to_auth_challenge(
+        ClientId=client_id,
+        ChallengeName=result["ChallengeName"],
+        ChallengeResponses={
+            "PASSWORD_CLAIM_SIGNATURE": str(uuid.uuid4()),
+            "PASSWORD_CLAIM_SECRET_BLOCK": result["Session"],
+            "TIMESTAMP": str(uuid.uuid4()),
+            "USERNAME": username,
+        },
+    )
+    assert result["ChallengeName"] == "NEW_PASSWORD_REQUIRED"
+    assert result["Session"] is not None
+    assert result["ChallengeParameters"]["USERNAME"] == username
+
+    # Sets a new password to the user and log it in
+    result = conn.respond_to_auth_challenge(
+        ClientId=client_id,
+        ChallengeName="NEW_PASSWORD_REQUIRED",
+        Session=result["Session"],
+        ChallengeResponses={
+            "USERNAME": username,
+            "NEW_PASSWORD": new_password,
+        },
+    )
+    assert result["AuthenticationResult"]["AccessToken"] is not None
+
+
 @mock_cognitoidp
 def test_set_user_pool_mfa_config():
     conn = boto3.client("cognito-idp", "us-west-2")
diff --git a/tests/test_kms/test_kms_boto3.py b/tests/test_kms/test_kms_boto3.py
index dd82e9c2c..3c1e0ab8d 100644
--- a/tests/test_kms/test_kms_boto3.py
+++ b/tests/test_kms/test_kms_boto3.py
@@ -42,6 +42,21 @@ def test_create_key_without_description():
     assert metadata["Description"] == ""
 
 
+@mock_kms
+def test_create_key_with_invalid_key_spec():
+    conn = boto3.client("kms", region_name="us-east-1")
+    unsupported_key_spec = "NotSupportedKeySpec"
+    with pytest.raises(ClientError) as ex:
+        conn.create_key(Policy="my policy", KeySpec=unsupported_key_spec)
+    err = ex.value.response["Error"]
+    assert err["Code"] == "ValidationException"
+    assert err["Message"] == (
+        "1 validation error detected: Value '{key_spec}' at 'KeySpec' failed "
+        "to satisfy constraint: Member must satisfy enum value set: "
+        "['ECC_NIST_P256', 'ECC_NIST_P384', 'ECC_NIST_P521', 'ECC_SECG_P256K1', 'HMAC_224', 'HMAC_256', 'HMAC_384', 'HMAC_512', 'RSA_2048', 'RSA_3072', 'RSA_4096', 'SM2', 'SYMMETRIC_DEFAULT']"
+    ).format(key_spec=unsupported_key_spec)
+
+
 @mock_kms
 def test_create_key():
     conn = boto3.client("kms", region_name="us-east-1")