Skip to content

Commit

Permalink
PYIC-6243: Add CheckMobileAppVcReceiptHandler
Browse files Browse the repository at this point in the history
  • Loading branch information
MikeCollingwood committed Oct 29, 2024
1 parent 39c0c33 commit 971d51e
Show file tree
Hide file tree
Showing 13 changed files with 636 additions and 23 deletions.
87 changes: 87 additions & 0 deletions deploy/template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1172,6 +1172,93 @@ Resources:
FilterPattern: ""
LogGroupName: !Ref ProcessMobileAppCallbackLogGroup

CheckMobileAppVcReceiptFunction:
Type: AWS::Serverless::Function
DependsOn:
- "CheckMobileAppVcReceiptLogGroup"
Properties:
# checkov:skip=CKV_AWS_115: We do not have enough data to allocate the concurrent execution allowance per function.
# checkov:skip=CKV_AWS_116: Lambdas invoked via API Gateway do not support Dead Letter Queues.
# checkov:skip=CKV_AWS_117: Lambdas will migrate to our own VPC in future work.
FunctionName: !Sub "check-mobile-app-vc-receipt-${Environment}"
Handler: uk.gov.di.ipv.core.checkmobileappvcreceipt.CheckMobileAppVcReceiptHandler::handleRequest
Runtime: java17
PackageType: Zip
CodeUri: ../lambdas/check-mobile-app-vc-receipt
MemorySize: 2048
Tracing: Active
Environment:
# checkov:skip=CKV_AWS_173: These environment variables do not require encryption.
Variables:
ENVIRONMENT: !Sub "${Environment}"
POWERTOOLS_SERVICE_NAME: !Sub check-mobile-app-vc-receipt-${Environment}
IPV_SESSIONS_TABLE_NAME: !Ref SessionsTable
CRI_RESPONSE_TABLE_NAME: !Ref CRIResponseTable
CLIENT_OAUTH_SESSIONS_TABLE_NAME: !Ref ClientOAuthSessionsTable
VpcConfig:
SubnetIds:
- Fn::ImportValue: !Sub ${VpcStackName}-ProtectedSubnetIdA
- Fn::ImportValue: !Sub ${VpcStackName}-ProtectedSubnetIdB
SecurityGroupIds:
- !GetAtt LambdaSecurityGroup.GroupId
Policies:
- VPCAccessPolicy: { }
- KMSDecryptPolicy:
KeyId: !Ref DynamoDBKmsKey
- DynamoDBReadPolicy:
TableName: !Ref SessionsTable
- DynamoDBReadPolicy:
TableName: !Ref ClientOAuthSessionsTable
- DynamoDBReadPolicy:
TableName: !Ref CRIResponseTable
- SSMParameterReadPolicy:
ParameterName: !Sub ${Environment}/core/*
- Statement:
- Sid: EnforceStayinSpecificVpc
Effect: Allow
Action:
- 'lambda:CreateFunction'
- 'lambda:UpdateFunctionConfiguration'
Resource:
- "*"
Condition:
StringEquals:
"lambda:VpcIds":
- Fn::ImportValue: !Sub ${VpcStackName}-VpcId
Events:
IPVCoreCheckMobileAppVcReceipt:
Type: Api
Properties:
RestApiId: !Ref IPVCorePrivateAPI
Path: /app/check-vc-receipt
Method: GET
AutoPublishAlias: live

CheckMobileAppVcReceiptFunctionTestingApiPermission:
Type: AWS::Lambda::Permission
Condition: IsTestApiEnv
Properties:
Action: "lambda:InvokeFunction"
FunctionName: !Ref CheckMobileAppVcReceiptFunction.Alias
Principal: "apigateway.amazonaws.com"
SourceArn: !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${IPVCoreInternalTestingApi}/*/GET/app/check-vc-receipt"

CheckMobileAppVcReceiptLogGroup:
Type: AWS::Logs::LogGroup
# checkov:skip=CKV_AWS_158: No need for customer managed keys for short lived logs
Properties:
RetentionInDays: 30
LogGroupName: !Sub "/aws/lambda/check-mobile-app-vc-receipt-${Environment}"

CheckMobileAppVcReceiptLogGroupSubscriptionFilter:
Type: AWS::Logs::SubscriptionFilter
Condition: IsSubscriptionEnviroment
Properties:
DestinationArn: "arn:aws:logs:eu-west-2:885513274347:destination:csls_cw_logs_destination_prodpython"
FilterPattern: ""
LogGroupName: !Ref CheckMobileAppVcReceiptLogGroup


BuildUserIdentityFunction:
Type: AWS::Serverless::Function
DependsOn:
Expand Down
49 changes: 49 additions & 0 deletions lambdas/check-mobile-app-vc-receipt/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
plugins {
id "java"
id "idea"
id "jacoco"
alias libs.plugins.postCompileWeaving
}

dependencies {

implementation libs.bundles.awsLambda,
project(":libs:common-services"),
project(":libs:cri-response-service"),
project(':libs:journey-uris'),
project(":libs:verifiable-credentials")

aspect libs.powertoolsLogging,
libs.powertoolsTracing,
libs.aspectj

compileOnly libs.lombok
annotationProcessor libs.lombok

testImplementation libs.junitJupiter,
libs.mockitoJunit,
libs.wiremock,
project(path: ':libs:common-services', configuration: 'tests')

testRuntimeOnly libs.junitPlatform
}

java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

test {
// Configures environment variable to avoid initialization of AWS X-Ray segments for each tests
environment "LAMBDA_TASK_ROOT", "handler"
useJUnitPlatform ()
finalizedBy jacocoTestReport
exclude 'uk/gov/di/ipv/core/checkmobileappvcreceipt/pact/**'
}

jacocoTestReport {
dependsOn test
reports {
xml.required.set(true)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package uk.gov.di.ipv.core.checkmobileappvcreceipt;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import com.nimbusds.oauth2.sdk.util.StringUtils;
import org.apache.http.HttpStatus;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import software.amazon.lambda.powertools.logging.Logging;
import software.amazon.lambda.powertools.tracing.Tracing;
import uk.gov.di.ipv.core.checkmobileappvcreceipt.dto.CheckMobileAppVcReceiptRequest;
import uk.gov.di.ipv.core.checkmobileappvcreceipt.exception.InvalidCheckMobileAppVcReceiptRequestException;
import uk.gov.di.ipv.core.library.annotations.ExcludeFromGeneratedCoverageReport;
import uk.gov.di.ipv.core.library.domain.Cri;
import uk.gov.di.ipv.core.library.domain.ErrorResponse;
import uk.gov.di.ipv.core.library.exceptions.ClientOauthSessionNotFoundException;
import uk.gov.di.ipv.core.library.exceptions.CredentialParseException;
import uk.gov.di.ipv.core.library.exceptions.IpvSessionNotFoundException;
import uk.gov.di.ipv.core.library.helpers.ApiGatewayResponseGenerator;
import uk.gov.di.ipv.core.library.helpers.LogHelper;
import uk.gov.di.ipv.core.library.helpers.RequestHelper;
import uk.gov.di.ipv.core.library.service.ClientOAuthSessionDetailsService;
import uk.gov.di.ipv.core.library.service.ConfigService;
import uk.gov.di.ipv.core.library.service.CriResponseService;
import uk.gov.di.ipv.core.library.service.IpvSessionService;
import uk.gov.di.ipv.core.library.service.exception.InvalidCriResponseException;
import uk.gov.di.ipv.core.library.verifiablecredential.service.VerifiableCredentialService;

import static uk.gov.di.ipv.core.library.helpers.LogHelper.buildErrorMessage;

public class CheckMobileAppVcReceiptHandler
implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
private static final Logger LOGGER = LogManager.getLogger();
private final ConfigService configService;
private final IpvSessionService ipvSessionService;
private final ClientOAuthSessionDetailsService clientOAuthSessionDetailsService;
private final CriResponseService criResponseService;
private final VerifiableCredentialService verifiableCredentialService;

public CheckMobileAppVcReceiptHandler(
ConfigService configService,
IpvSessionService ipvSessionService,
ClientOAuthSessionDetailsService clientOAuthSessionDetailsService,
CriResponseService criResponseService,
VerifiableCredentialService verifiableCredentialService) {
this.configService = configService;
this.ipvSessionService = ipvSessionService;
this.clientOAuthSessionDetailsService = clientOAuthSessionDetailsService;
this.criResponseService = criResponseService;
this.verifiableCredentialService = verifiableCredentialService;
}

@ExcludeFromGeneratedCoverageReport
public CheckMobileAppVcReceiptHandler() {
configService = ConfigService.create();
ipvSessionService = new IpvSessionService(configService);
clientOAuthSessionDetailsService = new ClientOAuthSessionDetailsService(configService);
criResponseService = new CriResponseService(configService);
this.verifiableCredentialService = new VerifiableCredentialService(configService);
}

@Override
@Tracing
@Logging(clearState = true)
public APIGatewayProxyResponseEvent handleRequest(
APIGatewayProxyRequestEvent input, Context context) {
try {
var callbackRequest = parseCallbackRequest(input);

var status = getStatus(callbackRequest);

return ApiGatewayResponseGenerator.proxyResponse(status);
} catch (InvalidCheckMobileAppVcReceiptRequestException e) {
LOGGER.info(buildErrorMessage(e.getErrorResponse()));
return ApiGatewayResponseGenerator.proxyResponse(HttpStatus.SC_BAD_REQUEST);
} catch (IpvSessionNotFoundException e) {
LOGGER.info(buildErrorMessage(ErrorResponse.IPV_SESSION_NOT_FOUND));
return ApiGatewayResponseGenerator.proxyResponse(HttpStatus.SC_BAD_REQUEST);
} catch (ClientOauthSessionNotFoundException e) {
LOGGER.info(buildErrorMessage(e.getErrorResponse()));
return ApiGatewayResponseGenerator.proxyResponse(HttpStatus.SC_BAD_REQUEST);
} catch (InvalidCriResponseException e) {
LOGGER.info(buildErrorMessage(e.getErrorResponse()));
return ApiGatewayResponseGenerator.proxyResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR);
} catch (CredentialParseException e) {
LOGGER.info(buildErrorMessage(ErrorResponse.FAILED_TO_PARSE_ISSUED_CREDENTIALS));
return ApiGatewayResponseGenerator.proxyResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR);
}
}

private CheckMobileAppVcReceiptRequest parseCallbackRequest(APIGatewayProxyRequestEvent input) {
return new CheckMobileAppVcReceiptRequest(
input.getHeaders().get("ipv-session-id"),
input.getHeaders().get("ip-address"),
input.getHeaders().get("txma-audit-encoded"),
RequestHelper.getFeatureSet(input.getHeaders()));
}

private int getStatus(CheckMobileAppVcReceiptRequest callbackRequest)
throws InvalidCheckMobileAppVcReceiptRequestException, IpvSessionNotFoundException,
ClientOauthSessionNotFoundException, InvalidCriResponseException,
CredentialParseException {
// Validate callback sessions
validateSessionId(callbackRequest);

// Get/ set session items/ config
var ipvSessionItem = ipvSessionService.getIpvSession(callbackRequest.getIpvSessionId());
var clientOAuthSessionItem =
clientOAuthSessionDetailsService.getClientOAuthSession(
ipvSessionItem.getClientOAuthSessionId());
var userId = clientOAuthSessionItem.getUserId();
configService.setFeatureSet(callbackRequest.getFeatureSet());

// Attach variables to logs
LogHelper.attachGovukSigninJourneyIdToLogs(
clientOAuthSessionItem.getGovukSigninJourneyId());
LogHelper.attachIpvSessionIdToLogs(callbackRequest.getIpvSessionId());
LogHelper.attachFeatureSetToLogs(callbackRequest.getFeatureSet());
LogHelper.attachComponentId(configService);

// Retrieve and check cri response
var criResponse = criResponseService.getCriResponseItem(userId, Cri.DCMAW_ASYNC);

if (criResponse == null) {
return HttpStatus.SC_INTERNAL_SERVER_ERROR;
}

if (!CriResponseService.STATUS_PENDING.equals(criResponse.getStatus())
|| verifiableCredentialService.getVc(userId, Cri.DCMAW_ASYNC.toString()) != null) {
return HttpStatus.SC_OK;
}

return HttpStatus.SC_NOT_FOUND;
}

private void validateSessionId(CheckMobileAppVcReceiptRequest callbackRequest)
throws InvalidCheckMobileAppVcReceiptRequestException {
var ipvSessionId = callbackRequest.getIpvSessionId();

if (StringUtils.isBlank(ipvSessionId)) {
throw new InvalidCheckMobileAppVcReceiptRequestException(
ErrorResponse.MISSING_IPV_SESSION_ID);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package uk.gov.di.ipv.core.checkmobileappvcreceipt.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import uk.gov.di.ipv.core.library.annotations.ExcludeFromGeneratedCoverageReport;

import java.util.List;

@ExcludeFromGeneratedCoverageReport
@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class CheckMobileAppVcReceiptRequest {
private String ipvSessionId;
private String ipAddress;
private String deviceInformation;
private List<String> featureSet;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package uk.gov.di.ipv.core.checkmobileappvcreceipt.exception;

import lombok.Getter;
import uk.gov.di.ipv.core.library.domain.ErrorResponse;

@Getter
public class InvalidCheckMobileAppVcReceiptRequestException extends Exception {
private final ErrorResponse errorResponse;

public InvalidCheckMobileAppVcReceiptRequestException(ErrorResponse errorResponse) {
super(errorResponse.getMessage());
this.errorResponse = errorResponse;
}
}
Loading

0 comments on commit 971d51e

Please sign in to comment.