packages/aws-cdk-lib/aws-cognito-identitypool/lib/identitypool.ts (294 lines of code) (raw):
import { Construct } from 'constructs';
import { IUserPoolAuthenticationProvider } from './identitypool-user-pool-authentication-provider';
import { CfnIdentityPool, CfnIdentityPoolRoleAttachment, IUserPool, IUserPoolClient } from '../../aws-cognito';
import { IOpenIdConnectProvider, ISamlProvider, Role, FederatedPrincipal, IRole } from '../../aws-iam';
import { Resource, IResource, Stack, ArnFormat, Lazy, Token, ValidationError, UnscopedValidationError } from '../../core';
import { addConstructMetadata, MethodMetadata } from '../../core/lib/metadata-resource';
/**
* Represents a Cognito Identity Pool
*/
export interface IIdentityPool extends IResource {
/**
* The ID of the Identity Pool in the format REGION:GUID
* @attribute
*/
readonly identityPoolId: string;
/**
* The ARN of the Identity Pool
* @attribute
*/
readonly identityPoolArn: string;
/**
* Name of the Identity Pool
* @attribute
*/
readonly identityPoolName: string;
}
/**
* Props for the Identity Pool construct
*/
export interface IdentityPoolProps {
/**
* The name of the Identity Pool
* @default - Automatically generated name by CloudFormation at deploy time
*/
readonly identityPoolName?: string;
/**
* The default Role to be assumed by authenticated users
* @default - A default authenticated Role will be added
*/
readonly authenticatedRole?: IRole;
/**
* The default Role to be assumed by unauthenticated users
* @default - A default unauthenticated Role will be added
*/
readonly unauthenticatedRole?: IRole;
/**
* Whether the Identity Pool supports unauthenticated logins
* @default - false
*/
readonly allowUnauthenticatedIdentities?: boolean;
/**
* Rules for mapping roles to users
* @default - no role mappings
*/
readonly roleMappings?: IdentityPoolRoleMapping[];
/**
* Enables the Basic (Classic) authentication flow
* @default - Classic Flow not allowed
*/
readonly allowClassicFlow?: boolean;
/**
* Authentication Providers for using in Identity Pool
* @default - No Authentication Providers passed directly to Identity Pool
*/
readonly authenticationProviders?: IdentityPoolAuthenticationProviders;
}
/**
* Types of Identity Pool Login Providers
*/
export enum IdentityPoolProviderType {
/** Facebook provider type */
FACEBOOK = 'Facebook',
/** Google provider type */
GOOGLE = 'Google',
/** Amazon provider type */
AMAZON = 'Amazon',
/** Apple provider type */
APPLE = 'Apple',
/** Twitter provider type */
TWITTER = 'Twitter',
/** Open Id provider type */
OPEN_ID = 'OpenId',
/** Saml provider type */
SAML = 'Saml',
/** User Pool provider type */
USER_POOL = 'UserPool',
/** Custom provider type */
CUSTOM = 'Custom',
}
/**
* Keys for Login Providers - each correspond to the client IDs of their respective federation Identity Providers
*/
export class IdentityPoolProviderUrl {
/** Facebook Provider url */
public static readonly FACEBOOK = new IdentityPoolProviderUrl(IdentityPoolProviderType.FACEBOOK, 'graph.facebook.com');
/** Google Provider url */
public static readonly GOOGLE = new IdentityPoolProviderUrl(IdentityPoolProviderType.GOOGLE, 'accounts.google.com');
/** Amazon Provider url */
public static readonly AMAZON = new IdentityPoolProviderUrl(IdentityPoolProviderType.AMAZON, 'www.amazon.com');
/** Apple Provider url */
public static readonly APPLE = new IdentityPoolProviderUrl(IdentityPoolProviderType.APPLE, 'appleid.apple.com');
/** Twitter Provider url */
public static readonly TWITTER = new IdentityPoolProviderUrl(IdentityPoolProviderType.TWITTER, 'api.twitter.com');
/** OpenId Provider url */
public static openId(url: string): IdentityPoolProviderUrl {
return new IdentityPoolProviderUrl(IdentityPoolProviderType.OPEN_ID, url);
}
/** Saml Provider url */
public static saml(url: string): IdentityPoolProviderUrl {
return new IdentityPoolProviderUrl(IdentityPoolProviderType.SAML, url);
}
/** User Pool Provider Url */
public static userPool(userPool: IUserPool, userPoolClient: IUserPoolClient): IdentityPoolProviderUrl {
const url = `${userPool.userPoolProviderName}:${userPoolClient.userPoolClientId}`;
return new IdentityPoolProviderUrl(IdentityPoolProviderType.USER_POOL, url);
}
/** Custom Provider url */
public static custom(url: string): IdentityPoolProviderUrl {
return new IdentityPoolProviderUrl(IdentityPoolProviderType.CUSTOM, url);
}
constructor(
/**
* The type of Identity Pool Provider
*/
public readonly type: IdentityPoolProviderType,
/**
* The value of the Identity Pool Provider
*/
public readonly value: string,
) {}
}
/**
* Login Provider for identity federation using Amazon credentials
*/
export interface IdentityPoolAmazonLoginProvider {
/**
* App ID for Amazon identity federation
*/
readonly appId: string;
}
/**
* Login Provider for identity federation using Facebook credentials
*/
export interface IdentityPoolFacebookLoginProvider {
/**
* App ID for Facebook identity federation
*/
readonly appId: string;
}
/**
* Login Provider for identity federation using Apple credentials
*/
export interface IdentityPoolAppleLoginProvider {
/**
* Services ID for Apple identity federation
*/
readonly servicesId: string;
}
/**
* Login Provider for identity federation using Google credentials
*/
export interface IdentityPoolGoogleLoginProvider {
/**
* Client ID for Google identity federation
*/
readonly clientId: string;
}
/**
* Login Provider for identity federation using Twitter credentials
*/
export interface IdentityPoolTwitterLoginProvider {
/**
* Consumer key for Twitter identity federation
*/
readonly consumerKey: string;
/**
* Consumer secret for identity federation
*/
readonly consumerSecret: string;
}
/**
* External Authentication Providers for usage in Identity Pool.
* @see https://docs.aws.amazon.com/cognito/latest/developerguide/external-identity-providers.html
*/
export interface IdentityPoolAuthenticationProviders {
/**
* The Facebook Authentication Provider associated with this Identity Pool
* @default - No Facebook Authentication Provider used without OpenIdConnect or a User Pool
*/
readonly facebook?: IdentityPoolFacebookLoginProvider;
/**
* The Google Authentication Provider associated with this Identity Pool
* @default - No Google Authentication Provider used without OpenIdConnect or a User Pool
*/
readonly google?: IdentityPoolGoogleLoginProvider;
/**
* The Amazon Authentication Provider associated with this Identity Pool
* @default - No Amazon Authentication Provider used without OpenIdConnect or a User Pool
*/
readonly amazon?: IdentityPoolAmazonLoginProvider;
/**
* The Apple Authentication Provider associated with this Identity Pool
* @default - No Apple Authentication Provider used without OpenIdConnect or a User Pool
*/
readonly apple?: IdentityPoolAppleLoginProvider;
/**
* The Twitter Authentication Provider associated with this Identity Pool
* @default - No Twitter Authentication Provider used without OpenIdConnect or a User Pool
*/
readonly twitter?: IdentityPoolTwitterLoginProvider;
/**
* The User Pool Authentication Providers associated with this Identity Pool
* @default - no User Pools associated
*/
readonly userPools?: IUserPoolAuthenticationProvider[];
/**
* The OpenIdConnect Provider associated with this Identity Pool
* @default - no OpenIdConnectProvider
*/
readonly openIdConnectProviders?: IOpenIdConnectProvider[];
/**
* The Security Assertion Markup Language provider associated with this Identity Pool
* @default - no SamlProvider
*/
readonly samlProviders?: ISamlProvider[];
/**
* The developer provider name to associate with this Identity Pool
* @default - no custom provider
*/
readonly customProvider?: string;
}
/**
* Map roles to users in the Identity Pool based on claims from the Identity Provider
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-identitypoolroleattachment.html
*/
export interface IdentityPoolRoleMapping {
/**
* The url of the Provider for which the role is mapped
*/
readonly providerUrl: IdentityPoolProviderUrl;
/**
* The key used for the role mapping in the role mapping hash. Required if the providerUrl is a token.
* @default - The provided providerUrl
*/
readonly mappingKey?: string;
/**
* If true then mapped roles must be passed through the cognito:roles or cognito:preferred_role claims from Identity Provider.
* @see https://docs.aws.amazon.com/cognito/latest/developerguide/role-based-access-control.html#using-tokens-to-assign-roles-to-users
*
* @default false
*/
readonly useToken?: boolean;
/**
* Allow for role assumption when results of role mapping are ambiguous
* @default false - Ambiguous role resolutions will lead to requester being denied
*/
readonly resolveAmbiguousRoles?: boolean;
/**
* The claim and value that must be matched in order to assume the role. Required if useToken is false
* @default - No role mapping rule
*/
readonly rules?: RoleMappingRule[];
}
/**
* Types of matches allowed for role mapping
*/
export enum RoleMappingMatchType {
/**
* The claim from the token must equal the given value in order for a match
*/
EQUALS = 'Equals',
/**
* The claim from the token must contain the given value in order for a match
*/
CONTAINS = 'Contains',
/**
* The claim from the token must start with the given value in order for a match
*/
STARTS_WITH = 'StartsWith',
/**
* The claim from the token must not equal the given value in order for a match
*/
NOTEQUAL = 'NotEqual',
}
/**
* Represents an Identity Pool Role Attachment role mapping rule
*/
export interface RoleMappingRule {
/**
* The key sent in the token by the federated Identity Provider
*/
readonly claim: string;
/**
* The role to be assumed when the claim value is matched
*/
readonly mappedRole: IRole;
/**
* The value of the claim that must be matched
*/
readonly claimValue: string;
/**
* How to match with the claim value
*
* @default RoleMappingMatchType.EQUALS
*/
readonly matchType?: RoleMappingMatchType;
}
/**
* Define a Cognito Identity Pool
*
* @resource AWS::Cognito::IdentityPool
*/
export class IdentityPool extends Resource implements IIdentityPool {
/**
* Import an existing Identity Pool from its ID
*/
public static fromIdentityPoolId(scope: Construct, id: string, identityPoolId: string): IIdentityPool {
const identityPoolArn = Stack.of(scope).formatArn({
service: 'cognito-identity',
resource: 'identitypool',
resourceName: identityPoolId,
arnFormat: ArnFormat.SLASH_RESOURCE_NAME,
});
return IdentityPool.fromIdentityPoolArn(scope, id, identityPoolArn);
}
/**
* Import an existing Identity Pool from its ARN
*/
public static fromIdentityPoolArn(scope: Construct, id: string, identityPoolArn: string): IIdentityPool {
const pool = Stack.of(scope).splitArn(identityPoolArn, ArnFormat.SLASH_RESOURCE_NAME);
const res = pool.resourceName || '';
if (!res) {
throw new ValidationError('Invalid Identity Pool ARN', scope);
}
if (!Token.isUnresolved(res)) {
const idParts = res.split(':');
if (!(idParts.length === 2)) {
throw new ValidationError('Invalid Identity Pool Id: Identity Pool Ids must follow the format <region>:<id>', scope);
}
if (!Token.isUnresolved(pool.region) && idParts[0] !== pool.region) {
throw new ValidationError('Invalid Identity Pool Id: Region in Identity Pool Id must match stack region', scope);
}
}
class ImportedIdentityPool extends Resource implements IIdentityPool {
public readonly identityPoolId = res;
public readonly identityPoolArn = identityPoolArn;
public readonly identityPoolName: string;
constructor() {
super(scope, id, {
account: pool.account,
region: pool.region,
});
this.identityPoolName = this.physicalName;
}
}
return new ImportedIdentityPool();
}
/**
* The ID of the Identity Pool in the format REGION:GUID
* @attribute
*/
public readonly identityPoolId: string;
/**
* The ARN of the Identity Pool
* @attribute
*/
public readonly identityPoolArn: string;
/**
* The name of the Identity Pool
* @attribute
*/
public readonly identityPoolName: string;
/**
* Default Role for authenticated users
*/
public readonly authenticatedRole: IRole;
/**
* Default Role for unauthenticated users
*/
public readonly unauthenticatedRole: IRole;
/**
* Role Provider for the default Role for authenticated users
*/
public readonly roleAttachment: CfnIdentityPoolRoleAttachment;
/**
* List of Identity Providers added in constructor for use with property overrides
*/
private cognitoIdentityProviders: CfnIdentityPool.CognitoIdentityProviderProperty[] = [];
constructor(scope: Construct, id: string, props: IdentityPoolProps = {}) {
super(scope, id, {
physicalName: props.identityPoolName,
});
// Enhanced CDK Analytics Telemetry
addConstructMetadata(this, props);
const authProviders: IdentityPoolAuthenticationProviders = props.authenticationProviders || {};
const providers = authProviders.userPools ? authProviders.userPools.map(userPool => userPool.bind(this, this)) : undefined;
if (providers && providers.length) this.cognitoIdentityProviders = providers;
const openIdConnectProviderArns = authProviders.openIdConnectProviders ?
authProviders.openIdConnectProviders.map(openIdProvider =>
openIdProvider.openIdConnectProviderArn,
) : undefined;
const samlProviderArns = authProviders.samlProviders ?
authProviders.samlProviders.map(samlProvider =>
samlProvider.samlProviderArn,
) : undefined;
let supportedLoginProviders:any = {};
if (authProviders.amazon) supportedLoginProviders[IdentityPoolProviderUrl.AMAZON.value] = authProviders.amazon.appId;
if (authProviders.facebook) supportedLoginProviders[IdentityPoolProviderUrl.FACEBOOK.value] = authProviders.facebook.appId;
if (authProviders.google) supportedLoginProviders[IdentityPoolProviderUrl.GOOGLE.value] = authProviders.google.clientId;
if (authProviders.apple) supportedLoginProviders[IdentityPoolProviderUrl.APPLE.value] = authProviders.apple.servicesId;
if (authProviders.twitter) supportedLoginProviders[IdentityPoolProviderUrl.TWITTER.value] = `${authProviders.twitter.consumerKey};${authProviders.twitter.consumerSecret}`;
if (!Object.keys(supportedLoginProviders).length) supportedLoginProviders = undefined;
const cfnIdentityPool = new CfnIdentityPool(this, 'Resource', {
allowUnauthenticatedIdentities: props.allowUnauthenticatedIdentities ? true : false,
allowClassicFlow: props.allowClassicFlow,
identityPoolName: this.physicalName,
developerProviderName: authProviders.customProvider,
openIdConnectProviderArns,
samlProviderArns,
supportedLoginProviders,
cognitoIdentityProviders: Lazy.any({ produce: () => this.cognitoIdentityProviders }),
});
this.identityPoolName = cfnIdentityPool.attrName;
this.identityPoolId = cfnIdentityPool.ref;
this.identityPoolArn = Stack.of(scope).formatArn({
service: 'cognito-identity',
resource: 'identitypool',
resourceName: this.identityPoolId,
arnFormat: ArnFormat.SLASH_RESOURCE_NAME,
});
this.authenticatedRole = props.authenticatedRole ? props.authenticatedRole : this.configureDefaultRole('Authenticated');
this.unauthenticatedRole = props.unauthenticatedRole ? props.unauthenticatedRole : this.configureDefaultRole('Unauthenticated');
// Set up Role Attachment
this.roleAttachment = new IdentityPoolRoleAttachment(this, 'DefaultRoleAttachment', {
identityPool: this,
authenticatedRole: this.authenticatedRole,
unauthenticatedRole: this.unauthenticatedRole,
roleMappings: props.roleMappings,
}).resource;
Array.isArray(this.roleAttachment);
}
/**
* Add a User Pool to the Identity Pool and configure the User Pool client to handle identities
*/
@MethodMetadata()
public addUserPoolAuthentication(userPool: IUserPoolAuthenticationProvider): void {
const providers = userPool.bind(this, this);
this.cognitoIdentityProviders = this.cognitoIdentityProviders.concat(providers);
}
/**
* Configure default Roles for Identity Pool
*/
private configureDefaultRole(type: string): IRole {
const assumedBy = this.configureDefaultGrantPrincipal(type.toLowerCase());
const role = new Role(this, `${type}Role`, {
description: `Default ${type} Role for Identity Pool ${this.identityPoolName}`,
assumedBy,
});
return role;
}
private configureDefaultGrantPrincipal(type: string) {
return new FederatedPrincipal('cognito-identity.amazonaws.com', {
'StringEquals': {
'cognito-identity.amazonaws.com:aud': this.identityPoolId,
},
'ForAnyValue:StringLike': {
'cognito-identity.amazonaws.com:amr': type,
},
}, 'sts:AssumeRoleWithWebIdentity');
}
}
/**
* Represents an Identity Pool Role Attachment
*/
interface IIdentityPoolRoleAttachment extends IResource {
/**
* ID of the Attachment's underlying Identity Pool
*/
readonly identityPoolId: string;
}
/**
* Props for an Identity Pool Role Attachment
*/
interface IdentityPoolRoleAttachmentProps {
/**
* ID of the Attachment's underlying Identity Pool
*/
readonly identityPool: IIdentityPool;
/**
* Default authenticated (User) Role
* @default - No default authenticated Role will be added
*/
readonly authenticatedRole?: IRole;
/**
* Default unauthenticated (Guest) Role
* @default - No default unauthenticated Role will be added
*/
readonly unauthenticatedRole?: IRole;
/**
* Rules for mapping roles to users
* @default - No role mappings
*/
readonly roleMappings?: IdentityPoolRoleMapping[];
}
/**
* Defines an Identity Pool Role Attachment
*
* @resource AWS::Cognito::IdentityPoolRoleAttachment
*/
class IdentityPoolRoleAttachment extends Resource implements IIdentityPoolRoleAttachment {
/**
* ID of the underlying Identity Pool
*/
public readonly identityPoolId: string;
/**
* The Identity Pool Role Attachment CFN resource.
*/
public readonly resource: CfnIdentityPoolRoleAttachment;
constructor(scope: Construct, id: string, props: IdentityPoolRoleAttachmentProps) {
super(scope, id);
// Enhanced CDK Analytics Telemetry
addConstructMetadata(this, props);
this.identityPoolId = props.identityPool.identityPoolId;
const mappings = props.roleMappings || [];
let roles: any = undefined, roleMappings: any = undefined;
if (props.authenticatedRole || props.unauthenticatedRole) {
roles = {};
if (props.authenticatedRole) roles.authenticated = props.authenticatedRole.roleArn;
if (props.unauthenticatedRole) roles.unauthenticated = props.unauthenticatedRole.roleArn;
}
if (mappings) {
roleMappings = this.configureRoleMappings(...mappings);
}
this.resource = new CfnIdentityPoolRoleAttachment(this, 'Resource', {
identityPoolId: this.identityPoolId,
roles,
roleMappings,
});
}
/**
* Configures role mappings for the Identity Pool Role Attachment
*/
private configureRoleMappings(
...props: IdentityPoolRoleMapping[]
): { [name:string]: CfnIdentityPoolRoleAttachment.RoleMappingProperty } | undefined {
if (!props || !props.length) return undefined;
return props.reduce((acc, prop) => {
let mappingKey;
if (prop.mappingKey) {
mappingKey = prop.mappingKey;
} else {
const providerUrl = prop.providerUrl.value;
if (Token.isUnresolved(providerUrl)) {
throw new UnscopedValidationError('mappingKey must be provided when providerUrl.value is a token');
}
mappingKey = providerUrl;
}
let roleMapping: any = {
ambiguousRoleResolution: prop.resolveAmbiguousRoles ? 'AuthenticatedRole' : 'Deny',
type: prop.useToken ? 'Token' : 'Rules',
identityProvider: prop.providerUrl.value,
};
if (roleMapping.type === 'Rules') {
if (!prop.rules) {
throw new UnscopedValidationError('IdentityPoolRoleMapping.rules is required when useToken is false');
}
roleMapping.rulesConfiguration = {
rules: prop.rules.map(rule => {
return {
claim: rule.claim,
value: rule.claimValue,
matchType: rule.matchType || RoleMappingMatchType.EQUALS,
roleArn: rule.mappedRole.roleArn,
};
}),
};
}
acc[mappingKey] = roleMapping;
return acc;
}, {} as { [name:string]: CfnIdentityPoolRoleAttachment.RoleMappingProperty });
}
}