Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cognito): add SAML user pool identity provider #21879

Merged
merged 12 commits into from
Sep 9, 2022
Merged
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-cognito/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,7 @@ The following third-party identity providers are currently supported in the CDK
- [Google Login](https://developers.google.com/identity/sign-in/web/sign-in)
- [Sign In With Apple](https://developer.apple.com/sign-in-with-apple/get-started/)
- [OpenID Connect](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-oidc-idp.html)
- [SAML](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-saml-idp.html)

The following code configures a user pool to federate with the third party provider, 'Login with Amazon'. The identity
provider needs to be configured with a set of credentials that the Cognito backend can use to federate with the
Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-cognito/lib/user-pool-idps/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './amazon';
export * from './facebook';
export * from './google';
export * from './oidc';
export * from './saml';
132 changes: 132 additions & 0 deletions packages/@aws-cdk/aws-cognito/lib/user-pool-idps/saml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { Names, Token } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { CfnUserPoolIdentityProvider } from '../cognito.generated';
import { UserPoolIdentityProviderProps } from './base';
import { UserPoolIdentityProviderBase } from './private/user-pool-idp-base';

/**
* Properties to initialize UserPoolIdentityProviderSaml.
*/
export interface UserPoolIdentityProviderSamlProps extends UserPoolIdentityProviderProps {
/**
* The name of the provider. Must be between 3 and 32 characters.
*
* @default - the unique ID of the construct
*/
readonly name?: string;

/**
* Identifiers
*
* Identifiers can be used to redirect users to the correct IdP in multitenant apps.
*
* @default - no identifiers used
*/
readonly identifiers?: string[]

/**
* The SAML metadata.
*/
readonly metadata: UserPoolIdentityProviderSamlMetadata;

/**
* Whether to enable the "Sign-out flow" feature.
*
* @default - false
*/
readonly idpSignout?: boolean;
}

/**
* Metadata types that can be used for a SAML user pool identity provider.
*/
export enum UserPoolIdentityProviderSamlMetadataType {
/** Metadata provided via a URL. */
URL = 'url',

/** Metadata provided via the contents of a file. */
FILE = 'file',
}

/**
* Metadata for a SAML user pool identity provider.
*/
export class UserPoolIdentityProviderSamlMetadata {

/**
* Specify SAML metadata via a URL.
*/
public static url(url: string): UserPoolIdentityProviderSamlMetadata {
return new UserPoolIdentityProviderSamlMetadata(url, UserPoolIdentityProviderSamlMetadataType.URL);
}

/**
* Specify SAML metadata via the contents of a file.
*/
public static file(fileContent: string): UserPoolIdentityProviderSamlMetadata {
return new UserPoolIdentityProviderSamlMetadata(fileContent, UserPoolIdentityProviderSamlMetadataType.FILE);
}

/**
* Construct the metadata for a SAML identity provider.
*
* @param metadataContent A URL hosting SAML metadata, or the content of a file containing SAML metadata.
* @param metadataType The type of metadata, either a URL or file content.
*/
private constructor(public readonly metadataContent: string, public readonly metadataType: UserPoolIdentityProviderSamlMetadataType) {
}
}

/**
* Represents a identity provider that integrates with SAML.
* @resource AWS::Cognito::UserPoolIdentityProvider
*/
export class UserPoolIdentityProviderSaml extends UserPoolIdentityProviderBase {
public readonly providerName: string;

constructor(scope: Construct, id: string, props: UserPoolIdentityProviderSamlProps) {
super(scope, id, props);

this.validateName(props.name);

const { metadataType, metadataContent } = props.metadata;

const resource = new CfnUserPoolIdentityProvider(this, 'Resource', {
userPoolId: props.userPool.userPoolId,
providerName: this.getProviderName(props.name),
providerType: 'SAML',
providerDetails: {
IDPSignout: props.idpSignout ?? false,
MetadataURL: metadataType === UserPoolIdentityProviderSamlMetadataType.URL ? metadataContent : undefined,
MetadataFile: metadataType === UserPoolIdentityProviderSamlMetadataType.FILE ? metadataContent : undefined,
},
idpIdentifiers: props.identifiers,
attributeMapping: super.configureAttributeMapping(),
});

this.providerName = super.getResourceNameAttribute(resource.ref);
}

private getProviderName(name?: string): string {
if (name) {
this.validateName(name);
return name;
}

const uniqueName = Names.uniqueResourceName(this, {
maxLength: 32,
});

if (uniqueName.length < 3) {
return `${uniqueName}saml`;
}

return uniqueName;
}

private validateName(name?: string) {
if (name && !Token.isUnresolved(name) && (name.length < 3 || name.length > 32)) {
throw new Error(`Expected provider name to be between 3 and 32 characters, received ${name} (${name.length} characters)`);
}
}
}
4 changes: 3 additions & 1 deletion packages/@aws-cdk/aws-cognito/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"@aws-cdk/assertions": "0.0.0",
"@aws-cdk/cdk-build-tools": "0.0.0",
"@aws-cdk/integ-runner": "0.0.0",
"@aws-cdk/integ-tests": "0.0.0",
"@aws-cdk/cfn2ts": "0.0.0",
"@aws-cdk/pkglint": "0.0.0",
"@types/jest": "^27.5.2",
Expand Down Expand Up @@ -126,7 +127,8 @@
"props-physical-name:@aws-cdk/aws-cognito.UserPoolIdentityProviderAmazonProps",
"props-physical-name:@aws-cdk/aws-cognito.UserPoolIdentityProviderGoogleProps",
"props-physical-name:@aws-cdk/aws-cognito.UserPoolIdentityProviderAppleProps",
"props-physical-name:@aws-cdk/aws-cognito.UserPoolIdentityProviderOidcProps"
"props-physical-name:@aws-cdk/aws-cognito.UserPoolIdentityProviderOidcProps",
"props-physical-name:@aws-cdk/aws-cognito.UserPoolIdentityProviderSamlProps"
]
},
"stability": "stable",
Expand Down
40 changes: 40 additions & 0 deletions packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.saml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { App, CfnOutput, RemovalPolicy, Stack } from '@aws-cdk/core';
import { IntegTest } from '@aws-cdk/integ-tests';
import { Construct } from 'constructs';
import { UserPool, UserPoolIdentityProviderSaml, UserPoolIdentityProviderSamlMetadata } from '../lib';

class TestStack extends Stack {
constructor(scope: Construct, id: string) {
super(scope, id);
const userpool = new UserPool(this, 'pool', {
removalPolicy: RemovalPolicy.DESTROY,
});

new UserPoolIdentityProviderSaml(this, 'cdk', {
userPool: userpool,
name: 'cdk',
metadata: UserPoolIdentityProviderSamlMetadata.url('https://fujifish.github.io/samling/public/metadata.xml'),
});

const client = userpool.addClient('client');

const domain = userpool.addDomain('domain', {
cognitoDomain: {
domainPrefix: 'cdk-test-pool',
},
});

new CfnOutput(this, 'SignInLink', {
value: domain.signInUrl(client, {
redirectUri: 'https://example.com',
}),
});
}
}

const app = new App();
const testCase = new TestStack(app, 'integ-user-pool-identity-provider-saml-stack');

new IntegTest(app, 'integ-user-pool-identity-provider-saml-test', {
testCases: [testCase],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"version":"21.0.0"}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"version": "21.0.0",
"files": {
"6f6f07786415216f13b738979cec5ad81dbab3283fae83b99324965935cc1d60": {
"source": {
"path": "integ-user-pool-identity-provider-saml-stack.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "6f6f07786415216f13b738979cec5ad81dbab3283fae83b99324965935cc1d60.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
}
},
"dockerImages": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
{
"Resources": {
"pool056F3F7E": {
"Type": "AWS::Cognito::UserPool",
"Properties": {
"AccountRecoverySetting": {
"RecoveryMechanisms": [
{
"Name": "verified_phone_number",
"Priority": 1
},
{
"Name": "verified_email",
"Priority": 2
}
]
},
"AdminCreateUserConfig": {
"AllowAdminCreateUserOnly": true
},
"EmailVerificationMessage": "The verification code to your new account is {####}",
"EmailVerificationSubject": "Verify your new account",
"SmsVerificationMessage": "The verification code to your new account is {####}",
"VerificationMessageTemplate": {
"DefaultEmailOption": "CONFIRM_WITH_CODE",
"EmailMessage": "The verification code to your new account is {####}",
"EmailSubject": "Verify your new account",
"SmsMessage": "The verification code to your new account is {####}"
}
},
"UpdateReplacePolicy": "Delete",
"DeletionPolicy": "Delete"
},
"poolclient2623294C": {
"Type": "AWS::Cognito::UserPoolClient",
"Properties": {
"UserPoolId": {
"Ref": "pool056F3F7E"
},
"AllowedOAuthFlows": [
"implicit",
"code"
],
"AllowedOAuthFlowsUserPoolClient": true,
"AllowedOAuthScopes": [
"profile",
"phone",
"email",
"openid",
"aws.cognito.signin.user.admin"
],
"CallbackURLs": [
"https://example.com"
],
"SupportedIdentityProviders": [
{
"Ref": "cdk52888317"
},
"COGNITO"
]
}
},
"pooldomain430FA744": {
"Type": "AWS::Cognito::UserPoolDomain",
"Properties": {
"Domain": "cdk-test-pool",
"UserPoolId": {
"Ref": "pool056F3F7E"
}
}
},
"cdk52888317": {
"Type": "AWS::Cognito::UserPoolIdentityProvider",
"Properties": {
"ProviderName": "cdk",
"ProviderType": "SAML",
"UserPoolId": {
"Ref": "pool056F3F7E"
},
"ProviderDetails": {
"IDPSignout": false,
"MetadataURL": "https://fujifish.github.io/samling/public/metadata.xml"
}
}
}
},
"Outputs": {
"SignInLink": {
"Value": {
"Fn::Join": [
"",
[
"https://",
{
"Ref": "pooldomain430FA744"
},
".auth.",
{
"Ref": "AWS::Region"
},
".amazoncognito.com/login?client_id=",
{
"Ref": "poolclient2623294C"
},
"&response_type=code&redirect_uri=https://example.com"
]
]
}
}
},
"Parameters": {
"BootstrapVersion": {
"Type": "AWS::SSM::Parameter::Value<String>",
"Default": "/cdk-bootstrap/hnb659fds/version",
"Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"
}
},
"Rules": {
"CheckBootstrapVersion": {
"Assertions": [
{
"Assert": {
"Fn::Not": [
{
"Fn::Contains": [
[
"1",
"2",
"3",
"4",
"5"
],
{
"Ref": "BootstrapVersion"
}
]
}
]
},
"AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
}
]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": "21.0.0",
"testCases": {
"integ-user-pool-identity-provider-saml-test/DefaultTest": {
"stacks": [
"integ-user-pool-identity-provider-saml-stack"
],
"assertionStack": "integ-user-pool-identity-provider-saml-test/DefaultTest/DeployAssert",
"assertionStackName": "integuserpoolidentityprovidersamltestDefaultTestDeployAssert97F09C26"
}
}
}
Loading