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';
114 changes: 114 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,114 @@
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 file type.
*/
readonly metadataType: UserPoolIdentityProviderSamlMetadataType;

/**
* The SAML metadata content.
* If metadataType is set to URL, this should be the metadata URL.
* If metadataType is set to FILE, this should be the metadata file contents.
*/
readonly metadataContent: string;

/**
* 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',
}

/**
* 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 providerDetails: Record<string, string | boolean> = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure this will run afoul of jsii. The union operator doesn't translate well in most cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the rework of how metadata is provided in 36965e7, this union operator has gone away.

IDPSignout: props.idpSignout ?? false,
};

if (props.metadataType === UserPoolIdentityProviderSamlMetadataType.URL) {
providerDetails.MetadataURL = props.metadataContent;
} else if (props.metadataType === UserPoolIdentityProviderSamlMetadataType.FILE) {
providerDetails.MetadataFile = props.metadataContent;
}

const resource = new CfnUserPoolIdentityProvider(this, 'Resource', {
userPoolId: props.userPool.userPoolId,
providerName: this.getProviderName(props.name),
providerType: 'SAML',
providerDetails,
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
41 changes: 41 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,41 @@
import { App, CfnOutput, RemovalPolicy, Stack } from '@aws-cdk/core';
import { IntegTest } from '@aws-cdk/integ-tests';
import { Construct } from 'constructs';
import { UserPool, UserPoolIdentityProviderSaml, UserPoolIdentityProviderSamlMetadataType } 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',
metadataType: UserPoolIdentityProviderSamlMetadataType.URL,
metadataContent: '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"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"version": "21.0.0",
"files": {
"21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": {
"source": {
"path": "integuserpoolidentityprovidersamltestDefaultTestDeployAssert97F09C26.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
}
},
"dockerImages": {}
}
Loading