Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/prototype teamproject parser #93

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/image_build_push.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ jobs:
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 }}
Expand Down
3 changes: 2 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
<spring.batch.repository.isolationLevelForCreate>ISOLATION_READ_COMMITTED</spring.batch.repository.isolationLevelForCreate>
<spring.profiles.active>default</spring.profiles.active>

<security.ohdsi.custom.authorization.mode>teamproject</security.ohdsi.custom.authorization.mode>
<security.provider>DisabledSecurity</security.provider>
<security.token.expiration>43200</security.token.expiration>
<security.origin>http://localhost</security.origin>
Expand Down Expand Up @@ -227,7 +228,7 @@
<spring.jpa.properties.hibernate.jdbc.batch_size>200</spring.jpa.properties.hibernate.jdbc.batch_size>
<spring.jpa.properties.hibernate.order_inserts>true</spring.jpa.properties.hibernate.order_inserts>
<logging.level.root>info</logging.level.root>
<logging.level.org.ohdsi>info</logging.level.org.ohdsi>
<logging.level.org.ohdsi>debug</logging.level.org.ohdsi>
<logging.level.org.springframework.orm>info</logging.level.org.springframework.orm>
<logging.level.org.springframework.jdbc>info</logging.level.org.springframework.jdbc>
<logging.level.org.springframework.web>info</logging.level.org.springframework.web>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() : "");
}
}
99 changes: 90 additions & 9 deletions src/main/java/org/ohdsi/webapi/shiro/PermissionManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -37,6 +39,7 @@
@Component
@Transactional
public class PermissionManager {
private final Logger logger = LoggerFactory.getLogger(PermissionManager.class);

@Autowired
private UserRepository userRepository;
Expand All @@ -59,6 +62,8 @@ public class PermissionManager {
private ThreadLocal<ConcurrentHashMap<String, UserSimpleAuthorizationInfo>> 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");
Expand Down Expand Up @@ -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<RoleEntity> getRoles(boolean includePersonalRoles) {
Expand Down Expand Up @@ -141,14 +170,29 @@ public void clearAuthorizationInfoCache() {
this.authorizationInfoCache.set(new ConcurrentHashMap<>());
}


@Transactional
public void registerUser(String login, String name, Set<String> defaultRoles, Set<String> newUserRoles,
boolean resetRoles) {
registerUser(login, name, UserOrigin.SYSTEM, defaultRoles, newUserRoles, resetRoles);
}

@Transactional
public UserEntity registerUser(final String login, final String name, final Set<String> 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<String> defaultRoles) {
return registerUser(login, name, userOrigin, defaultRoles, null, false);
}

@Transactional
public UserEntity registerUser(final String login, final String name, final UserOrigin userOrigin,
final Set<String> defaultRoles, final Set<String> 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);
Expand All @@ -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;
}

Expand All @@ -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<String> 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<String> roles) {
addRolesForUser(login, userOrigin, user, roles,true);
}

private void removeAllUserRolesFromUser(String login, UserEntity user) {
Set<RoleEntity> 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<UserEntity> getUsers() {
Expand Down Expand Up @@ -322,6 +402,7 @@ private Set<PermissionEntity> getRolePermissions(RoleEntity role) {

private Set<RoleEntity> getUserRoles(UserEntity user) {
Set<UserRoleEntity> userRoles = user.getUserRoles();
logger.debug("Called getUserRoles. Found: {}", userRoles);
Set<RoleEntity> roles = new LinkedHashSet<>();
for (UserRoleEntity userRole : userRoles) {
if (isRelationAllowed(userRole.getStatus())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<String> defaultRoles;
private final String onFailRedirectUrl;
private final String authorizationMode;

public UpdateAccessTokenFilter(
PermissionManager authorizer,
Set<String> 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
Expand Down Expand Up @@ -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<String> newUserRoles = new HashSet<String>();
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);
Expand Down Expand Up @@ -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<String> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,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
Expand All @@ -269,8 +272,9 @@ public Map<FilterTemplates, Filter> getFilters() {
Map<FilterTemplates, Filter> 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());
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,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}
Expand Down
Loading