.amazonaws.com
-
-Because our application interacts with Cognito via the OAuth 2.0 implicit flow, which requires a redirect, we need our website to use HTTPS. To have an HTTPS endpoint for an S3 static website, we can use a [CloudFront distribution](https://aws.amazon.com/cloudfront/).
-
-#### High-Level Instructions
-Use the console or AWS CLI to create an Amazon S3 bucket. Keep in mind that your bucket's name must be globally unique across all regions and customers. We recommend using a name like `unicornmanager-firstname-lastname`. If you get an error that your bucket name already exists, try adding additional numbers or characters until you find an unused name.
-
-You will need to add a bucket policy to your new Amazon S3 bucket to let anonymous users view your site. By default your bucket will only be accessible by authenticated users with access to your AWS account. See [this example](http://docs.aws.amazon.com/AmazonS3/latest/dev/example-bucket-policies.html#example-bucket-policies-use-case-2) of a policy that will grant read only access to anonymous users. This example policy allows anyone on the Internet to view your content. The easiest way to update a bucket policy is to use the console. Select the bucket, choose the permission tab and then select Bucket Policy.
-
-Using the console, enable static website hosting. You can do this on the Properties tab after you've selected the bucket. Set `index.html` as the index document, and leave the error document blank. See the documentation on [configuring a bucket for static website hosting](https://docs.aws.amazon.com/AmazonS3/latest/dev/HowDoIWebsiteConfiguration.html) for more details.
-
-Using the CloudFront console, create a new Distribution for web content specifying the S3 static website URL as the origin domain and / as the path. Make sure that the distribution only accepts HTTPS requests and HTTP requests are redirected to the HTTPS url.
-
-
-Step-by-step instructions (expand for details)
-
-1. In the AWS Management Console choose **Services** then select **S3** under Storage.
-
-1. Choose **+ Create Bucket**
-
-1. Provide a globally unique name for your bucket such as `unicornmanager-firstname-lastname`.
-
-1. Select the Region you've chosen to use for this workshop from the dropdown.
-
-1. Choose **Create** in the lower left of the dialog without selecting a bucket to copy settings from.
-
- 
-
-1. Open the bucket you just created.
-
-1. Choose the **Permissions** tab, then click the **Bucket Policy** button.
-
-1. Enter the following policy document into the bucket policy editor replacing `[YOUR_BUCKET_NAME]` with the name of the bucket you created in section 1:
-
- ```json
- {
- "Version": "2012-10-17",
- "Statement": [
- {
- "Effect": "Allow",
- "Principal": "*",
- "Action": "s3:GetObject",
- "Resource": "arn:aws:s3:::[YOUR_BUCKET_NAME]/*"
- }
- ]
- }
- ```
-
- 
-
-1. Choose **Save** to apply the new policy. You will see a warning indicating `This bucket has public access`. This is expected.
-
-1. Next, choose the **Properties** tab.
-
-1. Choose the **Static website hosting** card.
-
-1. Select **Use this bucket to host a website** and enter `index.html` for the Index document. Leave the other fields blank.
-
-1. Note the **Endpoint** URL at the top of the dialog before choosing **Save**.
-
-1. Click **Save** to save your changes.
-
- 
-
-1. Next, open the **CloudFront** console under the **Networking & Content Delivery**.
-
-1. In the **CloudFront Distributions** page, click **Create Distribution**.
-
-1. For the delivery method, under **Web** section, click **Get Started**.
-
-1. In the **Origin Domain Name** field, paste the URL for the S3 static website we just created and **/** as the origin path. **Do not select the bucket from dropdown list, paste the full website url including the http:// prefix. The origin type should be `custom`, not `s3`**.
-
-1. In the **Viewer Protocol Policy** make sure that **Redirect HTTP to HTTPS** is selected.
-
- 
-
-1. Under **Distrubution Settings** for **Price Class**, select **Use Only US, Canada and Europe**.
-
-1. Click **Create Distribution** at the bottom of the page.
-
-1. Creating a global distribution can take some time. Let CloudFront do its work in the background and move on the next step. We will come back to get the distribution endpoint at a later step.
-
-
-
-### 6. Declare a new client application
-
-#### Background
-Amazon Cognito User Pools allows you to declare multiple client applications that can interact with your pool. This includes both applications you own and apps by third party developers. Each application is identified by an application id and client secret. Cognito User Pools also offers a hosted login UI that supports the most common user operations such as registration, login, reset passwords, and MFA. You can also customize the look and feel of the hosted UI.
-
-#### High-Level Instructions
-Using the Cognito console, add a new client application called **UnicornManager**. Because the client application is a static website hosted on S3 and written in JavaScript, we do **not** need a client secret. Next, in the App Integration section of the Cognito console, configure a domain name prefix for your hosted login UI. We called this **WildRydes-<username>**.
-
-
-Step-by-step instructions (expand for details)
-
-1. In the AWS Management Console choose **Services** then select **Cognito** under Mobile.
-
-1. In the intro page, click **Manage your User Pools** an open the **WildRydes** pool.
-
-1. Open the **App clients** from the **General settings** menu on the left.
-
-1. Click **Add another app client**.
-
-1. Enter **UnicornManager** as the **App client name** and uncheck the **Generate client secret** checkbox.
-
- 
-
-1. Click **Create app client**.
-
-1. Open the **Domain name** configuration page.
-
-1. Specify a unique custom domain name, for example **wildrydes-sapessi**.
-
-1. Make sure that the domain name is available and then click **Save changes**.
-
-
-### 7. Create the Unicorns scope in the Cognito User Pool
-
-#### Background
-Amazon Cognito User Pools lets you declare custom resource servers. Custom resource servers have a unique identifier - normally the server uri - and can declare custom scopes. You can allow custom applications to request scopes in your user pools. When users authenticate with these applications, the Cognito hosted UI takes care of authenticating the user and authorizing the action. Custom claims are automatically added to the JWT access token.
-
-#### High-Level Instructions
-Using the Cognito console, open the **WildRydes** User Pool and create a new custom resource server called **UnicornServer**. The **UnicornServer** should use **UnicornManager** as the **Identifier** and allow the **unicorn** scope.
-
-
-Step-by-step instructions (expand for details)
-
-1. Open the **Services** menu and select **Cognito** in the Mobile section.
-
-1. In the main screen, select **Manage your User Pools**.
-
-1. Open the **WildRydes** pool and select **Resource Servers** under **App integration**.
-
- 
-
-1. In the resource servers screen, click **Add a resource server**.
-
-1. Specify **UnicornServer** as the **Name**.
-
-1. Use **UnicornManager** as the **Identifier** for the custom resource server.
-
-1. In the **Scopes** section, declare a new scope called **unicorn**. I've used "**Allow listing of rides for unicorns**" as the description.
-
- 
-
-1. Click **Save changes** to create your new custom resource server.
-
-
-### 8. Configure the new app client for OAuth
-
-#### Background
-Amazon Cognito User Pools supports the authorization code grant, implicit, and client credentials grants. Third party developers can load the Cognito hosted UI with their application ID and request any of the enabled flows. Cognito User Pools also exposes a set of client and server/admin APIs that you can use to build custom authentication flows. As a result of a successful authentication Cognito produces and OpenID Connect-compatible identity token and a JWT access token. The access token includes the custom scopes you declared for the application.
-
-In our example, we will use the implicit flow for the sake of simplicity. Implicit grant flows are mostly used by mobile applications. For web applications, you would normally require third party developers to host their own backend service and use the authorization code grant flow.
-
-#### High-Level Instructions
-Open the **App client settings** and configure the **UnicornManager** app to use **Cognito User Pool** as an identity provider and allow the **Implicit grant** flow. Make sure the application has access to the **custom scope** we created in step #7. As a callback URL, use the CloudFront distribution endpoint we created in step #5. The callback url will look like this: `https://xxxxxxxxxxx.cloudfront.net`.
-
-
-Step-by-step instructions (expand for details)
-
-1. In the AWS Management Console choose **Services** then select **Cognito** under Mobile.
-
-1. In the intro page, click **Manage your User Pools** an open the **WildRydes** pool.
-
-1. Open the **App clients settings** from the **App integration** menu on the left. This page lists both the app clients declared for your user pool. Make sure you make the following changes only to the **UnicornManager** client app.
-
-1. Select **Cognito User Pool** as an identity provider for the app client.
-
-1. Enable the **Implicit grant** OAuth flow and allow the **UnicornManager/unicorn** custom scope.
-
-1. In the **Callback** and **Signout** URLs, specify the HTTPS CloudFront distribution endpoint adding **https://** at the beginning and **/** at the end:
- 1. You can find the distribution endpoint in the **CloudFront** console.
- 1. Select the distribution we created in step #5.
- 1. In the **General** tab, copy the value for **Domain name**.
-
- 
-
-1. Click **Save changes**.
-
-
-
-### 9. Configure and upload the Unicorn Manager application to S3
-
-#### Background
-The last step is to configure the client code with the new Cognito application id and upload to our S3 bucket.
-
-#### High-Level Instructions
-Open the `config.js` file in the **UnicornManager** folder, replace the `userPoolClientId` with the new UnicornManager application id from Cognito, set the region and the domain prefix we configured in step #6. Finally, copy the **WildRydesApiInvokeUrl** value from the prerequisites CloudFormation stack output into the **invokeUrl** property of the config file. Save and close the file.
-
-Upload the content of the **UnicornManager** folder to the root of your S3 bucket. You can use the AWS Management Console (requires Google Chrome browser) or the AWS CLI to complete this step. If you already have the AWS CLI installed and configured on your local machine, we recommend using that method. Otherwise, use the console if you have the latest version of Google Chrome installed.
-
-
-CLI step-by-step instructions (expand for details)
-1. With a file manager, navigate to the folder where the lab content is located and open the **UnicornManager** directory from the **WebApplication/5_OAuth/** folder.
-
-1. Open a terminal window and navigate to the folder where the material for this workshop is located. Navigate to the `WebApplication/5_OAuth/UnicornManager` folder.
-
-1. Open the **js** folder.
-
-1. Using your preferred text editor, open the **config.js** file.
-
-1. From the Cognito User Pools console, copy the client app id for the **UnicornManager** application as the value of the **userPoolClientId** property. You can find the application id in the **App clients** menu of the Cognito console.
-
-1. Change the value of the **region** property to the region you are using for this workshop. For example, I'm using **us-east-2**.
-
-1. Still in the Cognito User Pools console, open the **Domain name** page and copy the custom prefix in the value for the **authDomainPrefix** property. In our sample, this was `wildrydes-sapessi`.
-
-1. Finally, open the CloudFormation console and select the pre-requisites stack we created at the beginning of this lab. With the stack selected, use the bottom section of the window to open the **Outputs** tab. Copy the value of the **WildRydesApiInvokeUrl** output variable to the **invokeUrl** property - this value should look like this: `https://xxxxxxxxx.execute-api.xx-xxxxx-x.amazonaws.com/prod`
-
-1. Next, we need to copy the files we just modified to the S3 bucket that hosts our static website. We created the bucket in step #5 of this lab and it should be called **unicornmanager-<username>**. You can use the AWS CLI or the management console with a compatible browser to upload the files.
-##### AWS CLI
-
-1. With a terminal, navigate to the **UnicornManager** directory in the lab material folder.
-
-1. Run the following command:
-
- ```
- aws s3 sync . s3://YOUR_BUCKET_NAME --region YOUR_BUCKET_REGION
- ```
-##### AWS Console
-
-1. Open the **S3** console and select the Unicorn Manager bucket.
-
-1. In the **Overview** tab, click the **Upload** button.
-
-1. From a file browser window, select all of the files in the **UnicornManager** folder and drag them to S3's upload window.
-
-
-### Testing the application
-Before we open the web page for the new Unicorn Manager application, we need to create a user for our unicorn. Using the **DynamoDB** console, open the **Tables** page and select the **Rides** table. In the **Items** tab, refresh the list of rides. Take the most common unicorn name from the **UnicornName** field and copy the value.
-
-Next, open the unicorn manager application by navigating to the CloudFront distribution domain we created in step #5 - the domain should look like this: **xxxxxxxxxxxx.cloudfront.net**. The application detects that we are not logged in an automatically redirects us to the Cognito hosted login page. On the login page, use the **Sign up** link at the bottom of the form.
-
-In the Sign up page, use the **UnicornName** value we copied from the DynamoDB table as the username, a valid email address, and create a password for the user. With most email addresses you can use a suffix preceded by **+** to create custom addresses. For example, you could sign up with **youremail+unicorn@emaildomain.com**.
-
-
-
-Click **Sign up** to create the unicorn account. The hosted registration ui will ask you for the verification code, you should have received this code via email. Paste the verification code in the form and click **Confirm account**.
-
-Once the account is confirmed, the application will redirect you to the main web page of the Unicorn manager. Use the **Refresh** button on the top right to load a list of the rides for the unicorn you registered.
-
-We have now turned **Wild Rydes** into a platform. Third party developers can now ask us for a new client app id, use our hosted UI to authenticate and register new users. This will allow us to grow our customer base and toolkit beyond what our team can produce by itself, **UnicornManager** is just the first step.
+The details of the flow above are as follows:
+1. The app starts the sign-up and sign-in process by directing your user to the UI hosted by AWS. A mobile app can use web view to show the pages hosted by AWS.
+2. User Pool determines the appropriate IdP based on your configuration. For ADFS the IdP is determined by the metadata file or metadata endpoint URL from your SAML IdP. For example, if you use Microsoft Active Directory Federation Service (AD FS), the metadata URL looks like: https:///FederationMetadata/2007-06/FederationMetadata.xml
+3. Your user is redirected to the identity provider.
+4. The IdP authenticates the user if necessary. If the IdP recognizes that the user has an active session, the IdP skips the authentication to provide a single sign-in (SSO) experience.
+5. The IdP POSTs the SAML assertion to the Amazon Cognito service.
+6. The user's profile is created within Amazon Cognito User Pools.
+7. After verifying the SAML assertion and collecting the user attributes (claims) from the assertion, Amazon Cognito returns OIDC tokens (id, access and refresh tokens) to the app for the now signed-in user.
+8. We make a GET request to the API Gateway. In the Authorization header of the GET request we use the id token. On the API Gateway side we have a Cognito Authorizer that will validate the id JWT token.
diff --git a/images/ADFS.png b/images/ADFS.png
new file mode 100644
index 0000000..59a8b59
Binary files /dev/null and b/images/ADFS.png differ
diff --git a/prerequisites.yaml b/prerequisites.yaml
deleted file mode 100755
index fb4bdbc..0000000
--- a/prerequisites.yaml
+++ /dev/null
@@ -1,695 +0,0 @@
----
-AWSTemplateFormatVersion: "2010-09-09"
-
-Description:
- Deploys the Wild Rydes workshop
-
-Parameters:
- BucketName:
- Type: String
- Description: The name for the bucket hosting your website, e.g. 'wildrydes-yourname'
-
- CodeBucket:
- Type: String
- Default: wildrydes-us-east-1
- Description: S3 bucket containing the code deployed by this template
-
-Metadata:
- AWS::CloudFormation::Interface:
- ParameterGroups:
- -
- Label:
- default: "Website Configuration"
- Parameters:
- - BucketName
- -
- Label:
- default: "Advanced Configuration"
- Parameters:
- - CodeBucket
- ParameterLabels:
- BucketName:
- default: "Website Bucket Name"
-
-Resources:
- WebsiteBucket:
- Properties:
- BucketName: !Ref BucketName
- WebsiteConfiguration:
- IndexDocument: index.html
- Type: "AWS::S3::Bucket"
-
- WebsiteBucketPolicy:
- Properties:
- Bucket: !Ref WebsiteBucket
- PolicyDocument:
- Version: 2012-10-17
- Statement:
- -
- Effect: Allow
- Principal: "*"
- Action: s3:GetObject
- Resource: !Sub "arn:aws:s3:::${WebsiteBucket}/*"
- Type: "AWS::S3::BucketPolicy"
-
- WebsiteContent:
- Properties:
- ServiceToken: !GetAtt CopyS3ObjectsFunction.Arn
- SourceBucket: !Ref CodeBucket
- SourcePrefix: "WebApplication/1_StaticWebHosting/website/"
- Bucket: !Ref WebsiteBucket
- Type: "Custom::S3Objects"
-
- S3CopyRole:
- Type: AWS::IAM::Role
- Properties:
-
- Path: /wildrydes/
- AssumeRolePolicyDocument:
- Version: 2012-10-17
- Statement:
- -
- Effect: Allow
- Principal:
- Service: lambda.amazonaws.com
- Action: sts:AssumeRole
- Policies:
- -
- PolicyName: S3Access
- PolicyDocument:
- Version: 2012-10-17
- Statement:
- -
- Sid: AllowLogging
- Effect: Allow
- Action:
- - "logs:CreateLogGroup"
- - "logs:CreateLogStream"
- - "logs:PutLogEvents"
- Resource: "*"
- -
- Sid: SourceBucketReadAccess
- Effect: Allow
- Action:
- - "s3:ListBucket"
- - "s3:GetObject"
- Resource:
- - !Sub "arn:aws:s3:::${CodeBucket}"
- - !Sub "arn:aws:s3:::${CodeBucket}/WebApplication/1_StaticWebHosting/*"
- -
- Sid: DestBucketWriteAccess
- Effect: Allow
- Action:
- - "s3:ListBucket"
- - "s3:GetObject"
- - "s3:PutObject"
- - "s3:PutObjectAcl"
- - "s3:PutObjectVersionAcl"
- - "s3:DeleteObject"
- - "s3:DeleteObjectVersion"
- - "s3:CopyObject"
- Resource:
- - !Sub "arn:aws:s3:::${WebsiteBucket}"
- - !Sub "arn:aws:s3:::${WebsiteBucket}/*"
-
- CopyS3ObjectsFunction:
- Properties:
- Description: Copies objects from a source S3 bucket to a destination
- Handler: index.handler
- Runtime: python2.7
- Role: !GetAtt S3CopyRole.Arn
- Timeout: 120
- Code:
- ZipFile: |
- import os
- import json
- import cfnresponse
-
- import boto3
- from botocore.exceptions import ClientError
- client = boto3.client('s3')
-
- import logging
- logger = logging.getLogger()
- logger.setLevel(logging.INFO)
-
- def handler(event, context):
- logger.info("Received event: %s" % json.dumps(event))
- source_bucket = event['ResourceProperties']['SourceBucket']
- source_prefix = event['ResourceProperties'].get('SourcePrefix') or ''
- bucket = event['ResourceProperties']['Bucket']
- prefix = event['ResourceProperties'].get('Prefix') or ''
-
- result = cfnresponse.SUCCESS
-
- try:
- if event['RequestType'] == 'Create' or event['RequestType'] == 'Update':
- result = copy_objects(source_bucket, source_prefix, bucket, prefix)
- elif event['RequestType'] == 'Delete':
- result = delete_objects(bucket, prefix)
- except ClientError as e:
- logger.error('Error: %s', e)
- result = cfnresponse.FAILED
-
- cfnresponse.send(event, context, result, {})
-
-
- def copy_objects(source_bucket, source_prefix, bucket, prefix):
- paginator = client.get_paginator('list_objects_v2')
- page_iterator = paginator.paginate(Bucket=source_bucket, Prefix=source_prefix)
- for key in {x['Key'] for page in page_iterator for x in page['Contents']}:
- dest_key = os.path.join(prefix, os.path.relpath(key, source_prefix))
- if not key.endswith('/'):
- print 'copy {} to {}'.format(key, dest_key)
- client.copy_object(CopySource={'Bucket': source_bucket, 'Key': key}, Bucket=bucket, Key = dest_key)
- return cfnresponse.SUCCESS
-
- def delete_objects(bucket, prefix):
- paginator = client.get_paginator('list_objects_v2')
- page_iterator = paginator.paginate(Bucket=bucket, Prefix=prefix)
- objects = [{'Key': x['Key']} for page in page_iterator for x in page['Contents']]
- client.delete_objects(Bucket=bucket, Delete={'Objects': objects})
- return cfnresponse.SUCCESS
-
-
- Type: AWS::Lambda::Function
-
- UserPool:
- Type: AWS::Cognito::UserPool
- Properties:
- UserPoolName: WildRydes
- AliasAttributes:
- - email
- AutoVerifiedAttributes:
- - email
- Schema:
- - AttributeDataType: String
- Name: email
- Required: true
-
- UserPoolClient:
- Type: AWS::Cognito::UserPoolClient
- Properties:
- ClientName: WildRydesWeb
- UserPoolId: !Ref UserPool
- GenerateSecret: false
-
- UpdateUserPoolConfig:
- Properties:
- ServiceToken: !GetAtt UpdateCognitoConfigFunction.Arn
- UserPool: !Ref UserPool
- Client: !Ref UserPoolClient
- Region: !Ref "AWS::Region"
- Bucket: !Ref WebsiteBucket
- Type: "Custom::CognitoConfigFile"
-
- UpdateConfigRole:
- Type: AWS::IAM::Role
- Properties:
-
- Path: /wildrydes/
- AssumeRolePolicyDocument:
- Version: 2012-10-17
- Statement:
- -
- Effect: Allow
- Principal:
- Service: lambda.amazonaws.com
- Action: sts:AssumeRole
- Policies:
- -
- PolicyName: CognitoConfig
- PolicyDocument:
- Version: 2012-10-17
- Statement:
- -
- Sid: Logging
- Effect: Allow
- Action:
- - "logs:CreateLogGroup"
- - "logs:CreateLogStream"
- - "logs:PutLogEvents"
- Resource: "*"
- -
- Sid: Cognito
- Effect: Allow
- Action:
- - "cognito-idp:CreateUserPool"
- - "cognito-idp:DeleteUserPool"
- - "cognito-idp:CreateUserPoolClient"
- - "cognito-idp:DeleteUserPoolClient"
- Resource: "*"
- -
- Sid: ConfigBucketWriteAccess
- Effect: Allow
- Action:
- - "s3:PutObject"
- - "s3:PutObjectAcl"
- - "s3:PutObjectVersionAcl"
- Resource:
- - !Sub "arn:aws:s3:::${WebsiteBucket}/*"
-
- UpdateCognitoConfigFunction:
- Properties:
- Description: Copies objects from a source S3 bucket to a destination
- Handler: index.handler
- Runtime: python2.7
- Role: !GetAtt UpdateConfigRole.Arn
- Timeout: 120
- Code:
- ZipFile: |
- import json
- import boto3
- import cfnresponse
-
- s3 = boto3.resource('s3')
-
- def create(properties, physical_id):
- userPoolId = properties['UserPool']
- clientId = properties['Client']
- region = properties['Region']
- bucket = properties['Bucket']
-
- object = s3.Object(bucket, 'js/config.js')
- config_content = """
- var _config = {
- cognito: {
- userPoolId: '%s', // e.g. us-east-2_uXboG5pAb
- userPoolClientId: '%s', // e.g. 25ddkmj4v6hfsfvruhpfi7n4hv
- region: '%s', // e.g. us-east-2
- },
- api: {
- invokeUrl: 'Base URL of your API including the stage', // e.g. https://rc7nyt4tql.execute-api.us-west-2.amazonaws.com/prod'
- }
- };
- """
- config_content = config_content % (userPoolId, clientId, region)
- print "Writing config content: %s" % config_content
- print "Writing to bucket: %s" % bucket
- config = s3.Object(bucket,'js/config.js')
- config.put(Body=config_content)
- return cfnresponse.SUCCESS, None
-
- def update(properties, physical_id):
- return create(properties, physical_id)
-
- def delete(properties, physical_id):
- return cfnresponse.SUCCESS, physical_id
-
- def handler(event, context):
- print "Received event: %s" % json.dumps(event)
-
- status = cfnresponse.FAILED
- new_physical_id = None
-
- try:
- properties = event.get('ResourceProperties')
- physical_id = event.get('PhysicalResourceId')
-
- status, new_physical_id = {
- 'Create': create,
- 'Update': update,
- 'Delete': delete
- }.get(event['RequestType'], lambda x, y: (cfnresponse.FAILED, None))(properties, physical_id)
- except Exception as e:
- print "Exception: %s" % e
- status = cfnresponse.FAILED
- finally:
- cfnresponse.send(event, context, status, {}, new_physical_id)
-
- Type: AWS::Lambda::Function
- DependsOn: WebsiteContent
-
- RidesTable:
- Type: AWS::DynamoDB::Table
- Properties:
- TableName: Rides
- AttributeDefinitions:
- -
- AttributeName: RideId
- AttributeType: S
- KeySchema:
- -
- AttributeName: RideId
- KeyType: HASH
- ProvisionedThroughput:
- ReadCapacityUnits: 5
- WriteCapacityUnits: 5
-
- RequestUnicornExecutionRole:
- Type: AWS::IAM::Role
- Properties:
- RoleName: WildRydesLambda
- AssumeRolePolicyDocument:
- Version: 2012-10-17
- Statement:
- -
- Effect: Allow
- Principal:
- Service:
- - lambda.amazonaws.com
- Action:
- - "sts:AssumeRole"
- Path: "/wildrydes/"
- ManagedPolicyArns:
- - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
- Policies:
- -
- PolicyName: PutRidePolicy
- PolicyDocument:
- Version: 2012-10-17
- Statement:
- -
- Effect: Allow
- Action:
- - dynamodb:PutItem
- - dynamodb:Scan
- Resource: !GetAtt RidesTable.Arn
-
- RequestUnicornFunction:
- Type: AWS::Lambda::Function
- Properties:
- FunctionName: RequestUnicorn
- Runtime: nodejs6.10
- Role: !GetAtt RequestUnicornExecutionRole.Arn
- Timeout: 5
- MemorySize: 128
- Handler: index.handler
- Code:
- ZipFile: >
- const randomBytes = require('crypto').randomBytes;
-
- const AWS = require('aws-sdk');
-
- const ddb = new AWS.DynamoDB.DocumentClient();
-
- const fleet = [
- {
- Name: 'Bucephalus',
- Color: 'Golden',
- Gender: 'Male',
- },
- {
- Name: 'Shadowfax',
- Color: 'White',
- Gender: 'Male',
- },
- {
- Name: 'Rocinante',
- Color: 'Yellow',
- Gender: 'Female',
- },
- ];
-
- exports.handler = (event, context, callback) => {
- if (!event.requestContext.authorizer) {
- errorResponse('Authorization not configured', context.awsRequestId, callback);
- return;
- }
-
- const rideId = toUrlString(randomBytes(16));
- console.log('Received event (', rideId, '): ', event);
-
- // Because we're using a Cognito User Pools authorizer, all of the claims
- // included in the authentication token are provided in the request context.
- // This includes the username as well as other attributes.
- const username = event.requestContext.authorizer.claims['cognito:username'];
-
- // The body field of the event in a proxy integration is a raw string.
- // In order to extract meaningful values, we need to first parse this string
- // into an object. A more robust implementation might inspect the Content-Type
- // header first and use a different parsing strategy based on that value.
- const requestBody = JSON.parse(event.body);
-
- const pickupLocation = requestBody.PickupLocation;
-
- const unicorn = findUnicorn(pickupLocation);
-
- recordRide(rideId, username, unicorn).then(() => {
- // You can use the callback function to provide a return value from your Node.js
- // Lambda functions. The first parameter is used for failed invocations. The
- // second parameter specifies the result data of the invocation.
-
- // Because this Lambda function is called by an API Gateway proxy integration
- // the result object must use the following structure.
- callback(null, {
- statusCode: 201,
- body: JSON.stringify({
- RideId: rideId,
- Unicorn: unicorn,
- UnicornName: unicorn.Name,
- Eta: '30 seconds',
- Rider: username,
- }),
- headers: {
- 'Access-Control-Allow-Origin': '*',
- },
- });
- }).catch((err) => {
- console.error(err);
-
- // If there is an error during processing, catch it and return
- // from the Lambda function successfully. Specify a 500 HTTP status
- // code and provide an error message in the body. This will provide a
- // more meaningful error response to the end client.
- errorResponse(err.message, context.awsRequestId, callback)
- });
- };
-
- // This is where you would implement logic to find the optimal unicorn for
- // this ride (possibly invoking another Lambda function as a microservice.)
- // For simplicity, we'll just pick a unicorn at random.
-
- function findUnicorn(pickupLocation) {
- console.log('Finding unicorn for ', pickupLocation.Latitude, ', ', pickupLocation.Longitude);
- return fleet[Math.floor(Math.random() * fleet.length)];
- }
-
- function recordRide(rideId, username, unicorn) {
- return ddb.put({
- TableName: 'Rides',
- Item: {
- RideId: rideId,
- User: username,
- Unicorn: unicorn,
- UnicornName: unicorn.Name,
- RequestTime: new Date().toISOString(),
- },
- }).promise();
- }
-
- function toUrlString(buffer) {
- return buffer.toString('base64')
- .replace(/\+/g, '-')
- .replace(/\//g, '_')
- .replace(/=/g, '');
- }
-
- function errorResponse(errorMessage, awsRequestId, callback) {
- callback(null, {
- statusCode: 500,
- body: JSON.stringify({
- Error: errorMessage,
- Reference: awsRequestId,
- }),
- headers: {
- 'Access-Control-Allow-Origin': '*',
- },
- });
- }
-
- WildRydesApi:
- Type: AWS::ApiGateway::RestApi
- Properties:
- Name: WildRydes
- Body:
- swagger: 2.0
- info:
- version: 1.0.0
- title: WildRydes
- paths:
- /ride:
- post:
- description: Requests a new ride
- consumes:
- - application/json
- produces:
- - application/json
- security:
- - CognitoAuthorizer: []
- responses:
- "200":
- description: "200 response"
- headers:
- Access-Control-Allow-Origin:
- type: "string"
- x-amazon-apigateway-integration:
- responses:
- default:
- statusCode: 200
- responseParameters:
- method.response.header.Access-Control-Allow-Origin: "'*'"
- uri:
- Fn::Join:
- - ""
- - - "arn:aws:apigateway:"
- - !Ref AWS::Region
- - ":lambda:path/2015-03-31/functions/"
- - !GetAtt RequestUnicornFunction.Arn
- - "/invocations"
- passthroughBehavior: "when_no_match"
- httpMethod: "POST"
- contentHandling: "CONVERT_TO_TEXT"
- type: "aws_proxy"
- options:
- responses:
- "200":
- description: "200 response"
- schema:
- $ref: "#/definitions/Empty"
- headers:
- Access-Control-Allow-Origin:
- type: "string"
- Access-Control-Allow-Methods:
- type: "string"
- Access-Control-Allow-Headers:
- type: "string"
- x-amazon-apigateway-integration:
- responses:
- default:
- statusCode: "200"
- responseParameters:
- method.response.header.Access-Control-Allow-Methods: "'GET,OPTIONS,POST'"
- method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'"
- method.response.header.Access-Control-Allow-Origin: "'*'"
- requestTemplates:
- application/json: "{\"statusCode\": 200}"
- passthroughBehavior: "when_no_match"
- type: "mock"
- securityDefinitions:
- CognitoAuthorizer:
- type: "apiKey"
- name: Authorization
- in: header
- x-amazon-apigateway-authtype: cognito_user_pools
- x-amazon-apigateway-authorizer:
- providerARNs:
- - Fn::Join:
- - ""
- - - "arn:aws:cognito-idp:"
- - !Ref AWS::Region
- - ":"
- - !Ref AWS::AccountId
- - ":userpool/"
- - !Ref UserPool
- type: "cognito_user_pools"
-
-
- WildRydesApiDeployment:
- Type: AWS::ApiGateway::Deployment
- Properties:
- Description: Prod deployment for wild Rydes API
- RestApiId: !Ref WildRydesApi
- StageName: prod
-
- WildRydesFunctionPermissions:
- Type: AWS::Lambda::Permission
- Properties:
- Action: lambda:InvokeFunction
- FunctionName: !Ref RequestUnicornFunction
- Principal: apigateway.amazonaws.com
- SourceArn:
- Fn::Join:
- - ""
- - - "arn:aws:execute-api:"
- - !Ref AWS::Region
- - ":"
- - !Ref AWS::AccountId
- - ":"
- - !Ref WildRydesApi
- - "/*"
-
- UpdateApiConfig:
- Type: "Custom::ApiConfigFile"
- Properties:
- ServiceToken: !GetAtt UpdateApiConfigFunction.Arn
- Bucket: !Ref WebsiteBucket
- InvokeUrl:
- Fn::Join:
- - ""
- - - "https://"
- - !Ref WildRydesApi
- - ".execute-api."
- - !Ref AWS::Region
- - ".amazonaws.com/prod"
-
- UpdateApiConfigFunction:
- Type: AWS::Lambda::Function
- DependsOn: UpdateCognitoConfigFunction
- Properties:
- Description: Adds the API endpoint to the config.js file
- Handler: index.handler
- Runtime: python2.7
- Role: !GetAtt UpdateConfigRole.Arn
- Timeout: 120
- Code:
- ZipFile: |
- import json
- import boto3
- import cfnresponse
-
- s3 = boto3.resource('s3')
-
- def create(properties, physical_id):
- bucket = properties['Bucket']
- config_object = s3.Object(bucket, 'js/config.js').get()
- config_data = config_object["Body"].read()
- print "Current config: %s" % config_data
- config_data = config_data.replace("Base URL of your API including the stage", properties["InvokeUrl"])
- print "Modified config: %s" % config_data
- config = s3.Object(bucket,'js/config.js')
- config.put(Body=config_data)
- return cfnresponse.SUCCESS, None
-
- def update(properties, physical_id):
- return create(properties, physical_id)
-
- def delete(properties, physical_id):
- return cfnresponse.SUCCESS, physical_id
-
- def handler(event, context):
- print "Received event: %s" % json.dumps(event)
-
- status = cfnresponse.FAILED
- new_physical_id = None
-
- try:
- properties = event.get('ResourceProperties')
- physical_id = event.get('PhysicalResourceId')
-
- status, new_physical_id = {
- 'Create': create,
- 'Update': update,
- 'Delete': delete
- }.get(event['RequestType'], lambda x, y: (cfnresponse.FAILED, None))(properties, physical_id)
- except Exception as e:
- print "Exception: %s" % e
- status = cfnresponse.FAILED
- finally:
- cfnresponse.send(event, context, status, {}, new_physical_id)
-
-Outputs:
- WebsiteURL:
- Value: !GetAtt WebsiteBucket.WebsiteURL
- WildRydesApiInvokeUrl:
- Description: URL for the deployed wild rydes API
- Value:
- Fn::Join:
- - ""
- - - "https://"
- - !Ref WildRydesApi
- - ".execute-api."
- - !Ref AWS::Region
- - ".amazonaws.com/prod"
- Export:
- Name: WildRydesApiUrl
diff --git a/prerequisitesv2.yaml b/prerequisitesv2.yaml
deleted file mode 100644
index 9b4f4a2..0000000
--- a/prerequisitesv2.yaml
+++ /dev/null
@@ -1,182 +0,0 @@
-AWSTemplateFormatVersion: '2010-09-09'
-Description: Creates an S3 bucket and CloudFormation configured for hosting a static website
-
-Parameters:
- BucketName:
- Type: String
- Description: The name for the bucket hosting your website, e.g. 'datamanager-yourname'
-
-Resources:
- WebsiteBucket:
- Type: AWS::S3::Bucket
- Properties:
- BucketName: !Ref BucketName
- AccessControl: PublicRead
- WebsiteConfiguration:
- IndexDocument: index.html
- DeletionPolicy: Retain
-
- WebsiteBucketPolicy:
- Type: AWS::S3::BucketPolicy
- Properties:
- Bucket: !Ref 'WebsiteBucket'
- PolicyDocument:
- Statement:
- - Sid: PublicReadForGetBucketObjects
- Effect: Allow
- Principal: '*'
- Action: s3:GetObject
- Resource: !Join ['', ['arn:aws:s3:::', !Ref 'WebsiteBucket', /*]]
-
- WebsiteCloudfront:
- Type: AWS::CloudFront::Distribution
- DependsOn:
- - WebsiteBucket
- Properties:
- DistributionConfig:
- Comment: Cloudfront Distribution pointing to S3 bucket
- Origins:
- - DomainName: !Select [2, !Split ["/", !GetAtt WebsiteBucket.WebsiteURL]]
- Id: S3Origin
- CustomOriginConfig:
- HTTPPort: '80'
- HTTPSPort: '443'
- OriginProtocolPolicy: http-only
- Enabled: true
- HttpVersion: 'http2'
- DefaultRootObject: index.html
- DefaultCacheBehavior:
- AllowedMethods:
- - GET
- - HEAD
- Compress: true
- TargetOriginId: S3Origin
- ForwardedValues:
- QueryString: true
- Cookies:
- Forward: none
- ViewerProtocolPolicy: redirect-to-https
- PriceClass: PriceClass_All
-
- SecretsTable:
- Type: AWS::DynamoDB::Table
- Properties:
- TableName: ADFSSecretData
- AttributeDefinitions:
- -
- AttributeName: email
- AttributeType: S
- KeySchema:
- -
- AttributeName: email
- KeyType: HASH
- ProvisionedThroughput:
- ReadCapacityUnits: 5
- WriteCapacityUnits: 5
-
- RequestSecretDataExecutionRole:
- Type: AWS::IAM::Role
- Properties:
- RoleName: SecretDataLambda
- AssumeRolePolicyDocument:
- Version: 2012-10-17
- Statement:
- -
- Effect: Allow
- Principal:
- Service:
- - lambda.amazonaws.com
- Action:
- - "sts:AssumeRole"
- ManagedPolicyArns:
- - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
- Policies:
- -
- PolicyName: SecretDataPolicy
- PolicyDocument:
- Version: 2012-10-17
- Statement:
- -
- Effect: Allow
- Action:
- - dynamodb:Scan
- Resource: !GetAtt SecretsTable.Arn
-
- RequestSecretDataFunction:
- Type: AWS::Lambda::Function
- Properties:
- FunctionName: ListSecretData
- Runtime: nodejs6.10
- Role: !GetAtt RequestSecretDataExecutionRole.Arn
- Timeout: 5
- MemorySize: 128
- Handler: index.handler
- Code:
- ZipFile: >
- const AWS = require('aws-sdk');
-
- const ddb = new AWS.DynamoDB.DocumentClient();
-
- exports.handler = (event, context, callback) => {
- if (!event.requestContext.authorizer) {
- errorResponse('Authorization not configured', context.awsRequestId, callback);
- return;
- }
-
- console.log('Received event: ', event);
-
- listSecrets(event.requestContext.authorizer.claims.email).then((data) => {
- // You can use the callback function to provide a return value from your Node.js
- // Lambda functions. The first parameter is used for failed invocations. The
- // second parameter specifies the result data of the invocation.
-
- // Because this Lambda function is called by an API Gateway proxy integration
- // the result object must use the following structure.
- callback(null, {
- statusCode: 200,
- body: JSON.stringify(data.Items),
- headers: {
- 'Access-Control-Allow-Origin': '*',
- },
- });
- }).catch((err) => {
- console.error(err);
-
- // If there is an error during processing, catch it and return
- // from the Lambda function successfully. Specify a 500 HTTP status
- // code and provide an error message in the body. This will provide a
- // more meaningful error response to the end client.
- errorResponse(err.message, context.awsRequestId, callback)
- });
- };
-
- function listSecrets(username) {
- return ddb.scan({
- ExpressionAttributeValues: {
- ":u": username
- },
- FilterExpression: 'email = :u',
- TableName: 'ADFSSecretData',
- }).promise();
- }
-
- function errorResponse(errorMessage, awsRequestId, callback) {
- callback(null, {
- statusCode: 500,
- body: JSON.stringify({
- Error: errorMessage,
- Reference: awsRequestId,
- }),
- headers: {
- 'Access-Control-Allow-Origin': '*',
- },
- });
- }
-
-Outputs:
- BucketName:
- Value: !Ref 'WebsiteBucket'
- Description: Name of S3 bucket to hold website content
- CloudfrontEndpoint:
- Value: !GetAtt [WebsiteCloudfront, DomainName]
- Description: Endpoint for Cloudfront distribution
diff --git a/prerequisitesv3.yaml b/prerequisitesv3.yaml
deleted file mode 100644
index 029ed0f..0000000
--- a/prerequisitesv3.yaml
+++ /dev/null
@@ -1,273 +0,0 @@
-AWSTemplateFormatVersion: '2010-09-09'
-Description: Creates an S3 bucket and CloudFormation configured for hosting a static website
-
-Parameters:
- BucketName:
- Type: String
- Description: The name for the bucket hosting your website, e.g. 'datamanager-yourname'
-
-Resources:
- WebsiteBucket:
- Type: AWS::S3::Bucket
- Properties:
- BucketName: !Ref BucketName
- AccessControl: PublicRead
- WebsiteConfiguration:
- IndexDocument: index.html
- DeletionPolicy: Retain
-
- WebsiteBucketPolicy:
- Type: AWS::S3::BucketPolicy
- Properties:
- Bucket: !Ref 'WebsiteBucket'
- PolicyDocument:
- Statement:
- - Sid: PublicReadForGetBucketObjects
- Effect: Allow
- Principal: '*'
- Action: s3:GetObject
- Resource: !Join ['', ['arn:aws:s3:::', !Ref 'WebsiteBucket', /*]]
-
- WebsiteCloudfront:
- Type: AWS::CloudFront::Distribution
- DependsOn:
- - WebsiteBucket
- Properties:
- DistributionConfig:
- Comment: Cloudfront Distribution pointing to S3 bucket
- Origins:
- - DomainName: !Select [2, !Split ["/", !GetAtt WebsiteBucket.WebsiteURL]]
- Id: S3Origin
- CustomOriginConfig:
- HTTPPort: '80'
- HTTPSPort: '443'
- OriginProtocolPolicy: http-only
- Enabled: true
- HttpVersion: 'http2'
- DefaultRootObject: index.html
- DefaultCacheBehavior:
- AllowedMethods:
- - GET
- - HEAD
- Compress: true
- TargetOriginId: S3Origin
- ForwardedValues:
- QueryString: true
- Cookies:
- Forward: none
- ViewerProtocolPolicy: redirect-to-https
- PriceClass: PriceClass_All
-
- SecretsTable:
- Type: AWS::DynamoDB::Table
- Properties:
- TableName: ADFSSecretData
- AttributeDefinitions:
- -
- AttributeName: email
- AttributeType: S
- KeySchema:
- -
- AttributeName: email
- KeyType: HASH
- ProvisionedThroughput:
- ReadCapacityUnits: 5
- WriteCapacityUnits: 5
-
- RequestSecretDataExecutionRole:
- Type: AWS::IAM::Role
- Properties:
- RoleName: SecretDataLambda
- AssumeRolePolicyDocument:
- Version: 2012-10-17
- Statement:
- -
- Effect: Allow
- Principal:
- Service:
- - lambda.amazonaws.com
- Action:
- - "sts:AssumeRole"
- ManagedPolicyArns:
- - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
- Policies:
- -
- PolicyName: SecretDataPolicy
- PolicyDocument:
- Version: 2012-10-17
- Statement:
- -
- Effect: Allow
- Action:
- - dynamodb:Scan
- Resource: !GetAtt SecretsTable.Arn
-
- RequestSecretDataFunction:
- Type: AWS::Lambda::Function
- Properties:
- FunctionName: ListSecretData
- Runtime: nodejs6.10
- Role: !GetAtt RequestSecretDataExecutionRole.Arn
- Timeout: 5
- MemorySize: 128
- Handler: index.handler
- Code:
- ZipFile: >
- const AWS = require('aws-sdk');
-
- const ddb = new AWS.DynamoDB.DocumentClient();
-
- exports.handler = (event, context, callback) => {
- if (!event.requestContext.authorizer) {
- errorResponse('Authorization not configured', context.awsRequestId, callback);
- return;
- }
-
- console.log('Received event: ', event);
-
- listSecrets(event.requestContext.authorizer.claims.email).then((data) => {
- // You can use the callback function to provide a return value from your Node.js
- // Lambda functions. The first parameter is used for failed invocations. The
- // second parameter specifies the result data of the invocation.
-
- // Because this Lambda function is called by an API Gateway proxy integration
- // the result object must use the following structure.
- callback(null, {
- statusCode: 200,
- body: JSON.stringify(data.Items),
- headers: {
- 'Access-Control-Allow-Origin': '*',
- },
- });
- }).catch((err) => {
- console.error(err);
-
- // If there is an error during processing, catch it and return
- // from the Lambda function successfully. Specify a 500 HTTP status
- // code and provide an error message in the body. This will provide a
- // more meaningful error response to the end client.
- errorResponse(err.message, context.awsRequestId, callback)
- });
- };
-
- function listSecrets(username) {
- return ddb.scan({
- ExpressionAttributeValues: {
- ":u": username
- },
- FilterExpression: 'email = :u',
- TableName: 'ADFSSecretData',
- }).promise();
- }
-
- function errorResponse(errorMessage, awsRequestId, callback) {
- callback(null, {
- statusCode: 500,
- body: JSON.stringify({
- Error: errorMessage,
- Reference: awsRequestId,
- }),
- headers: {
- 'Access-Control-Allow-Origin': '*',
- },
- });
- }
-
- WildRydesApi:
- Type: AWS::ApiGateway::RestApi
- Properties:
- Name: WildRydes
- Body:
- swagger: 2.0
- info:
- version: 1.0.0
- title: WildRydes
- paths:
- /ride:
- post:
- description: Requests a new ride
- consumes:
- - application/json
- produces:
- - application/json
- responses:
- "200":
- description: "200 response"
- headers:
- Access-Control-Allow-Origin:
- type: "string"
- x-amazon-apigateway-integration:
- responses:
- default:
- statusCode: 200
- responseParameters:
- method.response.header.Access-Control-Allow-Origin: "'*'"
- uri:
- Fn::Join:
- - ""
- - - "arn:aws:apigateway:"
- - !Ref AWS::Region
- - ":lambda:path/2015-03-31/functions/"
- - !GetAtt RequestSecretDataFunction.Arn
- - "/invocations"
- passthroughBehavior: "when_no_match"
- httpMethod: "POST"
- contentHandling: "CONVERT_TO_TEXT"
- type: "aws_proxy"
- options:
- responses:
- "200":
- description: "200 response"
- schema:
- $ref: "#/definitions/Empty"
- headers:
- Access-Control-Allow-Origin:
- type: "string"
- Access-Control-Allow-Methods:
- type: "string"
- Access-Control-Allow-Headers:
- type: "string"
- x-amazon-apigateway-integration:
- responses:
- default:
- statusCode: "200"
- responseParameters:
- method.response.header.Access-Control-Allow-Methods: "'GET,OPTIONS,POST'"
- method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'"
- method.response.header.Access-Control-Allow-Origin: "'*'"
- requestTemplates:
- application/json: "{\"statusCode\": 200}"
- passthroughBehavior: "when_no_match"
- type: "mock"
-
- WildRydesApiDeployment:
- Type: AWS::ApiGateway::Deployment
- Properties:
- Description: Prod deployment for wild Rydes API
- RestApiId: !Ref WildRydesApi
- StageName: prod
-
- WildRydesFunctionPermissions:
- Type: AWS::Lambda::Permission
- Properties:
- Action: lambda:InvokeFunction
- FunctionName: !Ref RequestSecretDataFunction
- Principal: apigateway.amazonaws.com
- SourceArn:
- Fn::Join:
- - ""
- - - "arn:aws:execute-api:"
- - !Ref AWS::Region
- - ":"
- - !Ref AWS::AccountId
- - ":"
- - !Ref WildRydesApi
- - "/*"
-
-Outputs:
- BucketName:
- Value: !Ref 'WebsiteBucket'
- Description: Name of S3 bucket to hold website content
- CloudfrontEndpoint:
- Value: !GetAtt [WebsiteCloudfront, DomainName]
- Description: Endpoint for Cloudfront distribution