use super::*;
impl ResourceProvisioner {
pub(super) fn create_cognito_user_pool(
&self,
resource: &ResourceDefinition,
) -> Result<ProvisionResult, String> {
let props = &resource.properties;
let pool_name = props
.get("PoolName")
.and_then(|v| v.as_str())
.unwrap_or(&resource.logical_id)
.to_string();
let pool_id = format!(
"{}_{}",
self.region,
Uuid::new_v4()
.simple()
.to_string()
.chars()
.take(9)
.collect::<String>()
);
let arn = format!(
"arn:aws:cognito-idp:{}:{}:userpool/{}",
self.region, self.account_id, pool_id
);
let now = Utc::now();
let password_policy = parse_cognito_password_policy(props.get("Policies"));
let auto_verified = parse_cognito_string_array(props.get("AutoVerifiedAttributes"));
let username_attributes = props
.get("UsernameAttributes")
.and_then(|v| v.as_array())
.map(|_| parse_cognito_string_array(props.get("UsernameAttributes")));
let alias_attributes = props
.get("AliasAttributes")
.and_then(|v| v.as_array())
.map(|_| parse_cognito_string_array(props.get("AliasAttributes")));
let mut schema_attributes = default_schema_attributes();
if let Some(arr) = props.get("Schema").and_then(|v| v.as_array()) {
for attr in arr {
if let Some(parsed) = parse_cognito_schema_attribute(attr) {
if !schema_attributes.iter().any(|a| a.name == parsed.name) {
schema_attributes.push(parsed);
}
}
}
}
let mfa_configuration = props
.get("MfaConfiguration")
.and_then(|v| v.as_str())
.unwrap_or("OFF")
.to_string();
let user_pool_tier = props
.get("UserPoolTier")
.and_then(|v| v.as_str())
.unwrap_or("ESSENTIALS")
.to_string();
let deletion_protection = props
.get("DeletionProtection")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let user_pool_tags = parse_cognito_tags(props.get("UserPoolTags"));
let email_configuration =
parse_cognito_email_configuration(props.get("EmailConfiguration"));
let sms_configuration = parse_cognito_sms_configuration(props.get("SmsConfiguration"));
let admin_create_user_config =
parse_cognito_admin_create_user_config(props.get("AdminCreateUserConfig"));
let account_recovery_setting =
parse_cognito_account_recovery(props.get("AccountRecoverySetting"));
let signing = fakecloud_cognito::jwt::generate_pool_signing_key();
let signing_key_pem = signing.private_key_pem;
let signing_kid = signing.kid;
let pool = UserPool {
id: pool_id.clone(),
name: pool_name,
arn: arn.clone(),
status: "ACTIVE".to_string(),
creation_date: now,
last_modified_date: now,
policies: PoolPolicies {
password_policy,
sign_in_policy: SignInPolicy {
allowed_first_auth_factors: vec!["PASSWORD".to_string()],
},
},
auto_verified_attributes: auto_verified,
username_attributes,
alias_attributes,
schema_attributes,
lambda_config: None,
mfa_configuration,
email_configuration,
sms_configuration,
admin_create_user_config,
user_pool_tags,
account_recovery_setting,
deletion_protection,
estimated_number_of_users: 0,
software_token_mfa_configuration: None,
sms_mfa_configuration: None,
user_pool_tier,
verification_message_template: None,
signing_key_pem: Some(signing_key_pem),
signing_kid: Some(signing_kid),
email_verification_message: None,
email_verification_subject: None,
sms_verification_message: None,
sms_authentication_message: None,
device_configuration: None,
user_attribute_update_settings: None,
user_pool_add_ons: None,
username_configuration: None,
};
let mut accounts = self.cognito_state.write();
let state = accounts.get_or_create(&self.account_id);
state.user_pools.insert(pool_id.clone(), pool);
let provider_name = format!("cognito-idp.{}.amazonaws.com/{}", self.region, pool_id);
let provider_url = format!("https://{provider_name}");
Ok(ProvisionResult::new(pool_id.clone())
.with("Arn", arn)
.with("ProviderName", provider_name)
.with("ProviderURL", provider_url)
.with("UserPoolId", pool_id))
}
pub(super) fn delete_cognito_user_pool(&self, physical_id: &str) -> Result<(), String> {
let mut accounts = self.cognito_state.write();
let state = accounts.get_or_create(&self.account_id);
state.user_pools.remove(physical_id);
state
.user_pool_clients
.retain(|_, c| c.user_pool_id != physical_id);
state.users.remove(physical_id);
state.groups.remove(physical_id);
state.user_groups.remove(physical_id);
state.identity_providers.remove(physical_id);
state.resource_servers.remove(physical_id);
state.import_jobs.remove(physical_id);
state.domains.retain(|_, d| d.user_pool_id != physical_id);
Ok(())
}
pub(super) fn create_cognito_user_pool_client(
&self,
resource: &ResourceDefinition,
) -> Result<ProvisionResult, String> {
let props = &resource.properties;
let pool_id = props
.get("UserPoolId")
.and_then(|v| v.as_str())
.ok_or_else(|| "UserPoolId is required".to_string())?
.to_string();
let client_name = props
.get("ClientName")
.and_then(|v| v.as_str())
.unwrap_or(&resource.logical_id)
.to_string();
let mut accounts = self.cognito_state.write();
let state = accounts.get_or_create(&self.account_id);
if !state.user_pools.contains_key(&pool_id) {
return Err(format!(
"User pool {pool_id} does not exist yet — retry once it has been provisioned"
));
}
let client_id: String = format!("{}{}", Uuid::new_v4().simple(), Uuid::new_v4().simple())
.chars()
.filter(|c| c.is_ascii_alphanumeric())
.take(26)
.collect::<String>()
.to_lowercase();
let generate_secret = props
.get("GenerateSecret")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let client_secret = if generate_secret {
use base64::Engine;
let mut bytes = Vec::with_capacity(48);
for _ in 0..3 {
bytes.extend_from_slice(Uuid::new_v4().as_bytes());
}
Some(
base64::engine::general_purpose::STANDARD
.encode(&bytes)
.chars()
.take(51)
.collect(),
)
} else {
None
};
let now = Utc::now();
let client = UserPoolClient {
client_id: client_id.clone(),
client_name,
user_pool_id: pool_id.clone(),
client_secret: client_secret.clone(),
explicit_auth_flows: parse_cognito_string_array(props.get("ExplicitAuthFlows")),
token_validity_units: None,
access_token_validity: props.get("AccessTokenValidity").and_then(|v| v.as_i64()),
id_token_validity: props.get("IdTokenValidity").and_then(|v| v.as_i64()),
refresh_token_validity: Some(
props
.get("RefreshTokenValidity")
.and_then(|v| v.as_i64())
.unwrap_or(30),
),
callback_urls: parse_cognito_string_array(props.get("CallbackURLs")),
logout_urls: parse_cognito_string_array(props.get("LogoutURLs")),
supported_identity_providers: parse_cognito_string_array(
props.get("SupportedIdentityProviders"),
),
allowed_o_auth_flows: parse_cognito_string_array(props.get("AllowedOAuthFlows")),
allowed_o_auth_scopes: parse_cognito_string_array(props.get("AllowedOAuthScopes")),
allowed_o_auth_flows_user_pool_client: props
.get("AllowedOAuthFlowsUserPoolClient")
.and_then(|v| v.as_bool())
.unwrap_or(false),
prevent_user_existence_errors: props
.get("PreventUserExistenceErrors")
.and_then(|v| v.as_str())
.map(|s| s.to_string()),
read_attributes: parse_cognito_string_array(props.get("ReadAttributes")),
write_attributes: parse_cognito_string_array(props.get("WriteAttributes")),
creation_date: now,
last_modified_date: now,
enable_token_revocation: props
.get("EnableTokenRevocation")
.and_then(|v| v.as_bool())
.unwrap_or(true),
auth_session_validity: Some(
props
.get("AuthSessionValidity")
.and_then(|v| v.as_i64())
.unwrap_or(3),
),
enable_propagate_additional_user_context_data: props
.get("EnablePropagateAdditionalUserContextData")
.and_then(|v| v.as_bool())
.unwrap_or(false),
client_secrets: Vec::new(),
refresh_token_rotation: None,
};
state.user_pool_clients.insert(client_id.clone(), client);
let mut result = ProvisionResult::new(client_id.clone())
.with("ClientId", client_id.clone())
.with("Name", client_id);
if let Some(secret) = client_secret {
result = result.with("ClientSecret", secret);
}
Ok(result)
}
pub(super) fn delete_cognito_user_pool_client(&self, physical_id: &str) -> Result<(), String> {
let mut accounts = self.cognito_state.write();
let state = accounts.get_or_create(&self.account_id);
state.user_pool_clients.remove(physical_id);
Ok(())
}
pub(super) fn create_cognito_user_pool_domain(
&self,
resource: &ResourceDefinition,
) -> Result<ProvisionResult, String> {
let props = &resource.properties;
let domain = props
.get("Domain")
.and_then(|v| v.as_str())
.ok_or_else(|| "Domain is required".to_string())?
.to_string();
let pool_id = props
.get("UserPoolId")
.and_then(|v| v.as_str())
.ok_or_else(|| "UserPoolId is required".to_string())?
.to_string();
let custom_domain_config = props
.get("CustomDomainConfig")
.and_then(|v| v.as_object())
.and_then(|m| {
m.get("CertificateArn")
.and_then(|v| v.as_str())
.map(|s| CustomDomainConfig {
certificate_arn: s.to_string(),
})
});
let mut accounts = self.cognito_state.write();
let state = accounts.get_or_create(&self.account_id);
if !state.user_pools.contains_key(&pool_id) {
return Err(format!(
"User pool {pool_id} does not exist yet — retry once it has been provisioned"
));
}
if state.domains.contains_key(&domain) {
return Err(format!("Domain {domain} already exists"));
}
state.domains.insert(
domain.clone(),
UserPoolDomain {
user_pool_id: pool_id,
domain: domain.clone(),
status: "ACTIVE".to_string(),
custom_domain_config: custom_domain_config.clone(),
creation_date: Utc::now(),
},
);
let cloudfront_distribution = if custom_domain_config.is_some() {
format!("{domain}.cloudfront.net")
} else {
format!("{domain}.auth.{}.amazoncognito.com", self.region)
};
Ok(ProvisionResult::new(domain.clone())
.with("Domain", domain)
.with("CloudFrontDistribution", cloudfront_distribution))
}
pub(super) fn delete_cognito_user_pool_domain(&self, physical_id: &str) -> Result<(), String> {
let mut accounts = self.cognito_state.write();
let state = accounts.get_or_create(&self.account_id);
state.domains.remove(physical_id);
Ok(())
}
pub(super) fn create_cognito_identity_pool(
&self,
resource: &ResourceDefinition,
) -> Result<ProvisionResult, String> {
let props = &resource.properties;
let identity_pool_name = props
.get("IdentityPoolName")
.and_then(|v| v.as_str())
.unwrap_or(&resource.logical_id)
.to_string();
let allow_unauth = props
.get("AllowUnauthenticatedIdentities")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let allow_classic = props
.get("AllowClassicFlow")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let developer_provider_name = props
.get("DeveloperProviderName")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let cognito_identity_providers = props
.get("CognitoIdentityProviders")
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|p| {
let obj = p.as_object()?;
let provider_name = obj
.get("ProviderName")
.and_then(|v| v.as_str())?
.to_string();
let client_id = obj.get("ClientId").and_then(|v| v.as_str())?.to_string();
let server_side_token_check = obj
.get("ServerSideTokenCheck")
.and_then(|v| v.as_bool())
.unwrap_or(false);
Some(CognitoIdentityProvider {
provider_name,
client_id,
server_side_token_check,
})
})
.collect::<Vec<_>>()
})
.unwrap_or_default();
let open_id_connect_provider_arns =
parse_cognito_string_array(props.get("OpenIdConnectProviderARNs"));
let saml_provider_arns = parse_cognito_string_array(props.get("SamlProviderARNs"));
let supported_login_providers = props
.get("SupportedLoginProviders")
.and_then(|v| v.as_object())
.map(|m| {
m.iter()
.filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
.collect()
})
.unwrap_or_default();
let identity_pool_tags = parse_cognito_tags(props.get("IdentityPoolTags"));
let cognito_streams = props.get("CognitoStreams").cloned();
let push_sync = props.get("PushSync").cloned();
let identity_pool_id = format!("{}:{}", self.region, Uuid::new_v4());
let pool = IdentityPool {
identity_pool_id: identity_pool_id.clone(),
identity_pool_name,
allow_unauthenticated_identities: allow_unauth,
allow_classic_flow: allow_classic,
developer_provider_name,
cognito_identity_providers,
open_id_connect_provider_arns,
saml_provider_arns,
supported_login_providers,
cognito_streams,
push_sync,
identity_pool_tags,
creation_date: Utc::now(),
};
let mut accounts = self.cognito_state.write();
let state = accounts.get_or_create(&self.account_id);
state.identity_pools.insert(identity_pool_id.clone(), pool);
Ok(ProvisionResult::new(identity_pool_id.clone()).with("Name", identity_pool_id))
}
pub(super) fn delete_cognito_identity_pool(&self, physical_id: &str) -> Result<(), String> {
let mut accounts = self.cognito_state.write();
let state = accounts.get_or_create(&self.account_id);
state.identity_pools.remove(physical_id);
state
.identity_pool_role_attachments
.retain(|_, a| a.identity_pool_id != physical_id);
Ok(())
}
pub(super) fn create_cognito_identity_pool_role_attachment(
&self,
resource: &ResourceDefinition,
) -> Result<ProvisionResult, String> {
let props = &resource.properties;
let identity_pool_id = props
.get("IdentityPoolId")
.and_then(|v| v.as_str())
.ok_or_else(|| "IdentityPoolId is required".to_string())?
.to_string();
let mut accounts = self.cognito_state.write();
let state = accounts.get_or_create(&self.account_id);
if !state.identity_pools.contains_key(&identity_pool_id) {
return Err(format!(
"Identity pool {identity_pool_id} does not exist yet — retry once it has been provisioned"
));
}
let roles = props
.get("Roles")
.and_then(|v| v.as_object())
.map(|m| {
m.iter()
.filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
.collect()
})
.unwrap_or_default();
let role_mappings = props
.get("RoleMappings")
.and_then(|v| v.as_object())
.map(|m| m.iter().map(|(k, v)| (k.clone(), v.clone())).collect())
.unwrap_or_default();
let attachment_id = Uuid::new_v4().simple().to_string();
let physical_id = format!("{identity_pool_id}:{attachment_id}");
let attachment = IdentityPoolRoleAttachment {
identity_pool_id: identity_pool_id.clone(),
attachment_id,
roles,
role_mappings,
};
state
.identity_pool_role_attachments
.insert(physical_id.clone(), attachment);
Ok(ProvisionResult::new(physical_id))
}
pub(super) fn delete_cognito_identity_pool_role_attachment(
&self,
physical_id: &str,
) -> Result<(), String> {
let mut accounts = self.cognito_state.write();
let state = accounts.get_or_create(&self.account_id);
state.identity_pool_role_attachments.remove(physical_id);
Ok(())
}
}