Skip to content

Commit

Permalink
Update release with Fence-Integration (#153)
Browse files Browse the repository at this point in the history
* [HOT-FIX] Add maven war plugin version to fix production build

[HOT-FIX] Add maven war plugin version to fix production build

* [ALS-5514] AIM-AHEAD PIC-SURE AuthN (#150)

[ALS-5332] BDC PIC-SURE: Support PSAMA
persist admin roles
fix compile issue
Add manual roles
check if roles are empty
Add StudyAccessService
Better error code; clean up
Bump swagger version?
Added jersey-server for tests
Add guava to pom
Adds method to look for accounts missing a subject
Check if subject is empty

* [ALS-5514] Add OktaOAuthAuthenticationService for user authentication
The new class, OktaOAuthAuthenticationService, has been added to manage user authentication via Okta. This involves the process of code-token exchange, token introspection, and user initialization. It also contains utility methods for making requests to Okta API and processing the responses.

* [ALS-5514] Add Okta authentication support in PIC-SURE
This commit includes the addition of an OktaAuthenticationController which is responsible for handling Okta-based authentication requests. It also restructures the JAXRSConfiguration class's startup sequence and adds more flexibility in handling different Identity Provider (IDP) setups; specifically, it enhances the class's ability to properly configure and handle Okta IDP.

* [ALS-5514] Remove unused import in AuthService.java
* [ALS-5514] The 'standalone.xml' file has been updated to include OTKA parameters configuration.
* [ALS-5514] Update maven war plugin
* [ALS-5514] Use unauthorizedError in place of less specific error

* [ALS-5514] Refactor UserRepository and improve user metadata generation
Updated UserRepository.java to streamline the user data querying and creation process, and added explicit save(user) method for better clarity. In OktaOAuthAuthenticationService.java, a refactoring was done to simplify the process of updating user metadata. A unit test for generating user metadata was also added in a new OktaOAuthAuthenticationServiceTest.java file for improved code coverage and reliability.

*[ALS-5514] Remove mapped clientId, refactor beanConfig scheme
The commit eliminates mapped clientId resource in JAXRSConfiguration.java, instead retrieving it through ctx.lookup. This resolves issues in the cases where client_id may be empty.
---------

Co-authored-by: James <[email protected]>

---------

Co-authored-by: James <[email protected]>
  • Loading branch information
Gcolon021 and JamesPeck committed Feb 2, 2024
1 parent 9a05aec commit dff78e3
Show file tree
Hide file tree
Showing 11 changed files with 862 additions and 431 deletions.
601 changes: 242 additions & 359 deletions pic-sure-auth-services/pom.xml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,16 @@
import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.SUPER_ADMIN;

/**
*<p>When you deploy the PSAMA application WAR file to a new server, this class is called to supply basic configuration information.</p>
* <p>When you deploy the PSAMA application WAR file to a new server, this class is called to supply basic configuration information.</p>
*/
@Startup
@ApplicationPath("auth")
public class JAXRSConfiguration extends Application {

private Logger logger = LoggerFactory.getLogger(JAXRSConfiguration.class);

@Resource(mappedName = "java:global/client_id")
public static String clientId;
@Resource(mappedName = "java:global/client_secret")
public static String clientSecret;
public static String clientSecret; // actually picsure_client_secret in standalone.xml
@Resource(mappedName = "java:global/clientSecretIsBase64")
public static String clientSecretIsBase64;

Expand All @@ -61,7 +59,7 @@ public class JAXRSConfiguration extends Application {

/**
* The default application UUID assumed for all operational contexts where
* one is not supplied.
* one is not supplied.
*/
@Resource(mappedName = "java:global/defaultApplicationUUID")
public static String defaultApplicationUUID;
Expand All @@ -76,7 +74,7 @@ public class JAXRSConfiguration extends Application {
public static String accessGrantEmailSubject;

@Resource(mappedName = "java:global/userActivationReplyTo")
public static String userActivationReplyTo;
public static String userActivationReplyTo;

@Resource(lookup = "java:jboss/mail/gmail")
public static Session mailSession;
Expand All @@ -86,11 +84,10 @@ public class JAXRSConfiguration extends Application {

@Resource(lookup = "java:global/deniedEmailEnabled")
public static String deniedEmailEnabled;

@Resource(lookup = "java:global/variantAnnotationColumns")
public static String variantAnnotationColumns;



// See checkIDPProvider method for setting these variables
public static String idp_provider;
public static String idp_provider_uri;
Expand All @@ -103,14 +100,14 @@ public class JAXRSConfiguration extends Application {
public static String fence_harmonized_consent_group_concept_path;
public static String fence_topmed_consent_group_concept_path;
public static String fence_allowed_query_types;


public static String defaultAdminRoleName = "PIC-SURE Top Admin";

public static String spClientSecret;

public static String connectionId;
public static String clientId;
public static long tokenExpirationTime;
// default expiration time is 1 hr
private static long defaultTokenExpirationTime = 1000L * 60 * 60;

public static long longTermTokenExpirationTime;
// default long term token expiration time is 30 days
private static long defaultLongTermTokenExpirationTime = 1000L * 60 * 60 * 24 * 30;
Expand All @@ -133,6 +130,15 @@ public class JAXRSConfiguration extends Application {
public void init() {
logger.info("Starting auth micro app");

logger.info("Start initializing JNDI context");
Context ctx;
try {
ctx = new InitialContext();
} catch (NamingException e) {
// If we can't get the context, we can't do anything
throw new RuntimeException("Failed to initialize JNDI context", e);
}

/*
create an admin role if there isn't one
*/
Expand All @@ -141,26 +147,24 @@ public void init() {
logger.info("Finished initializing admin role.");

logger.info("Start initializing tokens expiration time.");
initializeTokenExpirationTime();
initializeLongTermTokenExpirationTime();
initializeTokenExpirationTime(ctx);
initializeLongTermTokenExpirationTime(ctx);
logger.info("Finished initializing token expiration time.");

logger.info("Determine IDP provider");
checkIDPProvider();
checkIDPProvider(ctx);

mailSession.getProperties().put("mail.smtp.ssl.trust", "smtp.gmail.com");

logger.info("Auth micro app has been successfully started");

//Set info for the swagger.json
BeanConfig beanConfig = new BeanConfig();
beanConfig.setVersion("1.0.0");
beanConfig.setSchemes(new String[] { "https" });
beanConfig.setVersion("1.0.1");
beanConfig.setSchemes(new String[]{"https"});
beanConfig.setDescription("APIs for accessing PIC-SURE-AUTH-MICROAPP - a centralized authentication/authorization micro services");
beanConfig.setTitle("PIC-SURE-AUTH-MICROAPP");
beanConfig.setBasePath("/psama");
beanConfig.setResourcePackage(TokenService.class.getPackage().getName());
beanConfig.setScan(true);
logger.info("Auth micro app has been successfully started");
}

/*
Expand All @@ -172,27 +176,19 @@ public void init() {
* This is currently only works for FENCE integration.
*
*/
public void checkIDPProvider() {
public void checkIDPProvider(Context ctx) {
logger.debug("checkIDPProvider() starting....");

Context ctx = null;
try {
ctx = new InitialContext();
} catch (NamingException e) {
e.printStackTrace();
}

try {
idp_provider = (String)ctx.lookup("java:global/idp_provider");
} catch (NamingException | ClassCastException | NumberFormatException ex){
idp_provider = (String) ctx.lookup("java:global/idp_provider");
} catch (NamingException | ClassCastException | NumberFormatException ex) {
idp_provider = "default";
}
logger.info("checkIDPProvider() idp provider is now :"+idp_provider);
logger.info("checkIDPProvider() idp provider is now :" + idp_provider);

if (idp_provider.equalsIgnoreCase("fence")) {
try {
idp_provider_uri = (String)ctx.lookup("java:global/idp_provider_uri");

idp_provider_uri = (String) ctx.lookup("java:global/idp_provider_uri");
fence_client_id = (String) ctx.lookup("java:global/fence_client_id");
fence_client_secret = (String) ctx.lookup("java:global/fence_client_secret");
fence_redirect_url = (String) ctx.lookup("java:global/fence_redirect_url");
Expand All @@ -202,13 +198,13 @@ public void checkIDPProvider() {
logger.error("checkIDPProvider() Empty parent consent group concept path from standalone.xml. Using default!");
fence_parent_consent_group_concept_path = "\\\\_Consents\\\\Short Study Accession with Consent code\\\\";
}

fence_harmonized_consent_group_concept_path = (String) ctx.lookup("java:global/fence_harmonized_consent_group_concept_path");
if (fence_harmonized_consent_group_concept_path == null) {
logger.error("checkIDPProvider() Empty harmonized consent group concept path from standalone.xml. Using default!");
fence_harmonized_consent_group_concept_path = "\\\\_Consents\\\\Short Study Accession with Consent code\\\\";
}

fence_topmed_consent_group_concept_path = (String) ctx.lookup("java:global/fence_topmed_consent_group_concept_path");
if (fence_topmed_consent_group_concept_path == null) {
logger.error("checkIDPProvider() Empty topmed consent group concept path from standalone.xml. Using default!");
Expand All @@ -226,8 +222,7 @@ public void checkIDPProvider() {
logger.error("checkIDPProvider() Missing Allowed query types from standalone.xml. Using defaults.");
fence_allowed_query_types = "COUNT,CROSS_COUNT";
}




fence_harmonized_concept_path = (String) ctx.lookup("java:global/fence_harmonized_concept_path");
if (fence_harmonized_concept_path.isEmpty()) {
Expand All @@ -237,7 +232,7 @@ public void checkIDPProvider() {
logger.debug("checkIDPProvider() idp provider FENCE is configured");

// Upsert FENCE connection
Connection c = connectionRepo.getUniqueResultByColumn("label","FENCE");
Connection c = connectionRepo.getUniqueResultByColumn("label", "FENCE");
if (c != null) {
logger.debug("checkIDPProvider() FENCE connection already exists.");
} else {
Expand All @@ -252,49 +247,63 @@ public void checkIDPProvider() {
}

// For debugging purposes, here is a dump of most of the FENCE variables
logger.info("checkIDPProvider() fence_standard_access_rules "+fence_standard_access_rules);
logger.info("checkIDPProvider() fence_consent_group_concept_path "+fence_parent_consent_group_concept_path);
logger.info("checkIDPProvider() fence_harmonized_concept_path "+fence_harmonized_concept_path);
logger.info("checkIDPProvider() fence_standard_access_rules " + fence_standard_access_rules);
logger.info("checkIDPProvider() fence_consent_group_concept_path " + fence_parent_consent_group_concept_path);
logger.info("checkIDPProvider() fence_harmonized_concept_path " + fence_harmonized_concept_path);

} catch (Exception ex) {
logger.error("checkIDPProvider() " + ex.getMessage());
logger.error("checkIDPProvider() Invalid FENCE IDP Provider Setup. Mandatory fields are missing. " +
"Check configuration in standalone.xml");
}
} else if (idp_provider.equalsIgnoreCase("okta")) {
try {
idp_provider_uri = (String) ctx.lookup("java:global/idp_provider_uri");
spClientSecret = (String) ctx.lookup("java:global/sp_client_secret");
connectionId = (String) ctx.lookup("java:global/connection_id");
clientId = (String) ctx.lookup("java:global/client_id");

logger.debug("checkIDPProvider() idp provider OKTA is configured");
} catch (Exception ex) {
logger.error("checkIDPProvider() "+ex.getMessage());
logger.error("checkIDPProvider() Invalid FENCE IDP Provider Setup. Mandatory fields are missing. "+
logger.error("checkIDPProvider() " + ex.getMessage());
logger.error("checkIDPProvider() Invalid OKTA IDP Provider Setup. Mandatory fields are missing. " +
"Check configuration in standalone.xml");
}
} else {
logger.error("checkIDPProvider() Invalid IDP Provider Setup. Mandatory fields are missing. " +
"Check configuration in standalone.xml");
}
logger.debug("checkIDPProvider() finished");
}

private void initializeTokenExpirationTime(){
private void initializeTokenExpirationTime(Context ctx) {
try {
Context ctx = new InitialContext();
tokenExpirationTime = Long.parseLong((String)ctx.lookup("java:global/tokenExpirationTime"));
} catch (NamingException | ClassCastException | NumberFormatException ex){
tokenExpirationTime = Long.parseLong((String) ctx.lookup("java:global/tokenExpirationTime"));
} catch (NamingException | ClassCastException | NumberFormatException ex) {
tokenExpirationTime = defaultTokenExpirationTime;
}

logger.info("Set token expiration time to " + tokenExpirationTime + " milliseconds");

}

private void initializeLongTermTokenExpirationTime(){
private void initializeLongTermTokenExpirationTime(Context ctx) {
try {
Context ctx = new InitialContext();
longTermTokenExpirationTime = Long.parseLong((String)ctx.lookup("java:global/longTermTokenExpirationTime"));
} catch (NamingException | ClassCastException | NumberFormatException ex){
longTermTokenExpirationTime = Long.parseLong((String) ctx.lookup("java:global/longTermTokenExpirationTime"));
} catch (NamingException | ClassCastException | NumberFormatException ex) {
longTermTokenExpirationTime = defaultLongTermTokenExpirationTime;
}

logger.info("Set long term token expiration time to " + longTermTokenExpirationTime + " milliseconds");

}

private void initializeDefaultAdminRole(){
private void initializeDefaultAdminRole() {

// make sure system admin and super admin privileges are added in the database
checkAndAddAdminPrivileges();

if (checkIfAdminRoleExists()){
if (checkIfAdminRoleExists()) {
logger.info("Admin role already exists in database, no need for the creation.");
return;
}
Expand All @@ -319,7 +328,7 @@ private void initializeDefaultAdminRole(){
privileges.add(superAdmin);
role.setPrivileges(privileges);

if(isAdminRole){
if (isAdminRole) {
roleRepo.merge(role);
logger.info("Finished updating the admin role, roleId: " + role.getUuid());
} else {
Expand All @@ -329,7 +338,7 @@ private void initializeDefaultAdminRole(){
}
}

private void checkAndAddAdminPrivileges(){
private void checkAndAddAdminPrivileges() {
logger.info("Checking if system admin and super admin privileges are added");
List<Privilege> privileges = privilegeRepo.list();
if (privileges == null)
Expand All @@ -341,18 +350,18 @@ private void checkAndAddAdminPrivileges(){
if (superAdmin != null && systemAdmin != null)
break;

if (SUPER_ADMIN.equals(p.getName())){
if (SUPER_ADMIN.equals(p.getName())) {
superAdmin = p;
continue;
}

if (ADMIN.equals(p.getName())){
if (ADMIN.equals(p.getName())) {
systemAdmin = p;
continue;
}
}

if (superAdmin == null){
if (superAdmin == null) {
logger.info("Adding super admin");
superAdmin = new Privilege();
superAdmin.setName(SUPER_ADMIN);
Expand All @@ -369,7 +378,7 @@ private void checkAndAddAdminPrivileges(){
}
}

private boolean checkIfAdminRoleExists(){
private boolean checkIfAdminRoleExists() {
logger.info("Checking if admin role already exists in database");
List<Role> roles = roleRepo.list();
if (roles == null || roles.isEmpty()) {
Expand All @@ -395,7 +404,7 @@ private boolean checkIfAdminRoleExists(){
return systemAdmin && superAdmin;
}

public static String getPrincipalName(SecurityContext securityContext){
public static String getPrincipalName(SecurityContext securityContext) {
if (securityContext.getUserPrincipal() == null)
return "No security context set, ";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import edu.harvard.hms.dbmi.avillach.auth.data.entity.Role;
import edu.harvard.hms.dbmi.avillach.auth.data.entity.TermsOfService;
import edu.harvard.hms.dbmi.avillach.auth.data.entity.User;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -42,6 +43,19 @@ public User findBySubject(String subject) {
.getSingleResult();
}

public User findByEmailAndConnection(String email, String connectionId) {
CriteriaQuery<User> query = em.getCriteriaBuilder().createQuery(User.class);
Root<User> queryRoot = query.from(User.class);
query.select(queryRoot);
CriteriaBuilder cb = cb();
return em.createQuery(query
.where(
cb.equal(queryRoot.join("connection")
.get("id"), connectionId),
eq(cb, queryRoot, "email", email)))
.getSingleResult();
}

public User findBySubjectAndConnection(String subject, String connectionId) {
CriteriaQuery<User> query = em.getCriteriaBuilder().createQuery(User.class);
Root<User> queryRoot = query.from(User.class);
Expand Down Expand Up @@ -86,8 +100,20 @@ public User findOrCreate(User inputUser) {
+ ", 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);
" could not be found by `entityManager`, checking by email and connection");
try {
// If the user isn't found by subject then check by email and connection just
// in case they were created by jenkins
user = findByEmailAndConnection(inputUser.getEmail(), inputUser.getConnection().getId());
if (StringUtils.isEmpty(user.getSubject())) {
user.setSubject(inputUser.getSubject());
user.setGeneralMetadata(inputUser.getGeneralMetadata());
}
} catch (NoResultException ex) {
logger.debug("findOrCreate() email " + inputUser.getEmail() +
" could not be found by `entityManager`, creating a new user");
user = createUser(inputUser);
}
} catch (NonUniqueResultException e) {
logger.error("findOrCreate() " + e.getClass().getSimpleName() + ": " + e.getMessage());
}
Expand Down Expand Up @@ -203,4 +229,19 @@ public User createOpenAccessUser(Role openAccessRole) {
+ ", email: " + user.getEmail());
return user;
}

/**
* Saves the given user to the database
*
* @param user the user to save
*/
public void save(User user) {
// if user exists update, else create
if (user.getUuid() != null) {
em().merge(user);
} else {
em().persist(user);
}
}

}
Loading

0 comments on commit dff78e3

Please sign in to comment.