From 40c60699742a1e444a4958a3942487d7b38ace80 Mon Sep 17 00:00:00 2001 From: sapessi Date: Wed, 19 Apr 2017 16:08:35 -0700 Subject: [PATCH 1/7] First import of User Pool Authorizers support to address issue #24 --- .../model/ApiGatewayAuthorizerContext.java | 42 ++++- .../proxy/internal/model/AwsProxyRequest.java | 4 +- .../model/CognitoAuthorizerClaims.java | 165 ++++++++++++++++++ .../servlet/AwsHttpServletRequest.java | 1 + .../testutils/AwsProxyRequestBuilder.java | 17 +- .../ApiGatewayAuthorizerContextTest.java | 78 +++++++++ .../model/CognitoAuthorizerClaimsTest.java | 108 ++++++++++++ 7 files changed, 406 insertions(+), 9 deletions(-) create mode 100644 aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaims.java create mode 100644 aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/model/ApiGatewayAuthorizerContextTest.java create mode 100644 aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaimsTest.java diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/ApiGatewayAuthorizerContext.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/ApiGatewayAuthorizerContext.java index 294e96de8..0a3b5c4e4 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/ApiGatewayAuthorizerContext.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/ApiGatewayAuthorizerContext.java @@ -12,27 +12,57 @@ */ package com.amazonaws.serverless.proxy.internal.model; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; + import java.util.HashMap; +import java.util.Map; + /** - * Custom authorizer context object for the API Gateway request context. + * Context object used for custom authorizers and Cognito User Pool authorizers. + *

+ * Custom authorizers populate the principalId field. All other custom values + * returned by the authorizer are accessible via the getContextValue method. + *

+ *

+ * Cognito User Pool authorizers populate the

claims
object. + *

*/ -public class ApiGatewayAuthorizerContext extends HashMap { +public class ApiGatewayAuthorizerContext { + + private Map contextProperties = new HashMap<>(); + private String principalId; + private CognitoAuthorizerClaims claims; //------------------------------------------------------------- // Methods - Getter/Setter //------------------------------------------------------------- public String getPrincipalId() { - return get("principalId"); + return principalId; } - public void setPrincipalId(String principalId) { - put("principalId", principalId); + this.principalId = principalId; } + @JsonAnyGetter public String getContextValue(String key) { - return get(key); + return contextProperties.get(key); + } + + @JsonAnySetter + public void setContextValue(String key, String value) { + contextProperties.put(key, value); + } + + + public CognitoAuthorizerClaims getClaims() { + return claims; + } + + public void setClaims(CognitoAuthorizerClaims claims) { + this.claims = claims; } } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/AwsProxyRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/AwsProxyRequest.java index 6e9cda4c0..39ae6c586 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/AwsProxyRequest.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/AwsProxyRequest.java @@ -17,7 +17,7 @@ import java.util.Map; /** - * Default implementation of the request object from an API GAteway AWS_PROXY integration + * Default implementation of the request object from an API Gateway AWS_PROXY integration */ public class AwsProxyRequest { @@ -152,7 +152,7 @@ public boolean isBase64Encoded() { } - public void setBase64Encoded(boolean base64Encoded) { + public void setIsBase64Encoded(boolean base64Encoded) { isBase64Encoded = base64Encoded; } } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaims.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaims.java new file mode 100644 index 000000000..dd380c137 --- /dev/null +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaims.java @@ -0,0 +1,165 @@ +/* + * Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.proxy.internal.model; + + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + + +/** + * This object represents the claims property in the authorizer context of a request. The claims object is normally populated + * by a Cognito User Pool authorizer and contains the following fields: + *
+ * "claims": {
+ *     "sub": "42df3b02-29f1-4779-a3e5-eff92ff280b2",
+ *     "aud": "2k3no2j1rjjbqaskc4bk0ub29b",
+ *     "email_verified": "true",
+ *     "token_use": "id",
+ *     "auth_time": "1492467169",
+ *     "iss": "https://cognito-idp.us-east-2.amazonaws.com/us-east-2_Adx5ZHePg",
+ *     "cognito:username": "sapessi",
+ *     "exp": "Mon Apr 17 23:12:49 UTC 2017",
+ *     "iat": "Mon Apr 17 22:12:49 UTC 2017",
+ *     "email": "bulianis@amazon.com"
+ * }
+ * 
+ */ +public class CognitoAuthorizerClaims { + // Mon Apr 17 23:12:49 UTC 2017 + static final DateTimeFormatter TOKEN_DATE_FORMATTER = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss zzz yyyy"); + + @JsonProperty(value = "sub") + private String subject; + @JsonProperty(value = "aud") + private String audience; + @JsonProperty(value = "iss") + private String issuer; + @JsonProperty(value = "token_use") + private String tokenUse; + @JsonProperty(value = "cognito:username") + private String username; + private String email; + @JsonProperty(value = "email_verified") + private boolean emailVerified; + @JsonProperty(value = "auth_time") + private Long authTime; + private String exp; + private String iat; + + private String getSubject() { return this.subject; } + + public void setSubject(String subject) { + this.subject = subject; + } + + + public String getAudience() { + return audience; + } + + + public void setAudience(String audience) { + this.audience = audience; + } + + + public String getIssuer() { + return issuer; + } + + + public void setIssuer(String issuer) { + this.issuer = issuer; + } + + + public String getTokenUse() { + return tokenUse; + } + + + public void setTokenUse(String tokenUse) { + this.tokenUse = tokenUse; + } + + + public String getUsername() { + return username; + } + + + public void setUsername(String username) { + this.username = username; + } + + + public String getEmail() { + return email; + } + + + public void setEmail(String email) { + this.email = email; + } + + + public boolean isEmailVerified() { + return emailVerified; + } + + + public void setEmailVerified(boolean emailVerified) { + this.emailVerified = emailVerified; + } + + + public Long getAuthTime() { + return authTime; + } + + + public void setAuthTime(Long authTime) { + this.authTime = authTime; + } + + + public String getExp() { + return exp; + } + + + public void setExp(String expiration) { + this.exp = expiration; + } + + public ZonedDateTime getExpirationTime() { + return ZonedDateTime.from(TOKEN_DATE_FORMATTER.parse(getExp())); + } + + + public String getIat() { + return iat; + } + + + public void setIat(String issuedAt) { + this.iat = issuedAt; + } + + public ZonedDateTime getIssueTime() { + return ZonedDateTime.from((TOKEN_DATE_FORMATTER.parse(getIat()))); + } +} \ No newline at end of file diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java index 582728637..eb55a337a 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java @@ -280,6 +280,7 @@ protected List> parseHeaderValue(String headerValue) { if (headerValue == null) { return values; } + int entryCounter = 1; for (String kv : headerValue.split(HEADER_VALUE_SEPARATOR)) { String[] kvSplit = kv.split(HEADER_KEY_VALUE_SEPARATOR); diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java index a1aa3eee0..402c1d91c 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java @@ -21,6 +21,9 @@ import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; + +import java.io.File; +import java.io.IOException; import java.util.HashMap; /** @@ -156,7 +159,7 @@ public AwsProxyRequestBuilder authorizerContextValue(String key, String value) { if (this.request.getRequestContext().getAuthorizer() == null) { this.request.getRequestContext().setAuthorizer(new ApiGatewayAuthorizerContext()); } - this.request.getRequestContext().getAuthorizer().put(key, value); + this.request.getRequestContext().getAuthorizer().setContextValue(key, value); return this; } @@ -209,6 +212,18 @@ public AwsProxyRequestBuilder serverName(String serverName) { return this; } + public AwsProxyRequestBuilder fromJsonString(String jsonContent) + throws IOException { + request = mapper.readValue(jsonContent, AwsProxyRequest.class); + return this; + } + + public AwsProxyRequestBuilder fromJsonPath(String filePath) + throws IOException { + request = mapper.readValue(new File(filePath), AwsProxyRequest.class); + return this; + } + public AwsProxyRequest build() { return this.request; } diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/model/ApiGatewayAuthorizerContextTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/model/ApiGatewayAuthorizerContextTest.java new file mode 100644 index 000000000..75b80d816 --- /dev/null +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/model/ApiGatewayAuthorizerContextTest.java @@ -0,0 +1,78 @@ +package com.amazonaws.serverless.proxy.internal.model; + +import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; + +import org.junit.Test; + +import java.io.IOException; + +import static org.junit.Assert.*; + +public class ApiGatewayAuthorizerContextTest { + private static final String FIELD_NAME_1 = "CUSTOM_FIELD_1"; + private static final String FIELD_NAME_2 = "CUSTOM_FIELD_2"; + private static final String FIELD_VALUE_1 = "VALUE_1"; + private static final String FIELD_VALUE_2 = "VALUE_2"; + private static final String PRINCIPAL = "xxxxx"; + + private static final String AUTHORIZER_REQUEST = "{\n" + + " \"resource\": \"/restaurants\",\n" + + " \"path\": \"/restaurants\",\n" + + " \"httpMethod\": \"GET\",\n" + + " \"headers\": {\n" + + " \"Accept\": \"*/*\",\n" + + " \"Authorization\": \"eyJraWQiOiJKSm9VQUtrRThcL3NTU3Rwa3dPZTFWN2dvak1xS0k1NU8zTzB4WVgwMGNRdz0iLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiI0MmRmM2IwMi0yOWYxLTQ3NzktYTNlNS1lZmY5MmZmMjgwYjIiLCJhdWQiOiIyazNubzJqMXJqamJxYXNrYzRiazB1YjI5YiIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJ0b2tlbl91c2UiOiJpZCIsImF1dGhfdGltZSI6MTQ5MjQ2NzE2OSwiaXNzIjoiaHR0cHM6XC9cL2NvZ25pdG8taWRwLnVzLWVhc3QtMi5hbWF6b25hd3MuY29tXC91cy1lYXN0LTJfQWR4NVpIZVBnIiwiY29nbml0bzp1c2VybmFtZSI6InNhcGVzc2kiLCJleHAiOjE0OTI0NzA3NjksImlhdCI6MTQ5MjQ2NzE2OSwiZW1haWwiOiJidWxpYW5pc0BhbWF6b24uY29tIn0.aTODUMNib_pQhad1aWTHrlz7kwA5QkcvZptcbLFY5BuNqpr9zsK14EhHRvmvflK4MMQaxCE5Cxa9joR9g-HCmmF1usZhXO4Q2iyEWcBk0whjn3CnC55k6yEuMv6y9krts0YHSamsRkhW7wnCpuLmk2KgzHTfyt6oQ1qbg9QE8l9LRhjCHLnujlLIQaG9p9UfJVf-uGSg1k_bCyzl48lqkc7LDwqDZCHXGf1RYRQLg5jphXF_tjByDk_0t9Ah7pX2nFwl0SUz74enG8emq58g4pemeVekb9Mw0wyD-B5TWeGVs_nvmC3q4jgxMyJy3Xq4Ggd9qSgIN_Khdg3Q26F2bA\",\n" + + " \"CloudFront-Forwarded-Proto\": \"https\"\n" + + " },\n" + + " \"queryStringParameters\": null,\n" + + " \"pathParameters\": null,\n" + + " \"stageVariables\": null,\n" + + " \"requestContext\": {\n" + + " \"accountId\": \"XXXXXXXXXXXXXX\",\n" + + " \"resourceId\": \"xxxxx\",\n" + + " \"stage\": \"dev\",\n" + + " \"authorizer\": {\n" + + " \"principalId\": \"" + PRINCIPAL + "\",\n" + + " \"" + FIELD_NAME_1 + "\": \"" + FIELD_VALUE_1 + "\"," + + " \"" + FIELD_NAME_2 + "\": \"" + FIELD_VALUE_2 + "\"" + + " },\n" + + " \"requestId\": \"ad0a33ba-23bc-11e7-9b7d-235a67eb05bd\",\n" + + " \"identity\": {\n" + + " \"cognitoIdentityPoolId\": null,\n" + + " \"accountId\": null,\n" + + " \"cognitoIdentityId\": null,\n" + + " \"caller\": null,\n" + + " \"apiKey\": null,\n" + + " \"sourceIp\": \"54.240.196.171\",\n" + + " \"accessKey\": null,\n" + + " \"cognitoAuthenticationType\": null,\n" + + " \"cognitoAuthenticationProvider\": null,\n" + + " \"userArn\": null,\n" + + " \"userAgent\": \"PostmanRuntime/3.0.1\",\n" + + " \"user\": null\n" + + " },\n" + + " \"resourcePath\": \"/restaurants\",\n" + + " \"httpMethod\": \"GET\",\n" + + " \"apiId\": \"xxxxxxxx\"\n" + + " },\n" + + " \"body\": null,\n" + + " \"isBase64Encoded\": false\n" + + "}"; + + @Test + public void authorizerContext_serialize_customValues() { + try { + AwsProxyRequest req = new AwsProxyRequestBuilder().fromJsonString(AUTHORIZER_REQUEST).build(); + + assertNotNull(req.getRequestContext().getAuthorizer().getContextValue(FIELD_NAME_1)); + assertNotNull(req.getRequestContext().getAuthorizer().getContextValue(FIELD_NAME_2)); + assertEquals(FIELD_VALUE_1, req.getRequestContext().getAuthorizer().getContextValue(FIELD_NAME_1)); + assertEquals(FIELD_VALUE_2, req.getRequestContext().getAuthorizer().getContextValue(FIELD_NAME_2)); + assertEquals(PRINCIPAL, req.getRequestContext().getAuthorizer().getPrincipalId()); + assertNull(req.getRequestContext().getAuthorizer().getContextValue("principalId")); + } catch (IOException e) { + e.printStackTrace(); + fail(); + } + } +} diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaimsTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaimsTest.java new file mode 100644 index 000000000..b1d68bfcf --- /dev/null +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaimsTest.java @@ -0,0 +1,108 @@ +package com.amazonaws.serverless.proxy.internal.model; + +import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; + +import org.junit.Test; + +import java.io.IOException; +import java.time.ZonedDateTime; + +import static org.junit.Assert.*; + +public class CognitoAuthorizerClaimsTest { + + private static final String USERNAME = "test_username"; + private static final String SUB = "42df3b02-29f1-4779-a3e5-eff92ff280b2"; + private static final String AUD = "2k3no2j1rjjbqaskc4bk0ub29b"; + private static final String EMAIL = "testemail@test.com"; + + private static final String EXP_TIME = "Mon Apr 17 23:12:49 UTC 2017"; + private static final String ISSUE_TIME = "Mon Apr 17 22:12:49 UTC 2017"; + + private static final String USER_POOLS_REQUEST = "{\n" + + " \"resource\": \"/restaurants\",\n" + + " \"path\": \"/restaurants\",\n" + + " \"httpMethod\": \"GET\",\n" + + " \"headers\": {\n" + + " \"Accept\": \"*/*\",\n" + + " \"Authorization\": \"eyJraWQiOiJKSm9VQUtrRThcL3NTU3Rwa3dPZTFWN2dvak1xS0k1NU8zTzB4WVgwMGNRdz0iLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiI0MmRmM2IwMi0yOWYxLTQ3NzktYTNlNS1lZmY5MmZmMjgwYjIiLCJhdWQiOiIyazNubzJqMXJqamJxYXNrYzRiazB1YjI5YiIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJ0b2tlbl91c2UiOiJpZCIsImF1dGhfdGltZSI6MTQ5MjQ2NzE2OSwiaXNzIjoiaHR0cHM6XC9cL2NvZ25pdG8taWRwLnVzLWVhc3QtMi5hbWF6b25hd3MuY29tXC91cy1lYXN0LTJfQWR4NVpIZVBnIiwiY29nbml0bzp1c2VybmFtZSI6InNhcGVzc2kiLCJleHAiOjE0OTI0NzA3NjksImlhdCI6MTQ5MjQ2NzE2OSwiZW1haWwiOiJidWxpYW5pc0BhbWF6b24uY29tIn0.aTODUMNib_pQhad1aWTHrlz7kwA5QkcvZptcbLFY5BuNqpr9zsK14EhHRvmvflK4MMQaxCE5Cxa9joR9g-HCmmF1usZhXO4Q2iyEWcBk0whjn3CnC55k6yEuMv6y9krts0YHSamsRkhW7wnCpuLmk2KgzHTfyt6oQ1qbg9QE8l9LRhjCHLnujlLIQaG9p9UfJVf-uGSg1k_bCyzl48lqkc7LDwqDZCHXGf1RYRQLg5jphXF_tjByDk_0t9Ah7pX2nFwl0SUz74enG8emq58g4pemeVekb9Mw0wyD-B5TWeGVs_nvmC3q4jgxMyJy3Xq4Ggd9qSgIN_Khdg3Q26F2bA\",\n" + + " \"CloudFront-Forwarded-Proto\": \"https\"\n" + + " },\n" + + " \"queryStringParameters\": null,\n" + + " \"pathParameters\": null,\n" + + " \"stageVariables\": null,\n" + + " \"requestContext\": {\n" + + " \"accountId\": \"XXXXXXXXXXXXXX\",\n" + + " \"resourceId\": \"xxxxx\",\n" + + " \"stage\": \"dev\",\n" + + " \"authorizer\": {\n" + + " \"claims\": {\n" + + " \"sub\": \"" + SUB + "\",\n" + + " \"aud\": \"" + AUD + "\",\n" + + " \"email_verified\": \"true\",\n" + + " \"token_use\": \"id\",\n" + + " \"auth_time\": \"1492467169\",\n" + + " \"iss\": \"https://cognito-idp.us-east-2.amazonaws.com/us-east-2_xxXXxxXX\",\n" + + " \"cognito:username\": \"" + USERNAME + "\",\n" + + " \"exp\": \"" + EXP_TIME + "\",\n" + + " \"iat\": \"" + ISSUE_TIME + "\",\n" + + " \"email\": \"" + EMAIL + "\"\n" + + " }\n" + + " },\n" + + " \"requestId\": \"ad0a33ba-23bc-11e7-9b7d-235a67eb05bd\",\n" + + " \"identity\": {\n" + + " \"cognitoIdentityPoolId\": null,\n" + + " \"accountId\": null,\n" + + " \"cognitoIdentityId\": null,\n" + + " \"caller\": null,\n" + + " \"apiKey\": null,\n" + + " \"sourceIp\": \"54.240.196.171\",\n" + + " \"accessKey\": null,\n" + + " \"cognitoAuthenticationType\": null,\n" + + " \"cognitoAuthenticationProvider\": null,\n" + + " \"userArn\": null,\n" + + " \"userAgent\": \"PostmanRuntime/3.0.1\",\n" + + " \"user\": null\n" + + " },\n" + + " \"resourcePath\": \"/restaurants\",\n" + + " \"httpMethod\": \"GET\",\n" + + " \"apiId\": \"xxxxxxxx\"\n" + + " },\n" + + " \"body\": null,\n" + + " \"isBase64Encoded\": false\n" + + "}"; + + + @Test + public void claims_serialize_validJsonString() { + try { + AwsProxyRequest req = new AwsProxyRequestBuilder().fromJsonString(USER_POOLS_REQUEST).build(); + + assertEquals(USERNAME, req.getRequestContext().getAuthorizer().getClaims().getUsername()); + assertEquals(EMAIL, req.getRequestContext().getAuthorizer().getClaims().getEmail()); + assertTrue(req.getRequestContext().getAuthorizer().getClaims().isEmailVerified()); + } catch (IOException e) { + e.printStackTrace(); + fail(); + } + } + + @Test + public void claims_dateParse_issueTime() { + try { + AwsProxyRequest req = new AwsProxyRequestBuilder().fromJsonString(USER_POOLS_REQUEST).build(); + + assertEquals(EXP_TIME, req.getRequestContext().getAuthorizer().getClaims().getExp()); + assertNotNull(req.getRequestContext().getAuthorizer().getClaims().getExp()); + + ZonedDateTime expTime = ZonedDateTime.from(CognitoAuthorizerClaims.TOKEN_DATE_FORMATTER.parse(EXP_TIME)); + ZonedDateTime issueTime = ZonedDateTime.from(CognitoAuthorizerClaims.TOKEN_DATE_FORMATTER.parse(ISSUE_TIME)); + assertEquals(expTime, req.getRequestContext().getAuthorizer().getClaims().getExpirationTime()); + + assertEquals(expTime, issueTime.plusHours(1)); + } catch (IOException e) { + e.printStackTrace(); + fail(); + } + } +} \ No newline at end of file From 4f1e773a667ada725590455793e5f47ebd5ea748 Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 20 Apr 2017 09:43:59 -0700 Subject: [PATCH 2/7] Bug fixes and comments Fixed the principal id in the JaxRs security context to read the subject property from the user pools authorizer claims. Fixed a bug in the Claims object (private getSubject method). Added some comments to the `ZonedDateTime` methods in the claims object. This should completely address #24. --- .../internal/jaxrs/AwsProxySecurityContext.java | 4 ++-- .../internal/model/CognitoAuthorizerClaims.java | 14 +++++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsProxySecurityContext.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsProxySecurityContext.java index cba3a79c4..8a61a95dd 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsProxySecurityContext.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsProxySecurityContext.java @@ -71,7 +71,7 @@ public Principal getUserPrincipal() { } else if (getAuthenticationScheme().equals(AUTH_SCHEME_AWS_IAM)) { return event.getRequestContext().getIdentity().getUserArn(); } else if (getAuthenticationScheme().equals(AUTH_SCHEME_COGNITO_POOL)) { - return event.getRequestContext().getIdentity().getCognitoIdentityId(); + return event.getRequestContext().getAuthorizer().getClaims().getSubject(); } return null; @@ -90,7 +90,7 @@ public boolean isSecure() { public String getAuthenticationScheme() { - if (event.getRequestContext().getIdentity().getCognitoAuthenticationType() != null) { + if (event.getRequestContext().getAuthorizer().getClaims() != null && event.getRequestContext().getAuthorizer().getClaims().getSubject() != null) { return AUTH_SCHEME_COGNITO_POOL; } else if (event.getRequestContext().getAuthorizer() != null) { return AUTH_SCHEME_CUSTOM; diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaims.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaims.java index dd380c137..d064db417 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaims.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaims.java @@ -59,7 +59,7 @@ public class CognitoAuthorizerClaims { private String exp; private String iat; - private String getSubject() { return this.subject; } + public String getSubject() { return this.subject; } public void setSubject(String subject) { this.subject = subject; @@ -145,6 +145,12 @@ public void setExp(String expiration) { this.exp = expiration; } + + /** + * Returns the expiration time for the token as a ZonedDateTime from the exp property + * of the token. + * @return The parsed expiration time for the token. + */ public ZonedDateTime getExpirationTime() { return ZonedDateTime.from(TOKEN_DATE_FORMATTER.parse(getExp())); } @@ -159,6 +165,12 @@ public void setIat(String issuedAt) { this.iat = issuedAt; } + + /** + * Returns the parsed issued time for the token as a ZonedDateTime object. This is taken from the iat + * property of the token. + * @return The parsed issue time of the token + */ public ZonedDateTime getIssueTime() { return ZonedDateTime.from((TOKEN_DATE_FORMATTER.parse(getIat()))); } From 1f46c7e1db62fd4529a6e4e1d116eb0bd2f672bd Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 20 Apr 2017 09:50:31 -0700 Subject: [PATCH 3/7] Fixed tests for #24 --- .../proxy/internal/jaxrs/AwsProxySecurityContext.java | 2 +- .../proxy/internal/testutils/AwsProxyRequestBuilder.java | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsProxySecurityContext.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsProxySecurityContext.java index 8a61a95dd..c6698d86c 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsProxySecurityContext.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsProxySecurityContext.java @@ -90,7 +90,7 @@ public boolean isSecure() { public String getAuthenticationScheme() { - if (event.getRequestContext().getAuthorizer().getClaims() != null && event.getRequestContext().getAuthorizer().getClaims().getSubject() != null) { + if (event.getRequestContext().getAuthorizer() != null && event.getRequestContext().getAuthorizer().getClaims() != null && event.getRequestContext().getAuthorizer().getClaims().getSubject() != null) { return AUTH_SCHEME_COGNITO_POOL; } else if (event.getRequestContext().getAuthorizer() != null) { return AUTH_SCHEME_CUSTOM; diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java index 402c1d91c..c56139208 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java @@ -16,6 +16,8 @@ import com.amazonaws.serverless.proxy.internal.model.ApiGatewayRequestContext; import com.amazonaws.serverless.proxy.internal.model.ApiGatewayRequestIdentity; import com.amazonaws.serverless.proxy.internal.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.internal.model.CognitoAuthorizerClaims; + import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -167,6 +169,12 @@ public AwsProxyRequestBuilder authorizerContextValue(String key, String value) { public AwsProxyRequestBuilder cognitoUserPool(String identityId) { this.request.getRequestContext().getIdentity().setCognitoAuthenticationType("POOL"); this.request.getRequestContext().getIdentity().setCognitoIdentityId(identityId); + if (this.request.getRequestContext().getAuthorizer() == null) { + this.request.getRequestContext().setAuthorizer(new ApiGatewayAuthorizerContext()); + } + this.request.getRequestContext().getAuthorizer().setClaims(new CognitoAuthorizerClaims()); + this.request.getRequestContext().getAuthorizer().getClaims().setSubject(identityId); + return this; } From 908d7cda06fb2f98d600dcf78fce6fe0929834b6 Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 20 Apr 2017 10:07:21 -0700 Subject: [PATCH 4/7] Adding travis build file --- .travis.yml | 4 ++++ .../proxy/internal/testutils/AwsProxyRequestBuilder.java | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..edc619500 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: java +jdk: + - oraclejdk8 +script: mvn install diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java index c56139208..065b28f9b 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java @@ -174,7 +174,7 @@ public AwsProxyRequestBuilder cognitoUserPool(String identityId) { } this.request.getRequestContext().getAuthorizer().setClaims(new CognitoAuthorizerClaims()); this.request.getRequestContext().getAuthorizer().getClaims().setSubject(identityId); - + return this; } From 655a8cff8adba4e0a65f3ba3cf02bd2c5ff83fa4 Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 20 Apr 2017 10:27:04 -0700 Subject: [PATCH 5/7] Adding build status to readme file --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e40a948f9..be43b25f9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Serverless Java container +# Serverless Java container [![Build Status](https://travis-ci.org/awslabs/aws-serverless-java-container.svg?branch=master)](https://travis-ci.org/awslabs/aws-serverless-java-container) The `aws-serverless-java-container` is collection of interfaces and their implementations that let you run Java application written with frameworks such as [Jersey](https://jersey.java.net/) or [Spark](http://sparkjava.com/) in [AWS Lambda](https://aws.amazon.com/lambda/). The library contains a core artifact called `aws-serverless-java-container-core` that defines the interfaces and base classes required as well as default implementation of the Java servlet `HttpServletRequest` and `HttpServletResponse`. From 5b0fa253ced8d239b2a22e83924b5bc1cd77743b Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 20 Apr 2017 10:53:57 -0700 Subject: [PATCH 6/7] Code cleanup and gitter icon in readme --- README.md | 2 +- .../model/ApiGatewayAuthorizerContext.java | 31 +++++++++++++------ .../model/CognitoAuthorizerClaims.java | 18 ++++++++++- 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index be43b25f9..73315719e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Serverless Java container [![Build Status](https://travis-ci.org/awslabs/aws-serverless-java-container.svg?branch=master)](https://travis-ci.org/awslabs/aws-serverless-java-container) +# Serverless Java container [![Build Status](https://travis-ci.org/awslabs/aws-serverless-java-container.svg?branch=master)](https://travis-ci.org/awslabs/aws-serverless-java-container) [![Help](http://img.shields.io/badge/help-gitter-E91E63.svg?style=flat-square)](https://gitter.im/awslabs/aws-serverless-java-container) The `aws-serverless-java-container` is collection of interfaces and their implementations that let you run Java application written with frameworks such as [Jersey](https://jersey.java.net/) or [Spark](http://sparkjava.com/) in [AWS Lambda](https://aws.amazon.com/lambda/). The library contains a core artifact called `aws-serverless-java-container-core` that defines the interfaces and base classes required as well as default implementation of the Java servlet `HttpServletRequest` and `HttpServletResponse`. diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/ApiGatewayAuthorizerContext.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/ApiGatewayAuthorizerContext.java index 0a3b5c4e4..cb51feef6 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/ApiGatewayAuthorizerContext.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/ApiGatewayAuthorizerContext.java @@ -31,37 +31,50 @@ */ public class ApiGatewayAuthorizerContext { + //------------------------------------------------------------- + // Variables - Private + //------------------------------------------------------------- + private Map contextProperties = new HashMap<>(); private String principalId; private CognitoAuthorizerClaims claims; + //------------------------------------------------------------- - // Methods - Getter/Setter + // Methods - Public //------------------------------------------------------------- - public String getPrincipalId() { - return principalId; - } - - public void setPrincipalId(String principalId) { - this.principalId = principalId; - } - @JsonAnyGetter public String getContextValue(String key) { return contextProperties.get(key); } + @JsonAnySetter public void setContextValue(String key, String value) { contextProperties.put(key, value); } + //------------------------------------------------------------- + // Methods - Getter/Setter + //------------------------------------------------------------- + + public String getPrincipalId() { + return principalId; + } + + + public void setPrincipalId(String principalId) { + this.principalId = principalId; + } + + public CognitoAuthorizerClaims getClaims() { return claims; } + public void setClaims(CognitoAuthorizerClaims claims) { this.claims = claims; } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaims.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaims.java index d064db417..954326238 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaims.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaims.java @@ -38,9 +38,19 @@ * */ public class CognitoAuthorizerClaims { + + //------------------------------------------------------------- + // Constants + //------------------------------------------------------------- + // Mon Apr 17 23:12:49 UTC 2017 static final DateTimeFormatter TOKEN_DATE_FORMATTER = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss zzz yyyy"); + + //------------------------------------------------------------- + // Variables - Private + //------------------------------------------------------------- + @JsonProperty(value = "sub") private String subject; @JsonProperty(value = "aud") @@ -59,8 +69,14 @@ public class CognitoAuthorizerClaims { private String exp; private String iat; + + //------------------------------------------------------------- + // Methods - Getter/Setter + //------------------------------------------------------------- + public String getSubject() { return this.subject; } + public void setSubject(String subject) { this.subject = subject; } @@ -174,4 +190,4 @@ public void setIat(String issuedAt) { public ZonedDateTime getIssueTime() { return ZonedDateTime.from((TOKEN_DATE_FORMATTER.parse(getIat()))); } -} \ No newline at end of file +} From 8f0fcaa28abb2b6d701cc1ee296f93df9a2aa408 Mon Sep 17 00:00:00 2001 From: sapessi Date: Fri, 21 Apr 2017 09:50:39 -0700 Subject: [PATCH 7/7] Minor changes from review * Removed unused variable * Removed methods parsing issue and expiration time for token * Changed exp and iat getters and setters to friendly names --- .../model/CognitoAuthorizerClaims.java | 55 +++++-------------- .../servlet/AwsHttpServletRequest.java | 2 +- .../model/CognitoAuthorizerClaimsTest.java | 12 ++-- 3 files changed, 22 insertions(+), 47 deletions(-) diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaims.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaims.java index 954326238..552644df6 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaims.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaims.java @@ -15,7 +15,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; -import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; @@ -31,22 +30,14 @@ * "auth_time": "1492467169", * "iss": "https://cognito-idp.us-east-2.amazonaws.com/us-east-2_Adx5ZHePg", * "cognito:username": "sapessi", - * "exp": "Mon Apr 17 23:12:49 UTC 2017", - * "iat": "Mon Apr 17 22:12:49 UTC 2017", + * "expiration": "Mon Apr 17 23:12:49 UTC 2017", + * "issuedAt": "Mon Apr 17 22:12:49 UTC 2017", * "email": "bulianis@amazon.com" * } * */ public class CognitoAuthorizerClaims { - //------------------------------------------------------------- - // Constants - //------------------------------------------------------------- - - // Mon Apr 17 23:12:49 UTC 2017 - static final DateTimeFormatter TOKEN_DATE_FORMATTER = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss zzz yyyy"); - - //------------------------------------------------------------- // Variables - Private //------------------------------------------------------------- @@ -66,8 +57,10 @@ public class CognitoAuthorizerClaims { private boolean emailVerified; @JsonProperty(value = "auth_time") private Long authTime; - private String exp; - private String iat; + @JsonProperty(value = "exp") + private String expiration; + @JsonProperty(value = "iat") + private String issuedAt; //------------------------------------------------------------- @@ -152,42 +145,22 @@ public void setAuthTime(Long authTime) { } - public String getExp() { - return exp; - } - - - public void setExp(String expiration) { - this.exp = expiration; - } - - - /** - * Returns the expiration time for the token as a ZonedDateTime from the exp property - * of the token. - * @return The parsed expiration time for the token. - */ - public ZonedDateTime getExpirationTime() { - return ZonedDateTime.from(TOKEN_DATE_FORMATTER.parse(getExp())); + public String getExpiration() { + return expiration; } - public String getIat() { - return iat; + public void setExpiration(String expiration) { + this.expiration = expiration; } - public void setIat(String issuedAt) { - this.iat = issuedAt; + public String getIssuedAt() { + return issuedAt; } - /** - * Returns the parsed issued time for the token as a ZonedDateTime object. This is taken from the iat - * property of the token. - * @return The parsed issue time of the token - */ - public ZonedDateTime getIssueTime() { - return ZonedDateTime.from((TOKEN_DATE_FORMATTER.parse(getIat()))); + public void setIssuedAt(String issuedAt) { + this.issuedAt = issuedAt; } } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java index eb55a337a..53106fd89 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java @@ -280,7 +280,7 @@ protected List> parseHeaderValue(String headerValue) { if (headerValue == null) { return values; } - int entryCounter = 1; + for (String kv : headerValue.split(HEADER_VALUE_SEPARATOR)) { String[] kvSplit = kv.split(HEADER_KEY_VALUE_SEPARATOR); diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaimsTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaimsTest.java index b1d68bfcf..980ee1882 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaimsTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaimsTest.java @@ -6,6 +6,7 @@ import java.io.IOException; import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import static org.junit.Assert.*; @@ -18,6 +19,7 @@ public class CognitoAuthorizerClaimsTest { private static final String EXP_TIME = "Mon Apr 17 23:12:49 UTC 2017"; private static final String ISSUE_TIME = "Mon Apr 17 22:12:49 UTC 2017"; + static final DateTimeFormatter TOKEN_DATE_FORMATTER = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss zzz yyyy"); private static final String USER_POOLS_REQUEST = "{\n" + " \"resource\": \"/restaurants\",\n" @@ -92,12 +94,12 @@ public void claims_dateParse_issueTime() { try { AwsProxyRequest req = new AwsProxyRequestBuilder().fromJsonString(USER_POOLS_REQUEST).build(); - assertEquals(EXP_TIME, req.getRequestContext().getAuthorizer().getClaims().getExp()); - assertNotNull(req.getRequestContext().getAuthorizer().getClaims().getExp()); + assertEquals(EXP_TIME, req.getRequestContext().getAuthorizer().getClaims().getExpiration()); + assertNotNull(req.getRequestContext().getAuthorizer().getClaims().getExpiration()); - ZonedDateTime expTime = ZonedDateTime.from(CognitoAuthorizerClaims.TOKEN_DATE_FORMATTER.parse(EXP_TIME)); - ZonedDateTime issueTime = ZonedDateTime.from(CognitoAuthorizerClaims.TOKEN_DATE_FORMATTER.parse(ISSUE_TIME)); - assertEquals(expTime, req.getRequestContext().getAuthorizer().getClaims().getExpirationTime()); + ZonedDateTime expTime = ZonedDateTime.from(TOKEN_DATE_FORMATTER.parse(EXP_TIME)); + ZonedDateTime issueTime = ZonedDateTime.from(TOKEN_DATE_FORMATTER.parse(ISSUE_TIME)); + assertEquals(expTime, ZonedDateTime.from(TOKEN_DATE_FORMATTER.parse(req.getRequestContext().getAuthorizer().getClaims().getExpiration()))); assertEquals(expTime, issueTime.plusHours(1)); } catch (IOException e) {