Skip to content

Commit

Permalink
Merge pull request #1994 from govuk-one-login/pyic-6711-audit-event-j…
Browse files Browse the repository at this point in the history
…ourney-event

Pyic 6711 audit event journey event
  • Loading branch information
Joe-Edwards-GDS authored Jun 10, 2024
2 parents 226b511 + 779eab4 commit 6a22698
Show file tree
Hide file tree
Showing 27 changed files with 267 additions and 215 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import uk.gov.di.ipv.core.library.auditing.AuditEventUser;
import uk.gov.di.ipv.core.library.auditing.extension.AuditExtensionMitigationType;
import uk.gov.di.ipv.core.library.auditing.extension.AuditExtensionSubjourneyType;
import uk.gov.di.ipv.core.library.auditing.extension.AuditExtensions;
import uk.gov.di.ipv.core.library.auditing.restricted.AuditRestrictedDeviceInformation;
import uk.gov.di.ipv.core.library.config.ConfigurationVariable;
import uk.gov.di.ipv.core.library.domain.CoiSubjourneyType;
Expand Down Expand Up @@ -48,11 +49,11 @@

import java.io.IOException;
import java.time.Instant;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;

import static com.amazonaws.util.CollectionUtils.isNullOrEmpty;
import static uk.gov.di.ipv.core.library.config.ConfigurationVariable.BACKEND_SESSION_TIMEOUT;
import static uk.gov.di.ipv.core.library.domain.CoiSubjourneyType.isCoiSubjourneyEvent;
import static uk.gov.di.ipv.core.library.domain.IpvJourneyTypes.SESSION_TIMEOUT;
Expand All @@ -69,7 +70,7 @@ public class ProcessJourneyEventHandler
private static final String NEXT_EVENT = "next";
private static final String END_SESSION_EVENT = "build-client-oauth-response";
private static final StepResponse END_SESSION_RESPONSE =
new ProcessStepResponse(END_SESSION_EVENT, null, null, null);
new ProcessStepResponse(END_SESSION_EVENT, null);
private final IpvSessionService ipvSessionService;
private final AuditService auditService;
private final ConfigService configService;
Expand Down Expand Up @@ -163,27 +164,6 @@ public Map<String, Object> handleRequest(JourneyRequest journeyRequest, Context

ipvSessionService.updateIpvSession(ipvSessionItem);

if (stepResponse.getMitigationStart() != null) {
sendMitigationStartAuditEvent(
auditEventUser, stepResponse.getMitigationStart(), deviceInformation);
}

String journeyAuditEvent = stepResponse.getAuditEvent();
if (journeyAuditEvent != null) {
boolean eventTypeExists =
Arrays.stream(AuditEventTypes.values())
.anyMatch(eventType -> eventType.name().equals(journeyAuditEvent));
if (!eventTypeExists) {
LOGGER.error(
LogHelper.buildLogMessage("Invalid audit event type provided")
.with(LOG_JOURNEY_EVENT.getFieldName(), journeyAuditEvent));
throw new JourneyEngineException(
"Invalid audit event type provided, failed to execute journey engine step.");
}
AuditEventTypes auditEventType = AuditEventTypes.valueOf(journeyAuditEvent);
sendJourneyAuditEvent(auditEventType, auditEventUser, deviceInformation);
}

return stepResponse.value();
} catch (HttpResponseExceptionWithErrorBody e) {
return StepFunctionHelpers.generateErrorOutputMap(
Expand Down Expand Up @@ -214,7 +194,13 @@ private StepResponse executeJourneyEvent(
var initialJourneyType = ipvSessionItem.getJourneyType();

try {
var newState = executeStateTransition(ipvSessionItem, journeyEvent, currentPage);
var newState =
executeStateTransition(
ipvSessionItem,
journeyEvent,
currentPage,
auditEventUser,
deviceInformation);

while (newState instanceof JourneyChangeState journeyChangeState) {
LOGGER.info(
Expand All @@ -229,7 +215,13 @@ private StepResponse executeJourneyEvent(
ipvSessionItem.setUserState(journeyChangeState.getInitialState());
sendSubJourneyStartAuditEvent(
auditEventUser, journeyChangeState.getJourneyType(), deviceInformation);
newState = executeStateTransition(ipvSessionItem, NEXT_EVENT, null);
newState =
executeStateTransition(
ipvSessionItem,
NEXT_EVENT,
null,
auditEventUser,
deviceInformation);
}

var basicState = (BasicState) newState;
Expand Down Expand Up @@ -269,8 +261,13 @@ private StepResponse executeJourneyEvent(

@Tracing
private State executeStateTransition(
IpvSessionItem ipvSessionItem, String journeyEvent, String currentPage)
throws StateMachineNotFoundException, UnknownEventException, UnknownStateException {
IpvSessionItem ipvSessionItem,
String journeyEvent,
String currentPage,
AuditEventUser auditEventUser,
String deviceInformation)
throws StateMachineNotFoundException, SqsException, UnknownEventException,
UnknownStateException {
StateMachine stateMachine = stateMachines.get(ipvSessionItem.getJourneyType());
if (stateMachine == null) {
throw new StateMachineNotFoundException(
Expand All @@ -284,11 +281,21 @@ private State executeStateTransition(
"Found state machine for journey type: %s",
ipvSessionItem.getJourneyType().name())));

return stateMachine.transition(
ipvSessionItem.getUserState(),
journeyEvent,
new JourneyContext(configService),
currentPage);
var result =
stateMachine.transition(
ipvSessionItem.getUserState(),
journeyEvent,
new JourneyContext(configService),
currentPage);

if (!isNullOrEmpty(result.auditEvents())) {
for (var auditEventType : result.auditEvents()) {
sendJourneyAuditEvent(
auditEventType, result.auditContext(), auditEventUser, deviceInformation);
}
}

return result.state();
}

@Tracing
Expand Down Expand Up @@ -359,31 +366,30 @@ private Map<IpvJourneyTypes, StateMachine> loadStateMachines(
return stateMachinesMap;
}

private void sendMitigationStartAuditEvent(
AuditEventUser auditEventUser, String mitigationType, String deviceInformation)
throws SqsException {

auditService.sendAuditEvent(
new AuditEvent(
AuditEventTypes.IPV_MITIGATION_START,
configService.getSsmParameter(ConfigurationVariable.COMPONENT_ID),
auditEventUser,
new AuditExtensionMitigationType(mitigationType),
new AuditRestrictedDeviceInformation(deviceInformation)));
}

private void sendJourneyAuditEvent(
AuditEventTypes auditEventType, AuditEventUser auditEventUser, String deviceInformation)
AuditEventTypes auditEventType,
Map<String, String> auditContext,
AuditEventUser auditEventUser,
String deviceInformation)
throws SqsException {

auditService.sendAuditEvent(
new AuditEvent(
auditEventType,
configService.getSsmParameter(ConfigurationVariable.COMPONENT_ID),
auditEventUser,
getAuditExtensions(auditEventType, auditContext),
new AuditRestrictedDeviceInformation(deviceInformation)));
}

private AuditExtensions getAuditExtensions(
AuditEventTypes auditEventType, Map<String, String> auditContext) {
return switch (auditEventType) {
case IPV_MITIGATION_START -> new AuditExtensionMitigationType(
auditContext.get("mitigationType"));
default -> null;
};
}

private void sendSubJourneyStartAuditEvent(
AuditEventUser auditEventUser, IpvJourneyTypes journeyType, String deviceInformation)
throws SqsException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@

public class StateMachine {
public static final String DELIMITER = "/";
private static final String ATTEMPT_RECOVERY_EVENT = "attempt-recovery";

private final Map<String, State> states;

public StateMachine(StateMachineInitializer initializer) throws IOException {
this.states = initializer.initialize();
}

public State transition(
public TransitionResult transition(
String startState, String event, JourneyContext journeyContext, String currentPage)
throws UnknownEventException, UnknownStateException {
var state = states.get(startState.split(DELIMITER)[0]);
Expand All @@ -31,9 +33,10 @@ public State transition(
String.format("Unknown state provided to state machine: %s", startState));
}

// Check page event is allowed
if (currentPage != null && state instanceof BasicState basicState) {
if (isPageOrCriStateAndOutOfSync(basicState, currentPage)) {
return state;
return new TransitionResult(state);
} else if (basicState.getResponse() instanceof ProcessStepResponse) {
throw new UnknownStateException(
String.format(
Expand All @@ -42,12 +45,19 @@ public State transition(
}
}

State newState = state.transition(event, startState, journeyContext);
if (newState instanceof NestedJourneyInvokeState) {
return newState.transition(event, startState, journeyContext);
// Special recovery event
if (ATTEMPT_RECOVERY_EVENT.equals(event)) {
return new TransitionResult(state);
}

var result = state.transition(event, startState, journeyContext);

// Resolve nested journey
if (result.state() instanceof NestedJourneyInvokeState) {
return result.state().transition(event, startState, journeyContext);
}

return newState;
return result;
}

private boolean isPageOrCriStateAndOutOfSync(BasicState basicState, String currentPage) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package uk.gov.di.ipv.core.processjourneyevent.statemachine;

import uk.gov.di.ipv.core.library.auditing.AuditEventTypes;
import uk.gov.di.ipv.core.processjourneyevent.statemachine.states.State;

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

public record TransitionResult(
State state, List<AuditEventTypes> auditEvents, Map<String, String> auditContext) {
public TransitionResult(State state) {
this(state, null, null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
import lombok.Data;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import uk.gov.di.ipv.core.library.auditing.AuditEventTypes;
import uk.gov.di.ipv.core.library.domain.IpvJourneyTypes;
import uk.gov.di.ipv.core.processjourneyevent.statemachine.TransitionResult;
import uk.gov.di.ipv.core.processjourneyevent.statemachine.exceptions.UnknownEventException;
import uk.gov.di.ipv.core.processjourneyevent.statemachine.states.JourneyChangeState;
import uk.gov.di.ipv.core.processjourneyevent.statemachine.states.State;
import uk.gov.di.ipv.core.processjourneyevent.statemachine.stepresponses.JourneyContext;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

Expand All @@ -22,8 +25,10 @@ public class BasicEvent implements Event {
private State targetStateObj;
private LinkedHashMap<String, Event> checkIfDisabled;
private LinkedHashMap<String, Event> checkFeatureFlag;
private List<AuditEventTypes> auditEvents;
private LinkedHashMap<String, String> auditContext;

public State resolve(JourneyContext journeyContext) throws UnknownEventException {
public TransitionResult resolve(JourneyContext journeyContext) throws UnknownEventException {
if (checkIfDisabled != null) {
Optional<String> firstDisabledCri =
checkIfDisabled.keySet().stream()
Expand All @@ -50,7 +55,7 @@ public State resolve(JourneyContext journeyContext) throws UnknownEventException
return checkFeatureFlag.get(featureFlagValue).resolve(journeyContext);
}
}
return targetStateObj;
return new TransitionResult(targetStateObj, auditEvents, auditContext);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import uk.gov.di.ipv.core.processjourneyevent.statemachine.TransitionResult;
import uk.gov.di.ipv.core.processjourneyevent.statemachine.exceptions.UnknownEventException;
import uk.gov.di.ipv.core.processjourneyevent.statemachine.states.State;
import uk.gov.di.ipv.core.processjourneyevent.statemachine.stepresponses.JourneyContext;
Expand All @@ -14,7 +15,7 @@
@JsonSubTypes.Type(value = ExitNestedJourneyEvent.class)
})
public interface Event {
State resolve(JourneyContext journeyContext) throws UnknownEventException;
TransitionResult resolve(JourneyContext journeyContext) throws UnknownEventException;

void initialize(String name, Map<String, State> states);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package uk.gov.di.ipv.core.processjourneyevent.statemachine.events;

import lombok.Data;
import uk.gov.di.ipv.core.processjourneyevent.statemachine.TransitionResult;
import uk.gov.di.ipv.core.processjourneyevent.statemachine.exceptions.UnknownEventException;
import uk.gov.di.ipv.core.processjourneyevent.statemachine.states.State;
import uk.gov.di.ipv.core.processjourneyevent.statemachine.stepresponses.JourneyContext;
Expand All @@ -14,7 +15,7 @@ public class ExitNestedJourneyEvent implements Event {
private Map<String, Event> nestedJourneyExitEvents;

@Override
public State resolve(JourneyContext journeyContext) throws UnknownEventException {
public TransitionResult resolve(JourneyContext journeyContext) throws UnknownEventException {
Event event = nestedJourneyExitEvents.get(exitEventToEmit);
if (event == null) {
throw new UnknownEventException("Event '%s' not found in nested journey's exit events");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import lombok.NoArgsConstructor;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import uk.gov.di.ipv.core.processjourneyevent.statemachine.TransitionResult;
import uk.gov.di.ipv.core.processjourneyevent.statemachine.events.Event;
import uk.gov.di.ipv.core.processjourneyevent.statemachine.exceptions.UnknownEventException;
import uk.gov.di.ipv.core.processjourneyevent.statemachine.stepresponses.JourneyContext;
Expand All @@ -19,20 +20,16 @@
@AllArgsConstructor
public class BasicState implements State {
private static final Logger LOGGER = LogManager.getLogger();
private static final String ATTEMPT_RECOVERY_EVENT = "attempt-recovery";
private String name;
private String parent;
private BasicState parentObj;
private StepResponse response;
private Map<String, Event> events = new HashMap<>();

@Override
public State transition(String eventName, String startState, JourneyContext journeyContext)
public TransitionResult transition(
String eventName, String startState, JourneyContext journeyContext)
throws UnknownEventException {
if (ATTEMPT_RECOVERY_EVENT.equals(eventName)) {
return this;
}

return getEvent(eventName)
.orElseThrow(
() ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import lombok.AllArgsConstructor;
import lombok.Data;
import uk.gov.di.ipv.core.library.domain.IpvJourneyTypes;
import uk.gov.di.ipv.core.processjourneyevent.statemachine.TransitionResult;
import uk.gov.di.ipv.core.processjourneyevent.statemachine.exceptions.UnknownEventException;
import uk.gov.di.ipv.core.processjourneyevent.statemachine.exceptions.UnknownStateException;
import uk.gov.di.ipv.core.processjourneyevent.statemachine.stepresponses.JourneyContext;
Expand All @@ -14,7 +15,8 @@ public class JourneyChangeState implements State {
private String initialState;

@Override
public State transition(String eventName, String startState, JourneyContext journeyContext)
public TransitionResult transition(
String eventName, String startState, JourneyContext journeyContext)
throws UnknownEventException, UnknownStateException {
throw new IllegalStateException("Cannot transition from JourneyChangeState");
}
Expand Down
Loading

0 comments on commit 6a22698

Please sign in to comment.