Skip to content

Commit

Permalink
Merge pull request #2033 from govuk-one-login/PYIC-6066
Browse files Browse the repository at this point in the history
PYIC-6066: use f2f vcs with pending return from evcs
  • Loading branch information
Sam Barker authored Jun 20, 2024
2 parents 1fa4aa5 + f87149a commit 02f9f9b
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
import java.util.Objects;
import java.util.Optional;

import static com.amazonaws.util.CollectionUtils.isNullOrEmpty;
import static uk.gov.di.ipv.core.library.config.CoreFeatureFlag.EVCS_READ_ENABLED;
import static uk.gov.di.ipv.core.library.config.CoreFeatureFlag.EVCS_WRITE_ENABLED;
import static uk.gov.di.ipv.core.library.config.CoreFeatureFlag.INHERITED_IDENTITY;
Expand All @@ -74,6 +75,7 @@
import static uk.gov.di.ipv.core.library.domain.ProfileType.OPERATIONAL_HMRC;
import static uk.gov.di.ipv.core.library.domain.VocabConstants.VOT_CLAIM_NAME;
import static uk.gov.di.ipv.core.library.enums.EvcsVCState.CURRENT;
import static uk.gov.di.ipv.core.library.enums.EvcsVCState.PENDING_RETURN;
import static uk.gov.di.ipv.core.library.helpers.LogHelper.LogField.LOG_MESSAGE_DESCRIPTION;
import static uk.gov.di.ipv.core.library.helpers.LogHelper.LogField.LOG_VOT;
import static uk.gov.di.ipv.core.library.helpers.RequestHelper.getIpAddress;
Expand All @@ -90,13 +92,16 @@
import static uk.gov.di.ipv.core.library.journeyuris.JourneyUris.JOURNEY_REPEAT_FRAUD_CHECK_PATH;
import static uk.gov.di.ipv.core.library.journeyuris.JourneyUris.JOURNEY_REPROVE_IDENTITY_PATH;
import static uk.gov.di.ipv.core.library.journeyuris.JourneyUris.JOURNEY_REUSE_PATH;
import static uk.gov.di.ipv.core.library.journeyuris.JourneyUris.JOURNEY_REUSE_WITH_STORE_PATH;

/** Check Existing Identity response Lambda */
public class CheckExistingIdentityHandler
implements RequestHandler<JourneyRequest, Map<String, Object>> {
private static final Logger LOGGER = LogManager.getLogger();

private static final JourneyResponse JOURNEY_REUSE = new JourneyResponse(JOURNEY_REUSE_PATH);
private static final JourneyResponse JOURNEY_REUSE_WITH_STORE =
new JourneyResponse(JOURNEY_REUSE_WITH_STORE_PATH);
private static final JourneyResponse JOURNEY_OPERATIONAL_PROFILE_REUSE =
new JourneyResponse(JOURNEY_OPERATIONAL_PROFILE_REUSE_PATH);
private static final JourneyResponse JOURNEY_IN_MIGRATION_REUSE =
Expand Down Expand Up @@ -185,6 +190,15 @@ public CheckExistingIdentityHandler() {
VcHelper.setConfigService(this.configService);
}

private record VerifiableCredentialBundle(
List<VerifiableCredential> credentials,
boolean isEvcsIdentity,
boolean isPendingEvcsIdentity) {
private boolean isF2fIdentity() {
return credentials.stream().anyMatch(vc -> vc.getCriId().equals(F2F.getId()));
}
}

@Override
@Tracing
@Logging(clearState = true)
Expand Down Expand Up @@ -229,12 +243,16 @@ private JourneyResponse getJourneyResponse(
AuditEventUser auditEventUser =
new AuditEventUser(userId, ipvSessionId, govukSigninJourneyId, ipAddress);

var vcs = getVerifiableCredentials(userId, clientOAuthSessionItem.getEvcsAccessToken());
var hasF2fVc = vcs.stream().anyMatch(vc -> vc.getCriId().equals(F2F.getId()));
var evcsAccessToken = clientOAuthSessionItem.getEvcsAccessToken();
var vcs = getVerifiableCredentials(userId, evcsAccessToken);
CriResponseItem f2fRequest = criResponseService.getFaceToFaceRequest(userId);
final boolean hasF2fVc = vcs.isF2fIdentity();
final boolean isF2FIncomplete = !Objects.isNull(f2fRequest) && !hasF2fVc;
final boolean isF2FComplete = !Objects.isNull(f2fRequest) && hasF2fVc;

final boolean isF2FComplete =
!Objects.isNull(f2fRequest)
&& hasF2fVc
&& (!configService.enabled(EVCS_READ_ENABLED)
|| vcs.isPendingEvcsIdentity);
var contraIndicators =
ciMitService.getContraIndicators(
clientOAuthSessionItem.getUserId(), govukSigninJourneyId, ipAddress);
Expand All @@ -258,7 +276,7 @@ private JourneyResponse getJourneyResponse(
}

// Check for credentials correlation failure
var areGpg45VcsCorrelated = userIdentityService.areVcsCorrelated(vcs);
var areGpg45VcsCorrelated = userIdentityService.areVcsCorrelated(vcs.credentials);

var profileMatchResponse =
checkForProfileMatch(
Expand Down Expand Up @@ -310,16 +328,25 @@ private JourneyResponse getJourneyResponse(
}

@Tracing
private List<VerifiableCredential> getVerifiableCredentials(
private VerifiableCredentialBundle getVerifiableCredentials(
String userId, String evcsAccessToken)
throws CredentialParseException, EvcsServiceException {
if (configService.enabled(EVCS_READ_ENABLED)) {
var vcs = evcsService.getVerifiableCredentials(userId, evcsAccessToken, CURRENT);
if (vcs != null && !vcs.isEmpty()) {
return vcs;
var vcs =
evcsService.getVerifiableCredentialsByState(
userId, evcsAccessToken, CURRENT, PENDING_RETURN);
var pendingReturnVcs = vcs.get(PENDING_RETURN);
// use pending return vcs to determine identity if available
if (!isNullOrEmpty(pendingReturnVcs)) {
return new VerifiableCredentialBundle(pendingReturnVcs, true, true);
}
var currentVcs = vcs.get(CURRENT);
if (!isNullOrEmpty(currentVcs)) {
return new VerifiableCredentialBundle(currentVcs, true, false);
}
}
return verifiableCredentialService.getVcs(userId);
return new VerifiableCredentialBundle(
verifiableCredentialService.getVcs(userId), false, false);
}

@Tracing
Expand Down Expand Up @@ -362,15 +389,15 @@ private Optional<JourneyResponse> checkForProfileMatch(
ClientOAuthSessionItem clientOAuthSessionItem,
AuditEventUser auditEventUser,
String deviceInformation,
List<VerifiableCredential> vcs,
VerifiableCredentialBundle vcBundle,
boolean areGpg45VcsCorrelated)
throws ParseException, UnknownEvidenceTypeException, SqsException,
CredentialParseException, VerifiableCredentialException, EvcsServiceException {
// Check for attained vot from vtr
var strongestAttainedVotFromVtr =
getStrongestAttainedVotForVtr(
clientOAuthSessionItem.getVtr(),
vcs,
vcBundle.credentials,
auditEventUser,
deviceInformation,
areGpg45VcsCorrelated);
Expand All @@ -382,7 +409,7 @@ private Optional<JourneyResponse> checkForProfileMatch(
strongestAttainedVotFromVtr.get(),
ipvSessionItem,
clientOAuthSessionItem,
vcs,
vcBundle,
auditEventUser,
deviceInformation));
}
Expand Down Expand Up @@ -446,7 +473,7 @@ private JourneyResponse buildReuseResponse(
Vot attainedVot,
IpvSessionItem ipvSessionItem,
ClientOAuthSessionItem clientOAuthSessionItem,
List<VerifiableCredential> vcs,
VerifiableCredentialBundle vcBundle,
AuditEventUser auditEventUser,
String deviceInformation)
throws SqsException, VerifiableCredentialException, EvcsServiceException {
Expand All @@ -455,11 +482,11 @@ private JourneyResponse buildReuseResponse(
String evcsAccessToken = clientOAuthSessionItem.getEvcsAccessToken();
if (configService.enabled(REPEAT_FRAUD_CHECK)
&& attainedVot.getProfileType() == GPG45
&& !hasCurrentFraudVc(vcs)) {
&& !hasCurrentFraudVc(vcBundle.credentials)) {
LOGGER.info(LogHelper.buildLogMessage("Expired fraud VC found"));
sessionCredentialsService.persistCredentials(
allVcsExceptFraud(vcs), auditEventUser.getSessionId(), false);
migrateCredentialsToEVCS(userId, vcs, evcsAccessToken);
allVcsExceptFraud(vcBundle.credentials), auditEventUser.getSessionId(), false);
migrateCredentialsToEVCS(userId, vcBundle, evcsAccessToken);
return JOURNEY_REPEAT_FRAUD_CHECK;
}

Expand All @@ -474,7 +501,7 @@ private JourneyResponse buildReuseResponse(
boolean isCurrentlyMigrating = ipvSessionItem.isInheritedIdentityReceivedThisSession();

sessionCredentialsService.persistCredentials(
VcHelper.filterVCBasedOnProfileType(vcs, OPERATIONAL_HMRC),
VcHelper.filterVCBasedOnProfileType(vcBundle.credentials, OPERATIONAL_HMRC),
auditEventUser.getSessionId(),
isCurrentlyMigrating);

Expand All @@ -484,18 +511,21 @@ private JourneyResponse buildReuseResponse(
}

sessionCredentialsService.persistCredentials(
VcHelper.filterVCBasedOnProfileType(vcs, attainedVot.getProfileType()),
VcHelper.filterVCBasedOnProfileType(
vcBundle.credentials, attainedVot.getProfileType()),
auditEventUser.getSessionId(),
false);
migrateCredentialsToEVCS(userId, vcs, evcsAccessToken);
return JOURNEY_REUSE;
migrateCredentialsToEVCS(userId, vcBundle, evcsAccessToken);

return vcBundle.isPendingEvcsIdentity ? JOURNEY_REUSE_WITH_STORE : JOURNEY_REUSE;
}

private void migrateCredentialsToEVCS(
String userId, List<VerifiableCredential> credentials, String evcsAccessToken)
String userId, VerifiableCredentialBundle vcBundle, String evcsAccessToken)
throws EvcsServiceException, VerifiableCredentialException {
if (configService.enabled(EVCS_WRITE_ENABLED)) {
evcsMigrationService.migrateExistingIdentity(userId, credentials, evcsAccessToken);
if (configService.enabled(EVCS_WRITE_ENABLED) && !vcBundle.isEvcsIdentity) {
evcsMigrationService.migrateExistingIdentity(
userId, vcBundle.credentials, evcsAccessToken);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@
import uk.gov.di.ipv.core.library.domain.cimitvc.Mitigation;
import uk.gov.di.ipv.core.library.enums.EvcsVCState;
import uk.gov.di.ipv.core.library.enums.Vot;
import uk.gov.di.ipv.core.library.exception.EvcsServiceException;
import uk.gov.di.ipv.core.library.exceptions.ConfigException;
import uk.gov.di.ipv.core.library.exceptions.CredentialParseException;
import uk.gov.di.ipv.core.library.exceptions.HttpResponseExceptionWithErrorBody;
import uk.gov.di.ipv.core.library.exceptions.UnrecognisedCiException;
import uk.gov.di.ipv.core.library.exceptions.VerifiableCredentialException;
import uk.gov.di.ipv.core.library.gpg45.Gpg45ProfileEvaluator;
Expand All @@ -65,7 +67,7 @@
import uk.gov.di.ipv.core.library.verifiablecredential.service.VerifiableCredentialService;

import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -94,6 +96,7 @@
import static uk.gov.di.ipv.core.library.domain.Cri.F2F;
import static uk.gov.di.ipv.core.library.domain.Cri.HMRC_MIGRATION;
import static uk.gov.di.ipv.core.library.domain.VocabConstants.VOT_CLAIM_NAME;
import static uk.gov.di.ipv.core.library.enums.EvcsVCState.PENDING_RETURN;
import static uk.gov.di.ipv.core.library.enums.Vot.P2;
import static uk.gov.di.ipv.core.library.fixtures.TestFixtures.EC_PRIVATE_KEY_JWK;
import static uk.gov.di.ipv.core.library.fixtures.VcFixtures.EXPIRED_M1A_EXPERIAN_FRAUD_VC;
Expand All @@ -117,6 +120,7 @@
import static uk.gov.di.ipv.core.library.journeyuris.JourneyUris.JOURNEY_REPEAT_FRAUD_CHECK_PATH;
import static uk.gov.di.ipv.core.library.journeyuris.JourneyUris.JOURNEY_REPROVE_IDENTITY_PATH;
import static uk.gov.di.ipv.core.library.journeyuris.JourneyUris.JOURNEY_REUSE_PATH;
import static uk.gov.di.ipv.core.library.journeyuris.JourneyUris.JOURNEY_REUSE_WITH_STORE_PATH;

@ExtendWith(MockitoExtension.class)
class CheckExistingIdentityHandlerTest {
Expand All @@ -132,6 +136,8 @@ class CheckExistingIdentityHandlerTest {
public static final String EVCS_TEST_TOKEN = "evcsTestToken";
private static List<VerifiableCredential> VCS_FROM_STORE;
private static final JourneyResponse JOURNEY_REUSE = new JourneyResponse(JOURNEY_REUSE_PATH);
private static final JourneyResponse JOURNEY_REUSE_WITH_STORE =
new JourneyResponse(JOURNEY_REUSE_WITH_STORE_PATH);
private static final JourneyResponse JOURNEY_OP_PROFILE_REUSE =
new JourneyResponse(JOURNEY_OPERATIONAL_PROFILE_REUSE_PATH);
private static final JourneyResponse JOURNEY_IN_MIGRATION_REUSE =
Expand Down Expand Up @@ -243,28 +249,38 @@ public void reuseSetup() {
@Test
void shouldUseEvcsServiceWhenEnabled() throws Exception {
when(configService.enabled(EVCS_READ_ENABLED)).thenReturn(true);
when(mockEvcsService.getVerifiableCredentials(any(), any(), any(EvcsVCState.class)))
.thenReturn(List.of(gpg45Vc, vcHmrcMigration()));
when(mockEvcsService.getVerifiableCredentialsByState(
any(), any(), any(EvcsVCState.class), any(EvcsVCState.class)))
.thenReturn(Map.of(PENDING_RETURN, List.of(gpg45Vc, vcHmrcMigration())));

checkExistingIdentityHandler.handleRequest(event, context);

verify(clientOAuthSessionDetailsService, times(1)).getClientOAuthSession(any());
verify(mockEvcsService, times(1))
.getVerifiableCredentials(TEST_USER_ID, EVCS_TEST_TOKEN, EvcsVCState.CURRENT);
.getVerifiableCredentialsByState(
TEST_USER_ID,
EVCS_TEST_TOKEN,
EvcsVCState.CURRENT,
EvcsVCState.PENDING_RETURN);
verify(mockVerifiableCredentialService, never()).getVcs(TEST_USER_ID);
}

@Test
void shouldUseVcServiceWhenEvcsServiceWhenAndReturnsEmpty() throws Exception {
when(configService.enabled(EVCS_READ_ENABLED)).thenReturn(true);
when(mockEvcsService.getVerifiableCredentials(any(), any(), any(EvcsVCState.class)))
.thenReturn(new ArrayList<VerifiableCredential>());
when(mockEvcsService.getVerifiableCredentialsByState(
any(), any(), any(EvcsVCState.class), any(EvcsVCState.class)))
.thenReturn(new HashMap<EvcsVCState, List<VerifiableCredential>>());

checkExistingIdentityHandler.handleRequest(event, context);

verify(clientOAuthSessionDetailsService, times(1)).getClientOAuthSession(any());
verify(mockEvcsService, times(1))
.getVerifiableCredentials(TEST_USER_ID, EVCS_TEST_TOKEN, EvcsVCState.CURRENT);
.getVerifiableCredentialsByState(
TEST_USER_ID,
EVCS_TEST_TOKEN,
EvcsVCState.CURRENT,
EvcsVCState.PENDING_RETURN);
verify(mockVerifiableCredentialService, times(1)).getVcs(TEST_USER_ID);
}

Expand Down Expand Up @@ -311,6 +327,32 @@ void shouldReturnJourneyReuseResponseIfScoresSatisfyM1AGpg45Profile_alsoStoreVcs
TEST_USER_ID, List.of(gpg45Vc, hmrcMigrationVC), EVCS_TEST_TOKEN);
}

@Test
void shouldReturnJourneyReuseUpdateResponseIfVcIsF2fAndHasPendingReturnInEvcs()
throws CredentialParseException, EvcsServiceException,
HttpResponseExceptionWithErrorBody, VerifiableCredentialException {
when(configService.enabled(EVCS_READ_ENABLED)).thenReturn(true);

when(mockEvcsService.getVerifiableCredentialsByState(
any(), any(), any(EvcsVCState.class), any(EvcsVCState.class)))
.thenReturn(Map.of(PENDING_RETURN, List.of(vcF2fM1a())));

when(criResponseService.getFaceToFaceRequest(any())).thenReturn(new CriResponseItem());
when(gpg45ProfileEvaluator.getFirstMatchingProfile(
any(), eq(P2.getSupportedGpg45Profiles())))
.thenReturn(Optional.of(Gpg45Profile.M1A));
when(userIdentityService.areVcsCorrelated(any())).thenReturn(true);

JourneyResponse journeyResponse =
toResponseClass(
checkExistingIdentityHandler.handleRequest(event, context),
JourneyResponse.class);

assertEquals(JOURNEY_REUSE_WITH_STORE, journeyResponse);
// pending vcs should not be migrated
verify(mockEvcsMigrationService, never()).migrateExistingIdentity(any(), any(), any());
}

@Test
void shouldReturnJourneyReuseResponseIfScoresSatisfyM1BGpg45Profile() throws Exception {
when(gpg45ProfileEvaluator.getFirstMatchingProfile(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ states:
reuse:
targetJourney: REUSE_EXISTING_IDENTITY
targetState: START
reuse-with-store:
targetJourney: REUSE_EXISTING_IDENTITY
targetState: REUSE_WITH_STORE_IDENTITY
operational-profile-reuse:
targetJourney: OPERATIONAL_PROFILE_REUSE
targetState: START
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,33 @@ states:
next:
targetState: UPDATE_DETAILS_PAGE

REUSE_WITH_STORE_IDENTITY:
events:
next:
targetState: UPDATE_IDENTITY_BEFORE_IDENTITY_REUSE_PAGE

# Journey states

STORE_NEW_IDENTITY:
response:
type: process
lambda: store-identity
lambdaInput:
identityType: NEW
events:
error:
targetJourney: TECHNICAL_ERROR
targetState: ERROR
identity-stored:
targetState: IDENTITY_REUSE_PAGE
checkFeatureFlag:
ticfCriBeta:
targetState: CRI_TICF_BEFORE_REUSE
deleteDetailsEnabled:
targetState: IDENTITY_REUSE_PAGE_TEST
coiEnabled:
targetState: IDENTITY_REUSE_PAGE_COI

CRI_TICF_BEFORE_REUSE:
response:
type: process
Expand Down
Loading

0 comments on commit 02f9f9b

Please sign in to comment.