Skip to content

Commit

Permalink
Persist role updates and add audit columns to ApiUserRole and ApiUser…
Browse files Browse the repository at this point in the history
…Facility (#7828)

* add audit columns and persist role updates

* rework using constructors

* comment formatting

* assert exception on one method invocation

* give ben demo user a valid role

* use lombok getters and setters when possible

* put back getter to add override annotation
  • Loading branch information
mehansen authored Jun 25, 2024
1 parent 9f5c5c3 commit 25c1063
Show file tree
Hide file tree
Showing 8 changed files with 328 additions and 29 deletions.
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package gov.cdc.usds.simplereport.db.model;

import static jakarta.persistence.CascadeType.ALL;

import gov.cdc.usds.simplereport.config.authorization.OrganizationRole;
import gov.cdc.usds.simplereport.db.model.auxiliary.PersonName;
import jakarta.persistence.Column;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.OneToMany;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.DynamicUpdate;
Expand All @@ -21,21 +24,21 @@ public class ApiUser extends EternalSystemManagedEntity implements PersonEntity

@Column(nullable = false, updatable = true, unique = true)
@NaturalId(mutable = true)
@Getter
@Setter
private String loginEmail;

@Embedded private PersonName nameInfo;
@Setter @Embedded private PersonName nameInfo;

@Column(nullable = true)
@Getter
private Date lastSeen;

@ManyToMany
@JoinTable(
name = "api_user_facility",
joinColumns = @JoinColumn(name = "api_user_id"),
inverseJoinColumns = @JoinColumn(name = "facility_id"))
@Getter
@Setter
private Set<Facility> facilities;
@OneToMany(cascade = ALL, mappedBy = "apiUser", orphanRemoval = true)
private Set<ApiUserFacility> facilityAssignments = new HashSet<>();

@OneToMany(cascade = ALL, mappedBy = "apiUser", orphanRemoval = true)
private Set<ApiUserRole> roleAssignments = new HashSet<>();

protected ApiUser() {
/* for hibernate */ }
Expand All @@ -46,27 +49,37 @@ public ApiUser(String email, PersonName name) {
lastSeen = null;
}

public String getLoginEmail() {
return loginEmail;
}

public void setLoginEmail(String newEmail) {
loginEmail = newEmail;
}

public Date getLastSeen() {
return lastSeen;
@Override
public PersonName getNameInfo() {
return nameInfo;
}

public void updateLastSeen() {
lastSeen = new Date();
}

public PersonName getNameInfo() {
return nameInfo;
public Set<Facility> getFacilities() {
return this.facilityAssignments.stream()
.map(ApiUserFacility::getFacility)
.collect(Collectors.toSet());
}

public void setNameInfo(PersonName name) {
nameInfo = name;
public void setFacilities(Set<Facility> facilities) {
this.facilityAssignments.clear();
for (Facility facility : facilities) {
this.facilityAssignments.add(new ApiUserFacility(this, facility));
}
}

public void setRoles(Set<OrganizationRole> newOrgRoles, Organization org) {
this.roleAssignments.clear();
for (OrganizationRole orgRole : newOrgRoles) {
if (orgRole.equals(OrganizationRole.NO_ACCESS)) {
// the NO_ACCESS role is only relevant for the Okta implementation of authorization, and it
// doesn't need to be persisted in our tables
continue;
}
this.roleAssignments.add(new ApiUserRole(this, org, orgRole));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package gov.cdc.usds.simplereport.db.model;

import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import lombok.Getter;

@Entity(name = "api_user_facility")
public class ApiUserFacility extends AuditedEntity {

@ManyToOne
@JoinColumn(name = "api_user_id", nullable = false)
private ApiUser apiUser;

@ManyToOne
@JoinColumn(name = "facility_id", nullable = false)
@Getter
private Facility facility;

protected ApiUserFacility() {
/* for hibernate */
}

public ApiUserFacility(ApiUser user, Facility facility) {
this.apiUser = user;
this.facility = facility;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package gov.cdc.usds.simplereport.db.model;

import gov.cdc.usds.simplereport.config.authorization.OrganizationRole;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;

@Entity(name = "api_user_role")
public class ApiUserRole extends AuditedEntity {

@ManyToOne
@JoinColumn(name = "api_user_id", nullable = false)
private ApiUser apiUser;

@ManyToOne
@JoinColumn(name = "organization_id", nullable = false)
private Organization organization;

@Column(nullable = false, columnDefinition = "organization_role")
@JdbcTypeCode(SqlTypes.NAMED_ENUM)
@Enumerated(EnumType.STRING)
private OrganizationRole role;

protected ApiUserRole() {
/* for hibernate */
}

public ApiUserRole(ApiUser user, Organization org, OrganizationRole role) {
if (role.equals(OrganizationRole.NO_ACCESS)) {
throw new IllegalArgumentException(
"Invalid role NO_ACCESS when creating new user role assignment");
}
this.apiUser = user;
this.organization = org;
this.role = role;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ private UserInfo reprovisionUser(
apiUser.setNameInfo(name);
apiUser.setIsDeleted(false);
apiUser.setFacilities(facilitiesFound);
apiUser.setRoles(roles, org);

Optional<OrganizationRoles> orgRoles = roleClaims.map(c -> _orgService.getOrganizationRoles(c));
UserInfo user = new UserInfo(apiUser, orgRoles, false);
Expand Down Expand Up @@ -188,6 +189,7 @@ private UserInfo createUserHelper(
Set<OrganizationRole> roles = getOrganizationRoles(role, accessAllFacilities);
Set<Facility> facilitiesFound = getFacilitiesToGiveAccess(org, roles, facilities);
apiUser.setFacilities(facilitiesFound);
apiUser.setRoles(roles, org);

Optional<OrganizationRoleClaims> roleClaims =
_oktaRepo.createUser(userIdentity, org, facilitiesFound, roles, active);
Expand Down Expand Up @@ -245,6 +247,7 @@ public UserInfo updateUserPrivileges(
UserInfo user = new UserInfo(apiUser, orgRoles, false);

apiUser.setFacilities(facilitiesFound);
apiUser.setRoles(roles, org);

createUserUpdatedAuditLog(apiUser.getInternalId(), getCurrentApiUser().getInternalId());

Expand Down Expand Up @@ -713,6 +716,7 @@ public void updateUserPrivilegesAndGroupAccess(
Set<Facility> facilitiesToGiveAccessTo =
getFacilitiesToGiveAccess(newOrg, roles, new HashSet<>(facilities));
apiUser.setFacilities(facilitiesToGiveAccessTo);
apiUser.setRoles(roles, newOrg);

_oktaRepo.updateUserPrivilegesAndGroupAccess(
username, newOrg, facilitiesToGiveAccessTo, role.toOrganizationRole(), allFacilitiesAccess);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ private void configureDemoUsers(List<DemoUser> users, Map<String, Facility> faci
_orgRepo
.findByExternalId(authorization.getOrganizationExternalId())
.orElseThrow(MisconfiguredUserException::new);
apiUser.setRoles(roles, org);
log.info(
"User={} will have roles={} in organization={}",
identity.getUsername(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ simple-report:
organization-external-id: DIS_ORG
facilities:
- Testing Site
granted-roles: []
granted-roles: ENTRY_ONLY
- identity:
username: [email protected]
first-name: Jamar
Expand Down
Loading

0 comments on commit 25c1063

Please sign in to comment.