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

8195 Add SPG to Audit Report #156

Merged
merged 5 commits into from
Jan 24, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@
@RequestMapping("/audit")
@RestController
public class AuditController extends BaseController {

private static final String AUDIT_REPORT_PREFIX = "auditreport_";

private static final String AUDIT_REPORT_EXTENSION = ".csv";

private static final Logger logger = LoggerFactory.getLogger(AuditController.class);
Expand All @@ -63,14 +63,15 @@ public class AuditController extends BaseController {
public ResponseEntity<AuditReportResponse> getAuditReport(@Valid @RequestBody AuditReportRequest auditReportRequest,
HttpServletRequest request) {

Page<AffectedParty> pageable = auditService.getAffectedParties(
auditReportRequest.getTransactionTypes(), auditReportRequest.getOrganizations(),
auditReportRequest.getUserId(), auditReportRequest.getStartDate(), auditReportRequest.getEndDate(),
auditReportRequest.getPage(), auditReportRequest.getRows(), auditReportRequest.getSortField(), auditReportRequest.getSortDirection());
Page<AffectedParty> pageable = auditService.getAffectedParties(auditReportRequest.getTransactionTypes(),
auditReportRequest.getOrganizations(), auditReportRequest.getSpgRoles(), auditReportRequest.getUserId(),
auditReportRequest.getStartDate(), auditReportRequest.getEndDate(), auditReportRequest.getPage(),
auditReportRequest.getRows(), auditReportRequest.getSortField(), auditReportRequest.getSortDirection());
List<AuditRecord> auditReport = convertReport(pageable.getContent());

int first = auditReportRequest.getPage() * auditReportRequest.getRows();
logger.info("Returning {}-{} of {} audit records", first, first + pageable.getNumberOfElements(), pageable.getTotalElements());
logger.info("Returning {}-{} of {} audit records", first, first + pageable.getNumberOfElements(),
pageable.getTotalElements());

AuditReportResponse auditReportingResponse = new AuditReportResponse();
auditReportingResponse.setRecords(auditReport);
Expand All @@ -93,9 +94,10 @@ public ResponseEntity<List<String>> getOrganizations() {
ResponseEntity<List<String>> responseEntity = ResponseEntity.ok(organizations);
return responseEntity;
}

/**
* Retrieves audit records for download
*
* @param auditReportRequest
* @param request
* @return
Expand All @@ -106,7 +108,9 @@ public ResponseEntity<Resource> downloadAuditReport(@Valid @RequestBody AuditRep

List<AffectedParty> affectedPartiesForDownload = auditService.getAffectedPartiesForDownload(
auditReportRequest.getTransactionTypes(), auditReportRequest.getOrganizations(),
auditReportRequest.getUserId(), auditReportRequest.getStartDate(), auditReportRequest.getEndDate(), auditReportRequest.getSortField(), auditReportRequest.getSortDirection());
auditReportRequest.getSpgRoles(), auditReportRequest.getUserId(), auditReportRequest.getStartDate(),
auditReportRequest.getEndDate(), auditReportRequest.getSortField(),
auditReportRequest.getSortDirection());
List<AuditRecord> auditReport = convertReport(affectedPartiesForDownload);
logger.info("Number of records returned for download : {}", auditReport.size());

Expand All @@ -126,6 +130,7 @@ private List<AuditRecord> convertReport(List<AffectedParty> affectedParties) {
affectedParties.forEach(affectedParty -> {
AuditRecord model = new AuditRecord();
model.setOrganization(affectedParty.getTransaction().getOrganization());
model.setSpgRole(affectedParty.getTransaction().getSpgRole());
model.setTransactionId(affectedParty.getTransaction().getTransactionId().toString());
model.setType(affectedParty.getTransaction().getType());
model.setUserId(affectedParty.getTransaction().getUserId());
Expand All @@ -140,7 +145,8 @@ private List<AuditRecord> convertReport(List<AffectedParty> affectedParties) {
}

private List<String> convertOrganization(List<Organization> organizations) {
return organizations.stream().filter(org -> StringUtils.isNotBlank(org.getOrganization())).map(org -> org.getOrganization()).collect(Collectors.toList());
return organizations.stream().filter(org -> StringUtils.isNotBlank(org.getOrganization()))
.map(org -> org.getOrganization()).collect(Collectors.toList());
}

private LocalDateTime convertDate(Date date) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ public class AuditRecord {

private String transactionId;

private String spgRole;

public String getSpgRole() {
return spgRole;
}

public void setSpgRole(String spgRole) {
this.spgRole = spgRole;
}

public String getType() {
return type;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,23 @@ public class AuditReportRequest {
private List<String> organizations;

private List<String> transactionTypes;

private List<String> spgRoles;

private LocalDate startDate;

private LocalDate endDate;

private Integer page = 0;

public List<String> getSpgRoles() {
return spgRoles;
}

public void setSpgRoles(List<String> spgRoles) {
this.spgRoles = spgRoles;
}

private Integer rows = 10;

private String sortField;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,21 @@ public class Transaction {
@Column(name = "organization")
private String organization;

/**
* SPG of the user performing the transaction
*/
@Basic
@Column(name = "spg_role")
private String spgRole;

public String getSpgRole() {
return spgRole;
}

public void setSpgRole(String spgRole) {
this.spgRole = spgRole;
}

/**
* ID of the user that initiated the transaction
*/
Expand Down Expand Up @@ -139,7 +154,7 @@ public Date getStartTime() {
public void setStartTime(Date startTime) {
this.startTime = startTime;
}

@PrePersist
public void prePersist() {
if (startTime == null) {
Expand Down Expand Up @@ -216,8 +231,9 @@ public boolean equals(Object obj) {

@Override
public String toString() {
return "Transaction [transactionId=" + transactionId + ", type=" + type + ", sessionId=" + sessionId + ", server=" + server
+ ", sourceIp=" + sourceIp + ", organization=" + organization + ", userId=" + userId + ", startTime=" + startTime + "]";
return "Transaction [transactionId=" + transactionId + ", type=" + type + ", sessionId=" + sessionId
+ ", server=" + server + ", sourceIp=" + sourceIp + ", organization=" + organization + ", userId="
+ userId + ", startTime=" + startTime + "]";
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,28 @@

public interface AffectedPartyPageableRepository extends PagingAndSortingRepository<AffectedParty, Long> {

@Query("SELECT af from AffectedParty af where "
+"(COALESCE(:organizations, null) is null or af.transaction.organization IN (:organizations)) and "
+"(COALESCE(:type, null) is null or af.transaction.type IN (:type)) and "
+"(COALESCE(:userId, null) is null or :userId = '' or upper(af.transaction.userId)= upper(:userId)) and "
+"(af.direction=:direction) and "
+"(date_trunc('day', af.transaction.startTime) between :startDate and :endDate) ")
@Query("SELECT af from AffectedParty af where "
+ "(COALESCE(:organizations, null) is null or af.transaction.organization IN (:organizations)) and "
+ "(COALESCE(:type, null) is null or af.transaction.type IN (:type)) and "
+ "(COALESCE(:spgRoles, null) is null or af.transaction.spgRole IN (:spgRoles)) and "
+ "(COALESCE(:userId, null) is null or :userId = '' or upper(af.transaction.userId)= upper(:userId)) and "
+ "(af.direction=:direction) and "
+ "(date_trunc('day', af.transaction.startTime) between :startDate and :endDate) ")
Page<AffectedParty> findByTransactionAndDirection(@Param("type") List<String> type,
@Param("organizations") List<String> organizations, @Param("userId") String userId, @Param("direction") String direction, @Param("startDate") Date startDate, @Param("endDate") Date endDate, Pageable pageable);

@Query("SELECT af from AffectedParty af where "
+"(COALESCE(:organizations, null) is null or af.transaction.organization IN (:organizations)) and "
+"(COALESCE(:type, null) is null or af.transaction.type IN (:type)) and "
+"(COALESCE(:userId, null) is null or :userId = '' or upper(af.transaction.userId)= upper(:userId)) and "
+"(af.direction=:direction) and "
+"(date_trunc('day', af.transaction.startTime) between :startDate and :endDate) ")
@Param("organizations") List<String> organizations, @Param("spgRoles") List<String> spgRoles,
@Param("userId") String userId, @Param("direction") String direction, @Param("startDate") Date startDate,
@Param("endDate") Date endDate, Pageable pageable);

@Query("SELECT af from AffectedParty af where "
+ "(COALESCE(:organizations, null) is null or af.transaction.organization IN (:organizations)) and "
+ "(COALESCE(:type, null) is null or af.transaction.type IN (:type)) and "
+ "(COALESCE(:spgRoles, null) is null or af.transaction.spgRole IN (:spgRoles)) and "
+ "(COALESCE(:userId, null) is null or :userId = '' or upper(af.transaction.userId)= upper(:userId)) and "
+ "(af.direction=:direction) and "
+ "(date_trunc('day', af.transaction.startTime) between :startDate and :endDate) ")
List<AffectedParty> findByTransactionAndDirection(@Param("type") List<String> type,
@Param("organizations") List<String> organizations, @Param("userId") String userId, @Param("direction") String direction, @Param("startDate") Date startDate, @Param("endDate") Date endDate, Sort sort);
@Param("organizations") List<String> organizations, @Param("spgRoles") List<String> spgRoles,
@Param("userId") String userId, @Param("direction") String direction, @Param("startDate") Date startDate,
@Param("endDate") Date endDate, Sort sort);

}
139 changes: 97 additions & 42 deletions backend/src/main/java/ca/bc/gov/hlth/hnweb/security/SecurityUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
Expand All @@ -26,80 +26,135 @@ public class SecurityUtil {
private static final String CLAIM_RESOURCE_ACCESS = "resource_access";
private static final String CLAIM_SESSION_STATE = "session_state";
public static final String CLAIM_USERNAME = "preferred_username";
private static final String CLAIM_SUB = "sub"; //the Subject claim identifies the principal that is the subject of the JWT
private static final String CLAIM_SUB = "sub"; // the Subject claim identifies the principal that is the subject of the JWT
public static final String CLAIM_ORGANIZATION = "org_details";

private static final String ORGANIZATION_ID = "id";

private static final String USER_ROLES = "roles";

private static final String UNKNOWN_ROLE = "UNKNOWN";

private static String KEYCLOAK_CLIENT;

private static String KEYCLOAK_CLIENT;
@Value("${spring.security.oauth2.resourceserver.jwt.audience}")
private void setKeycloakClientStatic(String keycloakClient){
SecurityUtil.KEYCLOAK_CLIENT = keycloakClient;
}
private static SecurityProperties securityProperties;

@Autowired
public void setSecurityProperties(SecurityProperties properties) {
SecurityUtil.securityProperties = properties;
}

public static UserInfo loadUserInfo() {
@Value("${spring.security.oauth2.resourceserver.jwt.audience}")
private void setKeycloakClientStatic(String keycloakClient) {
SecurityUtil.KEYCLOAK_CLIENT = keycloakClient;
}

public static UserInfo loadUserInfo() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
Jwt jwt = (Jwt)auth.getPrincipal();
Jwt jwt = (Jwt) auth.getPrincipal();

UserInfo userInfo = new UserInfo();
userInfo.setOrganization(extractOrganization(jwt));

List<String> roles = loadRoles(jwt);
userInfo.setRole(StringUtils.join(roles, " "));
userInfo.setRoles(roles);

userInfo.setSessionState(jwt.getClaim(CLAIM_SESSION_STATE));
userInfo.setUsername(jwt.getClaim(CLAIM_USERNAME));
userInfo.setUserId(jwt.getClaim(CLAIM_SUB));

return userInfo;
}


public static String loadSPGBasedOnTransactionType(UserInfo userInfo, TransactionType transactionType) {
Map<String, List<String>> rolePermissions = securityProperties.getRolePermissions();
List<String> roles = userInfo.getRoles();
if (roles.size() == 1) {
return roles.get(0);
}
for (String role : roles) {
List<String> permissions = rolePermissions.get(role.toLowerCase());
if(permissions == null) {
weskubo-cgi marked this conversation as resolved.
Show resolved Hide resolved
continue;
}
switch (transactionType) {
case ENROLL_SUBSCRIBER:
if (permissions.contains("AddPermitHolderWOPHN") || permissions.contains("AddPermitHolderWithPHN")) {
return role;
}
break;
case GET_PERSON_DETAILS:
if (permissions.contains("AddPermitHolderWithPHN")) {
return role;
}
break;
case NAME_SEARCH:
if (permissions.contains("AddPermitHolderWOPHN")) {
return role;
}
break;
case CONTRACT_INQUIRY:
if (permissions.contains("ContractInquiry") || permissions.contains("GetContractAddress")) {
return role;
}
break;
case GET_PATIENT_REGISTRATION:
if (permissions.contains("PatientRegistration")) {
return role;
}
break;
default:
if (permissions.contains(transactionType.getValue())) {
return role;
}
}
}
return UNKNOWN_ROLE;
}

private static String extractOrganization(Jwt jwt) {
try {
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree((String)jwt.getClaim(CLAIM_ORGANIZATION));
JsonNode node = mapper.readTree((String) jwt.getClaim(CLAIM_ORGANIZATION));
return node.get(ORGANIZATION_ID).asText();
} catch (Exception e) {
logger.warn("User {} does not have claim {} set", jwt.getClaim(CLAIM_USERNAME), CLAIM_ORGANIZATION);
return null;
}
}

@SuppressWarnings("unchecked")
private static List<String> loadRoles(Jwt jwt) {
List<String> permissions = new ArrayList<>();

Map<String, Object> resourceAccesses = (Map<String, Object>) jwt.getClaims().get(CLAIM_RESOURCE_ACCESS);
Map<String, Object> resourceAccesses = (Map<String, Object>) jwt.getClaims().get(CLAIM_RESOURCE_ACCESS);

if (resourceAccesses == null) {
return permissions;
}
if (resourceAccesses == null) {
return permissions;
}

Map<String, Object> resource = (Map<String, Object>) resourceAccesses.get(KEYCLOAK_CLIENT);
if (resource == null) {
return permissions;
}
Map<String, Object> resource = (Map<String, Object>) resourceAccesses.get(KEYCLOAK_CLIENT);
if (resource == null) {
return permissions;
}

return (List<String>)resource.get(USER_ROLES);
return (List<String>) resource.get(USER_ROLES);
}

public static List<String> loadPermissions(Jwt jwt, Map<String, List<String>> rolePermissions) {
List<String> roles = loadRoles(jwt);
List<String> permissions = new ArrayList<>();
roles.forEach(role -> {
List<String> currentPermissions = rolePermissions.get(role.toLowerCase());
if (currentPermissions != null) {
permissions.addAll(currentPermissions);
} else {
logger.warn("Role {} has no permissions defined.", role);
}
});
return permissions;
List<String> roles = loadRoles(jwt);
List<String> permissions = new ArrayList<>();
roles.forEach(role -> {
List<String> currentPermissions = rolePermissions.get(role.toLowerCase());
if (currentPermissions != null) {
permissions.addAll(currentPermissions);
} else {
logger.warn("Role {} has no permissions defined.", role);
}

});

return permissions;
}

}
Loading