From 4ddc6064d89308fb63579e6ba70efc4445e86b4b Mon Sep 17 00:00:00 2001 From: pieterlukasse Date: Wed, 30 Aug 2023 19:59:37 +0200 Subject: [PATCH 01/31] feat: prototype for extracting teamProject from initial request ...and setting that as a user role. This prototype is still missing a method to validate the teamProject against Arborist. feat: introduce custom configuration option --- .github/workflows/image_build_push.yaml | 16 +++ pom.xml | 3 +- .../webapi/shiro/Entities/UserRoleEntity.java | 5 + .../ohdsi/webapi/shiro/PermissionManager.java | 99 +++++++++++++++++-- .../filters/UpdateAccessTokenFilter.java | 92 ++++++++++++++++- .../management/AtlasRegularSecurity.java | 6 +- src/main/resources/application.properties | 3 + 7 files changed, 209 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/image_build_push.yaml diff --git a/.github/workflows/image_build_push.yaml b/.github/workflows/image_build_push.yaml new file mode 100644 index 0000000000..7598078802 --- /dev/null +++ b/.github/workflows/image_build_push.yaml @@ -0,0 +1,16 @@ +name: Build Image and Push to Quay + +on: push + +jobs: + ci: + name: Build Image and Push to Quay + uses: uc-cdis/.github/.github/workflows/image_build_push.yaml@master + with: + OVERRIDE_REPO_NAME: "ohdsi-webapi" + BUILD_PLATFORMS: "linux/amd64" + secrets: + ECR_AWS_ACCESS_KEY_ID: ${{ secrets.ECR_AWS_ACCESS_KEY_ID }} + ECR_AWS_SECRET_ACCESS_KEY: ${{ secrets.ECR_AWS_SECRET_ACCESS_KEY }} + QUAY_USERNAME: ${{ secrets.QUAY_USERNAME }} + QUAY_ROBOT_TOKEN: ${{ secrets.QUAY_ROBOT_TOKEN }} diff --git a/pom.xml b/pom.xml index a152507cac..bcbe026882 100644 --- a/pom.xml +++ b/pom.xml @@ -78,6 +78,7 @@ ISOLATION_READ_COMMITTED default + teamproject DisabledSecurity 43200 http://localhost @@ -229,7 +230,7 @@ 200 true info - info + debug info info info diff --git a/src/main/java/org/ohdsi/webapi/shiro/Entities/UserRoleEntity.java b/src/main/java/org/ohdsi/webapi/shiro/Entities/UserRoleEntity.java index 2b3e2ba9f2..544e0ab5bd 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/Entities/UserRoleEntity.java +++ b/src/main/java/org/ohdsi/webapi/shiro/Entities/UserRoleEntity.java @@ -87,4 +87,9 @@ public UserOrigin getOrigin() { public void setOrigin(UserOrigin origin) { this.origin = origin; } + + public String toString() { + role = this.getRole(); + return (role != null ? role.getName() : ""); + } } diff --git a/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java b/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java index 4e0eeec5b7..c58f752adf 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java +++ b/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java @@ -19,6 +19,8 @@ import org.ohdsi.webapi.shiro.Entities.UserRepository; import org.ohdsi.webapi.shiro.Entities.UserRoleEntity; import org.ohdsi.webapi.shiro.Entities.UserRoleRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Component; @@ -37,6 +39,7 @@ @Component @Transactional public class PermissionManager { + private final Logger logger = LoggerFactory.getLogger(PermissionManager.class); @Autowired private UserRepository userRepository; @@ -59,6 +62,8 @@ public class PermissionManager { private ThreadLocal> authorizationInfoCache = ThreadLocal.withInitial(ConcurrentHashMap::new); public RoleEntity addRole(String roleName, boolean isSystem) { + logger.debug("Called addRole: {}", roleName); + Guard.checkNotEmpty(roleName); checkRoleIsAbsent(roleName, isSystem, "Can't create role - it already exists"); @@ -96,8 +101,32 @@ public void removeUserFromRole(String roleName, String login, UserOrigin origin) UserEntity user = this.getUserByLogin(login); UserRoleEntity userRole = this.userRoleRepository.findByUserAndRole(user, role); - if (userRole != null && (origin == null || origin.equals(userRole.getOrigin()))) + if (userRole != null && (origin == null || origin.equals(userRole.getOrigin()))) { + logger.debug("Removing user from role: {}, {}, {}", user.getLogin(), role.getName(), userRole.getOrigin()); this.userRoleRepository.delete(userRole); + } + } + + public void removeUserFromUserRole(String roleName, String login) { + Guard.checkNotEmpty(roleName); + Guard.checkNotEmpty(login); + + if (roleName.equalsIgnoreCase(login)) + throw new RuntimeException("Can't remove user from personal role"); + + logger.debug("Checking if role exists: {}", roleName); + RoleEntity role = this.roleRepository.findByNameAndSystemRole(roleName, false); + if (role != null) { + UserEntity user = this.getUserByLogin(login); + + UserRoleEntity userRole = this.userRoleRepository.findByUserAndRole(user, role); + if (userRole != null) { + logger.debug("Removing user from USER role: {}, {}", user.getLogin(), roleName); + this.userRoleRepository.delete(userRole); + } + } else { + logger.debug("Role {} not found", roleName); + } } public Iterable getRoles(boolean includePersonalRoles) { @@ -141,14 +170,29 @@ public void clearAuthorizationInfoCache() { this.authorizationInfoCache.set(new ConcurrentHashMap<>()); } + + @Transactional + public void registerUser(String login, String name, Set defaultRoles, Set newUserRoles, + boolean resetRoles) { + registerUser(login, name, UserOrigin.SYSTEM, defaultRoles, newUserRoles, resetRoles); + } + @Transactional public UserEntity registerUser(final String login, final String name, final Set defaultRoles) { - return registerUser(login, name, UserOrigin.SYSTEM, defaultRoles); + return registerUser(login, name, UserOrigin.SYSTEM, defaultRoles, null, false); } @Transactional public UserEntity registerUser(final String login, final String name, final UserOrigin userOrigin, final Set defaultRoles) { + return registerUser(login, name, userOrigin, defaultRoles, null, false); + } + + @Transactional + public UserEntity registerUser(final String login, final String name, final UserOrigin userOrigin, + final Set defaultRoles, final Set newUserRoles, boolean resetRoles) { + logger.debug("Called registerUser with resetRoles: login={}, reset roles={}, default roles={}, new user roles={}", + login, resetRoles, defaultRoles, newUserRoles); Guard.checkNotEmpty(login); UserEntity user = userRepository.findByLogin(login); @@ -162,6 +206,14 @@ public UserEntity registerUser(final String login, final String name, final User user.setOrigin(userOrigin); user = userRepository.save(user); } + if (resetRoles) { + // remove all user roles: + removeAllUserRolesFromUser(login, user); + // add back just the given newUserRoles: + addRolesForUser(login, userOrigin, user, newUserRoles, false); + } + // get user again, fresh from db with all new roles: + user = userRepository.findOne(user.getId()); return user; } @@ -176,18 +228,46 @@ public UserEntity registerUser(final String login, final String name, final User RoleEntity personalRole = this.addRole(login, false); this.addUser(user, personalRole, userOrigin, null); + addRolesForUser(login, userOrigin, user, newUserRoles, false); + addDefaultRolesForUser(login, userOrigin, user, defaultRoles); + // // get user again, fresh from db with all new roles: + user = userRepository.findOne(user.getId()); + return user; + } - if (defaultRoles != null) { - for (String roleName: defaultRoles) { - RoleEntity defaultRole = this.getSystemRoleByName(roleName); - if (defaultRole != null) { - this.addUser(user, defaultRole, userOrigin, null); + private void addRolesForUser(String login, UserOrigin userOrigin, UserEntity user, Set roles, boolean isSystemRole) { + if (roles != null) { + for (String roleName: roles) { + // Temporary patch/workaround (in reality the role should have been added by sysadmin?): + pocAddUserRole(roleName); + // end temporary patch + RoleEntity role = this.getRoleByName(roleName, isSystemRole); + if (role != null) { + this.addUser(user, role, userOrigin, null); } } } + } - user = userRepository.findOne(user.getId()); - return user; + private void addDefaultRolesForUser(String login, UserOrigin userOrigin, UserEntity user, Set roles) { + addRolesForUser(login, userOrigin, user, roles,true); + } + + private void removeAllUserRolesFromUser(String login, UserEntity user) { + Set userRoles = this.getUserRoles(user); + // remove all roles except the personal role: + userRoles.stream().filter(role -> !role.getName().equalsIgnoreCase(login)).forEach(userRole -> { + this.removeUserFromUserRole(userRole.getName(), login); + }); + } + + private RoleEntity pocAddUserRole(String roleName) { + RoleEntity role = this.roleRepository.findByNameAndSystemRole(roleName, false); + if (role != null) { + return role; + } else { + return addRole(roleName, false); + } } public Iterable getUsers() { @@ -322,6 +402,7 @@ private Set getRolePermissions(RoleEntity role) { private Set getUserRoles(UserEntity user) { Set userRoles = user.getUserRoles(); + logger.debug("Called getUserRoles. Found: {}", userRoles); Set roles = new LinkedHashSet<>(); for (UserRoleEntity userRole : userRoles) { if (isRelationAllowed(userRole.getStatus())) { diff --git a/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java b/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java index bfbb06eae1..faeaa70952 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java +++ b/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java @@ -11,10 +11,13 @@ import java.util.Calendar; import java.util.Collection; import java.util.Date; +import java.util.Enumeration; +import java.util.HashSet; import java.util.Objects; import java.util.Set; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import javax.ws.rs.core.UriBuilder; @@ -31,27 +34,34 @@ import org.ohdsi.webapi.shiro.TokenManager; import org.ohdsi.webapi.util.UserUtils; import org.pac4j.core.profile.CommonProfile; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * * @author gennadiy.anisimov */ public class UpdateAccessTokenFilter extends AdviceFilter { - + private final Logger logger = LoggerFactory.getLogger(UpdateAccessTokenFilter.class); + private final PermissionManager authorizer; private final int tokenExpirationIntervalInSeconds; private final Set defaultRoles; private final String onFailRedirectUrl; + private final String authorizationMode; public UpdateAccessTokenFilter( PermissionManager authorizer, Set defaultRoles, int tokenExpirationIntervalInSeconds, - String onFailRedirectUrl) { + String onFailRedirectUrl, + String authorizationMode) { this.authorizer = authorizer; this.tokenExpirationIntervalInSeconds = tokenExpirationIntervalInSeconds; this.defaultRoles = defaultRoles; this.onFailRedirectUrl = onFailRedirectUrl; + this.authorizationMode = authorizationMode; + logger.debug("AUTHORIZATION_MODE in UpdateAccessTokenFilter constructor == '{}'", this.authorizationMode); } @Override @@ -121,12 +131,29 @@ protected boolean preHandle(ServletRequest request, ServletResponse response) th session.stop(); } - if (jwt == null) { + if (jwt == null) { // dead check...jwt is always null... if (name == null) { name = login; } try { - this.authorizer.registerUser(login, name, defaultRoles); + logger.debug("AUTHORIZATION_MODE in UpdateAccessTokenFilter == '{}'", this.authorizationMode); + boolean resetRoles = false; + Set newUserRoles = new HashSet(); + if (this.authorizationMode.equals("teamproject")) { + // in case of "teamproject" mode, we want all roles to be reset always, and + // set to only the one requested/found in the request parameters (following lines below): + resetRoles = true; + // check if a teamproject parameter is found in the request: + String teamProjectRole = extractTeamProjectFromRequestParameters(request); + // if found, add teamproject as a role in the newUserRoles list: + if (teamProjectRole != null) { + newUserRoles.add(teamProjectRole); + // double check with Arborist if this role has really been granted to the user.... + // TODO + } + } + this.authorizer.registerUser(login, name, defaultRoles, newUserRoles, resetRoles); + } catch (Exception e) { WebUtils.toHttp(response).setHeader("x-auth-error", e.getMessage()); throw new Exception(e); @@ -174,4 +201,61 @@ private Date getExpirationDate(final int expirationIntervalInSeconds) { calendar.add(Calendar.SECOND, expirationIntervalInSeconds); return calendar.getTime(); } + + private String extractTeamProjectFromRequestParameters(ServletRequest request) { + // Get the url + HttpServletRequest httpRequest = (HttpServletRequest) request; + String url = httpRequest.getRequestURL().toString(); + + // try to find it in the redirectUrl parameter: + logger.debug("Looking for redirectUrl in request: {}....", url); + String[] redirectUrlParams = getParameterValues(request, "redirectUrl"); + if (redirectUrlParams != null) { + logger.debug("Parameter redirectUrl found. Checking if it contains teamproject...."); + // teamProject will be in first one in this case...as only parameter: + String firstParameter = redirectUrlParams[0].toLowerCase(); + if (firstParameter.contains("teamproject=")) { + String teamProject = firstParameter.split("teamproject=")[1]; + logger.debug("Found teamproject: {}", teamProject); + return teamProject; + } + } + + // try to find "teamproject" param in url itself (there will be no redirectUrl if user session is still valid): + logger.debug("Fallback1: Looking for teamproject in request: {}....", url); + String[] teamProjectParams = getParameterValues(request, "teamproject"); + if (teamProjectParams != null) { + logger.debug("Parameter teamproject found. Parsing...."); + String teamProject = teamProjectParams[0].toLowerCase(); + logger.debug("Found teamproject: {}", teamProject); + return teamProject; + } + + logger.debug("Fallback2: Looking for teamproject in Action-Location header of request: {}....", url); + String actionLocationUrl = httpRequest.getHeader("Action-Location"); + if (actionLocationUrl != null && actionLocationUrl.contains("teamproject=")) { + String teamProject = actionLocationUrl.split("teamproject=")[1]; + logger.debug("Found teamproject: {}", teamProject); + return teamProject; + } + + logger.debug("Found NO teamproject."); + return null; + } + + private String[] getParameterValues(ServletRequest request, String parameterName) { + // Get the parameters + logger.debug("Looking for parameter with name: {} ...", parameterName); + Enumeration paramNames = request.getParameterNames(); + while(paramNames.hasMoreElements()) { + String paramName = paramNames.nextElement(); + logger.debug("Parameter name: {}", paramName); + if (paramName.equals(parameterName)) { + String[] paramValues = request.getParameterValues(paramName); + return paramValues; + } + } + logger.debug("Found NO parameter with name: {}", parameterName); + return null; + } } diff --git a/src/main/java/org/ohdsi/webapi/shiro/management/AtlasRegularSecurity.java b/src/main/java/org/ohdsi/webapi/shiro/management/AtlasRegularSecurity.java index 67e27df70b..6029cbd164 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/management/AtlasRegularSecurity.java +++ b/src/main/java/org/ohdsi/webapi/shiro/management/AtlasRegularSecurity.java @@ -255,6 +255,9 @@ public class AtlasRegularSecurity extends AtlasSecurity { @Value("${security.auth.google.enabled}") private boolean googleAuthEnabled; + @Value("${security.ohdsi.custom.authorization.mode}") + private String authorizationMode; + private RestTemplate restTemplate = new RestTemplate(); @Autowired @@ -271,8 +274,9 @@ public Map getFilters() { Map filters = super.getFilters(); filters.put(LOGOUT, new LogoutFilter(eventPublisher)); + logger.debug("Initializing UpdateAccessTokenFilter with AUTHORIZATION_MODE === '{}'", this.authorizationMode); filters.put(UPDATE_TOKEN, new UpdateAccessTokenFilter(this.authorizer, this.defaultRoles, this.tokenExpirationIntervalInSeconds, - this.redirectUrl)); + this.redirectUrl, this.authorizationMode)); filters.put(ACCESS_AUTHC, new GoogleAccessTokenFilter(restTemplate, permissionManager, Collections.emptySet())); filters.put(JWT_AUTHC, new AtlasJwtAuthFilter()); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index cd1afb2013..8ded583041 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -201,6 +201,9 @@ security.auth.ldap.enabled=${security.auth.ldap.enabled} security.auth.ad.enabled=${security.auth.ad.enabled} security.auth.cas.enabled=${security.auth.cas.enabled} +#Authorization config +security.ohdsi.custom.authorization.mode=${security.ohdsi.custom.authorization.mode} + #Execution engine executionengine.updateStatusCallback=${executionengine.updateStatusCallback} executionengine.resultCallback=${executionengine.resultCallback} From f5494f8052d30d3f007cab04b64aebdd5f4292a9 Mon Sep 17 00:00:00 2001 From: pieterlukasse Date: Tue, 17 Oct 2023 22:13:13 +0200 Subject: [PATCH 02/31] feat: basic working version --- pom.xml | 1 + .../filters/UpdateAccessTokenFilter.java | 36 +++++++++++++++++-- .../management/AtlasRegularSecurity.java | 6 +++- src/main/resources/application.properties | 1 + 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index bcbe026882..3427013a1d 100644 --- a/pom.xml +++ b/pom.xml @@ -79,6 +79,7 @@ default teamproject + https://mygen3.arborist.url.here DisabledSecurity 43200 http://localhost diff --git a/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java b/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java index faeaa70952..04d0ca32cf 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java +++ b/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java @@ -5,6 +5,8 @@ import static org.ohdsi.webapi.shiro.management.AtlasSecurity.TOKEN_ATTRIBUTE; import io.buji.pac4j.subject.Pac4jPrincipal; +import org.json.JSONObject; + import java.net.URI; import java.net.URISyntaxException; import java.security.Principal; @@ -36,6 +38,7 @@ import org.pac4j.core.profile.CommonProfile; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.web.client.RestTemplate; /** * @@ -49,18 +52,21 @@ public class UpdateAccessTokenFilter extends AdviceFilter { private final Set defaultRoles; private final String onFailRedirectUrl; private final String authorizationMode; + private final String authorizationUrl; public UpdateAccessTokenFilter( PermissionManager authorizer, Set defaultRoles, int tokenExpirationIntervalInSeconds, String onFailRedirectUrl, - String authorizationMode) { + String authorizationMode, + String authorizationUrl) { this.authorizer = authorizer; this.tokenExpirationIntervalInSeconds = tokenExpirationIntervalInSeconds; this.defaultRoles = defaultRoles; this.onFailRedirectUrl = onFailRedirectUrl; this.authorizationMode = authorizationMode; + this.authorizationUrl = authorizationUrl; logger.debug("AUTHORIZATION_MODE in UpdateAccessTokenFilter constructor == '{}'", this.authorizationMode); } @@ -147,9 +153,13 @@ protected boolean preHandle(ServletRequest request, ServletResponse response) th String teamProjectRole = extractTeamProjectFromRequestParameters(request); // if found, add teamproject as a role in the newUserRoles list: if (teamProjectRole != null) { + // double check if this role has really been granted to the user: + if (checkGen3Authorization(teamProjectRole, login) == false) { + WebUtils.toHttp(response).sendError(HttpServletResponse.SC_FORBIDDEN, + "User is not authorized to access this team project's data"); + return false; + } newUserRoles.add(teamProjectRole); - // double check with Arborist if this role has really been granted to the user.... - // TODO } } this.authorizer.registerUser(login, name, defaultRoles, newUserRoles, resetRoles); @@ -177,6 +187,26 @@ protected boolean preHandle(ServletRequest request, ServletResponse response) th return true; } + private boolean checkGen3Authorization(String teamProjectRole, String login) throws Exception { + logger.debug("Checking Gen3 Authorization for 'team project'={} and user={} using service={}", teamProjectRole, login, this.authorizationUrl); + RestTemplate restTemplate = new RestTemplate(); + String arboristAuthorizationURL = this.authorizationUrl; + String requestBody = String.format("{\"username\": \"%s\"}", login); + String jsonResponseString = restTemplate.postForObject(arboristAuthorizationURL, requestBody, String.class); + + JSONObject jsonObject = new JSONObject(jsonResponseString); + + if (!jsonObject.keySet().contains(teamProjectRole)) { + logger.debug("User is not authorized to access this team project's data"); + return false; + } else { + // TODO add more checks, e.g. whether the expected service / method config are also present... + Object teamProjectAuthorizations = jsonObject.get(teamProjectRole); + logger.debug("Found authorizations={}", teamProjectAuthorizations); + return true; + } + } + private URI getOAuthFailUri() throws URISyntaxException { return getFailUri("oauth_error_email"); } diff --git a/src/main/java/org/ohdsi/webapi/shiro/management/AtlasRegularSecurity.java b/src/main/java/org/ohdsi/webapi/shiro/management/AtlasRegularSecurity.java index 6029cbd164..ac31d31815 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/management/AtlasRegularSecurity.java +++ b/src/main/java/org/ohdsi/webapi/shiro/management/AtlasRegularSecurity.java @@ -258,6 +258,9 @@ public class AtlasRegularSecurity extends AtlasSecurity { @Value("${security.ohdsi.custom.authorization.mode}") private String authorizationMode; + @Value("${security.ohdsi.custom.authorization.url}") + private String authorizationUrl; + private RestTemplate restTemplate = new RestTemplate(); @Autowired @@ -275,8 +278,9 @@ public Map getFilters() { filters.put(LOGOUT, new LogoutFilter(eventPublisher)); logger.debug("Initializing UpdateAccessTokenFilter with AUTHORIZATION_MODE === '{}'", this.authorizationMode); + logger.debug("Initializing UpdateAccessTokenFilter with AUTHORIZATION_URL === '{}'", this.authorizationUrl); filters.put(UPDATE_TOKEN, new UpdateAccessTokenFilter(this.authorizer, this.defaultRoles, this.tokenExpirationIntervalInSeconds, - this.redirectUrl, this.authorizationMode)); + this.redirectUrl, this.authorizationMode, this.authorizationUrl)); filters.put(ACCESS_AUTHC, new GoogleAccessTokenFilter(restTemplate, permissionManager, Collections.emptySet())); filters.put(JWT_AUTHC, new AtlasJwtAuthFilter()); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8ded583041..2b7a811ee5 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -203,6 +203,7 @@ security.auth.cas.enabled=${security.auth.cas.enabled} #Authorization config security.ohdsi.custom.authorization.mode=${security.ohdsi.custom.authorization.mode} +security.ohdsi.custom.authorization.url=${security.ohdsi.custom.authorization.url} #Execution engine executionengine.updateStatusCallback=${executionengine.updateStatusCallback} From 6cd5d3f3c19b519ffc71ac4a2862d74317d73187 Mon Sep 17 00:00:00 2001 From: pieterlukasse Date: Wed, 18 Oct 2023 20:19:55 +0200 Subject: [PATCH 03/31] feat: extra checks on "service" and "method" parts of the authorization --- .../filters/UpdateAccessTokenFilter.java | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java b/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java index 04d0ca32cf..f2f1b73457 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java +++ b/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java @@ -5,6 +5,8 @@ import static org.ohdsi.webapi.shiro.management.AtlasSecurity.TOKEN_ATTRIBUTE; import io.buji.pac4j.subject.Pac4jPrincipal; + +import org.json.JSONArray; import org.json.JSONObject; import java.net.URI; @@ -197,12 +199,35 @@ private boolean checkGen3Authorization(String teamProjectRole, String login) thr JSONObject jsonObject = new JSONObject(jsonResponseString); if (!jsonObject.keySet().contains(teamProjectRole)) { - logger.debug("User is not authorized to access this team project's data"); + logger.warn("User is not authorized to access this team project's data"); return false; } else { - // TODO add more checks, e.g. whether the expected service / method config are also present... - Object teamProjectAuthorizations = jsonObject.get(teamProjectRole); + JSONArray teamProjectAuthorizations = jsonObject.getJSONArray(teamProjectRole); logger.debug("Found authorizations={}", teamProjectAuthorizations); + // We expect only one authorization rule per teamproject: + if (teamProjectAuthorizations.length() != 1) { + logger.error("Only one authorization rule expected for 'teamproject'={}, found={}", teamProjectRole, + teamProjectAuthorizations.length()); + return false; + } + JSONObject teamProjectAuthorization = teamProjectAuthorizations.getJSONObject(0); + + // check if the authorization contains the right "service" and "method" values: + String expectedMethod = "access"; + String expectedService = "atlas-argo-wrapper-and-cohort-middleware"; // TODO - make the service name configurable? + String service = teamProjectAuthorization.getString("service"); + String method = teamProjectAuthorization.getString("method"); + logger.debug("Parsed service={} and method={}", service, method); + if (!method.equalsIgnoreCase(expectedMethod)) { + logger.error("The 'teamproject' authorization method should be '{}', but found '{}'", expectedMethod, method); + return false; + } + logger.debug("Parsed method is as expected"); + if (!service.equalsIgnoreCase(expectedService)) { + logger.error("The 'teamproject' authorization service should be '{}', but found '{}'", expectedService, service); + return false; + } + logger.debug("Parsed service is as expected"); return true; } } From d51a2c72103f7fc90627132607ef6e9dcb0ff984 Mon Sep 17 00:00:00 2001 From: pieterlukasse Date: Fri, 27 Oct 2023 17:06:13 +0200 Subject: [PATCH 04/31] Update pom.xml with a better default authorization url --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3427013a1d..df22b23aba 100644 --- a/pom.xml +++ b/pom.xml @@ -79,7 +79,7 @@ default teamproject - https://mygen3.arborist.url.here + http://arborist-service/auth/mapping DisabledSecurity 43200 http://localhost From 6e61e31076fbfa7d2a70e006b17ce48b7d0c66fb Mon Sep 17 00:00:00 2001 From: pieterlukasse Date: Fri, 27 Oct 2023 21:18:03 +0200 Subject: [PATCH 05/31] temp test --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index df22b23aba..72b7143e57 100644 --- a/pom.xml +++ b/pom.xml @@ -79,7 +79,7 @@ default teamproject - http://arborist-service/auth/mapping + http://arborist-service2/auth/mapping DisabledSecurity 43200 http://localhost From f700d790fb009f5cece8d26ac8e43268c9c39a96 Mon Sep 17 00:00:00 2001 From: pieterlukasse Date: Mon, 13 Nov 2023 14:34:55 +0100 Subject: [PATCH 06/31] Revert "temp test" This reverts commit 10488c629c08dd77466c7d7815beaf2adca452a1. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 72b7143e57..df22b23aba 100644 --- a/pom.xml +++ b/pom.xml @@ -79,7 +79,7 @@ default teamproject - http://arborist-service2/auth/mapping + http://arborist-service/auth/mapping DisabledSecurity 43200 http://localhost From 9b906c177bb5533719446547a311d9f474f38e4e Mon Sep 17 00:00:00 2001 From: pieterlukasse Date: Mon, 13 Nov 2023 14:35:50 +0100 Subject: [PATCH 07/31] feat: improve logging of jwt --- .../org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java b/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java index f2f1b73457..8e6275460b 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java +++ b/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java @@ -139,6 +139,7 @@ protected boolean preHandle(ServletRequest request, ServletResponse response) th session.stop(); } + logger.debug("Check JWT: '{}'", jwt); if (jwt == null) { // dead check...jwt is always null... if (name == null) { name = login; From d8023352a07ed0718db00da0c50aca553172679b Mon Sep 17 00:00:00 2001 From: pieterlukasse Date: Fri, 17 Nov 2023 17:06:52 +0100 Subject: [PATCH 08/31] wip: working version tying permissions to teamproject role --- pom.xml | 2 +- .../webapi/security/PermissionService.java | 8 +++--- .../model/EntityPermissionSchema.java | 25 +++++++++++++++++-- .../ohdsi/webapi/shiro/PermissionManager.java | 14 +++++++++++ .../filters/UpdateAccessTokenFilter.java | 1 + 5 files changed, 43 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index df22b23aba..7d39773bd5 100644 --- a/pom.xml +++ b/pom.xml @@ -193,7 +193,7 @@ - true + false 8080 diff --git a/src/main/java/org/ohdsi/webapi/security/PermissionService.java b/src/main/java/org/ohdsi/webapi/security/PermissionService.java index 5bb63729d3..b1bcd4217f 100644 --- a/src/main/java/org/ohdsi/webapi/security/PermissionService.java +++ b/src/main/java/org/ohdsi/webapi/security/PermissionService.java @@ -285,9 +285,9 @@ public boolean hasReadAccess(CommonEntity entity) { try { String login = this.permissionManager.getSubjectName(); UserSimpleAuthorizationInfo authorizationInfo = this.permissionManager.getAuthorizationInfo(login); - if (Objects.equals(authorizationInfo.getUserId(), entity.getCreatedBy().getId())){ - hasAccess = true; // the role is the one that created the artifact - } else { + // if (Objects.equals(authorizationInfo.getUserId(), entity.getCreatedBy().getId())){ + // hasAccess = true; // the role is the one that created the artifact - Is this for backwards compatibiliy mode? Should we write a migration instead? + // } else { EntityType entityType = entityPermissionSchemaResolver.getEntityType(entity.getClass()); List roles = getRolesHavingReadPermissions(entityType, entity.getId()); @@ -296,7 +296,7 @@ public boolean hasReadAccess(CommonEntity entity) { hasAccess = roles.stream() .anyMatch(r -> userRoles.stream() .anyMatch(re -> re.equals(r.getName()))); - } + // } } catch (Exception e) { logger.error("Error getting user roles and permissions", e); throw new RuntimeException(e); diff --git a/src/main/java/org/ohdsi/webapi/security/model/EntityPermissionSchema.java b/src/main/java/org/ohdsi/webapi/security/model/EntityPermissionSchema.java index aab9017933..1d63234a98 100644 --- a/src/main/java/org/ohdsi/webapi/security/model/EntityPermissionSchema.java +++ b/src/main/java/org/ohdsi/webapi/security/model/EntityPermissionSchema.java @@ -2,19 +2,27 @@ import org.ohdsi.webapi.model.CommonEntity; import org.ohdsi.webapi.shiro.Entities.RoleEntity; +import org.ohdsi.webapi.shiro.filters.UpdateAccessTokenFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.ohdsi.webapi.shiro.PermissionManager; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import java.util.Collections; import java.util.HashMap; import java.util.Map; public abstract class EntityPermissionSchema { + private final Logger logger = LoggerFactory.getLogger(UpdateAccessTokenFilter.class); private final EntityType entityType; private final Map readPermissions; private final Map writePermissions; + @Value("${security.ohdsi.custom.authorization.mode}") + private String authorizationMode; + @Autowired protected PermissionManager permissionManager; @@ -49,8 +57,12 @@ public Map getAllPermissions() { } public void onInsert(CommonEntity commonEntity) { - - addPermissionsToCurrentUserFromTemplate(commonEntity, getAllPermissions()); + logger.debug("AUTHORIZATION_MODE in EntityPermissionSchema == '{}'", this.authorizationMode); + if (this.authorizationMode.equals("teamproject")) { + addPermissionsToCurrentTeamProjectFromTemplate(commonEntity, getAllPermissions()); + } else { + addPermissionsToCurrentUserFromTemplate(commonEntity, getAllPermissions()); + } } public void onDelete(CommonEntity commonEntity) { @@ -65,4 +77,13 @@ protected void addPermissionsToCurrentUserFromTemplate(CommonEntity commonEntity RoleEntity role = permissionManager.getUserPersonalRole(login); permissionManager.addPermissionsFromTemplate(role, template, commonEntity.getId().toString()); } + + protected void addPermissionsToCurrentTeamProjectFromTemplate(CommonEntity commonEntity, Map template) { + + RoleEntity role = permissionManager.getCurrentTeamProjectRole(); + if (role == null) { + throw new RuntimeException("Expected a teamproject role but found none!"); + } + permissionManager.addPermissionsFromTemplate(role, template, commonEntity.getId().toString()); + } } diff --git a/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java b/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java index c58f752adf..c4463b6bf2 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java +++ b/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java @@ -61,6 +61,8 @@ public class PermissionManager { private ThreadLocal> authorizationInfoCache = ThreadLocal.withInitial(ConcurrentHashMap::new); + private String currentTeamProjectRole = null; + public RoleEntity addRole(String roleName, boolean isSystem) { logger.debug("Called addRole: {}", roleName); @@ -553,4 +555,16 @@ public void removePermissionsFromTemplate(Map template, String v public boolean roleExists(String roleName) { return this.roleRepository.existsByName(roleName); } + + public void setCurrentTeamProjectRole(String teamProjectRole) { + this.currentTeamProjectRole = teamProjectRole; + } + + public RoleEntity getCurrentTeamProjectRole() { + if (this.currentTeamProjectRole != null) { + return this.getRoleByName(this.currentTeamProjectRole, false); + } else { + return null; + } + } } diff --git a/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java b/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java index 8e6275460b..087c62f9c6 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java +++ b/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java @@ -163,6 +163,7 @@ protected boolean preHandle(ServletRequest request, ServletResponse response) th return false; } newUserRoles.add(teamProjectRole); + authorizer.setCurrentTeamProjectRole(teamProjectRole); } } this.authorizer.registerUser(login, name, defaultRoles, newUserRoles, resetRoles); From 5a0960ef973f901ffe0408e91fc2bd17e825a295 Mon Sep 17 00:00:00 2001 From: pieterlukasse Date: Fri, 17 Nov 2023 17:53:08 +0100 Subject: [PATCH 09/31] wip: add "Atlas users" role as default role when logged-in teamproject mode --- .../org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java b/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java index 087c62f9c6..598d094ee1 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java +++ b/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java @@ -163,6 +163,7 @@ protected boolean preHandle(ServletRequest request, ServletResponse response) th return false; } newUserRoles.add(teamProjectRole); + newUserRoles.add("Atlas users"); // TODO - review this part...maybe users can get this role when onboarding (system role?) authorizer.setCurrentTeamProjectRole(teamProjectRole); } } From a9c188f13b0e0ca245fcccccda3ebfcf74c60c0b Mon Sep 17 00:00:00 2001 From: pieterlukasse Date: Fri, 17 Nov 2023 20:50:40 +0100 Subject: [PATCH 10/31] fix: add "Atlas users" as default system role --- .../ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java b/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java index 598d094ee1..621f8f17f5 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java +++ b/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java @@ -148,6 +148,7 @@ protected boolean preHandle(ServletRequest request, ServletResponse response) th logger.debug("AUTHORIZATION_MODE in UpdateAccessTokenFilter == '{}'", this.authorizationMode); boolean resetRoles = false; Set newUserRoles = new HashSet(); + Set newDefaultRoles = new HashSet(defaultRoles); if (this.authorizationMode.equals("teamproject")) { // in case of "teamproject" mode, we want all roles to be reset always, and // set to only the one requested/found in the request parameters (following lines below): @@ -163,11 +164,11 @@ protected boolean preHandle(ServletRequest request, ServletResponse response) th return false; } newUserRoles.add(teamProjectRole); - newUserRoles.add("Atlas users"); // TODO - review this part...maybe users can get this role when onboarding (system role?) + newDefaultRoles.add("Atlas users"); // TODO - review this part...maybe users can get this role when onboarding (system role?) authorizer.setCurrentTeamProjectRole(teamProjectRole); } } - this.authorizer.registerUser(login, name, defaultRoles, newUserRoles, resetRoles); + this.authorizer.registerUser(login, name, newDefaultRoles, newUserRoles, resetRoles); } catch (Exception e) { WebUtils.toHttp(response).setHeader("x-auth-error", e.getMessage()); From b61f536369c0554426ab74b149bd0f333aa5bcfc Mon Sep 17 00:00:00 2001 From: pieterlukasse Date: Fri, 17 Nov 2023 21:25:42 +0100 Subject: [PATCH 11/31] wip: add more log statements for PermissionManager --- .../java/org/ohdsi/webapi/shiro/PermissionManager.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java b/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java index c4463b6bf2..3d56d9d994 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java +++ b/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java @@ -241,7 +241,9 @@ private void addRolesForUser(String login, UserOrigin userOrigin, UserEntity use if (roles != null) { for (String roleName: roles) { // Temporary patch/workaround (in reality the role should have been added by sysadmin?): - pocAddUserRole(roleName); + if (!isSystemRole) { + pocAddUserRole(roleName); + } // end temporary patch RoleEntity role = this.getRoleByName(roleName, isSystemRole); if (role != null) { @@ -252,6 +254,7 @@ private void addRolesForUser(String login, UserOrigin userOrigin, UserEntity use } private void addDefaultRolesForUser(String login, UserOrigin userOrigin, UserEntity user, Set roles) { + logger.debug("Adding the following system roles for the user: roles={}", roles); addRolesForUser(login, userOrigin, user, roles,true); } @@ -497,12 +500,15 @@ private UserRoleEntity addUser(final UserEntity user, final RoleEntity role, final UserOrigin userOrigin, final String status) { UserRoleEntity relation = this.userRoleRepository.findByUserAndRole(user, role); if (relation == null) { + logger.debug("The system role={} is new for this user. Adding...", role.getName()); relation = new UserRoleEntity(); relation.setUser(user); relation.setRole(role); relation.setStatus(status); relation.setOrigin(userOrigin); relation = this.userRoleRepository.save(relation); + } else { + logger.debug("The user already had the system role={}", role.getName()); } return relation; From fe96b57760934eaf7e1bd530d06e4a828212f6f4 Mon Sep 17 00:00:00 2001 From: pieterlukasse Date: Mon, 20 Nov 2023 13:39:50 +0100 Subject: [PATCH 12/31] tmp: add default roles on the fly ... just for testing at the moment. System roles should really be assigned to the user beforehand as part of the onboarding process... --- .../java/org/ohdsi/webapi/shiro/PermissionManager.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java b/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java index 3d56d9d994..cb41dc8ad0 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java +++ b/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java @@ -213,10 +213,12 @@ public UserEntity registerUser(final String login, final String name, final User removeAllUserRolesFromUser(login, user); // add back just the given newUserRoles: addRolesForUser(login, userOrigin, user, newUserRoles, false); + // make sure the default roles are there: TODO - discuss if really necessary.... + addDefaultRolesForUser(login, userOrigin, user, defaultRoles); } // get user again, fresh from db with all new roles: user = userRepository.findOne(user.getId()); - return user; + return user; // >>>>>>>>>> RETURN! Add else for readability??? } checkRoleIsAbsent(login, false, "User with such login has been improperly removed from the database. " + @@ -232,7 +234,7 @@ public UserEntity registerUser(final String login, final String name, final User this.addUser(user, personalRole, userOrigin, null); addRolesForUser(login, userOrigin, user, newUserRoles, false); addDefaultRolesForUser(login, userOrigin, user, defaultRoles); - // // get user again, fresh from db with all new roles: + // get user again, fresh from db with all new roles: user = userRepository.findOne(user.getId()); return user; } @@ -500,7 +502,7 @@ private UserRoleEntity addUser(final UserEntity user, final RoleEntity role, final UserOrigin userOrigin, final String status) { UserRoleEntity relation = this.userRoleRepository.findByUserAndRole(user, role); if (relation == null) { - logger.debug("The system role={} is new for this user. Adding...", role.getName()); + logger.debug("The role={} is new for this user. Adding...", role.getName()); relation = new UserRoleEntity(); relation.setUser(user); relation.setRole(role); @@ -508,7 +510,7 @@ private UserRoleEntity addUser(final UserEntity user, final RoleEntity role, relation.setOrigin(userOrigin); relation = this.userRoleRepository.save(relation); } else { - logger.debug("The user already had the system role={}", role.getName()); + logger.debug("The user already had the role={}", role.getName()); } return relation; From 1a8c6a890e9416e34de4745e85249e28eba8bf31 Mon Sep 17 00:00:00 2001 From: pieterlukasse Date: Mon, 20 Nov 2023 18:06:29 +0100 Subject: [PATCH 13/31] feat: ensure /user/me endpoint also triggers the UPDATE_TOKEN filter --- .../org/ohdsi/webapi/shiro/management/AtlasRegularSecurity.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/ohdsi/webapi/shiro/management/AtlasRegularSecurity.java b/src/main/java/org/ohdsi/webapi/shiro/management/AtlasRegularSecurity.java index ac31d31815..e8e313f774 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/management/AtlasRegularSecurity.java +++ b/src/main/java/org/ohdsi/webapi/shiro/management/AtlasRegularSecurity.java @@ -419,6 +419,7 @@ protected FilterChainBuilder getFilterChainBuilder() { .setAuthzFilter(AUTHZ) // login/logout .addRestPath("/user/refresh", JWT_AUTHC, UPDATE_TOKEN, SEND_TOKEN_IN_HEADER) + .addRestPath("/user/me", JWT_AUTHC, UPDATE_TOKEN, SEND_TOKEN_IN_HEADER) .addProtectedRestPath("/user/runas", RUN_AS, UPDATE_TOKEN, SEND_TOKEN_IN_HEADER) .addRestPath("/user/logout", LOGOUT); From 534f6bc4c0591a589164fbc937d94d4d60ae83c1 Mon Sep 17 00:00:00 2001 From: pieterlukasse Date: Mon, 20 Nov 2023 18:09:03 +0100 Subject: [PATCH 14/31] feat: ensure the teamproject is stored per user ...and allow reading current teamproject from cache in case of a request to /user/refresh endpoint --- .../model/EntityPermissionSchema.java | 2 +- .../ohdsi/webapi/shiro/PermissionManager.java | 25 +++++++++++-------- .../filters/UpdateAccessTokenFilter.java | 16 +++++++++--- .../management/AtlasRegularSecurity.java | 1 - 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/ohdsi/webapi/security/model/EntityPermissionSchema.java b/src/main/java/org/ohdsi/webapi/security/model/EntityPermissionSchema.java index 1d63234a98..2469143421 100644 --- a/src/main/java/org/ohdsi/webapi/security/model/EntityPermissionSchema.java +++ b/src/main/java/org/ohdsi/webapi/security/model/EntityPermissionSchema.java @@ -80,7 +80,7 @@ protected void addPermissionsToCurrentUserFromTemplate(CommonEntity commonEntity protected void addPermissionsToCurrentTeamProjectFromTemplate(CommonEntity commonEntity, Map template) { - RoleEntity role = permissionManager.getCurrentTeamProjectRole(); + RoleEntity role = permissionManager.getCurrentTeamProjectRoleForCurrentUser(); if (role == null) { throw new RuntimeException("Expected a teamproject role but found none!"); } diff --git a/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java b/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java index cb41dc8ad0..7513e5faf6 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java +++ b/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java @@ -27,11 +27,14 @@ import org.springframework.transaction.annotation.Transactional; import java.security.Principal; +import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import javax.management.relation.Role; + /** * * @author gennadiy.anisimov @@ -61,7 +64,7 @@ public class PermissionManager { private ThreadLocal> authorizationInfoCache = ThreadLocal.withInitial(ConcurrentHashMap::new); - private String currentTeamProjectRole = null; + private Map teamProjectRoles = new HashMap<>(); public RoleEntity addRole(String roleName, boolean isSystem) { logger.debug("Called addRole: {}", roleName); @@ -446,10 +449,12 @@ public UserEntity getUserById(Long userId) { } private UserEntity getUserByLogin(final String login) { + logger.debug("Looking for user login={}...", login); final UserEntity user = this.userRepository.findByLogin(login); - if (user == null) + if (user == null) { + logger.error("User does NOT exist for login={}...", login); throw new RuntimeException("User doesn't exist"); - + } return user; } @@ -564,15 +569,13 @@ public boolean roleExists(String roleName) { return this.roleRepository.existsByName(roleName); } - public void setCurrentTeamProjectRole(String teamProjectRole) { - this.currentTeamProjectRole = teamProjectRole; + public void setCurrentTeamProjectRoleForCurrentUser(String teamProjectRole, String login) { + logger.debug("Current user in setCurrentTeamProjectRoleForCurrentUser() {}", login); + this.teamProjectRoles.put(login, teamProjectRole); } - public RoleEntity getCurrentTeamProjectRole() { - if (this.currentTeamProjectRole != null) { - return this.getRoleByName(this.currentTeamProjectRole, false); - } else { - return null; - } + public RoleEntity getCurrentTeamProjectRoleForCurrentUser() { + logger.debug("Current user in getCurrentTeamProjectRoleForCurrentUser(): {}", getCurrentUser().getLogin()); + return this.getRoleByName(this.teamProjectRoles.get(getCurrentUser().getLogin()), false); } } diff --git a/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java b/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java index 621f8f17f5..6af2a6cc73 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java +++ b/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java @@ -41,6 +41,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.client.RestTemplate; +import org.ohdsi.webapi.shiro.Entities.RoleEntity; /** * @@ -147,6 +148,7 @@ protected boolean preHandle(ServletRequest request, ServletResponse response) th try { logger.debug("AUTHORIZATION_MODE in UpdateAccessTokenFilter == '{}'", this.authorizationMode); boolean resetRoles = false; + String teamProjectRole = null; Set newUserRoles = new HashSet(); Set newDefaultRoles = new HashSet(defaultRoles); if (this.authorizationMode.equals("teamproject")) { @@ -154,7 +156,7 @@ protected boolean preHandle(ServletRequest request, ServletResponse response) th // set to only the one requested/found in the request parameters (following lines below): resetRoles = true; // check if a teamproject parameter is found in the request: - String teamProjectRole = extractTeamProjectFromRequestParameters(request); + teamProjectRole = extractTeamProjectFromRequestParameters(request); // if found, add teamproject as a role in the newUserRoles list: if (teamProjectRole != null) { // double check if this role has really been granted to the user: @@ -165,11 +167,11 @@ protected boolean preHandle(ServletRequest request, ServletResponse response) th } newUserRoles.add(teamProjectRole); newDefaultRoles.add("Atlas users"); // TODO - review this part...maybe users can get this role when onboarding (system role?) - authorizer.setCurrentTeamProjectRole(teamProjectRole); } } this.authorizer.registerUser(login, name, newDefaultRoles, newUserRoles, resetRoles); - + authorizer.setCurrentTeamProjectRoleForCurrentUser(teamProjectRole, login); + } catch (Exception e) { WebUtils.toHttp(response).setHeader("x-auth-error", e.getMessage()); throw new Exception(e); @@ -298,6 +300,14 @@ private String extractTeamProjectFromRequestParameters(ServletRequest request) { return teamProject; } + logger.debug("Fallback3: Looking for teamproject in cache in case of /user/refresh or /user/me request: {}....", url); + if (url.endsWith("/user/refresh") || url.endsWith("/user/me")) { + RoleEntity teamProjectRole = authorizer.getCurrentTeamProjectRoleForCurrentUser(); + String teamProject = teamProjectRole.getName(); + logger.debug("Found teamproject: {}", teamProject); + return teamProject; + } + logger.debug("Found NO teamproject."); return null; } diff --git a/src/main/java/org/ohdsi/webapi/shiro/management/AtlasRegularSecurity.java b/src/main/java/org/ohdsi/webapi/shiro/management/AtlasRegularSecurity.java index e8e313f774..ac31d31815 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/management/AtlasRegularSecurity.java +++ b/src/main/java/org/ohdsi/webapi/shiro/management/AtlasRegularSecurity.java @@ -419,7 +419,6 @@ protected FilterChainBuilder getFilterChainBuilder() { .setAuthzFilter(AUTHZ) // login/logout .addRestPath("/user/refresh", JWT_AUTHC, UPDATE_TOKEN, SEND_TOKEN_IN_HEADER) - .addRestPath("/user/me", JWT_AUTHC, UPDATE_TOKEN, SEND_TOKEN_IN_HEADER) .addProtectedRestPath("/user/runas", RUN_AS, UPDATE_TOKEN, SEND_TOKEN_IN_HEADER) .addRestPath("/user/logout", LOGOUT); From 1f69b44ce6a8844ea241101a3d19852880633286 Mon Sep 17 00:00:00 2001 From: pieterlukasse Date: Wed, 22 Nov 2023 22:04:14 +0100 Subject: [PATCH 15/31] wip: moved logic to new filter class TeamProjectBasedAuthorizingFilter --- .../org/ohdsi/webapi/service/UserService.java | 2 +- .../ohdsi/webapi/shiro/PermissionManager.java | 29 ++- .../TeamProjectBasedAuthorizingFilter.java | 197 ++++++++++++++++++ .../filters/UpdateAccessTokenFilter.java | 163 +-------------- .../management/AtlasRegularSecurity.java | 5 +- .../shiro/management/AtlasSecurity.java | 9 + .../shiro/management/FilterChainBuilder.java | 11 +- .../shiro/management/FilterTemplates.java | 1 + 8 files changed, 250 insertions(+), 167 deletions(-) create mode 100644 src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java diff --git a/src/main/java/org/ohdsi/webapi/service/UserService.java b/src/main/java/org/ohdsi/webapi/service/UserService.java index fc19c071a8..0e28b4f905 100644 --- a/src/main/java/org/ohdsi/webapi/service/UserService.java +++ b/src/main/java/org/ohdsi/webapi/service/UserService.java @@ -161,7 +161,7 @@ public Role createRole(Role role) throws Exception { public Role updateRole(@PathParam("roleId") Long id, Role role) throws Exception { RoleEntity roleEntity = this.authorizer.getRole(id); if (roleEntity == null) { - throw new Exception("Role doesn't exist"); + throw new Exception("Role doesn't exist: " + id); } roleEntity.setName(role.role); roleEntity = this.authorizer.updateRole(roleEntity); diff --git a/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java b/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java index 7513e5faf6..0c1b613441 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java +++ b/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java @@ -2,6 +2,9 @@ import com.odysseusinc.logging.event.AddUserEvent; import com.odysseusinc.logging.event.DeleteRoleEvent; + +import io.buji.pac4j.subject.Pac4jPrincipal; + import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.subject.Subject; @@ -175,6 +178,11 @@ public void clearAuthorizationInfoCache() { this.authorizationInfoCache.set(new ConcurrentHashMap<>()); } + @Transactional + public void updateUser(String login, Set defaultRoles, Set newUserRoles, + boolean resetRoles) { + registerUser(login, null, UserOrigin.SYSTEM, defaultRoles, newUserRoles, resetRoles); + } @Transactional public void registerUser(String login, String name, Set defaultRoles, Set newUserRoles, @@ -461,7 +469,7 @@ private UserEntity getUserByLogin(final String login) { private RoleEntity getRoleByName(String roleName, Boolean isSystemRole) { final RoleEntity roleEntity = this.roleRepository.findByNameAndSystemRole(roleName, isSystemRole); if (roleEntity == null) - throw new RuntimeException("Role doesn't exist"); + throw new RuntimeException("Role doesn't exist:" + roleName); return roleEntity; } @@ -473,7 +481,7 @@ public RoleEntity getSystemRoleByName(String roleName) { private RoleEntity getRoleById(Long roleId) { final RoleEntity roleEntity = this.roleRepository.findById(roleId); if (roleEntity == null) - throw new RuntimeException("Role doesn't exist"); + throw new RuntimeException("Role doesn't exist:" + roleId); return roleEntity; } @@ -525,11 +533,20 @@ public String getSubjectName() { Subject subject = SecurityUtils.getSubject(); Object principalObject = subject.getPrincipals().getPrimaryPrincipal(); - if (principalObject instanceof String) + if (principalObject instanceof String) { + logger.debug("principal IS STRING: " + principalObject); return (String)principalObject; + } + + if (principalObject instanceof Pac4jPrincipal) { + String login = ((Pac4jPrincipal)principalObject).getProfile().getEmail(); + logger.debug("principal IS Pac4jPrincipal: " + login); + return login; + } if (principalObject instanceof Principal) { Principal principal = (Principal)principalObject; + logger.debug("principal IS Principal: " + principal.getName()); return principal.getName(); } @@ -576,6 +593,10 @@ public void setCurrentTeamProjectRoleForCurrentUser(String teamProjectRole, Stri public RoleEntity getCurrentTeamProjectRoleForCurrentUser() { logger.debug("Current user in getCurrentTeamProjectRoleForCurrentUser(): {}", getCurrentUser().getLogin()); - return this.getRoleByName(this.teamProjectRoles.get(getCurrentUser().getLogin()), false); + if (this.teamProjectRoles.get(getCurrentUser().getLogin()) == null) { + return null; + } else { + return this.getRoleByName(this.teamProjectRoles.get(getCurrentUser().getLogin()), false); + } } } diff --git a/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java b/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java new file mode 100644 index 0000000000..1b5666a7a6 --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java @@ -0,0 +1,197 @@ +package org.ohdsi.webapi.shiro.filters; + +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Set; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.web.servlet.AdviceFilter; +import org.apache.shiro.web.util.WebUtils; +import org.json.JSONArray; +import org.json.JSONObject; +import org.ohdsi.webapi.shiro.PermissionManager; +import org.ohdsi.webapi.shiro.Entities.RoleEntity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.client.RestTemplate; + +/** + * + * @author Pieter Lukasse + */ +public class TeamProjectBasedAuthorizingFilter extends AdviceFilter { + + private final Logger logger = LoggerFactory.getLogger(TeamProjectBasedAuthorizingFilter.class); + + private final PermissionManager authorizer; + private final Set defaultRoles; + private final String authorizationMode; + private final String authorizationUrl; + + public TeamProjectBasedAuthorizingFilter( + PermissionManager authorizer, + Set defaultRoles, + String authorizationMode, + String authorizationUrl) { + this.authorizer = authorizer; + this.defaultRoles = defaultRoles; + this.authorizationMode = authorizationMode; + this.authorizationUrl = authorizationUrl; + logger.debug("AUTHORIZATION_MODE in TeamProjectBasedAuthorizingFilter constructor == '{}'", this.authorizationMode); + logger.debug("AUTHORIZATION_URL in TeamProjectBasedAuthorizingFilter constructor == '{}'", this.authorizationUrl); + } + + @Override + protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception { + + try { + logger.debug("preHandle in TeamProjectBasedAuthorizingFilter == '{}'", this.authorizationMode); + boolean resetRoles = false; + String teamProjectRole = null; + Set newUserRoles = new HashSet(); + Set newDefaultRoles = new HashSet(defaultRoles); + if (this.authorizationMode.equals("teamproject") && SecurityUtils.getSubject().isAuthenticated()) { + // in case of "teamproject" mode, we want all roles to be reset always, and + // set to only the one requested/found in the request parameters (following lines below): + resetRoles = true; + // check if a teamproject parameter is found in the request: + teamProjectRole = extractTeamProjectFromRequestParameters(request); + // if found, and teamproject is different from current one, add teamproject as a role in the newUserRoles list: + if (teamProjectRole != null && + (this.authorizer.getCurrentTeamProjectRoleForCurrentUser() == null || !teamProjectRole.equals(this.authorizer.getCurrentTeamProjectRoleForCurrentUser().getName()))) { + // double check if this role has really been granted to the user: + String login = this.authorizer.getCurrentUser().getLogin(); + if (checkGen3Authorization(teamProjectRole, login) == false) { + WebUtils.toHttp(response).sendError(HttpServletResponse.SC_FORBIDDEN, + "User is not authorized to access this team project's data"); + return false; + } + newUserRoles.add(teamProjectRole); + newDefaultRoles.add("Atlas users"); // TODO - review this part...maybe users can get this role when onboarding (system role?) + this.authorizer.updateUser(login, newDefaultRoles, newUserRoles, resetRoles); + this.authorizer.setCurrentTeamProjectRoleForCurrentUser(teamProjectRole, login); + } + } + + } catch (Exception e) { + WebUtils.toHttp(response).setHeader("x-auth-error", e.getMessage()); + throw new Exception(e); + } + + return true; + } + + private boolean checkGen3Authorization(String teamProjectRole, String login) throws Exception { + logger.debug("Checking Gen3 Authorization for 'team project'={} and user={} using service={}", teamProjectRole, login, this.authorizationUrl); + RestTemplate restTemplate = new RestTemplate(); + String arboristAuthorizationURL = this.authorizationUrl; + String requestBody = String.format("{\"username\": \"%s\"}", login); + String jsonResponseString = restTemplate.postForObject(arboristAuthorizationURL, requestBody, String.class); + + JSONObject jsonObject = new JSONObject(jsonResponseString); + + if (!jsonObject.keySet().contains(teamProjectRole)) { + logger.warn("User is not authorized to access this team project's data"); + return false; + } else { + JSONArray teamProjectAuthorizations = jsonObject.getJSONArray(teamProjectRole); + logger.debug("Found authorizations={}", teamProjectAuthorizations); + // We expect only one authorization rule per teamproject: + if (teamProjectAuthorizations.length() != 1) { + logger.error("Only one authorization rule expected for 'teamproject'={}, found={}", teamProjectRole, + teamProjectAuthorizations.length()); + return false; + } + JSONObject teamProjectAuthorization = teamProjectAuthorizations.getJSONObject(0); + + // check if the authorization contains the right "service" and "method" values: + String expectedMethod = "access"; + String expectedService = "atlas-argo-wrapper-and-cohort-middleware"; // TODO - make the service name configurable? + String service = teamProjectAuthorization.getString("service"); + String method = teamProjectAuthorization.getString("method"); + logger.debug("Parsed service={} and method={}", service, method); + if (!method.equalsIgnoreCase(expectedMethod)) { + logger.error("The 'teamproject' authorization method should be '{}', but found '{}'", expectedMethod, method); + return false; + } + logger.debug("Parsed method is as expected"); + if (!service.equalsIgnoreCase(expectedService)) { + logger.error("The 'teamproject' authorization service should be '{}', but found '{}'", expectedService, service); + return false; + } + logger.debug("Parsed service is as expected"); + return true; + } + } + + private String extractTeamProjectFromRequestParameters(ServletRequest request) { + // Get the url + HttpServletRequest httpRequest = (HttpServletRequest) request; + String url = httpRequest.getRequestURL().toString(); + + // try to find it in the redirectUrl parameter: + logger.debug("Looking for redirectUrl in request: {}....", url); + String[] redirectUrlParams = getParameterValues(request, "redirectUrl"); + if (redirectUrlParams != null) { + logger.debug("Parameter redirectUrl found. Checking if it contains teamproject...."); + // teamProject will be in first one in this case...as only parameter: + String firstParameter = redirectUrlParams[0].toLowerCase(); + if (firstParameter.contains("teamproject=")) { + String teamProject = firstParameter.split("teamproject=")[1]; + logger.debug("Found teamproject: {}", teamProject); + return teamProject; + } + } + + // try to find "teamproject" param in url itself (there will be no redirectUrl if user session is still valid): + logger.debug("Fallback1: Looking for teamproject in request: {}....", url); + String[] teamProjectParams = getParameterValues(request, "teamproject"); + if (teamProjectParams != null) { + logger.debug("Parameter teamproject found. Parsing...."); + String teamProject = teamProjectParams[0].toLowerCase(); + logger.debug("Found teamproject: {}", teamProject); + return teamProject; + } + + logger.debug("Fallback2: Looking for teamproject in Action-Location header of request: {}....", url); + String actionLocationUrl = httpRequest.getHeader("Action-Location"); + if (actionLocationUrl != null && actionLocationUrl.contains("teamproject=")) { + String teamProject = actionLocationUrl.split("teamproject=")[1]; + logger.debug("Found teamproject: {}", teamProject); + return teamProject; + } + + // logger.debug("Fallback3: Looking for teamproject in cache in case of /user/refresh or /user/me request: {}....", url); + // if (url.endsWith("/user/refresh") || url.endsWith("/user/me")) { + // RoleEntity teamProjectRole = authorizer.getCurrentTeamProjectRoleForCurrentUser(); + // String teamProject = teamProjectRole.getName(); + // logger.debug("Found teamproject: {}", teamProject); + // return teamProject; + // } + + logger.debug("Found NO teamproject."); + return null; + } + + private String[] getParameterValues(ServletRequest request, String parameterName) { + // Get the parameters + logger.debug("Looking for parameter with name: {} ...", parameterName); + Enumeration paramNames = request.getParameterNames(); + while(paramNames.hasMoreElements()) { + String paramName = paramNames.nextElement(); + logger.debug("Parameter name: {}", paramName); + if (paramName.equals(parameterName)) { + String[] paramValues = request.getParameterValues(paramName); + return paramValues; + } + } + logger.debug("Found NO parameter with name: {}", parameterName); + return null; + } + +} diff --git a/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java b/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java index 6af2a6cc73..2ce898befb 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java +++ b/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java @@ -5,23 +5,16 @@ import static org.ohdsi.webapi.shiro.management.AtlasSecurity.TOKEN_ATTRIBUTE; import io.buji.pac4j.subject.Pac4jPrincipal; - -import org.json.JSONArray; -import org.json.JSONObject; - import java.net.URI; import java.net.URISyntaxException; import java.security.Principal; import java.util.Calendar; import java.util.Collection; import java.util.Date; -import java.util.Enumeration; -import java.util.HashSet; import java.util.Objects; import java.util.Set; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import javax.ws.rs.core.UriBuilder; @@ -38,39 +31,27 @@ import org.ohdsi.webapi.shiro.TokenManager; import org.ohdsi.webapi.util.UserUtils; import org.pac4j.core.profile.CommonProfile; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.web.client.RestTemplate; -import org.ohdsi.webapi.shiro.Entities.RoleEntity; /** * * @author gennadiy.anisimov */ public class UpdateAccessTokenFilter extends AdviceFilter { - private final Logger logger = LoggerFactory.getLogger(UpdateAccessTokenFilter.class); - + private final PermissionManager authorizer; private final int tokenExpirationIntervalInSeconds; private final Set defaultRoles; private final String onFailRedirectUrl; - private final String authorizationMode; - private final String authorizationUrl; public UpdateAccessTokenFilter( PermissionManager authorizer, Set defaultRoles, int tokenExpirationIntervalInSeconds, - String onFailRedirectUrl, - String authorizationMode, - String authorizationUrl) { + String onFailRedirectUrl) { this.authorizer = authorizer; this.tokenExpirationIntervalInSeconds = tokenExpirationIntervalInSeconds; this.defaultRoles = defaultRoles; this.onFailRedirectUrl = onFailRedirectUrl; - this.authorizationMode = authorizationMode; - this.authorizationUrl = authorizationUrl; - logger.debug("AUTHORIZATION_MODE in UpdateAccessTokenFilter constructor == '{}'", this.authorizationMode); } @Override @@ -140,38 +121,12 @@ protected boolean preHandle(ServletRequest request, ServletResponse response) th session.stop(); } - logger.debug("Check JWT: '{}'", jwt); - if (jwt == null) { // dead check...jwt is always null... + if (jwt == null) { if (name == null) { name = login; } try { - logger.debug("AUTHORIZATION_MODE in UpdateAccessTokenFilter == '{}'", this.authorizationMode); - boolean resetRoles = false; - String teamProjectRole = null; - Set newUserRoles = new HashSet(); - Set newDefaultRoles = new HashSet(defaultRoles); - if (this.authorizationMode.equals("teamproject")) { - // in case of "teamproject" mode, we want all roles to be reset always, and - // set to only the one requested/found in the request parameters (following lines below): - resetRoles = true; - // check if a teamproject parameter is found in the request: - teamProjectRole = extractTeamProjectFromRequestParameters(request); - // if found, add teamproject as a role in the newUserRoles list: - if (teamProjectRole != null) { - // double check if this role has really been granted to the user: - if (checkGen3Authorization(teamProjectRole, login) == false) { - WebUtils.toHttp(response).sendError(HttpServletResponse.SC_FORBIDDEN, - "User is not authorized to access this team project's data"); - return false; - } - newUserRoles.add(teamProjectRole); - newDefaultRoles.add("Atlas users"); // TODO - review this part...maybe users can get this role when onboarding (system role?) - } - } - this.authorizer.registerUser(login, name, newDefaultRoles, newUserRoles, resetRoles); - authorizer.setCurrentTeamProjectRoleForCurrentUser(teamProjectRole, login); - + this.authorizer.registerUser(login, name, defaultRoles); } catch (Exception e) { WebUtils.toHttp(response).setHeader("x-auth-error", e.getMessage()); throw new Exception(e); @@ -195,49 +150,6 @@ protected boolean preHandle(ServletRequest request, ServletResponse response) th return true; } - private boolean checkGen3Authorization(String teamProjectRole, String login) throws Exception { - logger.debug("Checking Gen3 Authorization for 'team project'={} and user={} using service={}", teamProjectRole, login, this.authorizationUrl); - RestTemplate restTemplate = new RestTemplate(); - String arboristAuthorizationURL = this.authorizationUrl; - String requestBody = String.format("{\"username\": \"%s\"}", login); - String jsonResponseString = restTemplate.postForObject(arboristAuthorizationURL, requestBody, String.class); - - JSONObject jsonObject = new JSONObject(jsonResponseString); - - if (!jsonObject.keySet().contains(teamProjectRole)) { - logger.warn("User is not authorized to access this team project's data"); - return false; - } else { - JSONArray teamProjectAuthorizations = jsonObject.getJSONArray(teamProjectRole); - logger.debug("Found authorizations={}", teamProjectAuthorizations); - // We expect only one authorization rule per teamproject: - if (teamProjectAuthorizations.length() != 1) { - logger.error("Only one authorization rule expected for 'teamproject'={}, found={}", teamProjectRole, - teamProjectAuthorizations.length()); - return false; - } - JSONObject teamProjectAuthorization = teamProjectAuthorizations.getJSONObject(0); - - // check if the authorization contains the right "service" and "method" values: - String expectedMethod = "access"; - String expectedService = "atlas-argo-wrapper-and-cohort-middleware"; // TODO - make the service name configurable? - String service = teamProjectAuthorization.getString("service"); - String method = teamProjectAuthorization.getString("method"); - logger.debug("Parsed service={} and method={}", service, method); - if (!method.equalsIgnoreCase(expectedMethod)) { - logger.error("The 'teamproject' authorization method should be '{}', but found '{}'", expectedMethod, method); - return false; - } - logger.debug("Parsed method is as expected"); - if (!service.equalsIgnoreCase(expectedService)) { - logger.error("The 'teamproject' authorization service should be '{}', but found '{}'", expectedService, service); - return false; - } - logger.debug("Parsed service is as expected"); - return true; - } - } - private URI getOAuthFailUri() throws URISyntaxException { return getFailUri("oauth_error_email"); } @@ -262,69 +174,4 @@ private Date getExpirationDate(final int expirationIntervalInSeconds) { calendar.add(Calendar.SECOND, expirationIntervalInSeconds); return calendar.getTime(); } - - private String extractTeamProjectFromRequestParameters(ServletRequest request) { - // Get the url - HttpServletRequest httpRequest = (HttpServletRequest) request; - String url = httpRequest.getRequestURL().toString(); - - // try to find it in the redirectUrl parameter: - logger.debug("Looking for redirectUrl in request: {}....", url); - String[] redirectUrlParams = getParameterValues(request, "redirectUrl"); - if (redirectUrlParams != null) { - logger.debug("Parameter redirectUrl found. Checking if it contains teamproject...."); - // teamProject will be in first one in this case...as only parameter: - String firstParameter = redirectUrlParams[0].toLowerCase(); - if (firstParameter.contains("teamproject=")) { - String teamProject = firstParameter.split("teamproject=")[1]; - logger.debug("Found teamproject: {}", teamProject); - return teamProject; - } - } - - // try to find "teamproject" param in url itself (there will be no redirectUrl if user session is still valid): - logger.debug("Fallback1: Looking for teamproject in request: {}....", url); - String[] teamProjectParams = getParameterValues(request, "teamproject"); - if (teamProjectParams != null) { - logger.debug("Parameter teamproject found. Parsing...."); - String teamProject = teamProjectParams[0].toLowerCase(); - logger.debug("Found teamproject: {}", teamProject); - return teamProject; - } - - logger.debug("Fallback2: Looking for teamproject in Action-Location header of request: {}....", url); - String actionLocationUrl = httpRequest.getHeader("Action-Location"); - if (actionLocationUrl != null && actionLocationUrl.contains("teamproject=")) { - String teamProject = actionLocationUrl.split("teamproject=")[1]; - logger.debug("Found teamproject: {}", teamProject); - return teamProject; - } - - logger.debug("Fallback3: Looking for teamproject in cache in case of /user/refresh or /user/me request: {}....", url); - if (url.endsWith("/user/refresh") || url.endsWith("/user/me")) { - RoleEntity teamProjectRole = authorizer.getCurrentTeamProjectRoleForCurrentUser(); - String teamProject = teamProjectRole.getName(); - logger.debug("Found teamproject: {}", teamProject); - return teamProject; - } - - logger.debug("Found NO teamproject."); - return null; - } - - private String[] getParameterValues(ServletRequest request, String parameterName) { - // Get the parameters - logger.debug("Looking for parameter with name: {} ...", parameterName); - Enumeration paramNames = request.getParameterNames(); - while(paramNames.hasMoreElements()) { - String paramName = paramNames.nextElement(); - logger.debug("Parameter name: {}", paramName); - if (paramName.equals(parameterName)) { - String[] paramValues = request.getParameterValues(paramName); - return paramValues; - } - } - logger.debug("Found NO parameter with name: {}", parameterName); - return null; - } -} +} \ No newline at end of file diff --git a/src/main/java/org/ohdsi/webapi/shiro/management/AtlasRegularSecurity.java b/src/main/java/org/ohdsi/webapi/shiro/management/AtlasRegularSecurity.java index ac31d31815..01631d01f8 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/management/AtlasRegularSecurity.java +++ b/src/main/java/org/ohdsi/webapi/shiro/management/AtlasRegularSecurity.java @@ -280,7 +280,7 @@ public Map getFilters() { logger.debug("Initializing UpdateAccessTokenFilter with AUTHORIZATION_MODE === '{}'", this.authorizationMode); logger.debug("Initializing UpdateAccessTokenFilter with AUTHORIZATION_URL === '{}'", this.authorizationUrl); filters.put(UPDATE_TOKEN, new UpdateAccessTokenFilter(this.authorizer, this.defaultRoles, this.tokenExpirationIntervalInSeconds, - this.redirectUrl, this.authorizationMode, this.authorizationUrl)); + this.redirectUrl)); filters.put(ACCESS_AUTHC, new GoogleAccessTokenFilter(restTemplate, permissionManager, Collections.emptySet())); filters.put(JWT_AUTHC, new AtlasJwtAuthFilter()); @@ -417,6 +417,7 @@ protected FilterChainBuilder getFilterChainBuilder() { .setRestFilters(SSL, NO_SESSION_CREATION, CORS, NO_CACHE) .setAuthcFilter(authcFilters.toArray(new FilterTemplates[0])) .setAuthzFilter(AUTHZ) + .setTeamProjectAuthzFilter(TEAM_PROJECT_AUTHZ) // login/logout .addRestPath("/user/refresh", JWT_AUTHC, UPDATE_TOKEN, SEND_TOKEN_IN_HEADER) .addProtectedRestPath("/user/runas", RUN_AS, UPDATE_TOKEN, SEND_TOKEN_IN_HEADER) @@ -433,7 +434,7 @@ protected FilterChainBuilder getFilterChainBuilder() { if (this.openidAuthEnabled) { filterChainBuilder - .addRestPath("/user/login/openid", FORCE_SESSION_CREATION, OIDC_AUTH, UPDATE_TOKEN, SEND_TOKEN_IN_URL) + .addRestPath("/user/login/openid", FORCE_SESSION_CREATION, OIDC_AUTH, UPDATE_TOKEN, TEAM_PROJECT_AUTHZ, SEND_TOKEN_IN_URL) .addRestPath("/user/login/openidDirect", FORCE_SESSION_CREATION, OIDC_DIRECT_AUTH, UPDATE_TOKEN, SEND_TOKEN_IN_HEADER); } diff --git a/src/main/java/org/ohdsi/webapi/shiro/management/AtlasSecurity.java b/src/main/java/org/ohdsi/webapi/shiro/management/AtlasSecurity.java index 47e086a572..32320ca822 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/management/AtlasSecurity.java +++ b/src/main/java/org/ohdsi/webapi/shiro/management/AtlasSecurity.java @@ -21,6 +21,7 @@ import org.ohdsi.webapi.shiro.filters.CorsFilter; import org.ohdsi.webapi.shiro.filters.ForceSessionCreationFilter; import org.ohdsi.webapi.shiro.filters.ResponseNoCacheFilter; +import org.ohdsi.webapi.shiro.filters.TeamProjectBasedAuthorizingFilter; import org.ohdsi.webapi.shiro.filters.UrlBasedAuthorizingFilter; import org.ohdsi.webapi.source.SourceRepository; import org.slf4j.Logger; @@ -37,6 +38,7 @@ import static org.ohdsi.webapi.shiro.management.FilterTemplates.NO_CACHE; import static org.ohdsi.webapi.shiro.management.FilterTemplates.NO_SESSION_CREATION; import static org.ohdsi.webapi.shiro.management.FilterTemplates.SSL; +import static org.ohdsi.webapi.shiro.management.FilterTemplates.TEAM_PROJECT_AUTHZ; /** * @@ -68,6 +70,12 @@ public abstract class AtlasSecurity extends Security { @Value("${security.ssl.enabled}") private boolean sslEnabled; + @Value("${security.ohdsi.custom.authorization.mode}") + private String authorizationMode; + + @Value("${security.ohdsi.custom.authorization.url}") + private String authorizationUrl; + private final EntityPermissionSchemaResolver permissionSchemaResolver; protected final Set defaultRoles = new LinkedHashSet<>(); @@ -130,6 +138,7 @@ private void fillFilters() { filters.put(NO_SESSION_CREATION, new NoSessionCreationFilter()); filters.put(FORCE_SESSION_CREATION, new ForceSessionCreationFilter()); filters.put(AUTHZ, new UrlBasedAuthorizingFilter()); + filters.put(TEAM_PROJECT_AUTHZ, new TeamProjectBasedAuthorizingFilter(this.authorizer, this.defaultRoles, this.authorizationMode, this.authorizationUrl)); filters.put(CORS, new CorsFilter()); filters.put(SSL, this.getSslFilter()); filters.put(NO_CACHE, this.getNoCacheFilter()); diff --git a/src/main/java/org/ohdsi/webapi/shiro/management/FilterChainBuilder.java b/src/main/java/org/ohdsi/webapi/shiro/management/FilterChainBuilder.java index 6bb2c96a19..30ae556f0d 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/management/FilterChainBuilder.java +++ b/src/main/java/org/ohdsi/webapi/shiro/management/FilterChainBuilder.java @@ -11,6 +11,7 @@ public class FilterChainBuilder { private String restFilters; private String authcFilter; private String authzFilter; + private String teamProjectAuthzFilter; private String filtersBeforeOAuth; private String filtersAfterOAuth; @@ -39,6 +40,12 @@ public FilterChainBuilder setAuthzFilter(FilterTemplates... authzFilters) { return this; } + public FilterChainBuilder setTeamProjectAuthzFilter(FilterTemplates... authzFilters) { + this.teamProjectAuthzFilter = convertArrayToString(authzFilters); + return this; + } + + public FilterChainBuilder addRestPath(String path, String filters) { return this.addPath(path, this.restFilters + ", " + filters); } @@ -56,13 +63,13 @@ public FilterChainBuilder addOAuthPath(String path, FilterTemplates... oauthFilt } public FilterChainBuilder addProtectedRestPath(String path) { - return this.addRestPath(path, this.authcFilter + ", " + this.authzFilter); + return this.addRestPath(path, this.authcFilter + ", " + this.authzFilter + ", " + this.teamProjectAuthzFilter); } public FilterChainBuilder addProtectedRestPath(String path, FilterTemplates... filters) { String filtersStr = convertArrayToString(filters); - return this.addRestPath(path, authcFilter + ", " + authzFilter + ", " + filtersStr); + return this.addRestPath(path, authcFilter + ", " + authzFilter + ", " + this.teamProjectAuthzFilter + ", " + filtersStr); } public FilterChainBuilder addPath(String path, FilterTemplates... filters) { diff --git a/src/main/java/org/ohdsi/webapi/shiro/management/FilterTemplates.java b/src/main/java/org/ohdsi/webapi/shiro/management/FilterTemplates.java index cc3e578fde..88903ae78b 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/management/FilterTemplates.java +++ b/src/main/java/org/ohdsi/webapi/shiro/management/FilterTemplates.java @@ -20,6 +20,7 @@ public enum FilterTemplates { NO_SESSION_CREATION("noSessionCreation"), FORCE_SESSION_CREATION("forceSessionCreation"), AUTHZ("authz"), + TEAM_PROJECT_AUTHZ("teamProjectAuthz"), CORS("cors"), SSL("ssl"), NO_CACHE("noCache"), From 6a0ae7b02492a2938c2551da26b00553498bd972 Mon Sep 17 00:00:00 2001 From: pieterlukasse Date: Wed, 22 Nov 2023 22:10:05 +0100 Subject: [PATCH 16/31] wip: remove commented-out code --- .../TeamProjectBasedAuthorizingFilter.java | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java b/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java index 1b5666a7a6..3c1f196b25 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java +++ b/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java @@ -15,7 +15,6 @@ import org.json.JSONArray; import org.json.JSONObject; import org.ohdsi.webapi.shiro.PermissionManager; -import org.ohdsi.webapi.shiro.Entities.RoleEntity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.client.RestTemplate; @@ -25,7 +24,7 @@ * @author Pieter Lukasse */ public class TeamProjectBasedAuthorizingFilter extends AdviceFilter { - + private final Logger logger = LoggerFactory.getLogger(TeamProjectBasedAuthorizingFilter.class); private final PermissionManager authorizer; @@ -48,7 +47,7 @@ public TeamProjectBasedAuthorizingFilter( @Override protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception { - + try { logger.debug("preHandle in TeamProjectBasedAuthorizingFilter == '{}'", this.authorizationMode); boolean resetRoles = false; @@ -82,7 +81,7 @@ protected boolean preHandle(ServletRequest request, ServletResponse response) th WebUtils.toHttp(response).setHeader("x-auth-error", e.getMessage()); throw new Exception(e); } - + return true; } @@ -166,14 +165,6 @@ private String extractTeamProjectFromRequestParameters(ServletRequest request) { return teamProject; } - // logger.debug("Fallback3: Looking for teamproject in cache in case of /user/refresh or /user/me request: {}....", url); - // if (url.endsWith("/user/refresh") || url.endsWith("/user/me")) { - // RoleEntity teamProjectRole = authorizer.getCurrentTeamProjectRoleForCurrentUser(); - // String teamProject = teamProjectRole.getName(); - // logger.debug("Found teamproject: {}", teamProject); - // return teamProject; - // } - logger.debug("Found NO teamproject."); return null; } From 584d5d8ee7e80b046de692f5ea566a3af604d6bc Mon Sep 17 00:00:00 2001 From: pieterlukasse Date: Fri, 5 Jan 2024 21:04:59 +0100 Subject: [PATCH 17/31] fix: working version with roles 15 and 1000 added ... these roles turned out to be essential for getting the error resolved (an error stating "you are not authorized to view the concept set expression") --- .../shiro/filters/TeamProjectBasedAuthorizingFilter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java b/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java index 3c1f196b25..1eaa948174 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java +++ b/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java @@ -71,7 +71,8 @@ protected boolean preHandle(ServletRequest request, ServletResponse response) th return false; } newUserRoles.add(teamProjectRole); - newDefaultRoles.add("Atlas users"); // TODO - review this part...maybe users can get this role when onboarding (system role?) + newDefaultRoles.add("read restricted Atlas Users"); // system role 15 + newDefaultRoles.add("Source user (EUNOMIA)"); // system role 1000 this.authorizer.updateUser(login, newDefaultRoles, newUserRoles, resetRoles); this.authorizer.setCurrentTeamProjectRoleForCurrentUser(teamProjectRole, login); } From 7bf4659c909b4ce03e43af8e92a5d34062fe269c Mon Sep 17 00:00:00 2001 From: pieterlukasse Date: Wed, 10 Jan 2024 17:21:20 +0100 Subject: [PATCH 18/31] wip: try adding only system role 15 --- .../webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java b/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java index 1eaa948174..bb82b0989d 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java +++ b/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java @@ -72,7 +72,6 @@ protected boolean preHandle(ServletRequest request, ServletResponse response) th } newUserRoles.add(teamProjectRole); newDefaultRoles.add("read restricted Atlas Users"); // system role 15 - newDefaultRoles.add("Source user (EUNOMIA)"); // system role 1000 this.authorizer.updateUser(login, newDefaultRoles, newUserRoles, resetRoles); this.authorizer.setCurrentTeamProjectRoleForCurrentUser(teamProjectRole, login); } From ad236d0d502a65082bd9083e1319f33b3e8859ab Mon Sep 17 00:00:00 2001 From: pieterlukasse Date: Wed, 10 Jan 2024 17:48:35 +0100 Subject: [PATCH 19/31] wip: improve logging --- .../shiro/filters/TeamProjectBasedAuthorizingFilter.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java b/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java index bb82b0989d..6f722a3720 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java +++ b/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java @@ -133,6 +133,10 @@ private String extractTeamProjectFromRequestParameters(ServletRequest request) { HttpServletRequest httpRequest = (HttpServletRequest) request; String url = httpRequest.getRequestURL().toString(); + String currentTeamProjectName = this.authorizer.getCurrentTeamProjectRoleForCurrentUser() != null ? this.authorizer.getCurrentTeamProjectRoleForCurrentUser().getName() : ""; + logger.debug("Current teamproject: {}...", currentTeamProjectName); + logger.debug("Checking if a teamproject has been specified in the request..."); + // try to find it in the redirectUrl parameter: logger.debug("Looking for redirectUrl in request: {}....", url); String[] redirectUrlParams = getParameterValues(request, "redirectUrl"); @@ -165,7 +169,8 @@ private String extractTeamProjectFromRequestParameters(ServletRequest request) { return teamProject; } - logger.debug("Found NO teamproject."); + logger.debug("Found NO teamproject explicitly set in the request, so keeping team project: {}.", + currentTeamProjectName); return null; } From 92946305012db747c9e1e9a02e13964bb37e4a6b Mon Sep 17 00:00:00 2001 From: pieterlukasse Date: Fri, 12 Jan 2024 18:10:07 +0100 Subject: [PATCH 20/31] wip: add log statement to Source classes --- .../datasource/BaseDataSourceAccessor.java | 10 +++++++++- .../java/org/ohdsi/webapi/source/SourceService.java | 12 ++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/ohdsi/webapi/shiro/management/datasource/BaseDataSourceAccessor.java b/src/main/java/org/ohdsi/webapi/shiro/management/datasource/BaseDataSourceAccessor.java index 8cf2b80bf1..bdea3958b7 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/management/datasource/BaseDataSourceAccessor.java +++ b/src/main/java/org/ohdsi/webapi/shiro/management/datasource/BaseDataSourceAccessor.java @@ -4,12 +4,16 @@ import org.ohdsi.webapi.shiro.management.DisabledSecurity; import org.ohdsi.webapi.shiro.management.Security; import org.ohdsi.webapi.source.Source; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import javax.ws.rs.ForbiddenException; public abstract class BaseDataSourceAccessor implements DataSourceAccessor { + private final Logger logger = LoggerFactory.getLogger(BaseDataSourceAccessor.class); + @Autowired(required = false) private DisabledSecurity disabledSecurity; @@ -26,9 +30,13 @@ public boolean hasAccess(T s) { Source source = extractSource(s); if (source == null) { + logger.debug("Found extractSource() to return null!"); return false; } - return SecurityUtils.getSubject().isPermitted(String.format(Security.SOURCE_ACCESS_PERMISSION, source.getSourceKey())); + + boolean isPermitted = SecurityUtils.getSubject().isPermitted(String.format(Security.SOURCE_ACCESS_PERMISSION, source.getSourceKey())); + logger.debug("Returning isPermitted={} for {}", isPermitted, String.format(Security.SOURCE_ACCESS_PERMISSION, source.getSourceKey())); + return isPermitted; } protected abstract Source extractSource(T source); diff --git a/src/main/java/org/ohdsi/webapi/source/SourceService.java b/src/main/java/org/ohdsi/webapi/source/SourceService.java index f2dca56b41..c698a61b84 100644 --- a/src/main/java/org/ohdsi/webapi/source/SourceService.java +++ b/src/main/java/org/ohdsi/webapi/source/SourceService.java @@ -7,6 +7,8 @@ import org.ohdsi.webapi.common.SourceMapKey; import org.ohdsi.webapi.service.AbstractDaoService; import org.ohdsi.webapi.shiro.management.datasource.SourceAccessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.jdbc.CannotGetJdbcConnectionException; import org.springframework.jdbc.core.JdbcTemplate; @@ -21,6 +23,8 @@ @Service public class SourceService extends AbstractDaoService { + private final Logger logger = LoggerFactory.getLogger(SourceService.class); + private static Collection cachedSources = null; @Value("${jasypt.encryptor.enabled}") @@ -111,6 +115,7 @@ public void checkConnection(Source source) { public Source getPrioritySourceForDaimon(SourceDaimon.DaimonType daimonType) { List sourcesByDaimonPriority = sourceRepository.findAllSortedByDiamonPrioirty(daimonType); + logger.debug("Found {} sources for daimon type {}.", sourcesByDaimonPriority.size(), daimonType.name()); for (Source source : sourcesByDaimonPriority) { if (!(sourceAccessor.hasAccess(source) && connectionAvailability.computeIfAbsent(source, this::checkConnectionSafe))) { @@ -118,6 +123,7 @@ public Source getPrioritySourceForDaimon(SourceDaimon.DaimonType daimonType) { } return source; } + logger.debug("Found NO sources that have access."); return null; } @@ -127,7 +133,8 @@ public Map getPriorityDaimons() { class SourceValidator { private Map checkedSources = new HashMap<>(); - private boolean isSourceAvaialble(Source source) { + private boolean isSourceAvailable(Source source) { + logger.debug("Checking source access via isSourceAvailable..."); return checkedSources.computeIfAbsent(source.getSourceId(), v -> sourceAccessor.hasAccess(source) && connectionAvailability.computeIfAbsent(source, SourceService.this::checkConnectionSafe)); } @@ -138,7 +145,8 @@ private boolean isSourceAvaialble(Source source) { Arrays.asList(SourceDaimon.DaimonType.values()).forEach(d -> { List sources = sourceRepository.findAllSortedByDiamonPrioirty(d); - Optional source = sources.stream().filter(sourceValidator::isSourceAvaialble) + logger.debug("Found {} sources.", sources.size()); + Optional source = sources.stream().filter(sourceValidator::isSourceAvailable) .findFirst(); source.ifPresent(s -> priorityDaimons.put(d, s)); }); From 44ca44ba83064c7a429313d7dd6761a182576ca9 Mon Sep 17 00:00:00 2001 From: pieterlukasse Date: Fri, 12 Jan 2024 22:08:56 +0100 Subject: [PATCH 21/31] wip: improve log statements --- src/main/java/org/ohdsi/webapi/source/SourceService.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/ohdsi/webapi/source/SourceService.java b/src/main/java/org/ohdsi/webapi/source/SourceService.java index c698a61b84..580fb8226c 100644 --- a/src/main/java/org/ohdsi/webapi/source/SourceService.java +++ b/src/main/java/org/ohdsi/webapi/source/SourceService.java @@ -175,8 +175,11 @@ private boolean checkConnectionSafe(Source source) { try { checkConnection(source); + logger.debug("Connection worked."); return true; } catch (CannotGetJdbcConnectionException ex) { + logger.debug("Connection failed: {}", ex.getMessage()); + ex.printStackTrace(); return false; } } From 0c4dbdf1cdb6aade650cf19723ed38f21c2e50c7 Mon Sep 17 00:00:00 2001 From: pieterlukasse Date: Mon, 15 Jan 2024 16:31:55 +0100 Subject: [PATCH 22/31] fix: ensure reset of roles always happens --- .../filters/TeamProjectBasedAuthorizingFilter.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java b/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java index 6f722a3720..eb058e22aa 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java +++ b/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java @@ -50,29 +50,29 @@ protected boolean preHandle(ServletRequest request, ServletResponse response) th try { logger.debug("preHandle in TeamProjectBasedAuthorizingFilter == '{}'", this.authorizationMode); - boolean resetRoles = false; String teamProjectRole = null; Set newUserRoles = new HashSet(); Set newDefaultRoles = new HashSet(defaultRoles); if (this.authorizationMode.equals("teamproject") && SecurityUtils.getSubject().isAuthenticated()) { // in case of "teamproject" mode, we want all roles to be reset always, and // set to only the one requested/found in the request parameters (following lines below): - resetRoles = true; + String login = this.authorizer.getCurrentUser().getLogin(); + newDefaultRoles.add("read restricted Atlas Users"); // system role 15 + this.authorizer.updateUser(login, newDefaultRoles, newUserRoles, true); // check if a teamproject parameter is found in the request: teamProjectRole = extractTeamProjectFromRequestParameters(request); // if found, and teamproject is different from current one, add teamproject as a role in the newUserRoles list: if (teamProjectRole != null && (this.authorizer.getCurrentTeamProjectRoleForCurrentUser() == null || !teamProjectRole.equals(this.authorizer.getCurrentTeamProjectRoleForCurrentUser().getName()))) { // double check if this role has really been granted to the user: - String login = this.authorizer.getCurrentUser().getLogin(); if (checkGen3Authorization(teamProjectRole, login) == false) { WebUtils.toHttp(response).sendError(HttpServletResponse.SC_FORBIDDEN, "User is not authorized to access this team project's data"); return false; } + // add teamproject role: newUserRoles.add(teamProjectRole); - newDefaultRoles.add("read restricted Atlas Users"); // system role 15 - this.authorizer.updateUser(login, newDefaultRoles, newUserRoles, resetRoles); + this.authorizer.updateUser(login, newDefaultRoles, newUserRoles, true); this.authorizer.setCurrentTeamProjectRoleForCurrentUser(teamProjectRole, login); } } From 419b864b6df3a3ce9107c52109574582ba75f43c Mon Sep 17 00:00:00 2001 From: pieterlukasse Date: Mon, 15 Jan 2024 17:15:40 +0100 Subject: [PATCH 23/31] fix: further fix of the roles reset logic --- .../filters/TeamProjectBasedAuthorizingFilter.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java b/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java index eb058e22aa..e4f465d35d 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java +++ b/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java @@ -57,24 +57,23 @@ protected boolean preHandle(ServletRequest request, ServletResponse response) th // in case of "teamproject" mode, we want all roles to be reset always, and // set to only the one requested/found in the request parameters (following lines below): String login = this.authorizer.getCurrentUser().getLogin(); - newDefaultRoles.add("read restricted Atlas Users"); // system role 15 - this.authorizer.updateUser(login, newDefaultRoles, newUserRoles, true); // check if a teamproject parameter is found in the request: teamProjectRole = extractTeamProjectFromRequestParameters(request); // if found, and teamproject is different from current one, add teamproject as a role in the newUserRoles list: - if (teamProjectRole != null && - (this.authorizer.getCurrentTeamProjectRoleForCurrentUser() == null || !teamProjectRole.equals(this.authorizer.getCurrentTeamProjectRoleForCurrentUser().getName()))) { + if (teamProjectRole != null) { // double check if this role has really been granted to the user: if (checkGen3Authorization(teamProjectRole, login) == false) { WebUtils.toHttp(response).sendError(HttpServletResponse.SC_FORBIDDEN, "User is not authorized to access this team project's data"); return false; } - // add teamproject role: + // add teamproject role and related system role that + // enables read restrictions/permissions based read access configurations: + newDefaultRoles.add("read restricted Atlas Users"); // system role 15 newUserRoles.add(teamProjectRole); - this.authorizer.updateUser(login, newDefaultRoles, newUserRoles, true); this.authorizer.setCurrentTeamProjectRoleForCurrentUser(teamProjectRole, login); } + this.authorizer.updateUser(login, newDefaultRoles, newUserRoles, true); } } catch (Exception e) { @@ -171,7 +170,7 @@ private String extractTeamProjectFromRequestParameters(ServletRequest request) { logger.debug("Found NO teamproject explicitly set in the request, so keeping team project: {}.", currentTeamProjectName); - return null; + return currentTeamProjectName; } private String[] getParameterValues(ServletRequest request, String parameterName) { From b59e8225ff3bfa14499b1964265b5a3152ef99c8 Mon Sep 17 00:00:00 2001 From: pieterlukasse Date: Tue, 16 Jan 2024 20:08:04 +0100 Subject: [PATCH 24/31] fix: avoid double deletes --- .../java/org/ohdsi/webapi/shiro/PermissionManager.java | 10 +++++----- .../filters/TeamProjectBasedAuthorizingFilter.java | 4 +++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java b/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java index 0c1b613441..e56b677df6 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java +++ b/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java @@ -221,7 +221,7 @@ public UserEntity registerUser(final String login, final String name, final User } if (resetRoles) { // remove all user roles: - removeAllUserRolesFromUser(login, user); + removeAllUserRolesFromUser(login, user, newUserRoles); // add back just the given newUserRoles: addRolesForUser(login, userOrigin, user, newUserRoles, false); // make sure the default roles are there: TODO - discuss if really necessary.... @@ -271,11 +271,11 @@ private void addDefaultRolesForUser(String login, UserOrigin userOrigin, UserEnt addRolesForUser(login, userOrigin, user, roles,true); } - private void removeAllUserRolesFromUser(String login, UserEntity user) { + private void removeAllUserRolesFromUser(String login, UserEntity user, final Set rolesToSkip) { Set userRoles = this.getUserRoles(user); - // remove all roles except the personal role: - userRoles.stream().filter(role -> !role.getName().equalsIgnoreCase(login)).forEach(userRole -> { - this.removeUserFromUserRole(userRole.getName(), login); + // remove all roles except the personal role and any role in the rolesToSkip: + userRoles.stream().filter(role -> !role.getName().equalsIgnoreCase(login) && !rolesToSkip.contains(role.getName())).forEach(role -> { + this.removeUserFromUserRole(role.getName(), login); }); } diff --git a/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java b/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java index e4f465d35d..9ce74e739d 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java +++ b/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java @@ -72,8 +72,10 @@ protected boolean preHandle(ServletRequest request, ServletResponse response) th newDefaultRoles.add("read restricted Atlas Users"); // system role 15 newUserRoles.add(teamProjectRole); this.authorizer.setCurrentTeamProjectRoleForCurrentUser(teamProjectRole, login); + this.authorizer.updateUser(login, newDefaultRoles, newUserRoles, true); + } else { + throw new Exception("The teamproject is compulsory when on authorizationMode==teamproject configuration"); } - this.authorizer.updateUser(login, newDefaultRoles, newUserRoles, true); } } catch (Exception e) { From cd9104adf4b7f3808bba6e4b7dbd33cfa1362e81 Mon Sep 17 00:00:00 2001 From: pieterlukasse Date: Thu, 18 Jan 2024 17:20:33 +0100 Subject: [PATCH 25/31] wip: testing amazoncorreto alternative to deprecated openJDK 8 --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5667da9724..ede43b0294 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,8 +29,8 @@ RUN mvn package ${MAVEN_PARAMS} \ && jar -xf WebAPI.war \ && rm WebAPI.war -# OHDSI WebAPI and ATLAS web application running as a Spring Boot application with Java 11 -FROM openjdk:8-jre-slim +# OHDSI WebAPI and ATLAS web application running as a Spring Boot application with Java 8 +FROM amazoncorretto:8u402-al2023 MAINTAINER Lee Evans - www.ltscomputingllc.com From c3775aa759b24a4ff51de71d276f48348424ba0b Mon Sep 17 00:00:00 2001 From: Aidan Hilt Date: Thu, 18 Jan 2024 15:52:29 -0500 Subject: [PATCH 26/31] Let's try this --- Dockerfile | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index ede43b0294..fabcaee1ea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ RUN curl -LSsO https://github.com/open-telemetry/opentelemetry-java-instrumentat COPY pom.xml /code/ RUN mkdir .git \ && mvn package \ - -P${MAVEN_PROFILE} + -P${MAVEN_PROFILE} ARG GIT_BRANCH=unknown ARG GIT_COMMIT_ID_ABBREV=unknown @@ -32,8 +32,6 @@ RUN mvn package ${MAVEN_PARAMS} \ # OHDSI WebAPI and ATLAS web application running as a Spring Boot application with Java 8 FROM amazoncorretto:8u402-al2023 -MAINTAINER Lee Evans - www.ltscomputingllc.com - # Any Java options to pass along, e.g. memory, garbage collection, etc. ENV JAVA_OPTS="" # Additional classpath parameters to pass along. If provided, start with colon ":" @@ -59,9 +57,16 @@ COPY --from=builder /code/war/META-INF META-INF EXPOSE 8080 +USER 0 + +RUN mkdir /usr/local/share/aws-certs \ + && curl https://truststore.pki.rds.amazonaws.com/us-east-1/us-east-1-bundle.pem -o /usr/local/share/aws-certs/us-east-1-bundle.pem \ + && cd $JAVA_HOME/jre/lib/security \ + && keytool -import -trustcacerts -storepass changeit -noprompt -alias aws -file /usr/local/share/aws-certs/us-east-1-bundle.pem + USER 101 # Directly run the code as a WAR. CMD exec java ${DEFAULT_JAVA_OPTS} ${JAVA_OPTS} \ -cp ".:WebAPI.jar:WEB-INF/lib/*.jar${CLASSPATH}" \ - org.springframework.boot.loader.WarLauncher + org.springframework.boot.loader.WarLauncher \ No newline at end of file From c7b73592a4335f43d3ebcc46ce42f4a78502e239 Mon Sep 17 00:00:00 2001 From: pieterlukasse Date: Fri, 19 Jan 2024 16:23:47 +0100 Subject: [PATCH 27/31] wip: trying update-ca-trust --- Dockerfile | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index fabcaee1ea..a98e61abe4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ RUN curl -LSsO https://github.com/open-telemetry/opentelemetry-java-instrumentat COPY pom.xml /code/ RUN mkdir .git \ && mvn package \ - -P${MAVEN_PROFILE} + -P${MAVEN_PROFILE} ARG GIT_BRANCH=unknown ARG GIT_COMMIT_ID_ABBREV=unknown @@ -57,16 +57,23 @@ COPY --from=builder /code/war/META-INF META-INF EXPOSE 8080 -USER 0 +#USER 0 -RUN mkdir /usr/local/share/aws-certs \ +RUN echo $JAVA_HOME && mkdir /usr/local/share/aws-certs \ && curl https://truststore.pki.rds.amazonaws.com/us-east-1/us-east-1-bundle.pem -o /usr/local/share/aws-certs/us-east-1-bundle.pem \ && cd $JAVA_HOME/jre/lib/security \ && keytool -import -trustcacerts -storepass changeit -noprompt -alias aws -file /usr/local/share/aws-certs/us-east-1-bundle.pem -USER 101 +#USER 101 + +# Copy your custom CA certificates to the container +RUN cp /usr/local/share/aws-certs/us-east-1-bundle.pem /etc/pki/ca-trust/source/anchors/ + +# Update the CA certificate store +RUN update-ca-trust + # Directly run the code as a WAR. CMD exec java ${DEFAULT_JAVA_OPTS} ${JAVA_OPTS} \ -cp ".:WebAPI.jar:WEB-INF/lib/*.jar${CLASSPATH}" \ - org.springframework.boot.loader.WarLauncher \ No newline at end of file + org.springframework.boot.loader.WarLauncher From 43b529dcfadbb11bf3e83963f25774e13c00c8aa Mon Sep 17 00:00:00 2001 From: pieterlukasse Date: Fri, 2 Feb 2024 21:21:09 +0100 Subject: [PATCH 28/31] fix: adding both session and user login as keys to the teamProject map ...to avoid the scenario where the user comes back in another session and still gets the same teamProject role assigned...while in reality he might have been removed from that team for example --- .../ohdsi/webapi/shiro/PermissionManager.java | 21 +++++++++++++++---- .../TeamProjectBasedAuthorizingFilter.java | 2 +- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java b/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java index e56b677df6..45bcac5b40 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java +++ b/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java @@ -30,6 +30,7 @@ import org.springframework.transaction.annotation.Transactional; import java.security.Principal; +import java.util.AbstractMap; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; @@ -67,7 +68,7 @@ public class PermissionManager { private ThreadLocal> authorizationInfoCache = ThreadLocal.withInitial(ConcurrentHashMap::new); - private Map teamProjectRoles = new HashMap<>(); + private Map, String> teamProjectRoles = new HashMap<>(); public RoleEntity addRole(String roleName, boolean isSystem) { logger.debug("Called addRole: {}", roleName); @@ -586,17 +587,29 @@ public boolean roleExists(String roleName) { return this.roleRepository.existsByName(roleName); } + private String getCurrentUserSessionId() { + Subject subject = SecurityUtils.getSubject(); + return subject.getSession().getId().toString(); + } + + private AbstractMap.SimpleEntry getCurrentUserAndSessionTuple() { + AbstractMap.SimpleEntry userAndSessionTuple = new AbstractMap.SimpleEntry<> + (getCurrentUser().getLogin(), getCurrentUserSessionId()); + return userAndSessionTuple; + } + public void setCurrentTeamProjectRoleForCurrentUser(String teamProjectRole, String login) { logger.debug("Current user in setCurrentTeamProjectRoleForCurrentUser() {}", login); - this.teamProjectRoles.put(login, teamProjectRole); + this.teamProjectRoles.put(getCurrentUserAndSessionTuple(), teamProjectRole); } public RoleEntity getCurrentTeamProjectRoleForCurrentUser() { logger.debug("Current user in getCurrentTeamProjectRoleForCurrentUser(): {}", getCurrentUser().getLogin()); - if (this.teamProjectRoles.get(getCurrentUser().getLogin()) == null) { + String teamProjectRole = this.teamProjectRoles.get(getCurrentUserAndSessionTuple()); + if (teamProjectRole == null) { return null; } else { - return this.getRoleByName(this.teamProjectRoles.get(getCurrentUser().getLogin()), false); + return this.getRoleByName(teamProjectRole, false); } } } diff --git a/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java b/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java index 9ce74e739d..ca317d5ed8 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java +++ b/src/main/java/org/ohdsi/webapi/shiro/filters/TeamProjectBasedAuthorizingFilter.java @@ -59,7 +59,7 @@ protected boolean preHandle(ServletRequest request, ServletResponse response) th String login = this.authorizer.getCurrentUser().getLogin(); // check if a teamproject parameter is found in the request: teamProjectRole = extractTeamProjectFromRequestParameters(request); - // if found, and teamproject is different from current one, add teamproject as a role in the newUserRoles list: + // if found, add teamproject as a role in the newUserRoles list: if (teamProjectRole != null) { // double check if this role has really been granted to the user: if (checkGen3Authorization(teamProjectRole, login) == false) { From b8fec5d74936a4aa88f50b025821f258fa6e202a Mon Sep 17 00:00:00 2001 From: pieterlukasse Date: Fri, 2 Feb 2024 21:56:24 +0100 Subject: [PATCH 29/31] fix: making two internal methods private methods --- src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java b/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java index 45bcac5b40..90849d0e4c 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java +++ b/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java @@ -186,7 +186,7 @@ public void updateUser(String login, Set defaultRoles, Set newUs } @Transactional - public void registerUser(String login, String name, Set defaultRoles, Set newUserRoles, + private void registerUser(String login, String name, Set defaultRoles, Set newUserRoles, boolean resetRoles) { registerUser(login, name, UserOrigin.SYSTEM, defaultRoles, newUserRoles, resetRoles); } @@ -203,7 +203,7 @@ public UserEntity registerUser(final String login, final String name, final User } @Transactional - public UserEntity registerUser(final String login, final String name, final UserOrigin userOrigin, + private UserEntity registerUser(final String login, final String name, final UserOrigin userOrigin, final Set defaultRoles, final Set newUserRoles, boolean resetRoles) { logger.debug("Called registerUser with resetRoles: login={}, reset roles={}, default roles={}, new user roles={}", login, resetRoles, defaultRoles, newUserRoles); From 32e90bc0df31ab692c3167ec9c17ba500780821a Mon Sep 17 00:00:00 2001 From: pieterlukasse Date: Fri, 9 Feb 2024 21:44:30 +0100 Subject: [PATCH 30/31] fix: remove session.stop() call from UpdateAccessTokenFilter ...and therefore from the flow of endpoints like /user/refresh. Not sure why this was added there, as the /user/logout should be the place to remove a session. This solves a org.apache.shiro.subject.support.DisabledSessionException. If the worry is that logout won`t be called, then the expiry time should just be set to a short period. The adjustment in JwtAuthRealm.java was to deal with the side effect that occurred after the removal of the .stop in the UpdateAccessTokenFilter filter: java.lang.ClassCastException: io.buji.pac4j.subject.Pac4jPrincipal cannot be cast to java.lang.String --- .../webapi/shiro/filters/UpdateAccessTokenFilter.java | 5 ----- .../org/ohdsi/webapi/shiro/realms/JwtAuthRealm.java | 11 ++++++++++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java b/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java index 2ce898befb..e32fce0352 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java +++ b/src/main/java/org/ohdsi/webapi/shiro/filters/UpdateAccessTokenFilter.java @@ -115,11 +115,6 @@ protected boolean preHandle(ServletRequest request, ServletResponse response) th login = UserUtils.toLowerCase(login); - // stop session to make logout of OAuth users possible - Session session = SecurityUtils.getSubject().getSession(false); - if (session != null) { - session.stop(); - } if (jwt == null) { if (name == null) { diff --git a/src/main/java/org/ohdsi/webapi/shiro/realms/JwtAuthRealm.java b/src/main/java/org/ohdsi/webapi/shiro/realms/JwtAuthRealm.java index 2c1a4514db..1f7e359c2a 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/realms/JwtAuthRealm.java +++ b/src/main/java/org/ohdsi/webapi/shiro/realms/JwtAuthRealm.java @@ -10,6 +10,8 @@ import org.ohdsi.webapi.shiro.PermissionManager; import org.ohdsi.webapi.shiro.tokens.JwtAuthToken; +import io.buji.pac4j.subject.Pac4jPrincipal; + /** * * @author gennadiy.anisimov @@ -30,7 +32,14 @@ public boolean supports(AuthenticationToken token) { @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { - final String login = (String) principals.getPrimaryPrincipal(); + String login; + Object principal = principals.getPrimaryPrincipal(); + if (principal instanceof Pac4jPrincipal) { + login = ((Pac4jPrincipal)principal).getProfile().getEmail(); + } + else { + login = (String) principals.getPrimaryPrincipal(); + } return authorizer.getAuthorizationInfo(login); } From f6acf9471808239ac32af206b892edace6eb170c Mon Sep 17 00:00:00 2001 From: pieterlukasse Date: Fri, 9 Feb 2024 22:22:46 +0100 Subject: [PATCH 31/31] fix: do not create a new session when requesting current session --- src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java b/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java index 90849d0e4c..e69dcb52a3 100644 --- a/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java +++ b/src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java @@ -589,7 +589,7 @@ public boolean roleExists(String roleName) { private String getCurrentUserSessionId() { Subject subject = SecurityUtils.getSubject(); - return subject.getSession().getId().toString(); + return subject.getSession(false).getId().toString(); } private AbstractMap.SimpleEntry getCurrentUserAndSessionTuple() {