SEBSERV-65 fixed - exam-admin has now institutional read privilege on
user accounts.
This commit is contained in:
parent
3d67b4ed9c
commit
37e75207b1
9 changed files with 95 additions and 46 deletions
|
@ -8,10 +8,14 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.gbl.api.authorization;
|
package ch.ethz.seb.sebserver.gbl.api.authorization;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.user.UserAccount;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||||
|
|
||||||
/** Defines a Privilege by combining a PrivilegeType for base (overall) rights,
|
/** Defines a Privilege by combining a PrivilegeType for base (overall) rights,
|
||||||
|
@ -165,4 +169,31 @@ public final class Privilege {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Checks if the current user has role based edit access to a specified user account.
|
||||||
|
*
|
||||||
|
* If user account has UserRole.SEB_SERVER_ADMIN this always gives true
|
||||||
|
* If user account has UserRole.INSTITUTIONAL_ADMIN this is true if the given user account has
|
||||||
|
* not the UserRole.SEB_SERVER_ADMIN (institutional administrators should not be able to edit SEB Server
|
||||||
|
* administrators)
|
||||||
|
* If the current user is the same as the given user account this is always true no matter if there are any
|
||||||
|
* user-account based privileges (every user shall see its own account)
|
||||||
|
*
|
||||||
|
* @param userAccount the user account the check role based edit access
|
||||||
|
* @return true if the current user has role based edit access to a specified user account */
|
||||||
|
public static boolean hasRoleBasedUserAccountEditGrant(final UserAccount userAccount, final UserInfo currentUser) {
|
||||||
|
final EnumSet<UserRole> userRolesOfUserAccount = userAccount.getUserRoles();
|
||||||
|
final EnumSet<UserRole> userRolesOfCurrentUser = currentUser.getUserRoles();
|
||||||
|
if (userRolesOfCurrentUser.contains(UserRole.SEB_SERVER_ADMIN)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (userRolesOfCurrentUser.contains(UserRole.INSTITUTIONAL_ADMIN)) {
|
||||||
|
return !userRolesOfUserAccount.contains(UserRole.SEB_SERVER_ADMIN);
|
||||||
|
}
|
||||||
|
if (currentUser.equals(userAccount)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -215,6 +215,31 @@ public final class UserInfo implements UserAccount, Activatable, Serializable {
|
||||||
return UserInfo.of(this);
|
return UserInfo.of(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + ((this.uuid == null) ? 0 : this.uuid.hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object obj) {
|
||||||
|
if (this == obj)
|
||||||
|
return true;
|
||||||
|
if (obj == null)
|
||||||
|
return false;
|
||||||
|
if (getClass() != obj.getClass())
|
||||||
|
return false;
|
||||||
|
final UserInfo other = (UserInfo) obj;
|
||||||
|
if (this.uuid == null) {
|
||||||
|
if (other.uuid != null)
|
||||||
|
return false;
|
||||||
|
} else if (!this.uuid.equals(other.uuid))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
final StringBuilder builder = new StringBuilder();
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
|
|
@ -19,6 +19,9 @@ import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.api.authorization.Privilege;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.Domain.USER_ROLE;
|
import ch.ethz.seb.sebserver.gbl.model.Domain.USER_ROLE;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||||
|
@ -123,8 +126,13 @@ public class UserAccountForm implements TemplateComposer {
|
||||||
|
|
||||||
final boolean ownAccount = user.uuid.equals(userAccount.getModelId());
|
final boolean ownAccount = user.uuid.equals(userAccount.getModelId());
|
||||||
final EntityGrantCheck userGrantCheck = currentUser.entityGrantCheck(userAccount);
|
final EntityGrantCheck userGrantCheck = currentUser.entityGrantCheck(userAccount);
|
||||||
final boolean writeGrant = userGrantCheck.w();
|
final boolean roleBasedEditGrant = Privilege.hasRoleBasedUserAccountEditGrant(userAccount, currentUser.get());
|
||||||
final boolean modifyGrant = userGrantCheck.m();
|
final boolean writeGrant = roleBasedEditGrant && userGrantCheck.w();
|
||||||
|
final boolean modifyGrant = roleBasedEditGrant && userGrantCheck.m();
|
||||||
|
final boolean institutionalWriteGrant = currentUser.hasInstitutionalPrivilege(
|
||||||
|
PrivilegeType.WRITE,
|
||||||
|
EntityType.USER);
|
||||||
|
|
||||||
final boolean institutionActive = restService.getBuilder(GetInstitution.class)
|
final boolean institutionActive = restService.getBuilder(GetInstitution.class)
|
||||||
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(userAccount.getInstitutionId()))
|
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(userAccount.getInstitutionId()))
|
||||||
.call()
|
.call()
|
||||||
|
@ -217,7 +225,7 @@ public class UserAccountForm implements TemplateComposer {
|
||||||
this.pageService.pageActionBuilder(formContext.clearEntityKeys())
|
this.pageService.pageActionBuilder(formContext.clearEntityKeys())
|
||||||
|
|
||||||
.newAction(ActionDefinition.USER_ACCOUNT_NEW)
|
.newAction(ActionDefinition.USER_ACCOUNT_NEW)
|
||||||
.publishIf(() -> writeGrant && readonly && institutionActive)
|
.publishIf(() -> institutionalWriteGrant && readonly && institutionActive)
|
||||||
|
|
||||||
.newAction(ActionDefinition.USER_ACCOUNT_MODIFY)
|
.newAction(ActionDefinition.USER_ACCOUNT_MODIFY)
|
||||||
.withEntityKey(entityKey)
|
.withEntityKey(entityKey)
|
||||||
|
|
|
@ -246,12 +246,13 @@ public class ResourceService {
|
||||||
|
|
||||||
public List<Tuple<String>> examSupporterResources() {
|
public List<Tuple<String>> examSupporterResources() {
|
||||||
final UserInfo userInfo = this.currentUser.get();
|
final UserInfo userInfo = this.currentUser.get();
|
||||||
return this.restService.getBuilder(GetUserAccountNames.class)
|
final List<EntityName> selection = this.restService.getBuilder(GetUserAccountNames.class)
|
||||||
.withQueryParam(Entity.FILTER_ATTR_INSTITUTION, String.valueOf(userInfo.institutionId))
|
.withQueryParam(Entity.FILTER_ATTR_INSTITUTION, String.valueOf(userInfo.institutionId))
|
||||||
.withQueryParam(Entity.FILTER_ATTR_ACTIVE, Constants.TRUE_STRING)
|
.withQueryParam(Entity.FILTER_ATTR_ACTIVE, Constants.TRUE_STRING)
|
||||||
.withQueryParam(UserInfo.FILTER_ATTR_ROLE, UserRole.EXAM_SUPPORTER.name())
|
.withQueryParam(UserInfo.FILTER_ATTR_ROLE, UserRole.EXAM_SUPPORTER.name())
|
||||||
.call()
|
.call()
|
||||||
.getOr(Collections.emptyList())
|
.getOr(Collections.emptyList());
|
||||||
|
return selection
|
||||||
.stream()
|
.stream()
|
||||||
.map(entityName -> new Tuple<>(entityName.modelId, entityName.name))
|
.map(entityName -> new Tuple<>(entityName.modelId, entityName.name))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.authorization;
|
package ch.ethz.seb.sebserver.webservice.servicelayer.authorization;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||||
|
@ -230,31 +229,4 @@ public interface AuthorizationService {
|
||||||
return check(PrivilegeType.WRITE, grantEntity);
|
return check(PrivilegeType.WRITE, grantEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Checks if the current user has role based view access to a specified user account.
|
|
||||||
*
|
|
||||||
* If user account has UserRole.SEB_SERVER_ADMIN this always gives true
|
|
||||||
* If user account has UserRole.INSTITUTIONAL_ADMIN this is true if the given user account has
|
|
||||||
* not the UserRole.SEB_SERVER_ADMIN (institutional administrators should not see SEB Server administrators)
|
|
||||||
* If the current user is the same as the given user account this is always true no matter if there are any
|
|
||||||
* user-account based privileges (every user shall see its own account)
|
|
||||||
*
|
|
||||||
* @param userAccount the user account the check role based view access
|
|
||||||
* @return true if the current user has role based view access to a specified user account */
|
|
||||||
default boolean hasRoleBasedUserAccountViewGrant(final UserInfo userAccount) {
|
|
||||||
final EnumSet<UserRole> userRolesOfUserAccount = userAccount.getUserRoles();
|
|
||||||
final SEBServerUser currentUser = getUserService().getCurrentUser();
|
|
||||||
final EnumSet<UserRole> userRolesOfCurrentUser = currentUser.getUserRoles();
|
|
||||||
if (userRolesOfCurrentUser.contains(UserRole.SEB_SERVER_ADMIN)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (userRolesOfCurrentUser.contains(UserRole.INSTITUTIONAL_ADMIN)) {
|
|
||||||
return !userRolesOfUserAccount.contains(UserRole.SEB_SERVER_ADMIN);
|
|
||||||
}
|
|
||||||
if (currentUser.uuid().equals(userAccount.uuid)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,7 @@ public class AuthorizationServiceImpl implements AuthorizationService {
|
||||||
.andForRole(UserRole.INSTITUTIONAL_ADMIN)
|
.andForRole(UserRole.INSTITUTIONAL_ADMIN)
|
||||||
.withInstitutionalPrivilege(PrivilegeType.WRITE)
|
.withInstitutionalPrivilege(PrivilegeType.WRITE)
|
||||||
.andForRole(UserRole.EXAM_ADMIN)
|
.andForRole(UserRole.EXAM_ADMIN)
|
||||||
|
.withInstitutionalPrivilege(PrivilegeType.READ)
|
||||||
.withOwnerPrivilege(PrivilegeType.MODIFY)
|
.withOwnerPrivilege(PrivilegeType.MODIFY)
|
||||||
.andForRole(UserRole.EXAM_SUPPORTER)
|
.andForRole(UserRole.EXAM_SUPPORTER)
|
||||||
.withOwnerPrivilege(PrivilegeType.MODIFY)
|
.withOwnerPrivilege(PrivilegeType.MODIFY)
|
||||||
|
|
|
@ -160,7 +160,7 @@ public class UserDAOImpl implements UserDAO {
|
||||||
? predicate.and(ui -> ui.roles.contains(userRole))
|
? predicate.and(ui -> ui.roles.contains(userRole))
|
||||||
: predicate;
|
: predicate;
|
||||||
|
|
||||||
return this.userRecordMapper
|
final Collection<UserInfo> userInfo = this.userRecordMapper
|
||||||
.selectByExample()
|
.selectByExample()
|
||||||
.where(
|
.where(
|
||||||
UserRecordDynamicSqlSupport.active,
|
UserRecordDynamicSqlSupport.active,
|
||||||
|
@ -187,6 +187,9 @@ public class UserDAOImpl implements UserDAO {
|
||||||
.flatMap(DAOLoggingSupport::logAndSkipOnError)
|
.flatMap(DAOLoggingSupport::logAndSkipOnError)
|
||||||
.filter(_predicate)
|
.filter(_predicate)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
;
|
||||||
|
|
||||||
|
return userInfo;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -174,8 +174,10 @@ public abstract class EntityController<T extends Entity, M extends Entity> {
|
||||||
filterMap.putIfAbsent(API.PARAM_INSTITUTION_ID, String.valueOf(institutionId));
|
filterMap.putIfAbsent(API.PARAM_INSTITUTION_ID, String.valueOf(institutionId));
|
||||||
}
|
}
|
||||||
|
|
||||||
return getAll(filterMap)
|
final Collection<T> all = getAll(filterMap)
|
||||||
.getOrThrow()
|
.getOrThrow();
|
||||||
|
|
||||||
|
return all
|
||||||
.stream()
|
.stream()
|
||||||
.map(Entity::toName)
|
.map(Entity::toName)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
@ -341,9 +343,10 @@ public abstract class EntityController<T extends Entity, M extends Entity> {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Result<Collection<T>> getAll(final FilterMap filterMap) {
|
protected Result<Collection<T>> getAll(final FilterMap filterMap) {
|
||||||
return this.entityDAO.allMatching(
|
final Result<Collection<T>> allMatching = this.entityDAO.allMatching(
|
||||||
filterMap,
|
filterMap,
|
||||||
this::hasReadAccess);
|
this::hasReadAccess);
|
||||||
|
return allMatching;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Result<T> notifyCreated(final T entity) {
|
protected Result<T> notifyCreated(final T entity) {
|
||||||
|
|
|
@ -8,10 +8,8 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.webservice.weblayer.api;
|
package ch.ethz.seb.sebserver.webservice.weblayer.api;
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
|
|
||||||
|
@ -19,6 +17,7 @@ import org.mybatis.dynamic.sql.SqlTable;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.context.ApplicationEventPublisher;
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.validation.FieldError;
|
import org.springframework.validation.FieldError;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
@ -32,6 +31,7 @@ import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
|
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.api.authorization.Privilege;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.user.PasswordChange;
|
import ch.ethz.seb.sebserver.gbl.model.user.PasswordChange;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.user.UserAccount;
|
import ch.ethz.seb.sebserver.gbl.model.user.UserAccount;
|
||||||
|
@ -46,7 +46,6 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.SEBServerUser;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.SEBServerUser;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
|
||||||
|
@ -101,12 +100,18 @@ public class UserAccountController extends ActivatableEntityController<UserInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Result<Collection<UserInfo>> getAll(final FilterMap filterMap) {
|
protected Result<UserInfo> checkModifyAccess(final UserInfo entity) {
|
||||||
return super.getAll(filterMap)
|
return super.checkModifyAccess(entity)
|
||||||
.map(result -> result
|
.map(this::checkRoleBasedEditGrant);
|
||||||
.stream()
|
}
|
||||||
.filter(this.authorization::hasRoleBasedUserAccountViewGrant)
|
|
||||||
.collect(Collectors.toList()));
|
private UserInfo checkRoleBasedEditGrant(final UserInfo userInfo) {
|
||||||
|
final SEBServerUser currentUser = this.authorization.getUserService().getCurrentUser();
|
||||||
|
if (Privilege.hasRoleBasedUserAccountEditGrant(userInfo, currentUser.getUserInfo())) {
|
||||||
|
return userInfo;
|
||||||
|
} else {
|
||||||
|
throw new AccessDeniedException("No edit right grant for user: " + currentUser.getUsername());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
Loading…
Reference in a new issue