cognito-idp standard attributes and pool schema validation (#4493)
This commit is contained in:
parent
0739537679
commit
07e8ba48ba
@ -39,6 +39,280 @@ class UserStatus(str, enum.Enum):
|
||||
RESET_REQUIRED = "RESET_REQUIRED"
|
||||
|
||||
|
||||
class CognitoIdpUserPoolAttribute(BaseModel):
|
||||
|
||||
STANDARD_SCHEMA = {
|
||||
"sub": {
|
||||
"AttributeDataType": "String",
|
||||
"Mutable": False,
|
||||
"Required": True,
|
||||
"StringAttributeConstraints": {"MinLength": "1", "MaxLength": "2048"},
|
||||
},
|
||||
"name": {
|
||||
"AttributeDataType": "String",
|
||||
"Mutable": True,
|
||||
"Required": False,
|
||||
"StringAttributeConstraints": {"MinLength": "0", "MaxLength": "2048"},
|
||||
},
|
||||
"given_name": {
|
||||
"AttributeDataType": "String",
|
||||
"Mutable": True,
|
||||
"Required": False,
|
||||
"StringAttributeConstraints": {"MinLength": "0", "MaxLength": "2048"},
|
||||
},
|
||||
"family_name": {
|
||||
"AttributeDataType": "String",
|
||||
"Mutable": True,
|
||||
"Required": False,
|
||||
"StringAttributeConstraints": {"MinLength": "0", "MaxLength": "2048"},
|
||||
},
|
||||
"middle_name": {
|
||||
"AttributeDataType": "String",
|
||||
"Mutable": True,
|
||||
"Required": False,
|
||||
"StringAttributeConstraints": {"MinLength": "0", "MaxLength": "2048"},
|
||||
},
|
||||
"nickname": {
|
||||
"AttributeDataType": "String",
|
||||
"Mutable": True,
|
||||
"Required": False,
|
||||
"StringAttributeConstraints": {"MinLength": "0", "MaxLength": "2048"},
|
||||
},
|
||||
"preferred_username": {
|
||||
"AttributeDataType": "String",
|
||||
"Mutable": True,
|
||||
"Required": False,
|
||||
"StringAttributeConstraints": {"MinLength": "0", "MaxLength": "2048"},
|
||||
},
|
||||
"profile": {
|
||||
"AttributeDataType": "String",
|
||||
"Mutable": True,
|
||||
"Required": False,
|
||||
"StringAttributeConstraints": {"MinLength": "0", "MaxLength": "2048"},
|
||||
},
|
||||
"picture": {
|
||||
"AttributeDataType": "String",
|
||||
"Mutable": True,
|
||||
"Required": False,
|
||||
"StringAttributeConstraints": {"MinLength": "0", "MaxLength": "2048"},
|
||||
},
|
||||
"website": {
|
||||
"AttributeDataType": "String",
|
||||
"Mutable": True,
|
||||
"Required": False,
|
||||
"StringAttributeConstraints": {"MinLength": "0", "MaxLength": "2048"},
|
||||
},
|
||||
"email": {
|
||||
"AttributeDataType": "String",
|
||||
"Mutable": True,
|
||||
"Required": False,
|
||||
"StringAttributeConstraints": {"MinLength": "0", "MaxLength": "2048"},
|
||||
},
|
||||
"email_verified": {
|
||||
"AttributeDataType": "Boolean",
|
||||
"Mutable": True,
|
||||
"Required": False,
|
||||
},
|
||||
"gender": {
|
||||
"AttributeDataType": "String",
|
||||
"Mutable": True,
|
||||
"Required": False,
|
||||
"StringAttributeConstraints": {"MinLength": "0", "MaxLength": "2048"},
|
||||
},
|
||||
"birthdate": {
|
||||
"AttributeDataType": "String",
|
||||
"Mutable": True,
|
||||
"Required": False,
|
||||
"StringAttributeConstraints": {"MinLength": "10", "MaxLength": "10"},
|
||||
},
|
||||
"zoneinfo": {
|
||||
"AttributeDataType": "String",
|
||||
"Mutable": True,
|
||||
"Required": False,
|
||||
"StringAttributeConstraints": {"MinLength": "0", "MaxLength": "2048"},
|
||||
},
|
||||
"locale": {
|
||||
"AttributeDataType": "String",
|
||||
"Mutable": True,
|
||||
"Required": False,
|
||||
"StringAttributeConstraints": {"MinLength": "0", "MaxLength": "2048"},
|
||||
},
|
||||
"phone_number": {
|
||||
"AttributeDataType": "String",
|
||||
"Mutable": True,
|
||||
"Required": False,
|
||||
"StringAttributeConstraints": {"MinLength": "0", "MaxLength": "2048"},
|
||||
},
|
||||
"phone_number_verified": {
|
||||
"AttributeDataType": "Boolean",
|
||||
"Mutable": True,
|
||||
"Required": False,
|
||||
},
|
||||
"address": {
|
||||
"AttributeDataType": "String",
|
||||
"Mutable": True,
|
||||
"Required": False,
|
||||
"StringAttributeConstraints": {"MinLength": "0", "MaxLength": "2048"},
|
||||
},
|
||||
"updated_at": {
|
||||
"AttributeDataType": "Number",
|
||||
"Mutable": True,
|
||||
"Required": False,
|
||||
"NumberAttributeConstraints": {"MinValue": "0"},
|
||||
},
|
||||
}
|
||||
|
||||
ATTRIBUTE_DATE_TYPES = {"Boolean", "DateTime", "String", "Number"}
|
||||
|
||||
def __init__(self, name, schema):
|
||||
self.name = name
|
||||
self.custom = self.name not in CognitoIdpUserPoolAttribute.STANDARD_SCHEMA
|
||||
attribute_data_type = schema.get("AttributeDataType", None)
|
||||
if (
|
||||
attribute_data_type
|
||||
and attribute_data_type
|
||||
not in CognitoIdpUserPoolAttribute.ATTRIBUTE_DATE_TYPES
|
||||
):
|
||||
raise InvalidParameterException(
|
||||
f"Validation error detected: Value '{attribute_data_type}' failed to satisfy constraint: Member must satisfy enum value set: [Boolean, Number, String, DateTime]"
|
||||
)
|
||||
|
||||
if self.custom:
|
||||
self._init_custom(schema)
|
||||
else:
|
||||
self._init_standard(schema)
|
||||
|
||||
def _init_custom(self, schema):
|
||||
self.name = "custom:" + self.name
|
||||
attribute_data_type = schema.get("AttributeDataType", None)
|
||||
if not attribute_data_type:
|
||||
raise InvalidParameterException(
|
||||
"Invalid AttributeDataType input, consider using the provided AttributeDataType enum."
|
||||
)
|
||||
self.data_type = attribute_data_type
|
||||
self.developer_only = schema.get("DeveloperOnlyAttribute", False)
|
||||
if self.developer_only:
|
||||
self.name = "dev:" + self.name
|
||||
self.mutable = schema.get("Mutable", True)
|
||||
if schema.get("Required", False):
|
||||
raise InvalidParameterException(
|
||||
"Required custom attributes are not supported currently."
|
||||
)
|
||||
self.required = False
|
||||
self._init_constraints(schema, None)
|
||||
|
||||
def _init_standard(self, schema):
|
||||
attribute_data_type = schema.get("AttributeDataType", None)
|
||||
default_attribute_data_type = CognitoIdpUserPoolAttribute.STANDARD_SCHEMA[
|
||||
self.name
|
||||
]["AttributeDataType"]
|
||||
if attribute_data_type and attribute_data_type != default_attribute_data_type:
|
||||
raise InvalidParameterException(
|
||||
f"You can not change AttributeDataType or set developerOnlyAttribute for standard schema attribute {self.name}"
|
||||
)
|
||||
self.data_type = default_attribute_data_type
|
||||
if schema.get("DeveloperOnlyAttribute", False):
|
||||
raise InvalidParameterException(
|
||||
f"You can not change AttributeDataType or set developerOnlyAttribute for standard schema attribute {self.name}"
|
||||
)
|
||||
else:
|
||||
self.developer_only = False
|
||||
self.mutable = schema.get(
|
||||
"Mutable",
|
||||
CognitoIdpUserPoolAttribute.STANDARD_SCHEMA[self.name]["Mutable"],
|
||||
)
|
||||
self.required = schema.get(
|
||||
"Required",
|
||||
CognitoIdpUserPoolAttribute.STANDARD_SCHEMA[self.name]["Required"],
|
||||
)
|
||||
constraints_key = None
|
||||
if self.data_type == "Number":
|
||||
constraints_key = "NumberAttributeConstraints"
|
||||
elif self.data_type == "String":
|
||||
constraints_key = "StringAttributeConstraints"
|
||||
default_constraints = (
|
||||
None
|
||||
if not constraints_key
|
||||
else CognitoIdpUserPoolAttribute.STANDARD_SCHEMA[self.name][constraints_key]
|
||||
)
|
||||
self._init_constraints(schema, default_constraints)
|
||||
|
||||
def _init_constraints(self, schema, default_constraints):
|
||||
def numeric_limit(num, constraint_type):
|
||||
if not num:
|
||||
return
|
||||
parsed = None
|
||||
try:
|
||||
parsed = int(num)
|
||||
except ValueError:
|
||||
pass
|
||||
if parsed is None or parsed < 0:
|
||||
raise InvalidParameterException(
|
||||
f"Invalid {constraint_type} for schema attribute {self.name}"
|
||||
)
|
||||
return parsed
|
||||
|
||||
self.string_constraints = None
|
||||
self.number_constraints = None
|
||||
|
||||
if "AttributeDataType" in schema:
|
||||
# Quirk - schema is set/validated only if AttributeDataType is specified
|
||||
if self.data_type == "String":
|
||||
string_constraints = schema.get(
|
||||
"StringAttributeConstraints", default_constraints
|
||||
)
|
||||
if not string_constraints:
|
||||
return
|
||||
min_len = numeric_limit(
|
||||
string_constraints.get("MinLength", None),
|
||||
"StringAttributeConstraints",
|
||||
)
|
||||
max_len = numeric_limit(
|
||||
string_constraints.get("MaxLength", None),
|
||||
"StringAttributeConstraints",
|
||||
)
|
||||
if (min_len and min_len > 2048) or (max_len and max_len > 2048):
|
||||
raise InvalidParameterException(
|
||||
f"user.{self.name}: String attributes cannot have a length of more than 2048"
|
||||
)
|
||||
if min_len and max_len and min_len > max_len:
|
||||
raise InvalidParameterException(
|
||||
f"user.{self.name}: Max length cannot be less than min length."
|
||||
)
|
||||
self.string_constraints = string_constraints
|
||||
elif self.data_type == "Number":
|
||||
number_constraints = schema.get(
|
||||
"NumberAttributeConstraints", default_constraints
|
||||
)
|
||||
if not number_constraints:
|
||||
return
|
||||
# No limits on either min or max value
|
||||
min_val = numeric_limit(
|
||||
number_constraints.get("MinValue", None),
|
||||
"NumberAttributeConstraints",
|
||||
)
|
||||
max_val = numeric_limit(
|
||||
number_constraints.get("MaxValue", None),
|
||||
"NumberAttributeConstraints",
|
||||
)
|
||||
if min_val and max_val and min_val > max_val:
|
||||
raise InvalidParameterException(
|
||||
f"user.{self.name}: Max value cannot be less than min value."
|
||||
)
|
||||
self.number_constraints = number_constraints
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
"Name": self.name,
|
||||
"AttributeDataType": self.data_type,
|
||||
"DeveloperOnlyAttribute": self.developer_only,
|
||||
"Mutable": self.mutable,
|
||||
"Required": self.required,
|
||||
"NumberAttributeConstraints": self.number_constraints,
|
||||
"StringAttributeConstraints": self.string_constraints,
|
||||
}
|
||||
|
||||
|
||||
class CognitoIdpUserPool(BaseModel):
|
||||
def __init__(self, region, name, extended_config):
|
||||
self.region = region
|
||||
@ -56,6 +330,21 @@ class CognitoIdpUserPool(BaseModel):
|
||||
self.sms_mfa_config = None
|
||||
self.token_mfa_config = None
|
||||
|
||||
self.schema_attributes = {
|
||||
schema["Name"]: CognitoIdpUserPoolAttribute(schema["Name"], schema)
|
||||
for schema in extended_config.pop("Schema", {})
|
||||
}
|
||||
for (
|
||||
standard_attribute_name,
|
||||
standard_attribute_schema,
|
||||
) in CognitoIdpUserPoolAttribute.STANDARD_SCHEMA.items():
|
||||
if standard_attribute_name not in self.schema_attributes:
|
||||
self.schema_attributes[
|
||||
standard_attribute_name
|
||||
] = CognitoIdpUserPoolAttribute(
|
||||
standard_attribute_name, standard_attribute_schema
|
||||
)
|
||||
|
||||
self.clients = OrderedDict()
|
||||
self.identity_providers = OrderedDict()
|
||||
self.groups = OrderedDict()
|
||||
@ -99,6 +388,13 @@ class CognitoIdpUserPool(BaseModel):
|
||||
user_pool_json = self._base_json()
|
||||
if extended:
|
||||
user_pool_json.update(self.extended_config)
|
||||
user_pool_json.update(
|
||||
{
|
||||
"SchemaAttributes": [
|
||||
att.to_json() for att in self.schema_attributes.values()
|
||||
]
|
||||
}
|
||||
)
|
||||
else:
|
||||
user_pool_json["LambdaConfig"] = (
|
||||
self.extended_config.get("LambdaConfig") or {}
|
||||
|
@ -43,6 +43,404 @@ def test_create_user_pool():
|
||||
result["UserPool"]["LambdaConfig"]["PreSignUp"].should.equal(value)
|
||||
|
||||
|
||||
@mock_cognitoidp
|
||||
def test_create_user_pool_should_have_all_default_attributes_in_schema():
|
||||
conn = boto3.client("cognito-idp", "us-west-2")
|
||||
|
||||
name = str(uuid.uuid4())
|
||||
result = conn.create_user_pool(PoolName=name)
|
||||
|
||||
result_schema = result["UserPool"]["SchemaAttributes"]
|
||||
result_schema = {s["Name"]: s for s in result_schema}
|
||||
|
||||
described_schema = conn.describe_user_pool(UserPoolId=result["UserPool"]["Id"])[
|
||||
"UserPool"
|
||||
]["SchemaAttributes"]
|
||||
described_schema = {s["Name"]: s for s in described_schema}
|
||||
|
||||
for schema in result_schema, described_schema:
|
||||
for (
|
||||
default_attr_name,
|
||||
default_attr,
|
||||
) in moto.cognitoidp.models.CognitoIdpUserPoolAttribute.STANDARD_SCHEMA.items():
|
||||
attribute = schema[default_attr_name]
|
||||
attribute["Required"].should.equal(default_attr["Required"])
|
||||
attribute["AttributeDataType"].should.equal(
|
||||
default_attr["AttributeDataType"]
|
||||
)
|
||||
attribute["Mutable"].should.equal(default_attr["Mutable"])
|
||||
attribute.get("StringAttributeConstraints", None).should.equal(
|
||||
default_attr.get("StringAttributeConstraints", None)
|
||||
)
|
||||
attribute.get("NumberAttributeConstraints", None).should.equal(
|
||||
default_attr.get("NumberAttributeConstraints", None)
|
||||
)
|
||||
attribute["DeveloperOnlyAttribute"].should.be.false
|
||||
|
||||
|
||||
@mock_cognitoidp
|
||||
def test_create_user_pool_unknown_attribute_data_type():
|
||||
conn = boto3.client("cognito-idp", "us-west-2")
|
||||
|
||||
name = str(uuid.uuid4())
|
||||
|
||||
attribute_data_type = "Banana"
|
||||
with pytest.raises(ClientError) as ex:
|
||||
conn.create_user_pool(
|
||||
PoolName=name,
|
||||
Schema=[{"Name": "custom", "AttributeDataType": attribute_data_type,},],
|
||||
)
|
||||
|
||||
ex.value.response["Error"]["Code"].should.equal("InvalidParameterException")
|
||||
ex.value.response["Error"]["Message"].should.equal(
|
||||
f"Validation error detected: Value '{attribute_data_type}' failed to satisfy constraint: Member must satisfy enum value set: [Boolean, Number, String, DateTime]"
|
||||
)
|
||||
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
|
||||
|
||||
@mock_cognitoidp
|
||||
def test_create_user_pool_custom_attribute_without_data_type():
|
||||
conn = boto3.client("cognito-idp", "us-west-2")
|
||||
with pytest.raises(ClientError) as ex:
|
||||
conn.create_user_pool(PoolName=str(uuid.uuid4()), Schema=[{"Name": "custom",},])
|
||||
|
||||
ex.value.response["Error"]["Code"].should.equal("InvalidParameterException")
|
||||
ex.value.response["Error"]["Message"].should.equal(
|
||||
"Invalid AttributeDataType input, consider using the provided AttributeDataType enum."
|
||||
)
|
||||
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
|
||||
|
||||
@mock_cognitoidp
|
||||
def test_create_user_pool_custom_attribute_defaults():
|
||||
conn = boto3.client("cognito-idp", "us-west-2")
|
||||
res = conn.create_user_pool(
|
||||
PoolName=str(uuid.uuid4()),
|
||||
Schema=[
|
||||
{"Name": "string", "AttributeDataType": "String",},
|
||||
{"Name": "number", "AttributeDataType": "Number",},
|
||||
],
|
||||
)
|
||||
string_attribute = next(
|
||||
attr
|
||||
for attr in res["UserPool"]["SchemaAttributes"]
|
||||
if attr["Name"] == "custom:string"
|
||||
)
|
||||
string_attribute["DeveloperOnlyAttribute"].should.be.false
|
||||
string_attribute["Mutable"].should.be.true
|
||||
string_attribute.get("StringAttributeConstraints").should.be.none
|
||||
|
||||
number_attribute = next(
|
||||
attr
|
||||
for attr in res["UserPool"]["SchemaAttributes"]
|
||||
if attr["Name"] == "custom:number"
|
||||
)
|
||||
number_attribute["DeveloperOnlyAttribute"].should.be.false
|
||||
number_attribute["Mutable"].should.be.true
|
||||
number_attribute.get("NumberAttributeConstraints").should.be.none
|
||||
|
||||
|
||||
@mock_cognitoidp
|
||||
def test_create_user_pool_custom_attribute_developer_only():
|
||||
conn = boto3.client("cognito-idp", "us-west-2")
|
||||
res = conn.create_user_pool(
|
||||
PoolName=str(uuid.uuid4()),
|
||||
Schema=[
|
||||
{
|
||||
"Name": "banana",
|
||||
"AttributeDataType": "String",
|
||||
"DeveloperOnlyAttribute": True,
|
||||
},
|
||||
],
|
||||
)
|
||||
# Note that this time we are looking for 'dev:xyz' attribute
|
||||
attribute = next(
|
||||
attr
|
||||
for attr in res["UserPool"]["SchemaAttributes"]
|
||||
if attr["Name"] == "dev:custom:banana"
|
||||
)
|
||||
attribute["DeveloperOnlyAttribute"].should.be.true
|
||||
|
||||
|
||||
@mock_cognitoidp
|
||||
def test_create_user_pool_custom_attribute_required():
|
||||
conn = boto3.client("cognito-idp", "us-west-2")
|
||||
|
||||
with pytest.raises(ClientError) as ex:
|
||||
conn.create_user_pool(
|
||||
PoolName=str(uuid.uuid4()),
|
||||
Schema=[
|
||||
{"Name": "banana", "AttributeDataType": "String", "Required": True},
|
||||
],
|
||||
)
|
||||
ex.value.response["Error"]["Code"].should.equal("InvalidParameterException")
|
||||
ex.value.response["Error"]["Message"].should.equal(
|
||||
"Required custom attributes are not supported currently."
|
||||
)
|
||||
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
|
||||
|
||||
@mock_cognitoidp
|
||||
@pytest.mark.parametrize(
|
||||
"attribute",
|
||||
[
|
||||
{"Name": "email", "AttributeDataType": "Number"},
|
||||
{"Name": "email", "DeveloperOnlyAttribute": True},
|
||||
],
|
||||
ids=["standard_attribute", "developer_only"],
|
||||
)
|
||||
def test_create_user_pool_standard_attribute_with_changed_data_type_or_developer_only(
|
||||
attribute,
|
||||
):
|
||||
conn = boto3.client("cognito-idp", "us-west-2")
|
||||
with pytest.raises(ClientError) as ex:
|
||||
conn.create_user_pool(PoolName=str(uuid.uuid4()), Schema=[attribute])
|
||||
ex.value.response["Error"]["Code"].should.equal("InvalidParameterException")
|
||||
ex.value.response["Error"]["Message"].should.equal(
|
||||
f"You can not change AttributeDataType or set developerOnlyAttribute for standard schema attribute {attribute['Name']}"
|
||||
)
|
||||
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
|
||||
|
||||
@mock_cognitoidp
|
||||
def test_create_user_pool_attribute_with_schema():
|
||||
conn = boto3.client("cognito-idp", "us-west-2")
|
||||
res = conn.create_user_pool(
|
||||
PoolName=str(uuid.uuid4()),
|
||||
Schema=[
|
||||
{
|
||||
"Name": "string",
|
||||
"AttributeDataType": "String",
|
||||
"NumberAttributeConstraints": {"MinValue": "10", "MaxValue": "20"},
|
||||
"StringAttributeConstraints": {"MinLength": "10", "MaxLength": "20"},
|
||||
},
|
||||
{
|
||||
"Name": "number",
|
||||
"AttributeDataType": "Number",
|
||||
"NumberAttributeConstraints": {"MinValue": "10", "MaxValue": "20"},
|
||||
"StringAttributeConstraints": {"MinLength": "10", "MaxLength": "20"},
|
||||
},
|
||||
{
|
||||
"Name": "boolean",
|
||||
"AttributeDataType": "Boolean",
|
||||
"NumberAttributeConstraints": {"MinValue": "10", "MaxValue": "20"},
|
||||
"StringAttributeConstraints": {"MinLength": "10", "MaxLength": "20"},
|
||||
},
|
||||
],
|
||||
)
|
||||
string_attribute = next(
|
||||
attr
|
||||
for attr in res["UserPool"]["SchemaAttributes"]
|
||||
if attr["Name"] == "custom:string"
|
||||
)
|
||||
string_attribute["StringAttributeConstraints"].should.equal(
|
||||
{"MinLength": "10", "MaxLength": "20"}
|
||||
)
|
||||
string_attribute.get("NumberAttributeConstraints").should.be.none
|
||||
|
||||
number_attribute = next(
|
||||
attr
|
||||
for attr in res["UserPool"]["SchemaAttributes"]
|
||||
if attr["Name"] == "custom:number"
|
||||
)
|
||||
number_attribute["NumberAttributeConstraints"].should.equal(
|
||||
{"MinValue": "10", "MaxValue": "20"}
|
||||
)
|
||||
number_attribute.get("StringAttributeConstraints").should.be.none
|
||||
|
||||
boolean_attribute = next(
|
||||
attr
|
||||
for attr in res["UserPool"]["SchemaAttributes"]
|
||||
if attr["Name"] == "custom:boolean"
|
||||
)
|
||||
boolean_attribute.get("NumberAttributeConstraints").should.be.none
|
||||
boolean_attribute.get("StringAttributeConstraints").should.be.none
|
||||
|
||||
|
||||
@mock_cognitoidp
|
||||
def test_create_user_pool_attribute_partial_schema():
|
||||
conn = boto3.client("cognito-idp", "us-west-2")
|
||||
res = conn.create_user_pool(
|
||||
PoolName=str(uuid.uuid4()),
|
||||
Schema=[
|
||||
{
|
||||
"Name": "string_no_min",
|
||||
"AttributeDataType": "String",
|
||||
"StringAttributeConstraints": {"MaxLength": "10"},
|
||||
},
|
||||
{
|
||||
"Name": "string_no_max",
|
||||
"AttributeDataType": "String",
|
||||
"StringAttributeConstraints": {"MinLength": "10"},
|
||||
},
|
||||
{
|
||||
"Name": "number_no_min",
|
||||
"AttributeDataType": "Number",
|
||||
"NumberAttributeConstraints": {"MaxValue": "10"},
|
||||
},
|
||||
{
|
||||
"Name": "number_no_max",
|
||||
"AttributeDataType": "Number",
|
||||
"NumberAttributeConstraints": {"MinValue": "10"},
|
||||
},
|
||||
],
|
||||
)
|
||||
string_no_min = next(
|
||||
attr
|
||||
for attr in res["UserPool"]["SchemaAttributes"]
|
||||
if attr["Name"] == "custom:string_no_min"
|
||||
)
|
||||
string_no_max = next(
|
||||
attr
|
||||
for attr in res["UserPool"]["SchemaAttributes"]
|
||||
if attr["Name"] == "custom:string_no_max"
|
||||
)
|
||||
number_no_min = next(
|
||||
attr
|
||||
for attr in res["UserPool"]["SchemaAttributes"]
|
||||
if attr["Name"] == "custom:number_no_min"
|
||||
)
|
||||
number_no_max = next(
|
||||
attr
|
||||
for attr in res["UserPool"]["SchemaAttributes"]
|
||||
if attr["Name"] == "custom:number_no_max"
|
||||
)
|
||||
|
||||
string_no_min["StringAttributeConstraints"]["MaxLength"].should.equal("10")
|
||||
string_no_min["StringAttributeConstraints"].get("MinLength", None).should.be.none
|
||||
string_no_max["StringAttributeConstraints"]["MinLength"].should.equal("10")
|
||||
string_no_max["StringAttributeConstraints"].get("MaxLength", None).should.be.none
|
||||
number_no_min["NumberAttributeConstraints"]["MaxValue"].should.equal("10")
|
||||
number_no_min["NumberAttributeConstraints"].get("MinValue", None).should.be.none
|
||||
number_no_max["NumberAttributeConstraints"]["MinValue"].should.equal("10")
|
||||
number_no_max["NumberAttributeConstraints"].get("MaxValue", None).should.be.none
|
||||
|
||||
|
||||
@mock_cognitoidp
|
||||
@pytest.mark.parametrize(
|
||||
("constraint_type", "attribute"),
|
||||
[
|
||||
(
|
||||
"StringAttributeConstraints",
|
||||
{
|
||||
"Name": "email",
|
||||
"AttributeDataType": "String",
|
||||
"StringAttributeConstraints": {"MinLength": "invalid_value"},
|
||||
},
|
||||
),
|
||||
(
|
||||
"StringAttributeConstraints",
|
||||
{
|
||||
"Name": "email",
|
||||
"AttributeDataType": "String",
|
||||
"StringAttributeConstraints": {"MaxLength": "invalid_value"},
|
||||
},
|
||||
),
|
||||
(
|
||||
"NumberAttributeConstraints",
|
||||
{
|
||||
"Name": "updated_at",
|
||||
"AttributeDataType": "Number",
|
||||
"NumberAttributeConstraints": {"MaxValue": "invalid_value"},
|
||||
},
|
||||
),
|
||||
(
|
||||
"NumberAttributeConstraints",
|
||||
{
|
||||
"Name": "updated_at",
|
||||
"AttributeDataType": "Number",
|
||||
"NumberAttributeConstraints": {"MinValue": "invalid_value"},
|
||||
},
|
||||
),
|
||||
],
|
||||
ids=[
|
||||
"invalid_min_length",
|
||||
"invalid_max_length",
|
||||
"invalid_max_value",
|
||||
"invalid_min_value",
|
||||
],
|
||||
)
|
||||
def test_create_user_pool_invalid_schema_values(constraint_type, attribute):
|
||||
conn = boto3.client("cognito-idp", "us-west-2")
|
||||
with pytest.raises(ClientError) as ex:
|
||||
conn.create_user_pool(PoolName=str(uuid.uuid4()), Schema=[attribute])
|
||||
ex.value.response["Error"]["Code"].should.equal("InvalidParameterException")
|
||||
ex.value.response["Error"]["Message"].should.equal(
|
||||
f"Invalid {constraint_type} for schema attribute {attribute['Name']}"
|
||||
)
|
||||
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
|
||||
|
||||
@mock_cognitoidp
|
||||
@pytest.mark.parametrize(
|
||||
"attribute",
|
||||
[
|
||||
{
|
||||
"Name": "email",
|
||||
"AttributeDataType": "String",
|
||||
"StringAttributeConstraints": {"MinLength": "2049"},
|
||||
},
|
||||
{
|
||||
"Name": "email",
|
||||
"AttributeDataType": "String",
|
||||
"StringAttributeConstraints": {"MaxLength": "2049"},
|
||||
},
|
||||
],
|
||||
ids=["invalid_min_length", "invalid_max_length"],
|
||||
)
|
||||
def test_create_user_pool_string_schema_max_length_over_2048(attribute):
|
||||
conn = boto3.client("cognito-idp", "us-west-2")
|
||||
with pytest.raises(ClientError) as ex:
|
||||
conn.create_user_pool(PoolName=str(uuid.uuid4()), Schema=[attribute])
|
||||
ex.value.response["Error"]["Code"].should.equal("InvalidParameterException")
|
||||
ex.value.response["Error"]["Message"].should.equal(
|
||||
f"user.{attribute['Name']}: String attributes cannot have a length of more than 2048"
|
||||
)
|
||||
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
|
||||
|
||||
@mock_cognitoidp
|
||||
def test_create_user_pool_string_schema_min_bigger_than_max():
|
||||
conn = boto3.client("cognito-idp", "us-west-2")
|
||||
with pytest.raises(ClientError) as ex:
|
||||
conn.create_user_pool(
|
||||
PoolName=str(uuid.uuid4()),
|
||||
Schema=[
|
||||
{
|
||||
"Name": "email",
|
||||
"AttributeDataType": "String",
|
||||
"StringAttributeConstraints": {"MinLength": "2", "MaxLength": "1"},
|
||||
}
|
||||
],
|
||||
)
|
||||
ex.value.response["Error"]["Code"].should.equal("InvalidParameterException")
|
||||
ex.value.response["Error"]["Message"].should.equal(
|
||||
f"user.email: Max length cannot be less than min length."
|
||||
)
|
||||
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
|
||||
|
||||
@mock_cognitoidp
|
||||
def test_create_user_pool_number_schema_min_bigger_than_max():
|
||||
conn = boto3.client("cognito-idp", "us-west-2")
|
||||
with pytest.raises(ClientError) as ex:
|
||||
conn.create_user_pool(
|
||||
PoolName=str(uuid.uuid4()),
|
||||
Schema=[
|
||||
{
|
||||
"Name": "updated_at",
|
||||
"AttributeDataType": "Number",
|
||||
"NumberAttributeConstraints": {"MinValue": "2", "MaxValue": "1"},
|
||||
}
|
||||
],
|
||||
)
|
||||
ex.value.response["Error"]["Code"].should.equal("InvalidParameterException")
|
||||
ex.value.response["Error"]["Message"].should.equal(
|
||||
f"user.updated_at: Max value cannot be less than min value."
|
||||
)
|
||||
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
|
||||
|
||||
@mock_cognitoidp
|
||||
def test_list_user_pools():
|
||||
conn = boto3.client("cognito-idp", "us-west-2")
|
||||
|
Loading…
Reference in New Issue
Block a user