diff --git a/CloudFormationCognitoUserPoolIdentityProvider.js b/CloudFormationCognitoUserPoolIdentityProvider.js new file mode 100644 index 0000000..4ebfa31 --- /dev/null +++ b/CloudFormationCognitoUserPoolIdentityProvider.js @@ -0,0 +1,84 @@ +const AWS = require('aws-sdk'); + +exports.handler = async (event) => { + try { + var cognitoIdentityServiceProvider = new AWS.CognitoIdentityServiceProvider(); + + switch (event.RequestType) { + case 'Create': + console.info(`CFE-Cognito-UserPoolFederation ${event.RequestType} - IN PROGRESS`); + await cognitoIdentityServiceProvider.createIdentityProvider({ + UserPoolId: event.ResourceProperties.UserPoolId, + ProviderName: event.ResourceProperties.ProviderName, + ProviderType: event.ResourceProperties.ProviderType, + ProviderDetails: event.ResourceProperties.ProviderDetails, + AttributeMapping: event.ResourceProperties.AttributeMapping, + }).promise(); + break; + + case 'Update': + console.info(`CFE-Cognito-UserPoolFederation ${event.RequestType} - IN PROGRESS`); + await deleteIdentityProvider(cognitoIdentityServiceProvider, + event.OldResourceProperties.UserPoolId, + event.OldResourceProperties.ProviderName); + await cognitoIdentityServiceProvider.createIdentityProvider({ + UserPoolId: event.ResourceProperties.UserPoolId, + ProviderName: event.ResourceProperties.ProviderName, + ProviderType: event.ResourceProperties.ProviderType, + ProviderDetails: event.ResourceProperties.ProviderDetails, + AttributeMapping: event.ResourceProperties.AttributeMapping + }).promise(); + break; + + case 'Delete': + console.info(`CFE-Cognito-UserPoolFederation ${event.RequestType} - IN PROGRESS`); + await deleteIdentityProvider(cognitoIdentityServiceProvider, + event.ResourceProperties.UserPoolId, + event.ResourceProperties.ProviderName); + break; + } + + await sendCloudFormationResponse(event, 'SUCCESS'); + console.info(`CFE-Cognito-UserPoolFederation ${event.RequestType} - SUCCESS`); + } catch (error) { + console.error(`CFE-Cognito-UserPoolFederation ${event.RequestType} - FAILED:`, error); + await sendCloudFormationResponse(event, 'FAILED', event); + } +} + +async function deleteIdentityProvider(cognitoIdentityServiceProvider, userPoolId, providerName) { + var response = await cognitoIdentityServiceProvider.describeIdentityProvider({ + UserPoolId: userPoolId, + ProviderName: providerName + }).promise(); + + if (response.IdentityProvider.UserPoolId) { + await cognitoIdentityServiceProvider.deleteIdentityProvider({ + UserPoolId: response.IdentityProvider.UserPoolId, + ProviderName: providerName + }).promise(); + } +} + +async function sendCloudFormationResponse(event, responseStatus, responseData) { + var params = { + FunctionName: 'CloudFormationSendResponse', + InvocationType: 'RequestResponse', + Payload: JSON.stringify({ + StackId: event.StackId, + RequestId: event.RequestId, + LogicalResourceId: event.LogicalResourceId, + ResponseURL: event.ResponseURL, + ResponseStatus: responseStatus, + ResponseData: responseData + }) + }; + + var lambda = new AWS.Lambda(); + var response = await lambda.invoke(params).promise(); + + if (response.FunctionError) { + var responseError = JSON.parse(response.Payload); + throw new Error(responseError.errorMessage); + } +} \ No newline at end of file diff --git a/SampleInfrastructure.template.yaml b/SampleInfrastructure.template.yaml index 4d4c2fc..1b3f538 100644 --- a/SampleInfrastructure.template.yaml +++ b/SampleInfrastructure.template.yaml @@ -51,6 +51,21 @@ Resources: Effect: Allow Action: 'cognito-idp:DescribeUserPoolDomain' Resource: '*' + - + PolicyName: ManageUserPoolIdentityProviders + PolicyDocument: + Version: '2012-10-17' + Statement: + - + Effect: Allow + Action: + - 'cognito-idp:CreateIdentityProvider' + - 'cognito-idp:DeleteIdentityProvider' + Resource: 'arn:aws:cognito-idp:*:*:userpool/*' + - + Effect: Allow + Action: 'cognito-idp:DescribeIdentityProvider' + Resource: '*' - PolicyName: InvokeLambdaFunction PolicyDocument: @@ -86,6 +101,15 @@ Resources: Handler: CloudFormationCognitoUserPoolDomain.handler Role: !GetAtt LambdaForCloudFormation.Arn DependsOn: CloudFormationSendResponse + CloudFormationCognitoUserPoolIdentityProvider: + Type: 'AWS::Lambda::Function' + Properties: + FunctionName: CloudFormationCognitoUserPoolIdentityProvider + Runtime: nodejs8.10 + Code: ./CloudFormationCognitoUserPoolIdentityProvider.js + Handler: CloudFormationCognitoUserPoolIdentityProvider.handler + Role: !GetAtt LambdaForCloudFormation.Arn + DependsOn: CloudFormationSendResponse UserPoolTest: Type: 'AWS::Cognito::UserPool' Properties: @@ -116,4 +140,23 @@ Resources: Properties: ServiceToken: !GetAtt CloudFormationCognitoUserPoolDomain.Arn UserPoolId: !Ref UserPoolTest - Domain: 'userpool-test-01' \ No newline at end of file + Domain: 'userpool-test-01' + UserPoolTestIdentityProvider: + Type: 'Custom::CognitoUserPoolIdentityProvider' + Properties: + ServiceToken: !GetAtt CloudFormationCognitoUserPoolIdentityProvider.Arn + UserPoolId: !Ref UserPoolTest + ProviderName: Google + ProviderType: Google + ProviderDetails: + authorize_scopes: 'email openid' + attributes_url_add_attributes: true + token_url: 'https://www.googleapis.com/oauth2/v4/token' + oidc_issuer: 'https://accounts.google.com' + client_id: '123456789012-12345exampleexampleexampleexampl.apps.googleusercontent.com' + attributes_url: 'https://people.googleapis.com/v1/people/me?personFields=' + client_secret: '1234567890exampleexample' + token_request_method: 'POST' + AttributeMapping: + username: 'sub' + email: 'email' \ No newline at end of file