Skip to content

Commit

Permalink
Merge pull request #2430 from govuk-one-login/pyic-7380-reinstate-ide…
Browse files Browse the repository at this point in the history
…ntity

PYIC-7380: Reinstate identity for failed COI journeys
  • Loading branch information
Joe-Edwards-GDS authored Sep 13, 2024
2 parents 4f0171e + 7eaeeab commit 086418d
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,19 @@ Feature: Identity reuse update details failures
When I submit a 'continue' event
Then I get an OAuth response
When I use the OAuth response to get my identity
Then I get a 'P0' identity
Then I get a 'P2' identity
When I start a new 'medium-confidence' journey
Then I get a 'page-ipv-reuse' page response

@FastFollow
Scenario: Given name change - fail-with-no-ci from DCMAW
Given I activate the 'updateDetailsAccountDeletion' feature set
When I submit 'kenneth-passport-verification-zero' details to the CRI stub
Then I get an 'update-details-failed' page response
When I submit a 'continue' event
Then I get an OAuth response
When I use the OAuth response to get my identity
Then I get a 'P2' identity
When I start a new 'medium-confidence' journey
Then I get a 'page-ipv-reuse' page response

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ states:
next:
targetState: EVALUATE_GPG45_SCORES

START_NO_STORE:
events:
next:
targetState: EVALUATE_GPG45_SCORES_NO_STORE

# Parent states
CRI_TICF_STATE:
events:
Expand Down Expand Up @@ -43,6 +48,20 @@ states:
targetJourney: FAILED
targetState: FAILED

EVALUATE_GPG45_SCORES_NO_STORE:
response:
type: process
lambda: evaluate-gpg45-scores
events:
met:
targetState: CRI_TICF_BEFORE_RP_RETURN_NO_STORE
unmet:
targetJourney: FAILED
targetState: FAILED
vcs-not-correlated:
targetJourney: FAILED
targetState: FAILED

CRI_TICF_BEFORE_SUCCESS:
response:
type: process
Expand All @@ -52,6 +71,15 @@ states:
next:
targetState: STORE_IDENTITY_BEFORE_SUCCESS

CRI_TICF_BEFORE_RP_RETURN_NO_STORE:
response:
type: process
lambda: call-ticf-cri
parent: CRI_TICF_STATE
events:
next:
targetState: RETURN_TO_RP

STORE_IDENTITY_BEFORE_SUCCESS:
response:
type: process
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,10 +251,21 @@ states:
pageId: update-details-failed
events:
continue:
targetState: RETURN_TO_RP
targetState: REINSTATE_EXISTING_IDENTITY
delete:
targetState: DELETE_HANDOVER_PAGE

REINSTATE_EXISTING_IDENTITY:
response:
type: process
lambda: reset-session-identity
lambdaInput:
resetType: REINSTATE
events:
next:
targetJourney: EVALUATE_SCORES
targetState: START_NO_STORE

COULD_NOT_UPDATE_DETAILS_PAGE_RFC:
response:
type: page
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
import uk.gov.di.ipv.core.library.domain.JourneyErrorResponse;
import uk.gov.di.ipv.core.library.domain.JourneyResponse;
import uk.gov.di.ipv.core.library.domain.ProcessRequest;
import uk.gov.di.ipv.core.library.enums.SessionCredentialsResetType;
import uk.gov.di.ipv.core.library.exception.EvcsServiceException;
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.IpvSessionNotFoundException;
import uk.gov.di.ipv.core.library.exceptions.UnknownResetTypeException;
Expand All @@ -34,9 +34,12 @@
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.domain.Cri.F2F;
import static uk.gov.di.ipv.core.library.domain.ErrorResponse.FAILED_TO_PARSE_ISSUED_CREDENTIALS;
import static uk.gov.di.ipv.core.library.domain.ErrorResponse.IPV_SESSION_NOT_FOUND;
import static uk.gov.di.ipv.core.library.domain.ErrorResponse.UNKNOWN_RESET_TYPE;
import static uk.gov.di.ipv.core.library.enums.EvcsVCState.CURRENT;
import static uk.gov.di.ipv.core.library.enums.SessionCredentialsResetType.PENDING_F2F_ALL;
import static uk.gov.di.ipv.core.library.enums.SessionCredentialsResetType.REINSTATE;
import static uk.gov.di.ipv.core.library.enums.Vot.P0;
import static uk.gov.di.ipv.core.library.helpers.LogHelper.LogField.LOG_RESET_TYPE;
import static uk.gov.di.ipv.core.library.helpers.RequestHelper.getIpvSessionId;
Expand Down Expand Up @@ -109,12 +112,27 @@ public Map<String, Object> handleRequest(ProcessRequest input, Context context)
ipvSessionItem.setVot(P0);
ipvSessionService.updateIpvSession(ipvSessionItem);

SessionCredentialsResetType sessionCredentialsResetType =
RequestHelper.getSessionCredentialsResetType(input);
var sessionCredentialsResetType = RequestHelper.getSessionCredentialsResetType(input);
sessionCredentialsService.deleteSessionCredentialsForResetType(
ipvSessionId, sessionCredentialsResetType);
LOGGER.info(LogHelper.buildLogMessage("Session credentials deleted"));

if (sessionCredentialsResetType == REINSTATE) {
var existingIdentityVcs =
configService.enabled(EVCS_READ_ENABLED)
? evcsService.getVerifiableCredentials(
clientOAuthSessionItem.getUserId(),
clientOAuthSessionItem.getEvcsAccessToken(),
CURRENT)
: verifiableCredentialService.getVcs(
clientOAuthSessionItem.getUserId());
sessionCredentialsService.persistCredentials(
existingIdentityVcs, ipvSessionId, false);
LOGGER.info(
LogHelper.buildLogMessage(
"Existing identity persisted in session credentials store"));
}

if (sessionCredentialsResetType.equals(PENDING_F2F_ALL)) {
doResetForPendingF2f(clientOAuthSessionItem);
}
Expand All @@ -139,6 +157,13 @@ public Map<String, Object> handleRequest(ProcessRequest input, Context context)
return new JourneyErrorResponse(
JOURNEY_ERROR_PATH, SC_INTERNAL_SERVER_ERROR, IPV_SESSION_NOT_FOUND)
.toObjectMap();
} catch (CredentialParseException e) {
LOGGER.error(LogHelper.buildErrorMessage("Failed to fetch existing credentials", e));
return new JourneyErrorResponse(
JOURNEY_ERROR_PATH,
SC_INTERNAL_SERVER_ERROR,
FAILED_TO_PARSE_ISSUED_CREDENTIALS)
.toObjectMap();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.InOrder;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import uk.gov.di.ipv.core.library.domain.JourneyErrorResponse;
import uk.gov.di.ipv.core.library.domain.JourneyResponse;
import uk.gov.di.ipv.core.library.domain.ProcessRequest;
import uk.gov.di.ipv.core.library.domain.VerifiableCredential;
import uk.gov.di.ipv.core.library.exception.EvcsServiceException;
import uk.gov.di.ipv.core.library.exceptions.CredentialParseException;
import uk.gov.di.ipv.core.library.exceptions.VerifiableCredentialException;
import uk.gov.di.ipv.core.library.helpers.SecureTokenHelper;
import uk.gov.di.ipv.core.library.persistence.DataStore;
Expand All @@ -27,6 +32,7 @@
import uk.gov.di.ipv.core.library.verifiablecredential.service.SessionCredentialsService;
import uk.gov.di.ipv.core.library.verifiablecredential.service.VerifiableCredentialService;

import java.util.List;
import java.util.Map;

import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR;
Expand All @@ -44,9 +50,11 @@
import static uk.gov.di.ipv.core.library.domain.ErrorResponse.FAILED_TO_DELETE_CREDENTIAL;
import static uk.gov.di.ipv.core.library.domain.ErrorResponse.MISSING_IPV_SESSION_ID;
import static uk.gov.di.ipv.core.library.domain.ErrorResponse.UNKNOWN_RESET_TYPE;
import static uk.gov.di.ipv.core.library.enums.EvcsVCState.CURRENT;
import static uk.gov.di.ipv.core.library.enums.SessionCredentialsResetType.ALL;
import static uk.gov.di.ipv.core.library.enums.SessionCredentialsResetType.NAME_ONLY_CHANGE;
import static uk.gov.di.ipv.core.library.enums.SessionCredentialsResetType.PENDING_F2F_ALL;
import static uk.gov.di.ipv.core.library.enums.SessionCredentialsResetType.REINSTATE;
import static uk.gov.di.ipv.core.library.enums.Vot.P0;
import static uk.gov.di.ipv.core.library.journeyuris.JourneyUris.JOURNEY_ERROR_PATH;

Expand Down Expand Up @@ -74,6 +82,7 @@ class ResetSessionIdentityHandlerTest {
@Mock private CriResponseService mockCriResponseService;
@Mock private VerifiableCredentialService mockVerifiableCredentialService;
@Mock private EvcsService mockEvcsService;
@Mock private VerifiableCredential mockVerifiableCredential;
@InjectMocks private ResetSessionIdentityHandler resetSessionIdentityHandler;
@Mock private ConfigService mockConfigService;

Expand Down Expand Up @@ -238,6 +247,85 @@ void shouldReturnErrorJourney_forPendingF2f_evenWhenEvcsFailsAndEvcsReadsIsEnabl
assertEquals(FAILED_AT_EVCS_HTTP_REQUEST_SEND.getMessage(), journeyResponse.get("message"));
}

@ParameterizedTest
@ValueSource(booleans = {true, false})
void shouldReinstateUsersIdentity(boolean evcsReadEnabled) throws Exception {
// Arrange
when(mockIpvSessionService.getIpvSession(TEST_SESSION_ID)).thenReturn(ipvSessionItem);
when(mockClientOAuthSessionDetailsService.getClientOAuthSession(any()))
.thenReturn(clientOAuthSessionItem);
when(mockConfigService.enabled(EVCS_READ_ENABLED)).thenReturn(evcsReadEnabled);
if (evcsReadEnabled) {
when(mockEvcsService.getVerifiableCredentials(TEST_USER_ID, TEST_EVCS_TOKEN, CURRENT))
.thenReturn(List.of(mockVerifiableCredential));
} else {
when(mockVerifiableCredentialService.getVcs(TEST_USER_ID))
.thenReturn(List.of(mockVerifiableCredential));
}

ProcessRequest event =
ProcessRequest.processRequestBuilder()
.ipvSessionId(TEST_SESSION_ID)
.featureSet(TEST_FEATURE_SET)
.lambdaInput(Map.of("resetType", REINSTATE.name()))
.build();

// Act
JourneyResponse journeyResponse =
OBJECT_MAPPER.convertValue(
resetSessionIdentityHandler.handleRequest(event, mockContext),
JourneyResponse.class);

// Assert
verifyVotSetToP0();

verify(mockSessionCredentialsService)
.deleteSessionCredentialsForResetType(ipvSessionItem.getIpvSessionId(), REINSTATE);
verify(mockSessionCredentialsService)
.persistCredentials(List.of(mockVerifiableCredential), TEST_SESSION_ID, false);

assertEquals(JOURNEY_NEXT.getJourney(), journeyResponse.getJourney());
}

@ParameterizedTest
@ValueSource(booleans = {true, false})
void shouldReturnErrorJourneyIfReinstateAndUnableToReadFromLongTermStore(
boolean evcsReadEnabled) throws Exception {
// Arrange
when(mockIpvSessionService.getIpvSession(TEST_SESSION_ID)).thenReturn(ipvSessionItem);
when(mockClientOAuthSessionDetailsService.getClientOAuthSession(any()))
.thenReturn(clientOAuthSessionItem);
when(mockConfigService.enabled(EVCS_READ_ENABLED)).thenReturn(evcsReadEnabled);
if (evcsReadEnabled) {
when(mockEvcsService.getVerifiableCredentials(TEST_USER_ID, TEST_EVCS_TOKEN, CURRENT))
.thenThrow(new CredentialParseException("Boop"));
} else {
when(mockVerifiableCredentialService.getVcs(TEST_USER_ID))
.thenThrow(new CredentialParseException("Beep"));
}

var event =
ProcessRequest.processRequestBuilder()
.ipvSessionId(TEST_SESSION_ID)
.featureSet(TEST_FEATURE_SET)
.lambdaInput(Map.of("resetType", REINSTATE.name()))
.build();

// Act
var journeyResponse =
OBJECT_MAPPER.convertValue(
resetSessionIdentityHandler.handleRequest(event, mockContext),
JourneyErrorResponse.class);

// Assert
verifyVotSetToP0();

verify(mockSessionCredentialsService)
.deleteSessionCredentialsForResetType(ipvSessionItem.getIpvSessionId(), REINSTATE);

assertEquals(JOURNEY_ERROR_PATH, journeyResponse.getJourney());
}

@Test
void shouldReturnErrorJourneyIfIpvSessionIdMissing() {
// Arrange
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ public enum SessionCredentialsResetType {
ALL,
NAME_ONLY_CHANGE,
ADDRESS_ONLY_CHANGE,
PENDING_F2F_ALL;
PENDING_F2F_ALL,
REINSTATE
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public void deleteSessionCredentialsForResetType(
var sessionCredentialItems = dataStore.getItems(ipvSessionId);
var vcsToDelete =
switch (resetType) {
case ALL, PENDING_F2F_ALL -> sessionCredentialItems;
case ALL, PENDING_F2F_ALL, REINSTATE -> sessionCredentialItems;
case ADDRESS_ONLY_CHANGE -> sessionCredentialItems.stream()
.filter(
item ->
Expand Down
Loading

0 comments on commit 086418d

Please sign in to comment.