All Downloads are FREE. Search and download functionalities are using the official Maven repository.

no.unit.commons.apigateway.authentication.RequestAuthorizer Maven / Gradle / Ivy

There is a newer version: 1.41.0
Show newest version
package no.unit.commons.apigateway.authentication;

import static nva.commons.core.attempt.Try.attempt;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayCustomAuthorizerEvent;
import java.util.Collections;
import java.util.Optional;
import nva.commons.core.attempt.Failure;
import nva.commons.core.exceptions.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Abstract class for implementing a Request Authorizer. Implementation is based on the AWS examples found in the
 * following page : "https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer
 * .html".
 */

public abstract class RequestAuthorizer implements RequestHandler {

    public static final String EXECUTE_API_ACTION = "execute-api:Invoke";
    public static final String ALLOW_EFFECT = "Allow";
    public static final String ANY_RESOURCE = "*";
    public static final String ANY_HTTP_METHOD = ANY_RESOURCE;
    public static final String ALL_PATHS = ANY_RESOURCE;
    public static final String PATH_DELIMITER = "/";
    public static final int API_GATEWAY_IDENTIFIER_INDEX = 0;
    public static final int STAGE_INDEX = 1;
    public static final String AUTHORIZATION_HEADER = "Authorization";
    public static final String COULD_NOT_READ_PRINCIPAL_ID_ERROR = "Error while trying to get the principal ID.";
    private static final String DENY_EFFECT = "Deny";
    private static final Logger logger = LoggerFactory.getLogger(RequestAuthorizer.class);

    protected RequestAuthorizer() {

    }

    @Override
    public AuthorizerResponse handleRequest(APIGatewayCustomAuthorizerEvent input, Context context) {
        return attempt(() -> callerIsAllowedToPerformAction(input))
            .map(callerIsAuthorized -> formatPolicyResource(input.getMethodArn()))
            .map(this::createAllowAuthPolicy)
            .map(this::createResponse)
            .orElse(fail -> createForbiddenResponse(fail.getException()));
    }

    /**
     * This method can be overridden to change the template of the accessed resource. The resource that access will be
     * allowed to. It can contain wildcards.
     *
     * 

Example methodARN: * arn:aws:execute-api:eu-west-1:884807050265:2lcqynkwke/Prod/GET/some/path/to/resource Example output: * arn:aws:execute-api:eu-west-1:884807050265:2lcqynkwke/Prod\/*\/* *

* Another possible output is: "*" *

* * @param methodArn the method ARN as provided by the API gateway * @return a resource for the policy */ protected String formatPolicyResource(String methodArn) { String[] resourcePathComponents = methodArn.split(PATH_DELIMITER); String apiGateway = resourcePathComponents[API_GATEWAY_IDENTIFIER_INDEX]; String stage = resourcePathComponents[STAGE_INDEX]; return String.join(PATH_DELIMITER, apiGateway, stage, ANY_HTTP_METHOD, ALL_PATHS); } protected AuthPolicy createAllowAuthPolicy(String methodArn) { StatementElement statement = StatementElement.newBuilder() .withResource(methodArn) .withAction(EXECUTE_API_ACTION) .withEffect(ALLOW_EFFECT) .build(); return AuthPolicy.newBuilder().withStatement(Collections.singletonList(statement)).build(); } protected AuthPolicy createDenyAuthPolicy() { StatementElement statement = StatementElement.newBuilder() .withResource(ANY_RESOURCE) .withAction(EXECUTE_API_ACTION) .withEffect(DENY_EFFECT) .build(); return AuthPolicy.newBuilder().withStatement(Collections.singletonList(statement)).build(); } protected abstract String principalId(); protected abstract String fetchSecret() throws ForbiddenException; protected boolean callerIsAllowedToPerformAction(APIGatewayCustomAuthorizerEvent requestInfo) throws ForbiddenException { return Optional.ofNullable(requestInfo.getHeaders().get(AUTHORIZATION_HEADER)) .map(this::validateSecret) .filter(this::validationSucceeded) .orElseThrow(ForbiddenException::new); } private String readPrincipalId() { try { return principalId(); } catch (Exception e) { throw new RuntimeException(COULD_NOT_READ_PRINCIPAL_ID_ERROR, e); } } private AuthorizerResponse createForbiddenResponse(Exception exception) { logger.warn(ExceptionUtils.stackTraceInSingleLine(exception)); return AuthorizerResponse .newBuilder() .withPrincipalId(readPrincipalId()) .withPolicyDocument(createDenyAuthPolicy()) .build(); } private Boolean validationSucceeded(Boolean check) { return check; } private boolean validateSecret(String clientSecret) { String correctSecret = attempt(this::fetchSecret).orElseThrow(this::logErrorAndThrowException); return clientSecret.equals(correctSecret); } private AuthorizerResponse createResponse(AuthPolicy authPolicy) { return AuthorizerResponse.newBuilder() .withPrincipalId(readPrincipalId()) .withPolicyDocument(authPolicy) .build(); } private RuntimeException logErrorAndThrowException(Failure failure) { logger.error(failure.getException().getMessage(), failure.getException()); return new RuntimeException(failure.getException()); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy