Skip to content

Commit

Permalink
v1.8.4 (#33)
Browse files Browse the repository at this point in the history
* improved access denied error message

* Fixed authentication when using SAML
  • Loading branch information
mfriesen committed Aug 29, 2022
1 parent 2203e3c commit 1ac7fa1
Show file tree
Hide file tree
Showing 13 changed files with 353 additions and 60 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ jobs:
npm install openapi-generator -g
alias openapi-generator=openapi-generator-cli
openapi-generator --version
- name: Build with Gradle
uses: gradle/gradle-build-action@0d13054264b0bb894ded474f08ebb30921341cee
with:
arguments: build --info
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Execute Gradle build
run: ./gradlew build --info
- name: Set AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ The following are AWS CloudFormation scripts that can be used to install FormKiQ

| AWS Region | Install Link |
| ------------- | -------------|
| us-east-1 | [Install FormKiQ Core in US-EAST-1 region](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=formkiq-core-prod&templateURL=https://formkiq-core-distribution-us-east-1.s3.amazonaws.com/1.8.3/template.yaml)
| us-east-2 | [Install FormKiQ Core in US-EAST-2 region](https://console.aws.amazon.com/cloudformation/home?region=us-east-2#/stacks/new?stackName=formkiq-core-prod&templateURL=https://formkiq-core-distribution-us-east-2.s3.amazonaws.com/1.8.3/template.yaml)
| ca-central-1| [Install FormKiQ Core in CA-CENTRAL-1 region](https://console.aws.amazon.com/cloudformation/home?region=ca-central-1#/stacks/new?stackName=formkiq-core-prod&templateURL=https://formkiq-core-distribution-ca-central-1.s3.amazonaws.com/1.8.3/template.yaml)
| eu-central-1| [Install FormKiQ Core in EU-CENTRAL-1 region](https://console.aws.amazon.com/cloudformation/home?region=eu-central-1#/stacks/new?stackName=formkiq-core-prod&templateURL=https://formkiq-core-distribution-eu-central-1.s3.amazonaws.com/1.8.3/template.yaml)
| us-east-1 | [Install FormKiQ Core in US-EAST-1 region](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=formkiq-core-prod&templateURL=https://formkiq-core-distribution-us-east-1.s3.amazonaws.com/1.8.4/template.yaml)
| us-east-2 | [Install FormKiQ Core in US-EAST-2 region](https://console.aws.amazon.com/cloudformation/home?region=us-east-2#/stacks/new?stackName=formkiq-core-prod&templateURL=https://formkiq-core-distribution-us-east-2.s3.amazonaws.com/1.8.4/template.yaml)
| ca-central-1| [Install FormKiQ Core in CA-CENTRAL-1 region](https://console.aws.amazon.com/cloudformation/home?region=ca-central-1#/stacks/new?stackName=formkiq-core-prod&templateURL=https://formkiq-core-distribution-ca-central-1.s3.amazonaws.com/1.8.4/template.yaml)
| eu-central-1| [Install FormKiQ Core in EU-CENTRAL-1 region](https://console.aws.amazon.com/cloudformation/home?region=eu-central-1#/stacks/new?stackName=formkiq-core-prod&templateURL=https://formkiq-core-distribution-eu-central-1.s3.amazonaws.com/1.8.4/template.yaml)

## Architecture

Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def moduleName = "formkiq-core"
repositories { mavenCentral() }

allprojects {
version = '1.8.3'
version = '1.8.4'
ext.awsCognitoVersion = '1.4.1'
group = 'com.formkiq.stacks'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,18 @@
*/
public abstract class AbstractRestApiRequestHandler implements RequestStreamHandler {

/** {@link ApiAuthorizerType}. */
private static ApiAuthorizerType authorizerType = ApiAuthorizerType.COGNITO;

/**
* Set AuthorizerType.
*
* @param type {@link ApiAuthorizerType}
*/
protected static void setAuthorizerType(final ApiAuthorizerType type) {
authorizerType = type;
}

/** {@link Gson}. */
protected Gson gson = GsonUtil.getInstance();

Expand Down Expand Up @@ -378,7 +390,7 @@ private void processApiGatewayRequest(final LambdaLogger logger,
final ApiGatewayRequestEvent event, final AwsServiceCache awsServices,
final OutputStream output) throws IOException {

ApiAuthorizer authorizer = new ApiAuthorizer(event);
ApiAuthorizer authorizer = new ApiAuthorizer(event, authorizerType);

try {

Expand Down Expand Up @@ -440,7 +452,8 @@ private ApiRequestHandlerResponse processRequest(final LambdaLogger logger,
ApiGatewayRequestHandler handler = findRequestHandler(urlMap, method, resource);

if (isCheckAccess(method) && !hasAccess(method, event.getPath(), handler, authorizer)) {
throw new ForbiddenException("Access Denied");
String s = String.format("fkq access denied (%s)", authorizer.accessSummary());
throw new ForbiddenException(s);
}

return callHandlerMethod(logger, method, event, authorizer, handler);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ public class ApiAuthorizer {
/** The suffix for the 'readonly' Cognito group. */
private static final String COGNITO_READ_SUFFIX = "_read";

/** {@link ApiAuthorizerType}. */
private ApiAuthorizerType authentication;

/** {@link ApiGatewayRequestEvent}. */
private ApiGatewayRequestEvent event;

Expand All @@ -57,39 +60,53 @@ public class ApiAuthorizer {

/** Request SiteId. */
private String siteId = null;

/** Calling User Arn. */
private String userArn = null;
/** {@link List} SiteIds. */
private List<String> siteIds = Collections.emptyList();
/** Calling User Arn. */
private String userArn = null;

/**
* constructor.
*
* @param requestEvent {@link ApiGatewayRequestEvent}
* @param userAuthentication {@link ApiAuthorizerType}
*/
public ApiAuthorizer(final ApiGatewayRequestEvent requestEvent) {
public ApiAuthorizer(final ApiGatewayRequestEvent requestEvent,
final ApiAuthorizerType userAuthentication) {

this.event = requestEvent;
this.authentication = userAuthentication;

List<String> cognitoGroups = getCognitoGroups();
List<String> cognitoGroups = getCognitoGroups(this.authentication);
this.siteIds = getPossibleSiteId();

this.userArn = getUserRoleArn();
this.isUserAdmin = cognitoGroups.contains(COGNITO_ADMIN_GROUP);
this.isUserAdmin = cognitoGroups.contains(COGNITO_ADMIN_GROUP)
|| cognitoGroups.contains(COGNITO_ADMIN_GROUP.toLowerCase());
this.siteId = getSiteIdFromQuery();
this.isUserReadAccess =
this.siteId != null ? cognitoGroups.contains(this.siteId + COGNITO_READ_SUFFIX) : false;
this.isUserWriteAccess = this.siteId != null ? cognitoGroups.contains(this.siteId) : false;
}

/**
* Return Access Summary.
*
* @return {@link String}
*/
public String accessSummary() {
List<String> groups = getCognitoGroups(this.authentication);
return !groups.isEmpty() ? "groups: " + String.join(",", groups) : "no groups";
}

/**
* Get the Cognito Groups of the calling Cognito Username.
*
*
* @param userAuthentication {@link ApiAuthorizerType}
* @return {@link List} {@link String}
*/
@SuppressWarnings("unchecked")
private List<String> getCognitoGroups() {
private List<String> getCognitoGroups(final ApiAuthorizerType userAuthentication) {

List<String> groups = Collections.emptyList();

Expand All @@ -114,6 +131,14 @@ private List<String> getCognitoGroups() {
}
}

if (ApiAuthorizerType.SAML.equals(userAuthentication)) {
groups = groups.stream().map(g -> g.replaceAll("^formkiq_", "")).collect(Collectors.toList());
}

if (groups.isEmpty()) {
groups = Arrays.asList("default");
}

return groups;
}

Expand All @@ -124,7 +149,7 @@ private List<String> getCognitoGroups() {
*/
private List<String> getPossibleSiteId() {

List<String> cognitoGroups = getCognitoGroups();
List<String> cognitoGroups = getCognitoGroups(this.authentication);
cognitoGroups.remove(COGNITO_ADMIN_GROUP);

List<String> sites = new ArrayList<>(cognitoGroups.stream().map(
Expand All @@ -142,7 +167,7 @@ private List<String> getPossibleSiteId() {
*/
public List<String> getReadSiteIds() {

List<String> cognitoGroups = getCognitoGroups();
List<String> cognitoGroups = getCognitoGroups(this.authentication);
cognitoGroups.remove(COGNITO_ADMIN_GROUP);

List<String> sites = cognitoGroups.stream().filter(s -> s.endsWith(COGNITO_READ_SUFFIX))
Expand All @@ -152,23 +177,6 @@ public List<String> getReadSiteIds() {
return sites;
}

/**
* Get Read SiteIds.
*
* @return {@link List} {@link String}
*/
public List<String> getWriteSiteIds() {

List<String> cognitoGroups = getCognitoGroups();
cognitoGroups.remove(COGNITO_ADMIN_GROUP);

List<String> sites = cognitoGroups.stream().filter(s -> !s.endsWith(COGNITO_READ_SUFFIX))
.collect(Collectors.toList());
Collections.sort(sites);

return sites;
}

/**
* Get Site Id.
*
Expand All @@ -178,15 +186,6 @@ public String getSiteId() {
return this.siteId != null && !this.siteId.equals(DEFAULT_SITE_ID) ? this.siteId : null;
}

/**
* Get Site Id, including DEFAULT.
*
* @return {@link String}
*/
public String getSiteIdIncludeDefault() {
return this.siteId;
}

/**
* Set siteId in {@link ApiGatewayRequestEvent}, if user is in only 1 CognitoGroup.
*
Expand All @@ -211,8 +210,13 @@ private String getSiteIdFromQuery() {
return site;
}

private boolean isIamCaller() {
return this.isCallerAssumeRole() || this.isCallerIamUser();
/**
* Get Site Id, including DEFAULT.
*
* @return {@link String}
*/
public String getSiteIdIncludeDefault() {
return this.siteId;
}

/**
Expand Down Expand Up @@ -249,6 +253,23 @@ private String getUserRoleArn() {
return arn;
}

/**
* Get Read SiteIds.
*
* @return {@link List} {@link String}
*/
public List<String> getWriteSiteIds() {

List<String> cognitoGroups = getCognitoGroups(this.authentication);
cognitoGroups.remove(COGNITO_ADMIN_GROUP);

List<String> sites = cognitoGroups.stream().filter(s -> !s.endsWith(COGNITO_READ_SUFFIX))
.collect(Collectors.toList());
Collections.sort(sites);

return sites;
}

/**
* Is {@link ApiGatewayRequestEvent} is an assumed role.
*
Expand All @@ -267,6 +288,10 @@ public boolean isCallerIamUser() {
return this.userArn != null && this.userArn.contains(":user/");
}

private boolean isIamCaller() {
return this.isCallerAssumeRole() || this.isCallerIamUser();
}

/**
* Is User Admin.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* MIT License
*
* Copyright (c) 2018 - 2020 FormKiQ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.formkiq.aws.services.lambda;

/**
* Types of API Authentication.
*
*/
public enum ApiAuthorizerType {
/** Cognito Authentication. */
COGNITO,
/** SAML Authetnication. */
SAML
}
Loading

0 comments on commit 1ac7fa1

Please sign in to comment.