diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/JAXRSConfiguration.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/JAXRSConfiguration.java index d0f7a11e3..54756508e 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/JAXRSConfiguration.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/JAXRSConfiguration.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.type.MapType; import edu.harvard.hms.dbmi.avillach.auth.data.entity.Connection; import edu.harvard.hms.dbmi.avillach.auth.data.entity.Privilege; import edu.harvard.hms.dbmi.avillach.auth.data.entity.Role; @@ -11,7 +10,6 @@ import edu.harvard.hms.dbmi.avillach.auth.data.repository.RoleRepository; import edu.harvard.hms.dbmi.avillach.auth.rest.TokenService; import io.swagger.jaxrs.config.BeanConfig; -import org.apache.commons.io.IOUtils; import org.apache.http.client.HttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.slf4j.Logger; @@ -28,13 +26,10 @@ import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; import javax.ws.rs.core.SecurityContext; -import java.io.InputStream; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.*; -import javax.net.ssl.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.ADMIN; import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.SUPER_ADMIN; diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/UserRepository.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/UserRepository.java index a627ccd5f..2188daa24 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/UserRepository.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/UserRepository.java @@ -13,152 +13,194 @@ import javax.persistence.NonUniqueResultException; import javax.persistence.criteria.*; import javax.transaction.Transactional; -import java.util.Date; -import java.util.List; -import java.util.Set; -import java.util.UUID; +import java.util.*; import java.util.stream.Collectors; /** *

Provides operations for the User entity to interact with a database.

+ * * @see User */ @Transactional @ApplicationScoped public class UserRepository extends BaseRepository { - private Logger logger = LoggerFactory.getLogger(UserRepository.class); - - protected UserRepository() { - super(User.class); - } - - public User findBySubject(String subject) { - CriteriaQuery query = em.getCriteriaBuilder().createQuery(User.class); - Root queryRoot = query.from(User.class); - query.select(queryRoot); - CriteriaBuilder cb = cb(); - return em.createQuery(query - .where( - eq(cb, queryRoot, "subject", subject))) - .getSingleResult(); - } - - public User findBySubjectAndConnection(String subject, String connectionId){ - CriteriaQuery query = em.getCriteriaBuilder().createQuery(User.class); - Root queryRoot = query.from(User.class); - query.select(queryRoot); - CriteriaBuilder cb = cb(); - try { + private Logger logger = LoggerFactory.getLogger(UserRepository.class); + + protected UserRepository() { + super(User.class); + } + + public User findBySubject(String subject) { + CriteriaQuery query = em.getCriteriaBuilder().createQuery(User.class); + Root queryRoot = query.from(User.class); + query.select(queryRoot); + CriteriaBuilder cb = cb(); + return em.createQuery(query + .where( + eq(cb, queryRoot, "subject", subject))) + .getSingleResult(); + } + + public User findBySubjectAndConnection(String subject, String connectionId) { + CriteriaQuery query = em.getCriteriaBuilder().createQuery(User.class); + Root queryRoot = query.from(User.class); + query.select(queryRoot); + CriteriaBuilder cb = cb(); + try { return em.createQuery(query - .where( - cb.and( - cb.equal(queryRoot.join("connection") - .get("id"), connectionId), - eq(cb, queryRoot, "subject", subject)))) + .where( + cb.and( + cb.equal(queryRoot.join("connection") + .get("id"), connectionId), + eq(cb, queryRoot, "subject", subject)))) .getSingleResult(); - } catch (NoResultException e){ - return null; + } catch (NoResultException e) { + return null; + } + } + + public List listUnmatchedByConnectionId(Connection connection) { + CriteriaQuery query = em.getCriteriaBuilder().createQuery(User.class); + Root queryRoot = query.from(User.class); + query.select(queryRoot); + CriteriaBuilder cb = cb(); + return em.createQuery(query + .where( + cb.and( + eq(cb, queryRoot, "connection", connection), + eq(cb, queryRoot, "matched", false)))) + .getResultList(); + } + + /** + * @return + */ + public User findOrCreate(User inputUser) { + User user = null; + String subject = inputUser.getSubject(); + try { + user = findBySubject(subject); + logger.info("findOrCreate(), trying to find user: {subject: " + subject + + "}, and found a user with uuid: " + user.getUuid() + + ", subject: " + user.getSubject()); + } catch (NoResultException e) { + logger.debug("findOrCreate() subject " + subject + + " could not be found by `entityManager`, going to create a new user."); + user = createUser(inputUser); + } catch (NonUniqueResultException e) { + logger.error("findOrCreate() " + e.getClass().getSimpleName() + ": " + e.getMessage()); + } + return user; + } + + private User createUser(User inputUser) { + String subject = inputUser.getSubject(); + logger.debug("createUser() creating user, subject: " + subject + " ......"); + em().persist(inputUser); + + User result = getById(inputUser.getUuid()); + if (result != null) + logger.info("createUser() created user, uuid: " + result.getUuid() + + ", subject: " + subject + + ", role: " + result.getRoleString() + + ", privilege: " + result.getPrivilegeString()); + + return result; + } + + public User changeRole(User user, Set roles) { + logger.info("Starting changing the role of user: " + user.getUuid() + + ", with subject: " + user.getSubject() + ", to " + roles.stream().map(role -> role.getName()).collect(Collectors.joining(","))); + user.setRoles(roles); + em().merge(user); + User updatedUser = getById(user.getUuid()); + logger.info("User: " + updatedUser.getUuid() + ", with subject: " + + updatedUser.getSubject() + ", now has a new role: " + updatedUser.getRoleString()); + return updatedUser; + } + + @Override + public void persist(User user) { + findOrCreate(user); + } + + public User findByEmail(String email) { + CriteriaQuery query = em.getCriteriaBuilder().createQuery(User.class); + Root queryRoot = query.from(User.class); + query.select(queryRoot); + CriteriaBuilder cb = cb(); + return em.createQuery(query + .where( + eq(cb, queryRoot, "email", email))) + .getSingleResult(); + } + + public boolean checkAgainstTOSDate(String userId) { + CriteriaQuery query = cb().createQuery(User.class); + Root queryRoot = query.from(User.class); + query.select(queryRoot); + CriteriaBuilder cb = cb(); + + Subquery subquery = query.subquery(Date.class); + Root tosRoot = subquery.from(TermsOfService.class); + subquery.select(cb.greatest(tosRoot.get("dateUpdated"))); + + return !em.createQuery(query + .where( + cb.and( + eq(cb, queryRoot, "subject", userId), + cb.greaterThanOrEqualTo(queryRoot.get("acceptedTOS"), subquery)))) + .getResultList().isEmpty(); + } + + /** + * @param uuid the uuid of the user to find + * @return the user with the given uuid, or null if no user is found + */ + public User findByUUID(UUID uuid) { + CriteriaQuery query = em.getCriteriaBuilder().createQuery(User.class); + Root queryRoot = query.from(User.class); + query.select(queryRoot); + CriteriaBuilder cb = cb(); + try { + return em.createQuery(query + .where( + eq(cb, queryRoot, "uuid", uuid))) + .getSingleResult(); + } catch (NoResultException e) { + logger.error("findByUUID() " + e.getClass().getSimpleName() + ": " + e.getMessage()); } - } - - public List listUnmatchedByConnectionId(Connection connection) { - CriteriaQuery query = em.getCriteriaBuilder().createQuery(User.class); - Root queryRoot = query.from(User.class); - query.select(queryRoot); - CriteriaBuilder cb = cb(); - return em.createQuery(query - .where( - cb.and( - eq(cb, queryRoot, "connection", connection), - eq(cb, queryRoot, "matched", false)))) - .getResultList(); - } - - /** - * - * @return - */ - public User findOrCreate(User inputUser) { - User user = null; - String subject = inputUser.getSubject(); - try{ - user = findBySubject(subject); - logger.info("findOrCreate(), trying to find user: {subject: " + subject+ - "}, and found a user with uuid: " + user.getUuid() - + ", subject: " + user.getSubject()); - } catch (NoResultException e) { - logger.debug("findOrCreate() subject " + subject + - " could not be found by `entityManager`, going to create a new user."); - user = createUser(inputUser); - }catch(NonUniqueResultException e){ - logger.error("findOrCreate() " + e.getClass().getSimpleName() + ": " + e.getMessage()); - } - return user; - } - - private User createUser(User inputUser) { - String subject = inputUser.getSubject(); -// if (subject == null && userId == null){ -// logger.error("createUser() cannot create user when both subject and userId are null"); -// return null; -// } - logger.debug("createUser() creating user, subject: " + subject + " ......"); - em().persist(inputUser); - - User result = getById(inputUser.getUuid()); - if (result != null) - logger.info("createUser() created user, uuid: " + result.getUuid() - + ", subject: " + subject - + ", role: " + result.getRoleString() - + ", privilege: "+ result.getPrivilegeString()); - - return result; - } - - public User changeRole(User user, Set roles){ - logger.info("Starting changing the role of user: " + user.getUuid() - + ", with subject: " + user.getSubject() + ", to " + roles.stream().map(role -> role.getName()).collect(Collectors.joining(","))); - user.setRoles(roles); - em().merge(user); - User updatedUser = getById(user.getUuid()); - logger.info("User: " + updatedUser.getUuid() + ", with subject: " + - updatedUser.getSubject() + ", now has a new role: " + updatedUser.getRoleString()); - return updatedUser; - } - - @Override - public void persist(User user) { - findOrCreate(user); - } - - public User findByEmail(String email) { - CriteriaQuery query = em.getCriteriaBuilder().createQuery(User.class); - Root queryRoot = query.from(User.class); - query.select(queryRoot); - CriteriaBuilder cb = cb(); - return em.createQuery(query - .where( - eq(cb, queryRoot, "email", email))) - .getSingleResult(); - } - - public boolean checkAgainstTOSDate(String userId){ - CriteriaQuery query = cb().createQuery(User.class); - Root queryRoot = query.from(User.class); - query.select(queryRoot); - CriteriaBuilder cb = cb(); - - Subquery subquery = query.subquery(Date.class); - Root tosRoot = subquery.from(TermsOfService.class); - subquery.select(cb.greatest(tosRoot.get("dateUpdated"))); - - return !em.createQuery(query - .where( - cb.and( - eq(cb, queryRoot, "subject", userId), - cb.greaterThanOrEqualTo(queryRoot.get("acceptedTOS"), subquery)))) - .getResultList().isEmpty(); - } + return null; + } + + /** + * Creates a user with a subject of "open_access|{uuid}" + * and an email of "{uuid}@open_access.com" + * + * @return the created user + */ + public User createOpenAccessUser(Role openAccessRole) { + User user = new User(); + em().persist(user); + + user = getById(user.getUuid()); + user.setSubject("open_access|" + user.getUuid().toString()); + if (openAccessRole != null) { + user.setRoles(new HashSet<>(List.of(openAccessRole))); + } else { + logger.error("createOpenAccessUser() openAccessRole is null"); + user.setRoles(new HashSet<>()); + } + user.setEmail(user.getUuid() + "@open_access.com"); + em().merge(user); + + logger.info("createOpenAccessUser() created user, uuid: " + user.getUuid() + + ", subject: " + user.getSubject() + + ", role: " + user.getRoleString() + + ", privilege: " + user.getPrivilegeString() + + ", email: " + user.getEmail()); + return user; + } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthService.java index 1abf0938e..b304af1d3 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthService.java @@ -31,10 +31,7 @@ @Produces("application/json") public class AuthService { - private Logger logger = LoggerFactory.getLogger(this.getClass()); - - @Inject - AuthorizationService authorizationService; + private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Inject AuthenticationService authenticationService; diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/OpenAuthService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/OpenAuthService.java new file mode 100644 index 000000000..35b059512 --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/OpenAuthService.java @@ -0,0 +1,49 @@ +package edu.harvard.hms.dbmi.avillach.auth.rest; + +import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; +import edu.harvard.hms.dbmi.avillach.auth.service.auth.OpenAuthenticationService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Response; +import java.util.Map; + +/** + *

The authentication endpoint for PSAMA.

+ */ +@Api +@Path("/open") +@Consumes("application/json") +@Produces("application/json") +public class OpenAuthService { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Inject + private OpenAuthenticationService openAuthenticationService; + + @ApiOperation(value = "The authentication endpoint for retrieving a valid user token") + @POST + @Path("/authentication") + public Response authentication(@ApiParam(required = true, value = "A json object that includes all Oauth authentication needs, for example, access_token and redirectURI") Map authRequest) { + logger.debug("authentication() starting..."); + + // idp_provider also has default value of "default" if not set in the config file + // This is a temporary solution to ensure that a user cannot authenticate against fence using the open endpoint + if (!JAXRSConfiguration.idp_provider.equalsIgnoreCase("fence")) { + return openAuthenticationService.authenticate(authRequest); + } + + // Fence authentication is not supported by the open endpoint + return Response.status(Response.Status.FORBIDDEN).build(); + } + +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserService.java index 4b6bb1d4b..e6b775af1 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserService.java @@ -281,7 +281,8 @@ public Response getCurrentUser( .setPrivileges(user.getPrivilegeNameSet()) .setUuid(user.getUuid().toString()) .setAcceptedTOS(true); //FENCE only returns valid users who have agreed to their terms - + + logger.debug("getCurrentUser() userForDisplay: " + userForDisplay.toString()); // currently, the queryScopes are simple combination of queryScope string together as a set. // We are expecting the queryScope string as plain string. If it is a JSON, we could change the @@ -313,6 +314,8 @@ public Response getCurrentUser( } } + logger.debug("getCurrentUser() returning userForDisplay: " + userForDisplay.toString()); + return PICSUREResponse.success(userForDisplay); } @@ -519,5 +522,4 @@ private void checkAssociation(List users) throws ProtocolException{ } } - } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/auth/AuthenticationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/auth/AuthenticationService.java index d17e60d80..c4c551750 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/auth/AuthenticationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/auth/AuthenticationService.java @@ -86,7 +86,6 @@ public Response getToken(Map authRequest){ throw new ApplicationException("cannot get sufficient user information. Please contact admin."); } - //Do we have this user already? User user = userRepository.findBySubjectAndConnection(userId, connectionId); if (user == null){ //Try to match 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 b0791b519..5bdadd09b 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 @@ -8,6 +8,8 @@ import java.io.File; import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.annotation.PostConstruct; import javax.inject.Inject; @@ -148,6 +150,12 @@ public Response getFENCEProfile(String callback_url, Map authReq logger.debug("getFENCEProfile() starting..."); String fence_code = authRequest.get("code"); + // Validate that the fence code is alphanumeric + if (!fence_code.matches("[a-zA-Z0-9]+")) { + logger.error("getFENCEProfile() fence code is not alphanumeric"); + throw new NotAuthorizedException("The fence code is not alphanumeric"); + } + JsonNode fence_user_profile = null; // Get the Gen3/FENCE user profile. It is a JsonNode object try { @@ -192,54 +200,10 @@ public Response getFENCEProfile(String callback_url, Map authReq // Update the user's roles (or create them if none exists) //Set actual_user_roles = u.getRoles(); - Iterator access_role_names = fence_user_profile.get("project_access").fieldNames(); - while (access_role_names.hasNext()) { - String access_role_name = access_role_names.next(); - logger.debug("getFENCEProfile() AccessRole:"+access_role_name); - - // These two special access does not matter. We are not using it. - if (access_role_name.equals("admin") || access_role_name.equals("parent")) { - logger.info("SKIPPING ACCESS ROLE: " + access_role_name); - continue; - } - //topmed ==> access to all studies (not just topmed) - if (access_role_name.equals("topmed") ) { - Map projects = getFENCEMapping(); - for(Map projectMetadata : projects.values()) { - String projectId = (String) projectMetadata.get("study_identifier"); - String consentCode = (String) projectMetadata.get("consent_group_code"); - String newRoleName = "FENCE_"+projectId+"_"+consentCode; - - if (upsertRole(current_user, newRoleName, "FENCE role "+newRoleName)) { - logger.info("getFENCEProfile() Updated TOPMED user role. Now it includes `"+newRoleName+"`"); - } else { - logger.error("getFENCEProfile() could not add roles to TOPMED user's profile"); - } - } - continue; - } - - - String[] parts = access_role_name.split("\\."); - - String newRoleName; - if (parts.length > 1) { - newRoleName = "FENCE_"+parts[0]+"_"+parts[parts.length-1]; - } else { - newRoleName = "FENCE_"+access_role_name; - } - 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+"`"); - } else { - logger.error("getFENCEProfile() could not add roles to user's profile"); - } - - // TODO: In case we need to do something with this part, we can uncomment it. - //JsonNode role_object = fence_user_profile.get("project_access").get(newRoleName); - //It is a an array of strings, like this: ["read-storage","read"] - //logger.debug("getFENCEProfile() object:"+role_object.toString()); + 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); } final String idp = extractIdp(current_user); @@ -271,6 +235,31 @@ public Response getFENCEProfile(String callback_url, Map authReq 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; + } + + String projectId = (String) projectMetadata.get("study_identifier"); + String consentCode = (String) projectMetadata.get("consent_group_code"); + String newRoleName = (consentCode != null && 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+"`"); + } else { + logger.error("getFENCEProfile() could not add roles to user's profile"); + } + } + private String extractIdp(User current_user) { try { final ObjectNode node; @@ -323,7 +312,7 @@ private User createUserFromFENCEProfile(JsonNode node) { * @param roleDescription Description of the Role * @return boolean Whether the Role was successfully added to the User or not */ - private boolean upsertRole(User u, String roleName, String roleDescription) { + public boolean upsertRole(User u, String roleName, String roleDescription) { boolean status = false; logger.debug("upsertRole() starting for user subject:"+u.getSubject()); @@ -369,13 +358,18 @@ private Set addFENCEPrivileges(User u, Role r) { //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 - String[] parts = roleName.split("_"); - String project_name = parts[1]; - String consent_group = parts.length > 2 ? parts[2] : ""; - - + + //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"); + } + String consent_group = extractConsentGroup(roleName); + if (consent_group.length() <= 0) { + logger.warn("addFENCEPrivileges() role name: "+roleName+" returned an empty consent group"); + } + logger.info("addFENCEPrivileges() project name: "+project_name+" consent group: "+consent_group); + // Look up the metadata by consent group. Map projectMetadata = getFENCEMappingforProjectAndConsent(project_name, consent_group); @@ -401,7 +395,7 @@ private Set addFENCEPrivileges(User u, Role r) { if(concept_path != null) { concept_path = concept_path.replaceAll("\\\\", "\\\\\\\\"); } - + if(dataType != null && dataType.contains("G")) { //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)); @@ -420,13 +414,41 @@ private Set addFENCEPrivileges(User u, Role r) { //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); + logger.warn("Missing study type for " + project_name + " " + consent_group + ". Skipping."); } logger.info("addPrivileges() Finished"); return privs; } + private static String extractProject(String roleName) { + String projectPattern = "FENCE_(.*?)(?:_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+)$"; + + 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, * this also creates an access rule to allow access when using the harmonized consent concept path. @@ -441,8 +463,8 @@ private Set addFENCEPrivileges(User u, Role r) { */ private Privilege upsertClinicalPrivilege(String studyIdentifier, String projectAlias, String consent_group, String conceptPath, boolean isHarmonized) { - String privilegeName = "PRIV_FENCE_"+studyIdentifier+"_"+consent_group+(isHarmonized?"_HARMONIZED":""); - Privilege priv = privilegeRepo.getUniqueResultByColumn("name", privilegeName); + String privilegeName = (consent_group != null && consent_group != "") ? "PRIV_FENCE_"+studyIdentifier+"_"+consent_group+(isHarmonized?"_HARMONIZED":"") : "PRIV_FENCE_"+studyIdentifier+(isHarmonized?"_HARMONIZED":"") ; + Privilege priv = privilegeRepo.getUniqueResultByColumn("name", privilegeName); if(priv != null) { logger.info("upsertClinicalPrivilege() " + privilegeName + " already exists"); return priv; @@ -469,10 +491,11 @@ private Privilege upsertClinicalPrivilege(String studyIdentifier, String project // TOOD: Change this to a mustache template + String studyIdentifierField = (consent_group != null && consent_group != "") ? studyIdentifier+"."+consent_group: studyIdentifier; String queryTemplateText = "{\"categoryFilters\": {\"" +consent_concept_path +"\":[\"" - +studyIdentifier+"."+consent_group + +studyIdentifierField +"\"]}," +"\"numericFilters\":{},\"requiredFields\":[]," +"\"fields\":[\"" + parentAccessionField + "\"]," @@ -946,7 +969,7 @@ private Privilege upsertTopmedPrivilege(String studyIdentifier, String projectAl // 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 = "AR_CONSENT_" + studyIdentifier+"_"+consent_group+ "_" +label; + String ar_name = (consent_group != null && consent_group != "") ? "AR_CONSENT_" + studyIdentifier+"_"+consent_group+ "_" +label : "AR_CONSENT_" + studyIdentifier; AccessRule ar = accessruleRepo.getUniqueResultByColumn("name", ar_name); if(ar != null) { @@ -959,14 +982,16 @@ private AccessRule createConsentAccessRule(String studyIdentifier, String consen logger.info("upsertConsentAccessRule() Creating new access rule "+ar_name); ar = new AccessRule(); ar.setName(ar_name); - ar.setDescription("FENCE AR for "+studyIdentifier+"."+consent_group + " clinical concepts"); + 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); - ar.setValue(studyIdentifier+"."+consent_group); + String arValue = (consent_group != null && consent_group != "") ? studyIdentifier+"."+consent_group : studyIdentifier; + ar.setValue(arValue); ar.setCheckMapKeyOnly(false); ar.setCheckMapNode(false); ar.setEvaluateOnlyByGates(false); @@ -981,7 +1006,7 @@ private AccessRule createConsentAccessRule(String studyIdentifier, String consen // 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 = "AR_TOPMED_"+project_name+"_"+consent_group + "_" + label; + 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."); @@ -998,7 +1023,8 @@ private AccessRule upsertTopmedAccessRule(String project_name, String consent_gr ruleText.append("[*]"); ar.setRule(ruleText.toString()); ar.setType(AccessRule.TypeNaming.ALL_CONTAINS); - ar.setValue(project_name+"."+consent_group); + 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); @@ -1075,7 +1101,7 @@ private AccessRule upsertConsentGate(String gateName, String rule, boolean is_pr private Map getFENCEMappingforProjectAndConsent(String projectId, String consent_group) { - String consentVal = projectId + "." + consent_group; + String consentVal = (consent_group != null && consent_group != "") ? projectId + "." + consent_group : projectId; logger.info("getFENCEMappingforProjectAndConsent() looking up "+consentVal); Object projectMetadata = getFENCEMapping().get(consentVal); @@ -1098,7 +1124,9 @@ private Map getFENCEMapping(){ logger.debug("getFENCEMapping: found FENCE mapping with " + projects.size() + " entries"); _projectMap = new HashMap(projects.size()); for(Map project : projects) { - String consentVal = "" + project.get("study_identifier") + "." + project.get("consent_group_code"); + 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); } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/auth/OpenAuthenticationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/auth/OpenAuthenticationService.java new file mode 100644 index 000000000..ed9e41620 --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/auth/OpenAuthenticationService.java @@ -0,0 +1,67 @@ +package edu.harvard.hms.dbmi.avillach.auth.service.auth; + +import edu.harvard.dbmi.avillach.util.response.PICSUREResponse; +import edu.harvard.hms.dbmi.avillach.auth.data.entity.Role; +import edu.harvard.hms.dbmi.avillach.auth.data.entity.User; +import edu.harvard.hms.dbmi.avillach.auth.data.repository.RoleRepository; +import edu.harvard.hms.dbmi.avillach.auth.data.repository.UserRepository; +import edu.harvard.hms.dbmi.avillach.auth.rest.UserService; +import edu.harvard.hms.dbmi.avillach.auth.utils.AuthUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.ws.rs.core.Response; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class OpenAuthenticationService { + + private final Logger logger = LoggerFactory.getLogger(OpenAuthenticationService.class); + + @Inject + private UserRepository userRepository; + + @Inject + private RoleRepository roleRepository; + + @Inject + AuthUtils authUtil; + + public Response authenticate(Map authRequest) { + String userUUID = authRequest.get("UUID"); + User current_user = null; + + // Try to get the user by UUID + if (StringUtils.isNotBlank(userUUID)) { + try { + UUID uuid = UUID.fromString(userUUID); + current_user = userRepository.findByUUID(uuid); + } catch (IllegalArgumentException e) { + logger.error("Invalid UUID: " + userUUID); + } + } + + // If we can't find the user by UUID, create a new one + if (current_user == null) { + Role openAccessRole = roleRepository.getUniqueResultByColumn("name", FENCEAuthenticationService.fence_open_access_role_name); + current_user = userRepository.createOpenAccessUser(openAccessRole); + + //clear some cache entries if we register a new login + AuthorizationService.clearCache(current_user); + UserService.clearCache(current_user); + } + + HashMap claims = new HashMap<>(); + claims.put("sub", current_user.getSubject()); + claims.put("email", current_user.getUuid() + "@open_access.com"); + claims.put("uuid", current_user.getUuid().toString()); + HashMap responseMap = authUtil.getUserProfileResponse(claims); + + logger.info("LOGIN SUCCESS ___ " + current_user.getEmail() + ":" + current_user.getUuid().toString() + " ___ Authorization will expire at ___ " + responseMap.get("expirationDate") + "___"); + + return PICSUREResponse.success(responseMap); + } +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/AuthUtils.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/AuthUtils.java index 85ddd170e..48bb53213 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/AuthUtils.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/AuthUtils.java @@ -106,15 +106,20 @@ public HashMap getUserProfileResponse(Map claims logger.debug("getUserProfileResponse() acceptedTOS is set"); - boolean acceptedTOS = JAXRSConfiguration.tosEnabled.startsWith("true") ? - tosService.getLatest() == null || tosService.hasUserAcceptedLatest(claims.get("subject").toString()) : true; + boolean acceptedTOS = !JAXRSConfiguration.tosEnabled.startsWith("true") || tosService.getLatest() == null || tosService.hasUserAcceptedLatest(claims.get("subject").toString()); - responseMap.put("acceptedTOS", ""+acceptedTOS); + responseMap.put("acceptedTOS", String.valueOf(acceptedTOS)); logger.debug("getUserProfileResponse() expirationDate is set"); Date expirationDate = new Date(Calendar.getInstance().getTimeInMillis() + JAXRSConfiguration.tokenExpirationTime); responseMap.put("expirationDate", ZonedDateTime.ofInstant(expirationDate.toInstant(), ZoneOffset.UTC).toString()); + // This is required for open access, but optional otherwise + if (claims.get("uuid") != null) { + logger.debug("getUserProfileResponse() uuid field is set"); + responseMap.put("uuid", claims.get("uuid").toString()); + } + logger.debug("getUserProfileResponse() finished"); return responseMap; }