diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/RoleRepository.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/RoleRepository.java index 63aa7f92d..7e87c6618 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/RoleRepository.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/RoleRepository.java @@ -5,6 +5,7 @@ import javax.enterprise.context.ApplicationScoped; import javax.transaction.Transactional; +import java.util.List; import java.util.UUID; /** @@ -18,4 +19,14 @@ public class RoleRepository extends BaseRepository { protected RoleRepository() { super(Role.class); } + + public void persistAll(List newRoles) { + for (Role newRole : newRoles) { + if (newRole.getUuid() == null) { + em.persist(newRole); + } else { + em.merge(newRole); + } + } + } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/StudyAccessService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/StudyAccessService.java index 3fe0db33b..efd963254 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/StudyAccessService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/StudyAccessService.java @@ -3,6 +3,7 @@ import edu.harvard.hms.dbmi.avillach.auth.data.entity.Role; import edu.harvard.hms.dbmi.avillach.auth.data.repository.RoleRepository; import edu.harvard.hms.dbmi.avillach.auth.service.auth.FENCEAuthenticationService; +import edu.harvard.hms.dbmi.avillach.auth.utils.FenceMappingUtility; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; @@ -37,6 +38,9 @@ public class StudyAccessService { public static final String STUDY_IDENTIFIER = "study_identifier"; public static final String CONSENT_GROUP_CODE = "consent_group_code"; + @Inject + private FenceMappingUtility fenceUtilityMapping; + @Inject FENCEAuthenticationService fenceAuthenticationService; @@ -55,7 +59,7 @@ public Response addStudyAccess(@ApiParam(value="The Study Identifier of the new Map fenceMappingForStudy = null; try { - Map fenceMapping = fenceAuthenticationService.getFENCEMapping(); + Map fenceMapping = this.fenceUtilityMapping.getFENCEMapping(); if (fenceMapping == null) { throw new Exception("Fence mapping is null"); } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/auth/FENCEAuthenticationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/auth/FENCEAuthenticationService.java index 6fb59d38e..926196572 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/auth/FENCEAuthenticationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/auth/FENCEAuthenticationService.java @@ -6,8 +6,8 @@ import static edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration.fence_standard_access_rules; import static edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration.fence_topmed_consent_group_concept_path; -import java.io.File; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -19,6 +19,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ObjectNode; +import edu.harvard.hms.dbmi.avillach.auth.utils.FenceMappingUtility; import org.apache.commons.lang3.StringUtils; import org.apache.http.Header; import org.apache.http.entity.StringEntity; @@ -39,7 +40,7 @@ import org.springframework.util.CollectionUtils; public class FENCEAuthenticationService { - private Logger logger = LoggerFactory.getLogger(FENCEAuthenticationService.class); + private final Logger logger = LoggerFactory.getLogger(FENCEAuthenticationService.class); @Inject UserRepository userRepo; @@ -67,17 +68,14 @@ public class FENCEAuthenticationService { private Application picSureApp; private Connection fenceConnection; - - //read the fence_mapping.json into this object to improve lookup speeds - private static Map _projectMap; - + private static final String parentAccessionField = "\\\\_Parent Study Accession with Subject ID\\\\"; private static final String topmedAccessionField = "\\\\_Topmed Study Accession with Subject ID\\\\"; public static final String fence_open_access_role_name = "FENCE_ROLE_OPEN_ACCESS"; private final Set openAccessIdpValues = Set.of("fence", "ras"); - + private static final String[] underscoreFields = new String[] { parentAccessionField, topmedAccessionField, @@ -91,6 +89,12 @@ public class FENCEAuthenticationService { "\\\\_Consents\\\\" ///old _Consents\Short Study... path no longer used, but still present in examples. }; + @Inject + private FenceMappingUtility fenceMappingUtility; + + private final ConcurrentHashMap accessRuleCache = new ConcurrentHashMap<>(); + private Set allowQueryTypeRules; + @PostConstruct public void initializeFenceService() { picSureApp = applicationRepo.getUniqueResultByColumn("name", "PICSURE"); @@ -102,7 +106,7 @@ private JsonNode getFENCEUserProfile(String access_token) { List
headers = new ArrayList<>(); headers.add(new BasicHeader("Authorization", "Bearer " + access_token)); - logger.debug("getFENCEUserProfile() getting user profile from uri:"+JAXRSConfiguration.idp_provider_uri+"/user/user"); + logger.debug("getFENCEUserProfile() getting user profile from uri:{}/user/user", JAXRSConfiguration.idp_provider_uri); JsonNode fence_user_profile_response = HttpClientUtil.simpleGet( JAXRSConfiguration.idp_provider_uri+"/user/user", JAXRSConfiguration.client, @@ -164,20 +168,20 @@ public Response getFENCEProfile(String callback_url, Map authReq try { logger.debug("getFENCEProfile() query FENCE for user profile with code"); fence_user_profile = getFENCEUserProfile(getFENCEAccessToken(callback_url, fence_code).get("access_token").asText()); - + if(logger.isTraceEnabled()){ // create object mapper instance ObjectMapper mapper = new ObjectMapper(); // `JsonNode` to JSON string String prettyString = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(fence_user_profile); - - logger.trace("getFENCEProfile() user profile structure:"+ prettyString); + + logger.trace("getFENCEProfile() user profile structure:{}", prettyString); } - logger.debug("getFENCEProfile() .username:" + fence_user_profile.get("username")); - logger.debug("getFENCEProfile() .user_id:" + fence_user_profile.get("user_id")); - logger.debug("getFENCEProfile() .email:" + fence_user_profile.get("email")); + logger.debug("getFENCEProfile() .username:{}", fence_user_profile.get("username")); + logger.debug("getFENCEProfile() .user_id:{}", fence_user_profile.get("user_id")); + logger.debug("getFENCEProfile() .email:{}", fence_user_profile.get("email")); } catch (Exception ex) { - logger.error("getFENCEProfile() could not retrieve the user profile from the auth provider, because "+ex.getMessage(), ex); + logger.error("getFENCEProfile() could not retrieve the user profile from the auth provider, because {}", ex.getMessage(), ex); throw new NotAuthorizedException("Could not get the user profile "+ "from the Gen3 authentication provider."+ex.getMessage()); } @@ -187,11 +191,8 @@ public Response getFENCEProfile(String callback_url, Map authReq // Create or retrieve the user profile from our database, based on the the key // in the Gen3/FENCE profile current_user = createUserFromFENCEProfile(fence_user_profile); - logger.info("getFENCEProfile() saved details for user with e-mail:" - +current_user.getEmail() - +" and subject:" - +current_user.getSubject()); - + logger.info("getFENCEProfile() saved details for user with e-mail:{} and subject:{}", current_user.getEmail(), current_user.getSubject()); + //clear some cache entries if we register a new login AuthorizationService.clearCache(current_user); UserService.clearCache(current_user); @@ -201,17 +202,58 @@ public Response getFENCEProfile(String callback_url, Map authReq throw new NotAuthorizedException("The user details could not be persisted. Please contact the administrator."); } + if (fence_harmonized_concept_path != null && !fence_harmonized_concept_path.contains("\\\\")) { + fence_harmonized_concept_path = fence_harmonized_concept_path.replaceAll("\\\\", "\\\\\\\\"); + logger.debug("Escaped harmonized consent path: {}", fence_harmonized_concept_path); + } + // Update the user's roles (or create them if none exists) //Set actual_user_roles = u.getRoles(); Iterator project_access_names = fence_user_profile.get("authz").fieldNames(); - while (project_access_names.hasNext()) { - String access_role_name = project_access_names.next(); - createAndUpsertRole(access_role_name, current_user); + + // I want to parallelize this, but I'm not sure if it's safe to do so. + Set roleNames = new HashSet<>(); + project_access_names.forEachRemaining(roleName -> { + // We need to add/remove the users roles based on what is in the project_access_names list + Map projectMetadata = this.fenceMappingUtility.getFenceMappingByAuthZ().get(roleName); + + if (projectMetadata == null) { + logger.error("getFENCEProfile() -> createAndUpsertRole could not find study in FENCE mapping SKIPPING: {}", roleName); + return; + } + + String projectId = (String) projectMetadata.get("study_identifier"); + String consentCode = (String) projectMetadata.get("consent_group_code"); + String newRoleName = StringUtils.isNotBlank(consentCode) ? "FENCE_"+projectId+"_"+consentCode : "FENCE_"+projectId; + + roleNames.add(newRoleName); + }); + + // find roles that are in the user's roles but not in the project_access_names. These are the roles that need to be removed. + // exclude userRole -> "PIC-SURE Top Admin".equals(userRole.getName()) || "Admin".equals(userRole.getName()) || userRole.getName().startsWith("MANUAL_") + Set rolesToRemove = current_user.getRoles().parallelStream() + .filter(role -> !roleNames.contains(role.getName()) && !role.getName().equals(fence_open_access_role_name) && !role.getName().startsWith("MANUAL_") && !role.getName().equals("PIC-SURE Top Admin") && !role.getName().equals("Admin")) + .collect(Collectors.toSet()); + + if (!rolesToRemove.isEmpty()) { + current_user.getRoles().removeAll(rolesToRemove); + logger.debug("upsertRole() removed {} roles from user", rolesToRemove.size()); + logger.debug("User roles after removal: {}", current_user.getRoles().size()); } - final String idp = extractIdp(current_user); + // find roles that are in the project_access_names but not in the user's roles. These are the roles that need to be added. + List newRoles = roleNames.parallelStream() + .map(roleName -> createRole(roleName, "FENCE role " + roleName)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); - if (current_user.getRoles() != null && (current_user.getRoles().size() > 0 || openAccessIdpValues.contains(idp))) { + if (!newRoles.isEmpty()) { + roleRepo.persistAll(newRoles); + current_user.getRoles().addAll(newRoles); + } + + final String idp = extractIdp(current_user); + if (current_user.getRoles() != null && (!current_user.getRoles().isEmpty() || openAccessIdpValues.contains(idp))) { Role openAccessRole = roleRepo.getUniqueResultByColumn("name", fence_open_access_role_name); if (openAccessRole != null) { current_user.getRoles().add(openAccessRole); @@ -219,62 +261,63 @@ public Response getFENCEProfile(String callback_url, Map authReq logger.warn("Unable to find fence OPEN ACCESS role"); } } - - + + try { userRepo.changeRole(current_user, current_user.getRoles()); - logger.debug("upsertRole() updated user, who now has "+current_user.getRoles().size()+" roles."); + logger.debug("upsertRole() updated user, who now has {} roles.", current_user.getRoles().size()); } catch (Exception ex) { - logger.error("upsertRole() Could not add roles to user, because "+ex.getMessage()); + logger.error("upsertRole() Could not add roles to user, because {}", ex.getMessage()); } HashMap claims = new HashMap(); claims.put("name", fence_user_profile.get("name")); claims.put("email", current_user.getEmail()); claims.put("sub", current_user.getSubject()); HashMap responseMap = authUtil.getUserProfileResponse(claims); - logger.info("LOGIN SUCCESS ___ " + current_user.getEmail() + ":" + current_user.getUuid().toString() + ":" + current_user.getSubject() + " ___ Authorization will expire at ___ " + responseMap.get("expirationDate") + "___"); + logger.info("LOGIN SUCCESS ___ {}:{}:{} ___ Authorization will expire at ___ {}___", current_user.getEmail(), current_user.getUuid().toString(), current_user.getSubject(), responseMap.get("expirationDate")); logger.debug("getFENCEProfile() UserProfile response object has been generated"); logger.debug("getFENCEToken() finished"); + return PICSUREResponse.success(responseMap); } - private void createAndUpsertRole(String access_role_name, User current_user) { - logger.debug("createAndUpsertRole() starting..."); - Map projectMetadata = getFENCEMapping().values().stream() - .filter(map -> access_role_name.equals( - map.get("authZ").toString().replace("\\/", "/")) - ).findFirst().orElse(null); - - if (projectMetadata == null) { - logger.error("getFENCEProfile() -> createAndUpsertRole could not find study in FENCE mapping SKIPPING: " + access_role_name); - return; + private Role createRole(String roleName, String roleDescription) { + if (roleName.isEmpty()) { + logger.error("createRole() roleName is empty"); + return null; } - - String projectId = (String) projectMetadata.get("study_identifier"); - String consentCode = (String) projectMetadata.get("consent_group_code"); - String newRoleName = StringUtils.isNotBlank(consentCode) ? "FENCE_"+projectId+"_"+consentCode : "FENCE_"+projectId; - - logger.info("getFENCEProfile() New PSAMA role name:"+newRoleName); - - if (upsertRole(current_user, newRoleName, "FENCE role "+newRoleName)) { - logger.info("getFENCEProfile() Updated user role. Now it includes `"+newRoleName+"`"); + logger.info("getFENCEProfile() New PSAMA role name:{}", roleName); + Role r; + // Create the Role in the repository, if it does not exist. Otherwise, add it. + Role existing_role = roleRepo.getUniqueResultByColumn("name", roleName); + if (existing_role != null) { + // Role already exists + logger.info("upsertRole() role already exists"); + r = existing_role; } else { - logger.error("getFENCEProfile() could not add roles to user's profile"); + // This is a new Role + r = new Role(); + r.setName(roleName); + r.setDescription(roleDescription); + // Since this is a new Role, we need to ensure that the + // corresponding Privilege (with gates) and AccessRule is added. + r.setPrivileges(addFENCEPrivileges(r)); + logger.info("upsertRole() created new role"); } + + return r; } - private String extractIdp(User current_user) { + private static String extractIdp(User current_user) { try { final ObjectNode node; node = new ObjectMapper().readValue(current_user.getGeneralMetadata(), ObjectNode.class); return node.get("idp").asText(); } catch (JsonProcessingException e) { - logger.warn("Error parsing idp value from medatada", e); return ""; } } - /** * Create or update a user record, based on the FENCE user profile, which is in JSON format. * @@ -286,7 +329,7 @@ private User createUserFromFENCEProfile(JsonNode node) { User new_user = new User(); new_user.setSubject("fence|"+node.get("user_id").asText()); - // This is not always an email address, but it is the only attribute other than the sub claim + // This is not always an email address, but it is the only attribute other than the sub claim // that is guaranteed to be populated by Fence and which makes sense as a display name for a // user. new_user.setEmail(node.get("username").asText()); @@ -297,20 +340,16 @@ private User createUserFromFENCEProfile(JsonNode node) { logger.debug("createUserFromFENCEProfile() finished setting fields"); User actual_user = userRepo.findOrCreate(new_user); - - Set roles = new HashSet<>(); - if (actual_user != null && !CollectionUtils.isEmpty(actual_user.getRoles())) { - roles = actual_user.getRoles().stream() - .filter(userRole -> "PIC-SURE Top Admin".equals(userRole.getName()) || "Admin".equals(userRole.getName()) || userRole.getName().startsWith("MANUAL_")) - .collect(Collectors.toSet()); + if (actual_user.getRoles() == null) { + actual_user.setRoles(new HashSet<>()); } - // Clear current set of roles every time we create or retrieve a user but persist admin status - actual_user.setRoles(roles); +// .filter(userRole -> "PIC-SURE Top Admin".equals(userRole.getName()) || "Admin".equals(userRole.getName()) || userRole.getName().startsWith("MANUAL_")) +// .collect(Collectors.toSet()); logger.debug("createUserFromFENCEProfile() cleared roles"); - userRepo.persist(actual_user); +// userRepo.persist(actual_user); logger.debug("createUserFromFENCEProfile() finished, user record inserted"); return actual_user; } @@ -344,7 +383,7 @@ public boolean upsertRole(User u, String roleName, String roleDescription) { r.setDescription(roleDescription); // Since this is a new Role, we need to ensure that the // corresponding Privilege (with gates) and AccessRule is added. - r.setPrivileges(addFENCEPrivileges(u, r)); + r.setPrivileges(addFENCEPrivileges(r)); roleRepo.persist(r); logger.info("upsertRole() created new role"); } @@ -353,7 +392,7 @@ public boolean upsertRole(User u, String roleName, String roleDescription) { } status = true; } catch (Exception ex) { - logger.error("upsertRole() Could not inser/update role "+roleName+" to repo", ex); + logger.error("upsertRole() Could not inser/update role {} to repo", roleName, ex); } @@ -361,43 +400,43 @@ public boolean upsertRole(User u, String roleName, String roleDescription) { return status; } - private Set addFENCEPrivileges(User u, Role r) { + private Set addFENCEPrivileges(Role r) { String roleName = r.getName(); - logger.info("addFENCEPrivileges() starting, adding privilege(s) to role "+roleName); + logger.info("addFENCEPrivileges() starting, adding privilege(s) to role {}", roleName); //each project can have up to three privileges: Parent | Harmonized | Topmed //harmonized has 2 ARs for parent + harminized and harmonized only - //Topmed has up to three ARs for topmed / topmed + parent / topmed + harmonized + //Topmed has up to three ARs for topmed / topmed + parent / topmed + harmonized Set privs = r.getPrivileges(); if (privs == null) { privs = new HashSet();} //e.g. FENCE_phs0000xx_c2 or FENCE_tutorial-biolinc_camp String project_name = extractProject(roleName); if (project_name.length() <= 0) { - logger.warn("addFENCEPrivileges() role name: "+roleName+" returned an empty project name"); + logger.warn("addFENCEPrivileges() role name: {} returned an empty project name", roleName); } String consent_group = extractConsentGroup(roleName); if (consent_group.length() <= 0) { - logger.warn("addFENCEPrivileges() role name: "+roleName+" returned an empty consent group"); + logger.warn("addFENCEPrivileges() role name: {} returned an empty consent group", roleName); } - logger.info("addFENCEPrivileges() project name: "+project_name+" consent group: "+consent_group); + logger.info("addFENCEPrivileges() project name: {} consent group: {}", project_name, consent_group); // Look up the metadata by consent group. Map projectMetadata = getFENCEMappingforProjectAndConsent(project_name, consent_group); - - if(projectMetadata == null || projectMetadata.size() == 0) { + + if(projectMetadata == null || projectMetadata.isEmpty()) { //no privileges means no access to this project. just return existing set of privs. - logger.warn("No metadata available for project " + project_name + "." + consent_group); + logger.warn("No metadata available for project {}.{}", project_name, consent_group); return privs; } - + logger.info("addPrivileges() This is a new privilege"); - + String dataType = (String) projectMetadata.get("data_type"); Boolean isHarmonized = "Y".equals(projectMetadata.get("is_harmonized")); String concept_path = (String) projectMetadata.get("top_level_path"); String projectAlias = (String) projectMetadata.get("abbreviated_name"); - + //we need to add escape sequence back in to the path for parsing later (also need to double escape the regex) // // OK... so, we need to do this for the query Template and scopes, but should NOT do this for the rules. @@ -412,353 +451,313 @@ private Set addFENCEPrivileges(User u, Role r) { //insert genomic/topmed privs - this will also add rules for including harmonized & parent data if applicable privs.add(upsertTopmedPrivilege(project_name, projectAlias, consent_group, concept_path, isHarmonized)); } - + if(dataType != null && dataType.contains("P")) { //insert clinical privs - logger.info("addPrivileges() project:"+project_name+" consent_group:"+consent_group+" concept_path:"+concept_path); + logger.info("addPrivileges() project:{} consent_group:{} concept_path:{}", project_name, consent_group, concept_path); privs.add(upsertClinicalPrivilege(project_name, projectAlias, consent_group, concept_path, false)); - + //if harmonized study, also create harmonized privileges if(Boolean.TRUE.equals(isHarmonized)) { privs.add(upsertClinicalPrivilege(project_name, projectAlias, consent_group, concept_path, true)); } } - + //projects without G or P in data_type are skipped if(dataType == null || (!dataType.contains("P") && !dataType.contains("G"))){ - logger.warn("Missing study type for " + project_name + " " + consent_group + ". Skipping."); + logger.warn("Missing study type for {} {}. Skipping.", project_name, consent_group); } - + logger.info("addPrivileges() Finished"); return privs; } - private static String extractProject(String roleName) { - String projectPattern = "FENCE_(.*?)(?:_c\\d+)?$"; - if (roleName.startsWith("MANUAL_")) { - projectPattern = "MANUAL_(.*?)(?:_c\\d+)?$"; - } - Pattern projectRegex = Pattern.compile(projectPattern); - Matcher projectMatcher = projectRegex.matcher(roleName); - String project = ""; - if (projectMatcher.find()) { - project = projectMatcher.group(1).trim(); - } else { - String[] parts = roleName.split("_", 1); - if (parts.length > 0) { - project = parts[1]; - } - } - return project; - } - - private static String extractConsentGroup(String roleName) { - String consentPattern = "FENCE_.*?_c(\\d+)$"; - if (roleName.startsWith("MANUAL_")) { - consentPattern = "MANUAL_.*?_c(\\d+)$"; - } - Pattern consentRegex = Pattern.compile(consentPattern); - Matcher consentMatcher = consentRegex.matcher(roleName); - String consentGroup = ""; - if (consentMatcher.find()) { - consentGroup = "c" + consentMatcher.group(1).trim(); - } - return consentGroup; - } - /** - * Creates a privilege with a set of access rules that allow queries containing a consent group to pass if the query only contains valid entries that match conceptPath. If the study is harmonized, + * Creates a privilege with a set of access rules that allow queries containing a consent group to pass if the query only contains valid entries that match conceptPath. If the study is harmonized, * this also creates an access rule to allow access when using the harmonized consent concept path. - * * Privileges created with this method will deny access if any genomic filters (topmed data) are included. - * - * @param studyIdentifier - * @param consent_group - * @param conceptPath - * @param isHarmonized - * @return + * + * @param studyIdentifier The study identifier + * @param consent_group The consent group + * @param conceptPath The concept path + * @param isHarmonized Whether the study is harmonized + * @return The created privilege */ private Privilege upsertClinicalPrivilege(String studyIdentifier, String projectAlias, String consent_group, String conceptPath, boolean isHarmonized) { - - String privilegeName = (consent_group != null && consent_group != "") ? "PRIV_FENCE_"+studyIdentifier+"_"+consent_group+(isHarmonized?"_HARMONIZED":"") : "PRIV_FENCE_"+studyIdentifier+(isHarmonized?"_HARMONIZED":"") ; + // Construct the privilege name + String privilegeName = (consent_group != null && !consent_group.isEmpty()) ? + "PRIV_FENCE_" + studyIdentifier + "_" + consent_group + (isHarmonized ? "_HARMONIZED" : "") : + "PRIV_FENCE_" + studyIdentifier + (isHarmonized ? "_HARMONIZED" : ""); + + // Check if the Privilege already exists Privilege priv = privilegeRepo.getUniqueResultByColumn("name", privilegeName); - if(priv != null) { - logger.info("upsertClinicalPrivilege() " + privilegeName + " already exists"); - return priv; - } - - priv = new Privilege(); + if (priv != null) { + logger.info("{} already exists", privilegeName); + return priv; + } + priv = new Privilege(); try { priv.setApplication(picSureApp); priv.setName(privilegeName); + // Set consent concept path String consent_concept_path = isHarmonized ? fence_harmonized_consent_group_concept_path : fence_parent_consent_group_concept_path; - - if(!consent_concept_path.contains("\\\\")){ - //these have to be escaped again so that jaxson can convert it correctly - consent_concept_path = consent_concept_path.replaceAll("\\\\", "\\\\\\\\"); - logger.debug(consent_concept_path); + if (!consent_concept_path.contains("\\\\")) { + consent_concept_path = consent_concept_path.replaceAll("\\\\", "\\\\\\\\"); + logger.debug("Escaped consent concept path: {}", consent_concept_path); } - if(fence_harmonized_concept_path != null && !fence_harmonized_concept_path.contains("\\\\")){ - //these have to be escaped again so that jaxson can convert it correctly - fence_harmonized_concept_path = fence_harmonized_concept_path.replaceAll("\\\\", "\\\\\\\\"); - logger.debug("upsertTopmedPrivilege(): escaped harmonized consent path" + fence_harmonized_concept_path); - } - - - // TOOD: Change this to a mustache template - String studyIdentifierField = (consent_group != null && consent_group != "") ? studyIdentifier+"."+consent_group: studyIdentifier; - String queryTemplateText = "{\"categoryFilters\": {\"" - +consent_concept_path - +"\":[\"" - +studyIdentifierField - +"\"]}," - +"\"numericFilters\":{},\"requiredFields\":[],"; - - if("fence".equalsIgnoreCase(JAXRSConfiguration.idp_provider)) { - queryTemplateText += "\"fields\":[\"" + parentAccessionField + "\"],"; - } else { - queryTemplateText += "\"fields\":[],"; - } - queryTemplateText+="\"variantInfoFilters\":[{\"categoryVariantInfoFilters\":{},\"numericVariantInfoFilters\":{}}]," - +"\"expectedResultType\": \"COUNT\"" - +"}"; + + String studyIdentifierField = (consent_group != null && !consent_group.isEmpty()) ? studyIdentifier + "." + consent_group : studyIdentifier; + String queryTemplateText = String.format( + "{\"categoryFilters\": {\"%s\":[\"%s\"]},\"numericFilters\":{},\"requiredFields\":[],\"fields\":[],\"variantInfoFilters\":[{\"categoryVariantInfoFilters\":{},\"numericVariantInfoFilters\":{}}],\"expectedResultType\": \"COUNT\"}", + consent_concept_path, studyIdentifierField + ); priv.setQueryTemplate(queryTemplateText); - if(isHarmonized) { - priv.setQueryScope("[\"" + conceptPath + "\",\"_\",\"" + fence_harmonized_concept_path + "\"]"); - } else { - priv.setQueryScope("[\"" + conceptPath + "\",\"_\"]"); - } + priv.setQueryScope(isHarmonized ? String.format("[\"%s\",\"_\",\"%s\"]", conceptPath, fence_harmonized_concept_path) : String.format("[\"%s\",\"_\"]", conceptPath)); + + // Initialize the set of AccessRules + Set accessrules = new HashSet<>(); - Set accessrules = new HashSet(); - - //just need one AR for parent study + // Create and add the parent consent access rule AccessRule ar = createConsentAccessRule(studyIdentifier, consent_group, "PARENT", fence_parent_consent_group_concept_path); - - //if this is a new rule, we need to populate it - if(ar.getGates() == null) { - ar.setGates(new HashSet()); - ar.getGates().addAll(getGates(true, false, false)); - - if(ar.getSubAccessRule() == null) { - ar.setSubAccessRule(new HashSet()); - } - ar.getSubAccessRule().addAll(getAllowedQueryTypeRules()); - ar.getSubAccessRule().addAll(getPhenotypeSubRules(studyIdentifier, conceptPath, projectAlias)); - ar.getSubAccessRule().addAll(getTopmedRestrictedSubRules()); - accessruleRepo.merge(ar); - } - + configureAccessRule(ar, studyIdentifier, consent_group, conceptPath, projectAlias, true, false, false); accessrules.add(ar); - - // here we add a rule to allow querying a parent study if genomic filters are included. This goes on all studies; - // if the study has no genomic data or the user is not authorizzed for genomic studies, there will be 0 patients returned. - ar = upsertTopmedAccessRule(studyIdentifier, consent_group, "TOPMED+PARENT"); - - //if this is a new rule, we need to populate it - if(ar.getGates() == null) { - ar.setGates(new HashSet()); - ar.getGates().addAll(getGates(true, false, true)); - if(ar.getSubAccessRule() == null) { - ar.setSubAccessRule(new HashSet()); - } - ar.getSubAccessRule().addAll(getAllowedQueryTypeRules()); - ar.getSubAccessRule().addAll(getPhenotypeSubRules(studyIdentifier, conceptPath, projectAlias)); - //this is added in the 'getPhenotypeRestrictedSubRules()' which is not called in this path - ar.getSubAccessRule().add(createPhenotypeSubRule(fence_topmed_consent_group_concept_path, "ALLOW_TOPMED_CONSENT", "$.query.query.categoryFilters", AccessRule.TypeNaming.ALL_CONTAINS, "", true)); - - accessruleRepo.merge(ar); - } + + // Create and add the Topmed+Parent access rule + ar = upsertTopmedAccessRule(studyIdentifier, consent_group, "TOPMED+PARENT"); + configureAccessRule(ar, studyIdentifier, consent_group, conceptPath, projectAlias, true, false, true); accessrules.add(ar); - - if(isHarmonized) { - //add a rule for accessing only harmonized data - ar = createConsentAccessRule(studyIdentifier, consent_group, "HARMONIZED", fence_harmonized_consent_group_concept_path); - - //if this is a new rule, we need to populate it - if(ar.getGates() == null) { - ar.setGates(new HashSet()); - ar.getGates().add(upsertConsentGate("HARMONIZED_CONSENT", "$.query.query.categoryFilters." + fence_harmonized_consent_group_concept_path + "[*]", true, "harmonized data")); - - if(ar.getSubAccessRule() == null) { - ar.setSubAccessRule(new HashSet()); - } - ar.getSubAccessRule().addAll(getAllowedQueryTypeRules()); - ar.getSubAccessRule().addAll(getHarmonizedSubRules()); - ar.getSubAccessRule().addAll(getPhenotypeSubRules(studyIdentifier, conceptPath, projectAlias)); - accessruleRepo.merge(ar); - } - accessrules.add(ar); - } - - // Add additional access rules; (these are still created through that SQL script) - for(String arName: fence_standard_access_rules.split(",")) { - if (arName.startsWith("AR_")) { - logger.info("upsertClinicalPrivilege() Adding AccessRule "+arName+" to privilege "+priv.getName()); - ar = accessruleRepo.getUniqueResultByColumn("name",arName); - if(ar != null) { - accessrules.add(ar); - } - else { - logger.warn("upsertClinicalPrivilege() unable to find an access rule with name " + arName); - } - } + + // If harmonized, create and add the harmonized access rule + if (isHarmonized) { + ar = createConsentAccessRule(studyIdentifier, consent_group, "HARMONIZED", fence_harmonized_consent_group_concept_path); + configureHarmonizedAccessRule(ar, studyIdentifier, consent_group, conceptPath, projectAlias); + accessrules.add(ar); } + + // Add standard access rules + addStandardAccessRules(accessrules); + priv.setAccessRules(accessrules); - logger.info("createNewPrivilege() Added "+accessrules.size()+" access_rules to privilege"); + logger.info("Added {} access_rules to privilege", accessrules.size()); privilegeRepo.persist(priv); - logger.info("createNewPrivilege() Added new privilege "+priv.getName()+" to DB"); + logger.info("Added new privilege {} to DB", priv.getName()); } catch (Exception ex) { - ex.printStackTrace(); - logger.error("createNewPrivilege() could not save privilege"); + logger.error("Could not save privilege", ex); } return priv; } - - private Set getAllowedQueryTypeRules() { - Set rules = new HashSet(); - String[] allowedTypes = JAXRSConfiguration.fence_allowed_query_types.split(","); - for(String queryType : allowedTypes) { - - String ar_name = "AR_ALLOW_" + queryType ; - - AccessRule ar = accessruleRepo.getUniqueResultByColumn("name", ar_name); - if(ar != null) { - logger.debug("createTopmedRestrictedSubRule() Found existing rule: " + ar.getName()); - rules.add(ar); - continue; + + /** + * Configures the AccessRule with gates and sub-rules. + * + * @param ar The AccessRule to configure. + * @param studyIdentifier The study identifier. + * @param consent_group The consent group. + * @param conceptPath The concept path. + * @param projectAlias The project alias. + * @param parent Whether to include parent gates. + * @param harmonized Whether to include harmonized gates. + * @param topmed Whether to include Topmed gates. + */ + private void configureAccessRule(AccessRule ar, String studyIdentifier, String consent_group, String conceptPath, String projectAlias, boolean parent, boolean harmonized, boolean topmed) { + if (ar.getGates() == null) { + ar.setGates(new HashSet<>()); + ar.getGates().addAll(getGates(parent, harmonized, topmed)); + + if (ar.getSubAccessRule() == null) { + ar.setSubAccessRule(new HashSet<>()); } - - logger.info("upsertPhenotypeSubRule() Creating new access rule "+ar_name); - ar = new AccessRule(); - ar.setName(ar_name); - ar.setDescription("FENCE SUB AR to allow " + queryType + " Queries"); - ar.setRule( "$.query.query.expectedResultType"); - ar.setType(AccessRule.TypeNaming.ALL_EQUALS); - ar.setValue(queryType); - ar.setCheckMapKeyOnly(false); - ar.setCheckMapNode(false); - ar.setEvaluateOnlyByGates(false); - ar.setGateAnyRelation(false); - - accessruleRepo.persist(ar); - rules.add(ar); - - } - return rules; - } + ar.getSubAccessRule().addAll(getAllowedQueryTypeRules()); + ar.getSubAccessRule().addAll(getPhenotypeSubRules(studyIdentifier, conceptPath, projectAlias)); + ar.getSubAccessRule().addAll(getTopmedRestrictedSubRules()); + accessruleRepo.merge(ar); + } + } - private Collection getTopmedRestrictedSubRules() { + /** + * Configures the harmonized AccessRule with gates and sub-rules. + * + * @param ar The AccessRule to configure. + * @param studyIdentifier The study identifier. + * @param consent_group The consent group. + * @param conceptPath The concept path. + * @param projectAlias The project alias. + */ + private void configureHarmonizedAccessRule(AccessRule ar, String studyIdentifier, String consent_group, String conceptPath, String projectAlias) { + if (ar.getGates() == null) { + ar.setGates(new HashSet<>()); + ar.getGates().add(upsertConsentGate("HARMONIZED_CONSENT", "$.query.query.categoryFilters." + fence_harmonized_consent_group_concept_path + "[*]", true, "harmonized data")); + + if (ar.getSubAccessRule() == null) { + ar.setSubAccessRule(new HashSet<>()); + } + ar.getSubAccessRule().addAll(getAllowedQueryTypeRules()); + ar.getSubAccessRule().addAll(getHarmonizedSubRules()); + ar.getSubAccessRule().addAll(getPhenotypeSubRules(studyIdentifier, conceptPath, projectAlias)); + accessruleRepo.merge(ar); + } + } + + private Set getAllowedQueryTypeRules() { + if (allowQueryTypeRules == null) { + allowQueryTypeRules = loadAllowedQueryTypeRules(); + } + + return allowQueryTypeRules; + } + + /** + * Retrieves or creates AccessRules for allowed query types. + * + * @return A set of AccessRules for allowed query types. + */ + private Set loadAllowedQueryTypeRules() { + // Initialize a set to hold the AccessRules + Set rules = new HashSet<>(); + // Split the allowed query types from the configuration + String[] allowedTypes = JAXRSConfiguration.fence_allowed_query_types.split(","); + + // Iterate over each allowed query type + for (String queryType : allowedTypes) { + // Construct the AccessRule name + String ar_name = "AR_ALLOW_" + queryType; + + // Log the creation of a new AccessRule + AccessRule ar = getOrCreateAccessRule( + ar_name, + "FENCE SUB AR to allow " + queryType + " Queries", + "$.query.query.expectedResultType", + AccessRule.TypeNaming.ALL_EQUALS, + queryType, + false, + false, + false, + false + ); + + // Add the newly created rule to the set + rules.add(ar); + } + // Return the set of AccessRules + return rules; + } + + private Collection getTopmedRestrictedSubRules() { Set rules = new HashSet(); rules.add(upsertTopmedRestrictedSubRule("CATEGORICAL", "$.query.query.variantInfoFilters[*].categoryVariantInfoFilters.*")); rules.add(upsertTopmedRestrictedSubRule("NUMERIC", "$.query.query.variantInfoFilters[*].numericVariantInfoFilters.*")); - + return rules; } - - //topmed restriction rules don't need much configuration. Just deny all access. + + /** + * Creates and returns a restricted sub-rule AccessRule for Topmed. + * topmed restriction rules don't need much configuration. Just deny all access. + * @param type The type of the Topmed restriction. + * @param rule The rule expression. + * @return The created AccessRule. + */ private AccessRule upsertTopmedRestrictedSubRule(String type, String rule) { + // Construct the AccessRule name String ar_name = "AR_TOPMED_RESTRICTED_" + type; - + // Check if the AccessRule already exists AccessRule ar = accessruleRepo.getUniqueResultByColumn("name", ar_name); - if(ar != null) { - logger.debug("createTopmedRestrictedSubRule() Found existing rule: " + ar.getName()); - return ar; + if (ar != null) { + // Log and return the existing rule + logger.debug("Found existing rule: {}", ar.getName()); + return ar; } - - logger.info("upsertTopmedRestrictedSubRule() Creating new access rule "+ar_name); - ar = new AccessRule(); - ar.setName(ar_name); - ar.setDescription("FENCE SUB AR for retricting " + type + " genomic concepts"); - ar.setRule(rule); - ar.setType(AccessRule.TypeNaming.IS_EMPTY); - ar.setValue(null); - ar.setCheckMapKeyOnly(false); - ar.setCheckMapNode(false); - ar.setEvaluateOnlyByGates(false); - ar.setGateAnyRelation(false); - - accessruleRepo.persist(ar); - - return ar; + + // Log the creation of a new AccessRule + // Create the AccessRule using the createAccessRule method + return getOrCreateAccessRule( + ar_name, + "FENCE SUB AR for restricting " + type + " genomic concepts", + rule, + AccessRule.TypeNaming.IS_EMPTY, + null, + false, + false, + false, + false + ); } - private Collection getPhenotypeSubRules(String studyIdentifier, String conceptPath, String alias) { - + private Collection getPhenotypeSubRules(String studyIdentifier, String conceptPath, String alias) { + Set rules = new HashSet(); //categorical filters will always contain at least one entry (for the consent groups); it will never be empty rules.add(createPhenotypeSubRule(fence_parent_consent_group_concept_path, "ALLOW_PARENT_CONSENT", "$.query.query.categoryFilters", AccessRule.TypeNaming.ALL_CONTAINS, "", true)); - - for(String underscorePath : underscoreFields ) { + + for(String underscorePath : underscoreFields) { rules.add(createPhenotypeSubRule(underscorePath, "ALLOW " + underscorePath, "$.query.query.fields.[*]", AccessRule.TypeNaming.ALL_CONTAINS_OR_EMPTY, "FIELDS", false)); rules.add(createPhenotypeSubRule(underscorePath, "ALLOW " + underscorePath, "$.query.query.categoryFilters", AccessRule.TypeNaming.ALL_CONTAINS, "CATEGORICAL", true)); rules.add(createPhenotypeSubRule(underscorePath, "ALLOW " + underscorePath, "$.query.query.requiredFields.[*]", AccessRule.TypeNaming.ALL_CONTAINS_OR_EMPTY, "REQ_FIELDS", false)); } - + rules.add(createPhenotypeSubRule(conceptPath, alias+ "_" + studyIdentifier, "$.query.query.categoryFilters", AccessRule.TypeNaming.ALL_CONTAINS, "CATEGORICAL", true)); rules.add(createPhenotypeSubRule(conceptPath, alias+ "_" + studyIdentifier, "$.query.query.numericFilters", AccessRule.TypeNaming.ALL_CONTAINS_OR_EMPTY, "NUMERIC", true)); rules.add(createPhenotypeSubRule(conceptPath, alias+ "_" + studyIdentifier, "$.query.query.fields.[*]", AccessRule.TypeNaming.ALL_CONTAINS_OR_EMPTY, "FIELDS", false)); rules.add(createPhenotypeSubRule(conceptPath, alias+ "_" + studyIdentifier, "$.query.query.requiredFields.[*]", AccessRule.TypeNaming.ALL_CONTAINS_OR_EMPTY, "REQUIRED_FIELDS", false)); - + return rules; } - + /** - * Harmonized rules should allow the user to supply paretn and top med consent groups; this allows a single harmonized - * rules instead of splitting between a topmed+harmonized and parent+harmonized - * + * Harmonized rules should allow the user to supply paretn and top med consent groups; this allows a single harmonized + * rules instead of splitting between a topmed+harmonized and parent+harmonized + * * @return */ private Collection getHarmonizedSubRules() { - + Set rules = new HashSet(); //categorical filters will always contain at least one entry (for the consent groups); it will never be empty rules.add(createPhenotypeSubRule(fence_parent_consent_group_concept_path, "ALLOW_PARENT_CONSENT", "$.query.query.categoryFilters", AccessRule.TypeNaming.ALL_CONTAINS, "", true)); rules.add(createPhenotypeSubRule(fence_harmonized_consent_group_concept_path, "ALLOW_HARMONIZED_CONSENT", "$.query.query.categoryFilters", AccessRule.TypeNaming.ALL_CONTAINS, "", true)); rules.add(createPhenotypeSubRule(fence_topmed_consent_group_concept_path, "ALLOW_TOPMED_CONSENT", "$.query.query.categoryFilters", AccessRule.TypeNaming.ALL_CONTAINS, "", true)); - - for(String underscorePath : underscoreFields ) { + + for(String underscorePath : underscoreFields) { rules.add(createPhenotypeSubRule(underscorePath, "ALLOW " + underscorePath, "$.query.query.fields.[*]", AccessRule.TypeNaming.ALL_CONTAINS_OR_EMPTY, "FIELDS", false)); rules.add(createPhenotypeSubRule(underscorePath, "ALLOW " + underscorePath, "$.query.query.categoryFilters", AccessRule.TypeNaming.ALL_CONTAINS, "CATEGORICAL", true)); rules.add(createPhenotypeSubRule(underscorePath, "ALLOW " + underscorePath, "$.query.query.requiredFields.[*]", AccessRule.TypeNaming.ALL_CONTAINS_OR_EMPTY, "REQ_FIELDS", false)); } - + rules.add(createPhenotypeSubRule(fence_harmonized_concept_path, "HARMONIZED", "$.query.query.categoryFilters", AccessRule.TypeNaming.ALL_CONTAINS, "CATEGORICAL", true)); rules.add(createPhenotypeSubRule(fence_harmonized_concept_path, "HARMONIZED", "$.query.query.numericFilters", AccessRule.TypeNaming.ALL_CONTAINS_OR_EMPTY, "NUMERIC", true)); rules.add(createPhenotypeSubRule(fence_harmonized_concept_path, "HARMONIZED", "$.query.query.fields.[*]", AccessRule.TypeNaming.ALL_CONTAINS_OR_EMPTY, "FIELDS", false)); rules.add(createPhenotypeSubRule(fence_harmonized_concept_path, "HARMONIZED", "$.query.query.requiredFields.[*]", AccessRule.TypeNaming.ALL_CONTAINS_OR_EMPTY, "REQUIRED_FIELDS", false)); - + return rules; } - + /** * generate and return a set of rules that disallow access to phenotype data (only genomic filters allowed) * @return */ private Collection getPhenotypeRestrictedSubRules(String studyIdentifier, String consentCode, String alias) { - Set rules = new HashSet(); //categorical filters will always contain at least one entry (for the consent groups); it will never be empty rules.add(createPhenotypeSubRule(fence_topmed_consent_group_concept_path, "ALLOW_TOPMED_CONSENT", "$.query.query.categoryFilters", AccessRule.TypeNaming.ALL_CONTAINS, "", true)); - + for(String underscorePath : underscoreFields ) { rules.add(createPhenotypeSubRule(underscorePath, "ALLOW " + underscorePath, "$.query.query.fields.[*]", AccessRule.TypeNaming.ALL_CONTAINS_OR_EMPTY, "FIELDS", false)); rules.add(createPhenotypeSubRule(underscorePath, "ALLOW " + underscorePath, "$.query.query.categoryFilters", AccessRule.TypeNaming.ALL_CONTAINS, "CATEGORICAL", true)); rules.add(createPhenotypeSubRule(underscorePath, "ALLOW " + underscorePath, "$.query.query.requiredFields.[*]", AccessRule.TypeNaming.ALL_CONTAINS_OR_EMPTY, "REQ_FIELDS", false)); } - + rules.add(createPhenotypeSubRule(null, alias + "_" + studyIdentifier+ "_" + consentCode, "$.query.query.numericFilters.[*]", AccessRule.TypeNaming.IS_EMPTY, "DISALLOW_NUMERIC", false)); -// rules.add(createPhenotypeSubRule(null, alias + "_" + studyIdentifier+ "_" + consentCode, "$.query.query.fields.[*]", AccessRule.TypeNaming.IS_EMPTY, "DISALLOW FIELDS", false, parentRule)); rules.add(createPhenotypeSubRule(null, alias + "_" + studyIdentifier+ "_" + consentCode, "$.query.query.requiredFields.[*]", AccessRule.TypeNaming.IS_EMPTY, "DISALLOW_REQUIRED_FIELDS", false)); - + return rules; } - + /** * Return a set of gates that identify which consent values have been provided. the boolean parameters indicate * if a value in the specified consent location should allow this gate to pass. @@ -768,399 +767,359 @@ private Collection getPhenotypeRestrictedSubRules(String s * @return */ private Collection getGates(boolean parent, boolean harmonized, boolean topmed) { - Set gates = new HashSet(); gates.add(upsertConsentGate("PARENT_CONSENT", "$.query.query.categoryFilters." + fence_parent_consent_group_concept_path + "[*]", parent, "parent study data")); gates.add(upsertConsentGate("HARMONIZED_CONSENT", "$.query.query.categoryFilters." + fence_harmonized_consent_group_concept_path + "[*]", harmonized, "harmonized data")); gates.add(upsertConsentGate("TOPMED_CONSENT", "$.query.query.categoryFilters." + fence_topmed_consent_group_concept_path + "[*]", topmed, "Topmed data")); - + return gates; } - - private AccessRule createPhenotypeSubRule(String conceptPath, String alias, String rule, int ruleType, String label, boolean useMapKey) { - - //remove double escape sequence from path evaluation expression - if(conceptPath != null && conceptPath.contains("\\\\")) { - //replaceall regex needs to be double escaped (again) - conceptPath = conceptPath.replaceAll("\\\\\\\\", "\\\\"); - } - - String ar_name = "AR_PHENO_"+alias + "_" + label; - - AccessRule ar = accessruleRepo.getUniqueResultByColumn("name", ar_name); - if(ar != null) { - logger.debug("createPhenotypeSubRule() Found existing rule: " + ar.getName()); - return ar; - } - - logger.info("createPhenotypeSubRule() Creating new access rule "+ar_name); - ar = new AccessRule(); - ar.setName(ar_name); - ar.setDescription("FENCE SUB AR for " + alias + " " + label + " clinical concepts"); - ar.setRule(rule); - ar.setType(ruleType); - ar.setValue(ruleType == AccessRule.TypeNaming.IS_NOT_EMPTY ? null : conceptPath); - ar.setCheckMapKeyOnly(useMapKey); - ar.setCheckMapNode(useMapKey); - ar.setEvaluateOnlyByGates(false); - ar.setGateAnyRelation(false); - - accessruleRepo.persist(ar); - - return ar; - } - - /** - * Creates a privilege for Topmed access. This has (up to) three access rules - 1) topmed only 2) topmed + parent 3) topmed+ hermonized (??) - * @param studyIdentifier - * @param consent_group - * @return - */ - private Privilege upsertTopmedPrivilege(String studyIdentifier, String projectAlias, String consent_group, String parentConceptPath, boolean isHarmonized) { - - String privilegeName = "PRIV_FENCE_"+studyIdentifier+"_"+consent_group + "_TOPMED"; - Privilege priv = privilegeRepo.getUniqueResultByColumn("name", privilegeName); - if(priv != null) { - logger.info("upsertTopmedPrivilege() " + privilegeName + " already exists"); - return priv; - } - - priv = new Privilege(); + /** + * Creates a privilege for Topmed access. This has (up to) three access rules: + * 1) topmed only 2) topmed + parent 3) topmed + harmonized. + * @param studyIdentifier + * @param projectAlias + * @param consentGroup + * @param parentConceptPath + * @param isHarmonized + * @return Privilege + */ + private Privilege upsertTopmedPrivilege(String studyIdentifier, String projectAlias, String consentGroup, String parentConceptPath, boolean isHarmonized) { + String privilegeName = "PRIV_FENCE_" + studyIdentifier + "_" + consentGroup + "_TOPMED"; + Privilege priv = privilegeRepo.getUniqueResultByColumn("name", privilegeName); + if (priv != null) { + logger.info("upsertTopmedPrivilege() {} already exists", privilegeName); + return priv; + } + + priv = new Privilege(); - // Build Privilege Object try { - priv.setApplication(picSureApp); - priv.setName(privilegeName); - priv.setDescription("FENCE privilege for Topmed "+studyIdentifier+"."+consent_group); - - String consent_concept_path = fence_topmed_consent_group_concept_path; - if(!consent_concept_path.contains("\\\\")){ - //these have to be escaped again so that jaxson can convert it correctly - consent_concept_path = consent_concept_path.replaceAll("\\\\", "\\\\\\\\"); - logger.debug("upsertTopmedPrivilege(): escaped parent consent path" + consent_concept_path); - } - - if(fence_harmonized_concept_path != null && !fence_harmonized_concept_path.contains("\\\\")){ - //these have to be escaped again so that jaxson can convert it correctly - fence_harmonized_concept_path = fence_harmonized_concept_path.replaceAll("\\\\", "\\\\\\\\"); - logger.debug("upsertTopmedPrivilege(): escaped harmonized consent path" + fence_harmonized_concept_path); - } - - // TODO: Change this to a mustache template - String queryTemplateText = "{\"categoryFilters\": {\"" - +consent_concept_path - +"\":[\"" - +studyIdentifier+"."+consent_group - +"\"]}," - +"\"numericFilters\":{},\"requiredFields\":[]," - +"\"fields\":[\"" + topmedAccessionField + "\"]," - +"\"variantInfoFilters\":[{\"categoryVariantInfoFilters\":{},\"numericVariantInfoFilters\":{}}]," - +"\"expectedResultType\": \"COUNT\"" - +"}"; - priv.setQueryTemplate(queryTemplateText); - //need a non-null scope that is a list of strings - - - - String variantColumns = JAXRSConfiguration.variantAnnotationColumns; - if(variantColumns == null || variantColumns.isEmpty()) { - priv.setQueryScope("[\"_\"]"); - } else { - StringBuilder builder = new StringBuilder(); - for(String annotationPath : variantColumns.split(",")) { - if(builder.length() == 0) { - builder.append("["); - } else { - builder.append(","); - } - builder.append("\""+annotationPath+"\""); - } - builder.append(",\"_\""); - builder.append("]"); - priv.setQueryScope(builder.toString()); - } - - - Set accessrules = new HashSet(); - - AccessRule ar = upsertTopmedAccessRule(studyIdentifier, consent_group, "TOPMED"); - - //if this is a new rule, we need to populate it - if(ar.getGates() == null) { - ar.setGates(new HashSet()); - ar.getGates().addAll(getGates(false, false, true)); - - if(ar.getSubAccessRule() == null) { - ar.setSubAccessRule(new HashSet()); - } - ar.getSubAccessRule().addAll(getAllowedQueryTypeRules()); - ar.getSubAccessRule().addAll(getPhenotypeRestrictedSubRules(studyIdentifier, consent_group, projectAlias)); - accessruleRepo.merge(ar); - } - accessrules.add(ar); - - if(parentConceptPath != null) { - - ar = upsertTopmedAccessRule(studyIdentifier, consent_group, "TOPMED+PARENT"); - - //if this is a new rule, we need to populate it - if(ar.getGates() == null) { - ar.setGates(new HashSet()); - ar.getGates().addAll(getGates(true, false, true)); - if(ar.getSubAccessRule() == null) { - ar.setSubAccessRule(new HashSet()); - } - ar.getSubAccessRule().addAll(getAllowedQueryTypeRules()); - ar.getSubAccessRule().addAll(getPhenotypeSubRules(studyIdentifier, parentConceptPath, projectAlias)); - //this is added in the 'getPhenotypeRestrictedSubRules()' which is not called in this path - ar.getSubAccessRule().add(createPhenotypeSubRule(fence_topmed_consent_group_concept_path, "ALLOW_TOPMED_CONSENT", "$.query.query.categoryFilters", AccessRule.TypeNaming.ALL_CONTAINS, "", true)); - - accessruleRepo.merge(ar); - } - accessrules.add(ar); - - - if(isHarmonized) { - ar = upsertHarmonizedAccessRule(studyIdentifier, consent_group, "HARMONIZED"); - - //if this is a new rule, we need to populate it - if(ar.getGates() == null) { - ar.setGates(new HashSet()); -// ar.getGates().addAll(getGates(true, true, false)); - ar.getGates().add(upsertConsentGate("HARMONIZED_CONSENT", "$.query.query.categoryFilters." + fence_harmonized_consent_group_concept_path + "[*]", true, "harmonized data")); - - if(ar.getSubAccessRule() == null) { - ar.setSubAccessRule(new HashSet()); - } - ar.getSubAccessRule().addAll(getAllowedQueryTypeRules()); - ar.getSubAccessRule().addAll(getHarmonizedSubRules()); - ar.getSubAccessRule().addAll(getPhenotypeSubRules(studyIdentifier, parentConceptPath, projectAlias)); - accessruleRepo.merge(ar); - } - accessrules.add(ar); - - -// ar = upsertHarmonizedAccessRule(studyIdentifier, consent_group, "TOPMED+HARMONIZED+PARENT"); -// -// //if this is a new rule, we need to populate it -// if(ar.getGates() == null) { -// ar.setGates(new HashSet()); -// ar.getGates().addAll(getGates(true, true, true)); -// -// if(ar.getSubAccessRule() == null) { -// ar.setSubAccessRule(new HashSet()); -// } -// ar.getSubAccessRule().addAll(getAllowedQueryTypeRules()); -// ar.getSubAccessRule().addAll(getPhenotypeSubRules(studyIdentifier, parentConceptPath, projectAlias)); -// ar.getSubAccessRule().addAll(getHarmonizedSubRules()); -// ar.getSubAccessRule().add(createPhenotypeSubRule(fence_topmed_consent_group_concept_path, "ALLOW_TOPMED_CONSENT", "$.query.query.categoryFilters", AccessRule.TypeNaming.ALL_CONTAINS, "", true)); -// -// accessruleRepo.merge(ar); -// } -// accessrules.add(ar); - } - - } - - // Add additional access rules; - for(String arName: fence_standard_access_rules.split(",")) { - if (arName.startsWith("AR_")) { - logger.info("upsertTopmedPrivilege() Adding AccessRule "+arName+" to privilege "+priv.getName()); - ar = accessruleRepo.getUniqueResultByColumn("name",arName); - if(ar != null) { - accessrules.add(ar); - } - else { - logger.warn("uupsertTopmedPrivilege() nable to find an access rule with name " + arName); - } + buildPrivilegeObject(priv, privilegeName, studyIdentifier, consentGroup); + + Set accessRules = new HashSet<>(); + AccessRule topmedRule = upsertTopmedAccessRule(studyIdentifier, consentGroup, "TOPMED"); + + populateAccessRule(topmedRule, false, false, true); + topmedRule.getSubAccessRule().addAll(getPhenotypeRestrictedSubRules(studyIdentifier, consentGroup, projectAlias)); + accessRules.add(topmedRule); + + if (parentConceptPath != null) { + AccessRule topmedParentRule = upsertTopmedAccessRule(studyIdentifier, consentGroup, "TOPMED+PARENT"); + populateAccessRule(topmedParentRule, true, false, true); + topmedParentRule.getSubAccessRule().addAll(getPhenotypeSubRules(studyIdentifier, parentConceptPath, projectAlias)); + accessRules.add(topmedParentRule); + + if (isHarmonized) { + AccessRule harmonizedRule = upsertHarmonizedAccessRule(studyIdentifier, consentGroup, "HARMONIZED"); + populateHarmonizedAccessRule(harmonizedRule, parentConceptPath, studyIdentifier, projectAlias); + accessRules.add(harmonizedRule); } } - priv.setAccessRules(accessrules); - logger.info("upsertTopmedPrivilege() Added "+accessrules.size()+" access_rules to privilege"); + + addStandardAccessRules(accessRules); + + priv.setAccessRules(accessRules); + logger.info("upsertTopmedPrivilege() Added {} access_rules to privilege", accessRules.size()); privilegeRepo.persist(priv); - logger.info("upsertTopmedPrivilege() Added new privilege "+priv.getName()+" to DB"); + logger.info("upsertTopmedPrivilege() Added new privilege {} to DB", priv.getName()); } catch (Exception ex) { - ex.printStackTrace(); - logger.error("upsertTopmedPrivilege() could not save privilege"); + logger.error("upsertTopmedPrivilege() could not save privilege", ex); } + return priv; } - //Generates Main rule only; gates & sub rules attached after calling this - // prentRule should be null if this is the main rule, or the appropriate value if this is a sub rule - private AccessRule createConsentAccessRule(String studyIdentifier, String consent_group, String label, String consent_path) { - logger.debug("upsertConsentAccessRule() starting"); - String ar_name = (consent_group != null && consent_group != "") ? "AR_CONSENT_" + studyIdentifier+"_"+consent_group+ "_" +label : "AR_CONSENT_" + studyIdentifier; + private void buildPrivilegeObject(Privilege priv, String privilegeName, String studyIdentifier, String consentGroup) { + priv.setApplication(picSureApp); + priv.setName(privilegeName); + priv.setDescription("FENCE privilege for Topmed " + studyIdentifier + "." + consentGroup); - AccessRule ar = accessruleRepo.getUniqueResultByColumn("name", ar_name); - if(ar != null) { - logger.debug("upsertConsentAccessRule() Found existing rule: " + ar.getName()); - return ar; + String consentConceptPath = escapePath(fence_topmed_consent_group_concept_path); + fence_harmonized_concept_path = escapePath(fence_harmonized_concept_path); + + String queryTemplateText = "{\"categoryFilters\": {\"" + consentConceptPath + "\":[\"" + studyIdentifier + "." + consentGroup + "\"]}," + + "\"numericFilters\":{},\"requiredFields\":[]," + + "\"fields\":[\"" + topmedAccessionField + "\"]," + + "\"variantInfoFilters\":[{\"categoryVariantInfoFilters\":{},\"numericVariantInfoFilters\":{}}]," + + "\"expectedResultType\": \"COUNT\"" + + "}"; + + priv.setQueryTemplate(queryTemplateText); + + String variantColumns = JAXRSConfiguration.variantAnnotationColumns; + priv.setQueryScope(buildQueryScope(variantColumns)); + } + + private String escapePath(String path) { + if (path != null && !path.contains("\\\\")) { + return path.replaceAll("\\\\", "\\\\\\\\"); } - - - - logger.info("upsertConsentAccessRule() Creating new access rule "+ar_name); - ar = new AccessRule(); - ar.setName(ar_name); - String description = (consent_group != null && consent_group != "") ? "FENCE AR for "+studyIdentifier+"."+consent_group + " clinical concepts" : "FENCE AR for "+studyIdentifier+" clinical concepts"; - ar.setDescription(description); - StringBuilder ruleText = new StringBuilder(); - ruleText.append("$.query.query.categoryFilters."); - ruleText.append(consent_path); - ruleText.append("[*]"); - ar.setRule(ruleText.toString()); - ar.setType(AccessRule.TypeNaming.ALL_CONTAINS); - String arValue = (consent_group != null && consent_group != "") ? studyIdentifier+"."+consent_group : studyIdentifier; - ar.setValue(arValue); - ar.setCheckMapKeyOnly(false); - ar.setCheckMapNode(false); - ar.setEvaluateOnlyByGates(false); - ar.setGateAnyRelation(false); - - accessruleRepo.persist(ar); - - logger.debug("upsertConsentAccessRule() finished"); - return ar; + return path; } - - // Generates Main Rule only; gates & sub rules attached by calling method - private AccessRule upsertTopmedAccessRule(String project_name, String consent_group, String label ) { - logger.debug("upsertTopmedAccessRule() starting"); - String ar_name = (consent_group != null && consent_group != "") ? "AR_TOPMED_"+project_name+"_"+consent_group + "_" + label : "AR_TOPMED_"+project_name+"_"+label; - AccessRule ar = accessruleRepo.getUniqueResultByColumn("name", ar_name); - if (ar != null) { - logger.info("upsertTopmedAccessRule() AccessRule "+ar_name+" already exists."); - return ar; + + private String buildQueryScope(String variantColumns) { + if (variantColumns == null || variantColumns.isEmpty()) { + return "[\"_\"]"; } - logger.info("upsertTopmedAccessRule() Creating new access rule "+ar_name); - ar = new AccessRule(); - ar.setName(ar_name); - ar.setDescription("FENCE AR for "+project_name+"."+consent_group + " Topmed data"); - StringBuilder ruleText = new StringBuilder(); - ruleText.append("$.query.query.categoryFilters."); - ruleText.append(fence_topmed_consent_group_concept_path); - ruleText.append("[*]"); - ar.setRule(ruleText.toString()); - ar.setType(AccessRule.TypeNaming.ALL_CONTAINS); - String arValue = (consent_group != null && consent_group != "") ? project_name+"."+consent_group : project_name; - ar.setValue(arValue); - ar.setCheckMapKeyOnly(false); - ar.setCheckMapNode(false); - ar.setEvaluateOnlyByGates(false); - ar.setGateAnyRelation(false); - - accessruleRepo.persist(ar); - - logger.debug("upsertTopmedAccessRule() finished"); - return ar; + return Arrays.stream(variantColumns.split(",")) + .map(path -> "\"" + path + "\"") + .collect(Collectors.joining(",", "[", ",\"_\"]")); } - - - // Generates Main Rule only; gates & sub rules attached by calling method - private AccessRule upsertHarmonizedAccessRule(String project_name, String consent_group, String label ) { - logger.debug("upsertTopmedAccessRule() starting"); - String ar_name = "AR_TOPMED_"+project_name+"_"+consent_group + "_" + label; - AccessRule ar = accessruleRepo.getUniqueResultByColumn("name", ar_name); - if (ar != null) { - logger.info("upsertTopmedAccessRule() AccessRule "+ar_name+" already exists."); - return ar; + + private void populateAccessRule(AccessRule rule, boolean includeParent, boolean includeHarmonized, boolean includeTopmed) { + if (rule.getGates() == null) { + rule.setGates(new HashSet<>(getGates(includeParent, includeHarmonized, includeTopmed))); + } + + if (rule.getSubAccessRule() == null) { + rule.setSubAccessRule(new HashSet<>(getAllowedQueryTypeRules())); + } + + accessruleRepo.merge(rule); + } + + private void populateHarmonizedAccessRule(AccessRule rule, String parentConceptPath, String studyIdentifier, String projectAlias) { + if (rule.getGates() == null) { + rule.setGates(new HashSet<>(Collections.singletonList( + upsertConsentGate("HARMONIZED_CONSENT", "$.query.query.categoryFilters." + fence_harmonized_consent_group_concept_path + "[*]", true, "harmonized data") + ))); } - logger.info("upsertTopmedAccessRule() Creating new access rule "+ar_name); - ar = new AccessRule(); - ar.setName(ar_name); - ar.setDescription("FENCE AR for "+project_name+"."+consent_group + " Topmed data"); - StringBuilder ruleText = new StringBuilder(); - ruleText.append("$.query.query.categoryFilters."); - ruleText.append(fence_harmonized_consent_group_concept_path); - ruleText.append("[*]"); - ar.setRule(ruleText.toString()); - ar.setType(AccessRule.TypeNaming.ALL_CONTAINS); - ar.setValue(project_name+"."+consent_group); - ar.setCheckMapKeyOnly(false); - ar.setCheckMapNode(false); - ar.setEvaluateOnlyByGates(false); - ar.setGateAnyRelation(false); - - accessruleRepo.persist(ar); - - logger.debug("upsertTopmedAccessRule() finished"); - return ar; + if (rule.getSubAccessRule() == null) { + rule.setSubAccessRule(new HashSet<>(getAllowedQueryTypeRules())); + rule.getSubAccessRule().addAll(getHarmonizedSubRules()); + rule.getSubAccessRule().addAll(getPhenotypeSubRules(studyIdentifier, parentConceptPath, projectAlias)); + } + + accessruleRepo.merge(rule); + } + + // A set of standard access rules that are added to all privileges + // to cache the standard access rules + private Set standardAccessRules; + + private void addStandardAccessRules(Set accessRules) { + if (standardAccessRules != null && !standardAccessRules.isEmpty()) { + accessRules.addAll(standardAccessRules); + } else { + standardAccessRules = new HashSet<>(); + for (String arName : fence_standard_access_rules.split(",")) { + if (arName.startsWith("AR_")) { + logger.info("Adding AccessRule {} to privilege", arName); + AccessRule ar = accessruleRepo.getUniqueResultByColumn("name", arName); + if (ar != null) { + standardAccessRules.add(ar); + } else { + logger.warn("Unable to find an access rule with name {}", arName); + } + } + } + + accessRules.addAll(standardAccessRules); + } + } + + + /** + * Creates and returns a consent access rule AccessRule. + * Generates Main rule only; gates & sub-rules attached after calling this + * prentRule should be null if this is the main rule, or the appropriate value if this is a sub-rule + * + * @param studyIdentifier The study identifier. + * @param consent_group The consent group. + * @param label The label for the rule. + * @param consent_path The consent path. + * @return The created AccessRule. + */ + private AccessRule createConsentAccessRule(String studyIdentifier, String consent_group, String label, String consent_path) { + String ar_name = (consent_group != null && !consent_group.isEmpty()) ? "AR_CONSENT_" + studyIdentifier + "_" + consent_group + "_" + label : "AR_CONSENT_" + studyIdentifier; + String description = (consent_group != null && !consent_group.isEmpty()) ? "FENCE AR for " + studyIdentifier + "." + consent_group + " clinical concepts" : "FENCE AR for " + studyIdentifier + " clinical concepts"; + String ruleText = "$.query.query.categoryFilters." + consent_path + "[*]"; + String arValue = (consent_group != null && !consent_group.isEmpty()) ? studyIdentifier + "." + consent_group : studyIdentifier; + + return getOrCreateAccessRule( + ar_name, + description, + ruleText, + AccessRule.TypeNaming.ALL_CONTAINS, + arValue, + false, + false, + false, + false + ); + } + + /** + * Creates and returns a Topmed access rule AccessRule. + * Generates Main Rule only; gates & sub-rules attached by calling method + * @param project_name The name of the project. + * @param consent_group The consent group. + * @param label The label for the rule. + * @return The created AccessRule. + */ + private AccessRule upsertTopmedAccessRule(String project_name, String consent_group, String label) { + String ar_name = (consent_group != null && !consent_group.isEmpty()) ? "AR_TOPMED_" + project_name + "_" + consent_group + "_" + label : "AR_TOPMED_" + project_name + "_" + label; + String description = "FENCE AR for " + project_name + "." + consent_group + " Topmed data"; + String ruleText = "$.query.query.categoryFilters." + fence_topmed_consent_group_concept_path + "[*]"; + String arValue = (consent_group != null && !consent_group.isEmpty()) ? project_name + "." + consent_group : project_name; + + return getOrCreateAccessRule( + ar_name, + description, + ruleText, + AccessRule.TypeNaming.ALL_CONTAINS, + arValue, + false, + false, + false, + false + ); } /** - * Insert a new gate (if it doesn't exist yet) to identify if consent values are present in the query. + * Creates and returns a harmonized access rule AccessRule for Topmed. + * Generates Main Rule only; gates & sub rules attached by calling method + * + * @param project_name The name of the project. + * @param consent_group The consent group. + * @param label The label for the rule. + * @return The created AccessRule. + */ + private AccessRule upsertHarmonizedAccessRule(String project_name, String consent_group, String label) { + String ar_name = "AR_TOPMED_" + project_name + "_" + consent_group + "_" + label; + logger.info("upsertHarmonizedAccessRule() Creating new access rule {}", ar_name); + String description = "FENCE AR for " + project_name + "." + consent_group + " Topmed data"; + String ruleText = "$.query.query.categoryFilters." + fence_harmonized_consent_group_concept_path + "[*]"; + String arValue = project_name + "." + consent_group; + + return getOrCreateAccessRule( + ar_name, + description, + ruleText, + AccessRule.TypeNaming.ALL_CONTAINS, + arValue, + false, + false, + false, + false + ); + } + + /** + * Creates and returns a consent gate AccessRule. + * Insert a new gate (if it doesn't exist yet) to identify if consent values are present in the query. * return an existing gate named GATE_{gateName}_(PRESENT|MISSING) if it exists. + * + * @param gateName The name of the gate. + * @param rule The rule expression. + * @param is_present Whether the gate is for present or missing consent. + * @param description The description of the gate. + * @return The created AccessRule. */ - private AccessRule upsertConsentGate(String gateName, String rule, boolean is_present, String description) { - - gateName = "GATE_" + gateName + "_" + (is_present ? "PRESENT": "MISSING"); - - AccessRule gate = accessruleRepo.getUniqueResultByColumn("name", gateName); - if (gate != null) { - logger.info("upsertConsentGate() AccessRule "+gateName+" already exists."); - return gate; + private AccessRule upsertConsentGate(String gateName, String rule, boolean is_present, String description) { + gateName = "GATE_" + gateName + "_" + (is_present ? "PRESENT" : "MISSING"); + return getOrCreateAccessRule( + gateName, + "FENCE GATE for " + description + " consent " + (is_present ? "present" : "missing"), + rule, + is_present ? AccessRule.TypeNaming.IS_NOT_EMPTY : AccessRule.TypeNaming.IS_EMPTY, + null, + false, + false, + false, + false + ); + } + + private AccessRule createPhenotypeSubRule(String conceptPath, String alias, String rule, int ruleType, String label, boolean useMapKey) { + String ar_name = "AR_PHENO_" + alias + "_" + label; + logger.info("createPhenotypeSubRule() Creating new access rule {}", ar_name); + return getOrCreateAccessRule( + ar_name, + "FENCE SUB AR for " + alias + " " + label + " clinical concepts", + rule, + ruleType, + ruleType == AccessRule.TypeNaming.IS_NOT_EMPTY ? null : conceptPath, + useMapKey, + useMapKey, + false, + false + ); + } + + private AccessRule getOrCreateAccessRule(String name, String description, String rule, int type, String value, boolean checkMapKeyOnly, boolean checkMapNode, boolean evaluateOnlyByGates, boolean gateAnyRelation) { + return accessRuleCache.computeIfAbsent(name, key -> { + AccessRule ar = accessruleRepo.getUniqueResultByColumn("name", key); + if (ar == null) { + logger.info("Creating new access rule {}", key); + ar = new AccessRule(); + ar.setName(name); + ar.setDescription(description); + ar.setRule(rule); + ar.setType(type); + ar.setValue(value); + ar.setCheckMapKeyOnly(checkMapKeyOnly); + ar.setCheckMapNode(checkMapNode); + ar.setEvaluateOnlyByGates(evaluateOnlyByGates); + ar.setGateAnyRelation(gateAnyRelation); + accessruleRepo.persist(ar); + } + + return ar; + }); + } + + private String extractProject(String roleName) { + String projectPattern = "FENCE_(.*?)(?:_c\\d+)?$"; + if (roleName.startsWith("MANUAL_")) { + projectPattern = "MANUAL_(.*?)(?:_c\\d+)?$"; + } + Pattern projectRegex = Pattern.compile(projectPattern); + Matcher projectMatcher = projectRegex.matcher(roleName); + String project = ""; + if (projectMatcher.find()) { + project = projectMatcher.group(1).trim(); + } else { + logger.info("extractProject() Could not extract project from role name: {}", roleName); + String[] parts = roleName.split("_", 1); + if (parts.length > 0) { + project = parts[1]; + } } + return project; + } + + private static String extractConsentGroup(String roleName) { + String consentPattern = "FENCE_.*?_c(\\d+)$"; + if (roleName.startsWith("MANUAL_")) { + consentPattern = "MANUAL_.*?_c(\\d+)$"; + } + Pattern consentRegex = Pattern.compile(consentPattern); + Matcher consentMatcher = consentRegex.matcher(roleName); + String consentGroup = ""; + if (consentMatcher.find()) { + consentGroup = "c" + consentMatcher.group(1).trim(); + } + return consentGroup; + } + + private Map getFENCEMappingforProjectAndConsent(String projectId, String consent_group) { + String consentVal = (consent_group != null && !consent_group.isEmpty()) ? projectId + "." + consent_group : projectId; + logger.info("getFENCEMappingforProjectAndConsent() looking up {}", consentVal); + + Object projectMetadata = this.fenceMappingUtility.getFENCEMapping().get(consentVal); + if(projectMetadata instanceof Map) { + return (Map)projectMetadata; + } else if (projectMetadata != null) { + logger.info("getFENCEMappingforProjectAndConsent() Obj instance of " + projectMetadata.getClass().getCanonicalName()); + } + return null; + } - logger.info("upsertClinicalGate() Creating new access rule "+gateName); - gate = new AccessRule(); - gate.setName(gateName); - gate.setDescription("FENCE GATE for " + description + " consent " + (is_present ? "present" : "missing")); - gate.setRule(rule); - gate.setType(is_present ? AccessRule.TypeNaming.IS_NOT_EMPTY : AccessRule.TypeNaming.IS_EMPTY ); - gate.setValue(null); - gate.setCheckMapKeyOnly(false); - gate.setCheckMapNode(false); - gate.setEvaluateOnlyByGates(false); - gate.setGateAnyRelation(false); - - accessruleRepo.persist(gate); - return gate; - } - - private Map getFENCEMappingforProjectAndConsent(String projectId, String consent_group) { - - String consentVal = (consent_group != null && consent_group != "") ? projectId + "." + consent_group : projectId; - logger.info("getFENCEMappingforProjectAndConsent() looking up "+consentVal); - - Object projectMetadata = getFENCEMapping().get(consentVal); - if( projectMetadata instanceof Map) { - return (Map)projectMetadata; - } else if (projectMetadata != null) { - logger.info("getFENCEMappingforProjectAndConsent() Obj instance of " + projectMetadata.getClass().getCanonicalName()); - } - return null; - } - - public Map getFENCEMapping(){ - if(_projectMap == null || _projectMap.isEmpty()) { - try { - Map fenceMapping = JAXRSConfiguration.objectMapper.readValue( - new File(String.join(File.separator, - new String[] {JAXRSConfiguration.templatePath ,"fence_mapping.json"})) - , Map.class); - List projects = (List) fenceMapping.get("bio_data_catalyst"); - logger.debug("getFENCEMapping: found FENCE mapping with " + projects.size() + " entries"); - _projectMap = new HashMap(projects.size()); - for(Map project : projects) { - String consentVal = (project.get("consent_group_code") != null && project.get("consent_group_code") != "") ? - "" + project.get("study_identifier") + "." + project.get("consent_group_code") : - "" + project.get("study_identifier"); - logger.debug("adding study " + consentVal); - _projectMap.put(consentVal, project); - } - - } catch (Exception e) { - logger.error("getFENCEMapping: Non-fatal error parsing fence_mapping.json: "+JAXRSConfiguration.templatePath, e); - return new HashMap(); - } - } - - return _projectMap; - } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/FenceMappingUtility.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/FenceMappingUtility.java new file mode 100644 index 000000000..53a240c67 --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/FenceMappingUtility.java @@ -0,0 +1,88 @@ +package edu.harvard.hms.dbmi.avillach.auth.utils; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.PostConstruct; +import javax.ejb.Singleton; +import javax.ejb.Startup; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Singleton +@Startup +public class FenceMappingUtility { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private Map fenceMappingByConsent; + private Map fenceMappingByAuthZ; + private static String templatePath; + private ObjectMapper objectMapper; + + @PostConstruct + public void init() { + try { + Context ctx = new InitialContext(); + templatePath = (String) ctx.lookup("java:global/templatePath"); + objectMapper = new ObjectMapper(); + + initializeFENCEMappings(); + } catch (NamingException e) { + throw new RuntimeException(e); + } + } + + private void initializeFENCEMappings() { + if (fenceMappingByConsent == null || fenceMappingByAuthZ == null) { + ArrayList studies = loadBioDataCatalystFenceMappingData(); + ConcurrentHashMap tempFenceMappingByConsent = new ConcurrentHashMap<>(); + ConcurrentHashMap tempFenceMappingByAuthZ = new ConcurrentHashMap<>(); + + studies.parallelStream().forEach(study -> { + String consentVal = (study.get("consent_group_code") != null && !study.get("consent_group_code").toString().isEmpty()) ? + study.get("study_identifier") + "." + study.get("consent_group_code") : + study.get("study_identifier").toString(); + tempFenceMappingByConsent.put(consentVal, study); + tempFenceMappingByAuthZ.put(study.get("authZ").toString().replace("\\/", "/"), study); + }); + + fenceMappingByConsent = Collections.unmodifiableMap(tempFenceMappingByConsent); + fenceMappingByAuthZ = Collections.unmodifiableMap(tempFenceMappingByAuthZ); + } + } + + public Map getFENCEMapping() { + return fenceMappingByConsent; + } + + public Map getFenceMappingByAuthZ() { + return fenceMappingByAuthZ; + } + + private ArrayList loadBioDataCatalystFenceMappingData() { + Map fenceMapping; + ArrayList studies; + try { + logger.debug("getFENCEMapping: loading FENCE mapping from {}", templatePath); + fenceMapping = objectMapper.readValue( + new File(String.join(File.separator, + new String[]{templatePath, "fence_mapping.json"})) + , Map.class); + + studies = (ArrayList) fenceMapping.get("bio_data_catalyst"); + logger.debug("getFENCEMapping: found FENCE mapping with {} entries", studies.size()); + } catch (Exception e) { + logger.error("loadFenceMappingData: Non-fatal error parsing fence_mapping.json: {}", templatePath, e); + return new ArrayList<>(); + } + return studies; + } + +} diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/StudyAccessServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/StudyAccessServiceTest.java index a3811e3ee..0058a21ad 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/StudyAccessServiceTest.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/StudyAccessServiceTest.java @@ -3,6 +3,7 @@ import edu.harvard.hms.dbmi.avillach.auth.data.repository.RoleRepository; import edu.harvard.hms.dbmi.avillach.auth.rest.StudyAccessService; import edu.harvard.hms.dbmi.avillach.auth.service.auth.FENCEAuthenticationService; +import edu.harvard.hms.dbmi.avillach.auth.utils.FenceMappingUtility; import org.junit.Before; import org.junit.Test; import org.mockito.InjectMocks; @@ -25,6 +26,9 @@ public class StudyAccessServiceTest { @Mock private FENCEAuthenticationService fenceAuthenticationService; + @Mock + private FenceMappingUtility fenceUtilityMapping; + @Before public void init() { MockitoAnnotations.initMocks(this); @@ -42,7 +46,7 @@ public void testAddStudyAccessWithBlankIdentifier() { @Test public void testAddStudyAccess() { String studyIdentifier = "testStudy"; - when(fenceAuthenticationService.getFENCEMapping()).thenReturn(Map.of(studyIdentifier, Map.of(StudyAccessService.STUDY_IDENTIFIER, studyIdentifier,StudyAccessService.CONSENT_GROUP_CODE, ""))); + when(fenceUtilityMapping.getFENCEMapping()).thenReturn(Map.of(studyIdentifier, Map.of(StudyAccessService.STUDY_IDENTIFIER, studyIdentifier,StudyAccessService.CONSENT_GROUP_CODE, ""))); when(fenceAuthenticationService.upsertRole(null, "MANUAL_testStudy", "MANUAL_ role MANUAL_testStudy")).thenReturn(true); Response response = studyAccessService.addStudyAccess(studyIdentifier); @@ -53,7 +57,7 @@ public void testAddStudyAccess() { @Test public void testAddStudyAccessWithConsent() { String studyIdentifier = "testStudy2.c2"; - when(fenceAuthenticationService.getFENCEMapping()).thenReturn(Map.of(studyIdentifier, Map.of(StudyAccessService.STUDY_IDENTIFIER, "testStudy2", StudyAccessService.CONSENT_GROUP_CODE, "c2"))); + when(fenceUtilityMapping.getFENCEMapping()).thenReturn(Map.of(studyIdentifier, Map.of(StudyAccessService.STUDY_IDENTIFIER, "testStudy2", StudyAccessService.CONSENT_GROUP_CODE, "c2"))); when(fenceAuthenticationService.upsertRole(null, "MANUAL_testStudy2_c2", "MANUAL_ role MANUAL_testStudy2_c2")).thenReturn(true); Response response = studyAccessService.addStudyAccess(studyIdentifier);