From 9640ec20d125248ac91243591c7db50daabfd135 Mon Sep 17 00:00:00 2001 From: Fedorenko Dmitrij Date: Mon, 13 Jun 2022 13:14:22 +0300 Subject: [PATCH] Stable cognito user pool (#5194) --- docs/docs/services/cognito-idp.rst | 7 +++ moto/cognitoidp/models.py | 12 ++++- moto/cognitoidp/utils.py | 24 +++++++++ moto/settings.py | 4 ++ tests/test_cognitoidp/test_cognitoidp.py | 69 ++++++++++++++++++++++++ 5 files changed, 115 insertions(+), 1 deletion(-) diff --git a/docs/docs/services/cognito-idp.rst b/docs/docs/services/cognito-idp.rst index 5f9265ad6..3065ea664 100644 --- a/docs/docs/services/cognito-idp.rst +++ b/docs/docs/services/cognito-idp.rst @@ -145,3 +145,10 @@ cognito-idp - [ ] verify_user_attribute + +|start-h3| Stable Cognito User Pool Id |end-h3| + +In some cases, you need to have reproducible IDs for the user pool. +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. diff --git a/moto/cognitoidp/models.py b/moto/cognitoidp/models.py index 6c9da7750..276effb5d 100644 --- a/moto/cognitoidp/models.py +++ b/moto/cognitoidp/models.py @@ -24,6 +24,7 @@ from .exceptions import ( from .utils import ( create_id, check_secret_hash, + generate_id, validate_username_format, flatten_attrs, expand_attrs, @@ -31,6 +32,7 @@ from .utils import ( ) from moto.utilities.paginator import paginate from moto.utilities.utils import md5_hash +from ..settings import get_cognito_idp_user_pool_id_strategy class UserStatus(str, enum.Enum): @@ -366,12 +368,20 @@ DEFAULT_USER_POOL_CONFIG = { class CognitoIdpUserPool(BaseModel): + + MAX_ID_LENGTH = 56 + def __init__(self, region, name, extended_config): self.region = region - self.id = "{}_{}".format(self.region, str(uuid.uuid4().hex)) + + user_pool_id = generate_id( + get_cognito_idp_user_pool_id_strategy(), region, name, extended_config + ) + self.id = "{}_{}".format(self.region, user_pool_id)[: self.MAX_ID_LENGTH] self.arn = "arn:aws:cognito-idp:{}:{}:userpool/{}".format( self.region, get_account_id(), self.id ) + self.name = name self.status = None diff --git a/moto/cognitoidp/utils.py b/moto/cognitoidp/utils.py index 8a4970957..6b2fc5a0b 100644 --- a/moto/cognitoidp/utils.py +++ b/moto/cognitoidp/utils.py @@ -4,6 +4,7 @@ import hashlib import hmac import base64 import re +import uuid FORMATS = { "email": r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", @@ -66,3 +67,26 @@ def flatten_attrs(attrs): def expand_attrs(attrs): return [{"Name": k, "Value": v} for k, v in attrs.items()] + + +ID_HASH_STRATEGY = "HASH" + + +def generate_id(strategy, *args): + if strategy == ID_HASH_STRATEGY: + return _generate_id_hash(args) + else: + return _generate_id_uuid() + + +def _generate_id_uuid(): + return uuid.uuid4().hex + + +def _generate_id_hash(args): + hasher = hashlib.sha256() + + for arg in args: + hasher.update(str(arg).encode()) + + return hasher.hexdigest() diff --git a/moto/settings.py b/moto/settings.py index e3f833360..13e1b2a17 100644 --- a/moto/settings.py +++ b/moto/settings.py @@ -125,3 +125,7 @@ def get_docker_host(): ) print(f"{type(e)}::{e}") return "http://host.docker.internal" + + +def get_cognito_idp_user_pool_id_strategy(): + return os.environ.get("MOTO_COGNITO_IDP_USER_POOL_ID_STRATEGY") diff --git a/tests/test_cognitoidp/test_cognitoidp.py b/tests/test_cognitoidp/test_cognitoidp.py index 1a83cf933..9a34cf309 100644 --- a/tests/test_cognitoidp/test_cognitoidp.py +++ b/tests/test_cognitoidp/test_cognitoidp.py @@ -5,6 +5,7 @@ import os import random import re +import mock import moto.cognitoidp.models import requests import hmac @@ -506,6 +507,74 @@ def test_add_custom_attributes_existing_attribute(): ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) +@mock_cognitoidp +def test_create_user_pool_default_id_strategy(): + conn = boto3.client("cognito-idp", "us-west-2") + + first_pool = conn.create_user_pool(PoolName=str("default-pool")) + second_pool = conn.create_user_pool(PoolName=str("default-pool")) + + first_pool["UserPool"]["Id"].should_not.equal(second_pool["UserPool"]["Id"]) + + +@mock_cognitoidp +@mock.patch.dict(os.environ, {"MOTO_COGNITO_IDP_USER_POOL_ID_STRATEGY": "HASH"}) +def test_create_user_pool_hash_id_strategy_with_equal_pool_name(): + if settings.TEST_SERVER_MODE: + raise SkipTest("Cannot set environemnt variables in ServerMode") + + conn = boto3.client("cognito-idp", "us-west-2") + + first_pool = conn.create_user_pool(PoolName=str("default-pool")) + second_pool = conn.create_user_pool(PoolName=str("default-pool")) + + first_pool["UserPool"]["Id"].should.equal(second_pool["UserPool"]["Id"]) + + +@mock_cognitoidp +@mock.patch.dict(os.environ, {"MOTO_COGNITO_IDP_USER_POOL_ID_STRATEGY": "HASH"}) +def test_create_user_pool_hash_id_strategy_with_different_pool_name(): + if settings.TEST_SERVER_MODE: + raise SkipTest("Cannot set environemnt variables in ServerMode") + + conn = boto3.client("cognito-idp", "us-west-2") + + first_pool = conn.create_user_pool(PoolName=str("first-pool")) + second_pool = conn.create_user_pool(PoolName=str("second-pool")) + + first_pool["UserPool"]["Id"].should_not.equal(second_pool["UserPool"]["Id"]) + + +@mock_cognitoidp +@mock.patch.dict(os.environ, {"MOTO_COGNITO_IDP_USER_POOL_ID_STRATEGY": "HASH"}) +def test_create_user_pool_hash_id_strategy_with_different_attributes(): + if settings.TEST_SERVER_MODE: + raise SkipTest("Cannot set environemnt variables in ServerMode") + + conn = boto3.client("cognito-idp", "us-west-2") + + first_pool = conn.create_user_pool( + PoolName=str("default-pool"), + Schema=[ + { + "Name": "first", + "AttributeDataType": "String", + } + ], + ) + second_pool = conn.create_user_pool( + PoolName=str("default-pool"), + Schema=[ + { + "Name": "second", + "AttributeDataType": "String", + } + ], + ) + + first_pool["UserPool"]["Id"].should_not.equal(second_pool["UserPool"]["Id"]) + + @mock_cognitoidp def test_list_user_pools(): conn = boto3.client("cognito-idp", "us-west-2")