Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CCD-6015: Investigate error changing restricted security classification to public #2500

Open
wants to merge 47 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
f4707ec
Initial logging
Nov 26, 2024
d24f4b0
Fix SonarQube
Nov 26, 2024
3ef5402
Update logging
Nov 26, 2024
1af28f1
Experimental refactor of method applyClassification()
Nov 27, 2024
a9c26dc
Refactor applyClassification() to Functions (instead of lambdas)
Nov 27, 2024
15b6651
Update SecurityClassificationServiceImpl logging
Dec 2, 2024
6f947fe
Sonar temporary ignore SecurityClassificationServiceImpl.java
Dec 2, 2024
492c8b4
Enable deserialisation of java.util.Optional for logging purposes
Dec 3, 2024
3b71a39
Enable deserialisation of java.time.LocalDateTime
Dec 4, 2024
8a043a0
Update
Dec 4, 2024
981555a
Update logging , added SecurityClassificationServiceLogger
Dec 6, 2024
d030f47
Clean up SecurityClassificationServiceImpl
Dec 6, 2024
5759fa9
Update SecurityClassificationServiceLogger constructor
Dec 6, 2024
581051f
Refactor SecurityClassificationServiceLogger
Dec 6, 2024
5b0acb5
Update SecurityClassificationServiceLogger
Dec 6, 2024
eb03fe0
Fix unit tests
Dec 9, 2024
6762889
Merge branch 'master' into CCD-6015-investigate-changing-restricted-s…
JamesCollettCGI Dec 9, 2024
00ce4e7
Add more logging to caseHasClassificationEqualOrLowerThan()
Dec 10, 2024
e1a3d45
Add logging to ClassifiedStartEventOperation.triggerStartForCase
Dec 11, 2024
c65d07b
Possible fix: Do not filter when applying classification change to Re…
Dec 12, 2024
4ff94f3
New unit tests for ClassifiedStartEventOperationTest. (SecurityClass…
Dec 13, 2024
bf3a895
Fix checkstyleTest
Dec 13, 2024
559da38
Merge branch 'master' into CCD-6015-investigate-changing-restricted-s…
JamesCollettCGI Dec 13, 2024
d7c02cd
Correct spelling
Dec 16, 2024
99eafc1
New unit tests for SecurityClassificationServiceTest
Dec 16, 2024
3a9a451
Add to ClassifiedStartEventOperation logging
Dec 17, 2024
41d1e43
ClassifiedStartEventOperation enable serialisation of java.util.Optio…
Dec 17, 2024
16188d7
Add logging to applyClassificationToRestrictedCase()
Dec 17, 2024
f9cae2a
Add and update logging
Dec 18, 2024
bfe29d3
Merge branch 'master' into CCD-6015-investigate-changing-restricted-s…
JamesCollettCGI Dec 18, 2024
6972cdc
Fix Sonarqube
Dec 18, 2024
7871daa
Update logging
Dec 18, 2024
0f6fbf6
Update logging [20-Dec-2024]
Dec 20, 2024
f89860c
Update logging [20-Dec-2024]
Dec 20, 2024
804c5e9
Merge branch 'master' into CCD-6015-investigate-changing-restricted-s…
JamesCollettCGI Jan 6, 2025
02f63dc
Update logging
Jan 6, 2025
fc13447
Update logging
Jan 8, 2025
5798cf7
Update logging
Jan 8, 2025
ff20ab2
Update logging
Jan 8, 2025
1a13717
Update logging: ClassifiedStartEventOperation report call stack , Cas…
Jan 10, 2025
0dab338
Fix unit tests
Jan 10, 2025
9f07195
Update CaseDetails logging to show call stack when case marked Restri…
Jan 10, 2025
913b6af
Update logging
Jan 10, 2025
bbaee73
Update
Jan 12, 2025
51d29ce
Add logging to CreateCaseEventService to check if methods called in b…
Jan 13, 2025
c4472cd
Updates to logging to 'quieten' overall logging
Jan 13, 2025
6e82004
Incorporate CCD-6020 changes from PR-2517
Jan 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ tasks.withType(Test) {

dependencies {

// Enables serialisation of java.util.Optional and java.time.LocalDateTime
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.18.2'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.2'

implementation('org.springframework.cloud:spring-cloud-starter-bootstrap') {
version {
strictly '4.0.5'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.springframework.http.ResponseEntity;
import uk.gov.hmcts.ccd.data.casedetails.SecurityClassification;
import uk.gov.hmcts.ccd.domain.model.callbacks.AfterSubmitCallbackResponse;
import uk.gov.hmcts.ccd.domain.service.common.JcLogger;

import java.time.LocalDate;
import java.time.LocalDateTime;
Expand Down Expand Up @@ -42,6 +43,8 @@ public class CaseDetails implements Cloneable {
private static final Logger LOG = LoggerFactory.getLogger(CaseDetails.class);
public static final String DRAFT_ID = "DRAFT%s";

final JcLogger jcLogger = new JcLogger("CaseDetails", true);

private String id;

@JsonIgnore
Expand Down Expand Up @@ -195,6 +198,12 @@ public SecurityClassification getSecurityClassification() {
}

public void setSecurityClassification(SecurityClassification securityClassification) {
jcLogger.jclog("setSecurityClassification() "
+ (securityClassification == null ? "NULL" : securityClassification.toString()));
if (securityClassification == SecurityClassification.RESTRICTED) {
jcLogger.jclog("setSecurityClassification() CALL STACK = "
+ JcLogger.getStackTraceAsString(new Exception()));
}
this.securityClassification = securityClassification;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import uk.gov.hmcts.ccd.domain.model.callbacks.CallbackResponse;
import uk.gov.hmcts.ccd.domain.model.definition.CaseDetails;
import uk.gov.hmcts.ccd.domain.model.definition.CaseEventDefinition;
import uk.gov.hmcts.ccd.domain.service.common.JcLogger;
import uk.gov.hmcts.ccd.endpoint.exceptions.ApiException;
import uk.gov.hmcts.ccd.endpoint.exceptions.CallbackException;
import uk.gov.hmcts.ccd.util.ClientContextUtil;
Expand Down Expand Up @@ -52,6 +53,8 @@ public class CallbackService {
private final AppInsights appinsights;
private final HttpServletRequest request;

final JcLogger jcLogger = new JcLogger("CallbackService", true);

@Autowired
public CallbackService(final SecurityUtils securityUtils,
@Qualifier("restTemplate") final RestTemplate restTemplate,
Expand Down Expand Up @@ -102,6 +105,9 @@ public Optional<CallbackResponse> sendSingleRequest(final String url,
final Optional<ResponseEntity<CallbackResponse>> responseEntity =
sendRequest(url, callbackType, CallbackResponse.class, callbackRequest);
return responseEntity.map(re -> Optional.of(re.getBody())).orElseThrow(() -> {
jcLogger.jclog("sendSingleRequest1() Unsuccessful callback to " + url + " for caseType "
+ caseDetails.getCaseTypeId() + " and event " + caseEvent.getId());
jcLogger.jclog("sendSingleRequest1() CALL STACK = " + JcLogger.getStackTraceAsString(new Exception()));
LOG.warn("Unsuccessful callback to {} for caseType {} and event {}", url, caseDetails.getCaseTypeId(),
caseEvent.getId());
String callbackTypeString = callbackType != null ? callbackType.getValue() : "null";
Expand All @@ -120,6 +126,9 @@ public <T> ResponseEntity<T> sendSingleRequest(final String url,
final CallbackRequest callbackRequest = new CallbackRequest(caseDetails, caseDetailsBefore, caseEvent.getId());
final Optional<ResponseEntity<T>> requestEntity = sendRequest(url, callbackType, clazz, callbackRequest);
return requestEntity.orElseThrow(() -> {
jcLogger.jclog("sendSingleRequest2() Unsuccessful callback to " + url + " for caseType "
+ caseDetails.getCaseTypeId() + " and event " + caseEvent.getId());
jcLogger.jclog("sendSingleRequest2() CALL STACK = " + JcLogger.getStackTraceAsString(new Exception()));
LOG.warn("Unsuccessful callback to {} for caseType {} and event {}", url, caseDetails.getCaseTypeId(),
caseEvent.getId());
return new CallbackException("Callback to service has been unsuccessful for event " + caseEvent.getName());
Expand Down
84 changes: 84 additions & 0 deletions src/main/java/uk/gov/hmcts/ccd/domain/service/common/JcLogger.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package uk.gov.hmcts.ccd.domain.service.common;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uk.gov.hmcts.ccd.data.casedetails.SecurityClassification;
import uk.gov.hmcts.ccd.domain.model.callbacks.StartEventResult;
import uk.gov.hmcts.ccd.domain.model.definition.CaseDetails;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Optional;

public class JcLogger {

private static final Logger LOG = LoggerFactory.getLogger(JcLogger.class);

private final String classname;

private final boolean enabled;

private final ObjectMapper objectMapper = new ObjectMapper();

public JcLogger(final String classname, final boolean enabled) {
this.classname = classname;
this.enabled = enabled;
// Enables serialisation of java.util.Optional and java.time.LocalDateTime
objectMapper.registerModule(new Jdk8Module());
objectMapper.registerModule(new JavaTimeModule());
}

public void jclog(String message) {
if (enabled) {
LOG.info("| JCDEBUG: {}: {}", classname, message);
}
}

public void jclog(String message, int i) {
jclog(message + ": " + i);
}

public void jclog(String message, Optional optional) {
try {
jclog(message + ": " + objectMapper.writeValueAsString(optional));
} catch (JsonProcessingException e) {
jclog(message + ": JSON ERROR: " + e.getMessage());
}
}

public void jclog(String message, CaseDetails caseDetails) {
try {
jclog(message + ": " + objectMapper.writeValueAsString(caseDetails));
} catch (JsonProcessingException e) {
jclog(message + ": JSON ERROR: " + e.getMessage());
}
}

public void jclog(String message, SecurityClassification securityClassification) {
try {
jclog(message + ": " + objectMapper.writeValueAsString(securityClassification));
} catch (JsonProcessingException e) {
jclog(message + ": JSON ERROR: " + e.getMessage());
}
}

public void jclog(String message, StartEventResult startEventResult) {
try {
jclog(message + ": " + objectMapper.writeValueAsString(startEventResult));
} catch (JsonProcessingException e) {
jclog(message + ": JSON ERROR: " + e.getMessage());
}
}

public static String getStackTraceAsString(Throwable throwable) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
throwable.printStackTrace(pw);
String stackTrace = sw.toString().replaceAll("\r\n", " ").replaceAll("\n", " ");
return stackTrace.hashCode() + " " + stackTrace;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down Expand Up @@ -45,6 +46,9 @@ public class SecurityClassificationServiceImpl implements SecurityClassification
private final CaseDataAccessControl caseDataAccessControl;
private final CaseDefinitionRepository caseDefinitionRepository;

// JcLogger disabled to 'quieten' overall logging.
final JcLogger jcLogger = new JcLogger("SecurityClassificationServiceImpl", false);

@Autowired
public SecurityClassificationServiceImpl(CaseDataAccessControl caseDataAccessControl,
@Qualifier(CachedCaseDefinitionRepository.QUALIFIER)
Expand All @@ -53,31 +57,48 @@ public SecurityClassificationServiceImpl(CaseDataAccessControl caseDataAccessCon
this.caseDefinitionRepository = caseDefinitionRepository;
}

public Optional<CaseDetails> applyClassification(CaseDetails caseDetails) {
private Function<CaseDetails, CaseDetails> mapFunction(final CaseDetails caseDetails,
final SecurityClassification securityClassification) {
return cd -> {
if (cd.getDataClassification() == null) {
LOG.warn("No data classification for case with reference={},"
+ " all fields removed", cd.getReference());
jcLogger.jclog("No data classification for case with reference " + cd.getReference());
cd.setDataClassification(Maps.newHashMap());
}

JsonNode data = filterNestedObject(JacksonUtils.convertValueJsonNode(caseDetails.getData()),
JacksonUtils.convertValueJsonNode(caseDetails.getDataClassification()),
securityClassification);
caseDetails.setData(JacksonUtils.convertValue(data));
return cd;
};
}

public Optional<CaseDetails> applyClassification(final CaseDetails caseDetails) {
return applyClassification(caseDetails, false);
}

public Optional<CaseDetails> applyClassification(CaseDetails caseDetails, boolean create) {
jcLogger.jclog("applyClassification [NORMAL case 1]", caseDetails);
jcLogger.jclog("applyClassification [NORMAL case 2]", caseDetails.getSecurityClassification());
Optional<SecurityClassification> userClassificationOpt = getUserClassification(caseDetails, create);
return userClassificationOpt
Optional<CaseDetails> caseDetails1 = userClassificationOpt
.flatMap(securityClassification ->
Optional.of(caseDetails).filter(caseHasClassificationEqualOrLowerThan(securityClassification))
.map(cd -> {
if (cd.getDataClassification() == null) {
LOG.warn("No data classification for case with reference={},"
+ " all fields removed", cd.getReference());
cd.setDataClassification(Maps.newHashMap());
}

JsonNode data = filterNestedObject(JacksonUtils.convertValueJsonNode(caseDetails.getData()),
JacksonUtils.convertValueJsonNode(caseDetails.getDataClassification()),
securityClassification);
caseDetails.setData(JacksonUtils.convertValue(data));
return cd;
}));
.map(mapFunction(caseDetails, securityClassification)));
jcLogger.jclog("applyClassification [NORMAL case 3]", caseDetails1);
try {
jcLogger.jclog("applyClassification [NORMAL case 4]" + (caseDetails1.isPresent()
? caseDetails1.get().getSecurityClassification().toString() : "NOT PRESENT"));
} catch (Exception e) {
jcLogger.jclog("applyClassification [NORMAL case 5] ERROR GETTING SECURITY CLASSIFICATION");
}
return caseDetails1;
}

public List<AuditEvent> applyClassification(CaseDetails caseDetails, List<AuditEvent> events) {
jcLogger.jclog("applyClassification (AuditEvent)");
final Optional<SecurityClassification> userClassification = getUserClassification(caseDetails, false);

if (null == events || !userClassification.isPresent()) {
Expand All @@ -95,8 +116,27 @@ public List<AuditEvent> applyClassification(CaseDetails caseDetails, List<AuditE
return classifiedEvents;
}

public Optional<CaseDetails> applyClassificationToRestrictedCase(CaseDetails caseDetails) {
jcLogger.jclog("applyClassification [RESTRICTED case 1]", caseDetails);
jcLogger.jclog("applyClassification [RESTRICTED case 2]", caseDetails.getSecurityClassification());
Optional<SecurityClassification> userClassificationOpt = getUserClassification(caseDetails, false);
Optional<CaseDetails> caseDetails1 = userClassificationOpt
.flatMap(securityClassification ->
Optional.of(caseDetails)
.map(mapFunction(caseDetails, securityClassification)));
jcLogger.jclog("applyClassification [RESTRICTED case 3]", caseDetails1);
try {
jcLogger.jclog("applyClassification [RESTRICTED case 4]" + (caseDetails1.isPresent()
? caseDetails1.get().getSecurityClassification().toString() : "NOT PRESENT"));
} catch (Exception e) {
jcLogger.jclog("applyClassification [RESTRICTED case 5] ERROR GETTING SECURITY CLASSIFICATION");
}
return caseDetails1;
}

public SecurityClassification getClassificationForEvent(CaseTypeDefinition caseTypeDefinition,
CaseEventDefinition caseEventDefinition) {
jcLogger.jclog("getClassificationForEvent()");
return caseTypeDefinition
.getEvents()
.stream()
Expand All @@ -109,29 +149,37 @@ public SecurityClassification getClassificationForEvent(CaseTypeDefinition caseT
public boolean userHasEnoughSecurityClassificationForField(String jurisdictionId,
CaseTypeDefinition caseTypeDefinition,
String fieldId) {
jcLogger.jclog("userHasEnoughSecurityClassificationForField()");
final Optional<SecurityClassification> userClassification =
getUserClassification(caseTypeDefinition, false);
return userClassification.map(securityClassification ->
boolean b = userClassification.map(securityClassification ->
securityClassification.higherOrEqualTo(caseTypeDefinition.getClassificationForField(fieldId)))
.orElse(false);
jcLogger.jclog("userHasEnoughSecurityClassificationForField() " + b);
return b;
}

public boolean userHasEnoughSecurityClassificationForField(CaseTypeDefinition caseTypeDefinition,
SecurityClassification otherClassification) {
jcLogger.jclog("userHasEnoughSecurityClassificationForField()");
final Optional<SecurityClassification> userClassification = getUserClassification(caseTypeDefinition, false);
return userClassification.map(securityClassification ->
boolean b = userClassification.map(securityClassification ->
securityClassification.higherOrEqualTo(otherClassification))
.orElse(false);
jcLogger.jclog("userHasEnoughSecurityClassificationForField() " + b);
return b;
}

public Optional<SecurityClassification> getUserClassification(CaseTypeDefinition caseTypeDefinition,
boolean isCreateProfile) {
jcLogger.jclog("getUserClassification()");
return maxSecurityClassification(caseDataAccessControl
.getUserClassifications(caseTypeDefinition, isCreateProfile));
}

@Override
public Optional<SecurityClassification> getUserClassification(CaseDetails caseDetails, boolean create) {
jcLogger.jclog("getUserClassification()");
if (create) {
return maxSecurityClassification(caseDataAccessControl.getUserClassifications(
caseDefinitionRepository.getCaseType(caseDetails.getCaseTypeId()), true));
Expand All @@ -140,13 +188,15 @@ public Optional<SecurityClassification> getUserClassification(CaseDetails caseDe
}

private Optional<SecurityClassification> maxSecurityClassification(Set<SecurityClassification> classifications) {
jcLogger.jclog("maxSecurityClassification()");
return classifications.stream()
.filter(classification -> classification != null)
.max(comparingInt(SecurityClassification::getRank));
}

private JsonNode filterNestedObject(JsonNode data, JsonNode dataClassification,
SecurityClassification userClassification) {
jcLogger.jclog("filterNestedObject()");
if (isAnyNull(data, dataClassification)) {
return EMPTY_NODE;
}
Expand Down Expand Up @@ -181,6 +231,7 @@ private void filterCollection(SecurityClassification userClassification,
Iterator<Map.Entry<String, JsonNode>> dataIterator,
JsonNode dataClassificationElement,
JsonNode dataElementValue) {
jcLogger.jclog("filterCollection()");
// Apply collection-level classification
filterSimpleField(userClassification,
dataIterator,
Expand All @@ -205,6 +256,8 @@ private void filterCollection(SecurityClassification userClassification,
} else {
LOG.warn("Invalid security classification structure for collection item: {}",
relevantDataClassificationValue.toString());
jcLogger.jclog("Invalid security classification structure for collection item: "
+ relevantDataClassificationValue.toString());
dataCollectionIterator.remove();
}
} else {
Expand All @@ -229,6 +282,7 @@ private void filterObject(SecurityClassification userClassification,
Iterator<Map.Entry<String, JsonNode>> dataIterator,
JsonNode dataClassificationParent,
JsonNode dataElementValue) {
jcLogger.jclog("filterObject()");
filterNestedObject(dataElementValue,
dataClassificationParent.get(VALUE),
userClassification);
Expand All @@ -241,6 +295,7 @@ private void filterObject(SecurityClassification userClassification,

private void filterSimpleField(SecurityClassification userClassification, Iterator iterator,
JsonNode dataClassificationValue) {
jcLogger.jclog("filterSimpleField()");
Optional<SecurityClassification> securityClassification = getSecurityClassification(dataClassificationValue);
if (!securityClassification.isPresent() || !userClassification.higherOrEqualTo(securityClassification.get())) {
iterator.remove();
Expand Down
Loading