SEBSERV-13 #general implementation of entity activation service

This commit is contained in:
anhefti 2018-12-19 12:08:53 +01:00
parent 70d66e6806
commit 41f9f25cd8
10 changed files with 373 additions and 156 deletions

View file

@ -8,8 +8,6 @@
package ch.ethz.seb.sebserver.gbl.model.user;
import javax.validation.constraints.NotNull;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
@ -95,16 +93,4 @@ public final class UserFilter {
+ this.email + ", active=" + this.active + ", locale=" + this.locale + "]";
}
public static UserFilter ofActive() {
return new UserFilter(null, null, null, null, true, null);
}
public static UserFilter ofInactive() {
return new UserFilter(null, null, null, null, false, null);
}
public static UserFilter ofInstitution(@NotNull final Long institutionId) {
return new UserFilter(institutionId, null, null, null, true, null);
}
}

View file

@ -13,11 +13,6 @@ import java.util.Collections;
import java.util.Locale;
import java.util.Set;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.apache.commons.lang3.BooleanUtils;
import org.joda.time.DateTimeZone;
@ -25,6 +20,7 @@ import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import ch.ethz.seb.sebserver.gbl.model.Activatable;
import ch.ethz.seb.sebserver.gbl.model.Domain.USER;
import ch.ethz.seb.sebserver.gbl.model.Domain.USER_ROLE;
import ch.ethz.seb.sebserver.gbl.model.EntityType;
@ -36,7 +32,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.GrantEntity;
* to and from JSON within the Jackson library.
*
* This domain model is immutable and thread-save */
public final class UserInfo implements GrantEntity, Serializable {
public final class UserInfo implements GrantEntity, Activatable, Serializable {
private static final long serialVersionUID = 2526446136264377808L;
@ -45,45 +41,34 @@ public final class UserInfo implements GrantEntity, Serializable {
public final String uuid;
/** The foreign key identifier to the institution where the User belongs to */
@NotNull
@JsonProperty(USER.ATTR_INSTITUTION_ID)
public final Long institutionId;
/** Full name of the user */
@NotNull
@Size(min = 3, max = 255, message = "user:name:size:{min}:{max}:${validatedValue}")
@JsonProperty(USER.ATTR_NAME)
public final String name;
/** The internal user name */
@NotNull
@Size(min = 3, max = 255, message = "user:username:size:{min}:{max}:${validatedValue}")
@JsonProperty(USER.ATTR_USER_NAME)
public final String userName;
/** E-mail address of the user */
@Email(message = "user:email:email:_:_:${validatedValue}")
@JsonProperty(USER.ATTR_EMAIL)
public final String email;
/** Indicates whether this user is still active or not */
@NotNull
@JsonProperty(USER.ATTR_ACTIVE)
public final Boolean active;
/** The users locale */
@NotNull
@JsonProperty(USER.ATTR_LOCALE)
public final Locale locale;
/** The users time zone */
@NotNull
@JsonProperty(USER.ATTR_TIMEZONE)
public final DateTimeZone timeZone;
/** The users roles in a unmodifiable set. Is never null */
@NotNull
@NotEmpty(message = "user:roles:notEmpty:_:_:_")
@JsonProperty(USER_ROLE.REFERENCE_NAME)
public final Set<String> roles;
@ -155,6 +140,12 @@ public final class UserInfo implements GrantEntity, Serializable {
return this.active;
}
@JsonIgnore
@Override
public boolean isActive() {
return this.active;
}
public Locale getLocale() {
return this.locale;
}

View file

@ -8,23 +8,70 @@
package ch.ethz.seb.sebserver.gbl.model.user;
import java.util.Collections;
import java.util.Locale;
import java.util.Set;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.joda.time.DateTimeZone;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import ch.ethz.seb.sebserver.gbl.model.Domain.USER;
import ch.ethz.seb.sebserver.gbl.model.Domain.USER_ROLE;
import ch.ethz.seb.sebserver.gbl.model.EntityType;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.GrantEntity;
public final class UserMod implements GrantEntity {
public static final String ATTR_NAME_USER_INFO = "userInfo";
public static final String ATTR_NAME_NEW_PASSWORD = "newPassword";
public static final String ATTR_NAME_RETYPED_NEW_PASSWORD = "retypedNewPassword";
@JsonProperty(ATTR_NAME_USER_INFO)
private final UserInfo userInfo;
public final String uuid;
/** The foreign key identifier to the institution where the User belongs to */
@NotNull
@JsonProperty(USER.ATTR_INSTITUTION_ID)
public final Long institutionId;
/** Full name of the user */
@NotNull
@Size(min = 3, max = 255, message = "user:name:size:{min}:{max}:${validatedValue}")
@JsonProperty(USER.ATTR_NAME)
public final String name;
/** The internal user name */
@NotNull
@Size(min = 3, max = 255, message = "user:username:size:{min}:{max}:${validatedValue}")
@JsonProperty(USER.ATTR_USER_NAME)
public final String userName;
/** E-mail address of the user */
@Email(message = "user:email:email:_:_:${validatedValue}")
@JsonProperty(USER.ATTR_EMAIL)
public final String email;
/** The users locale */
@NotNull
@JsonProperty(USER.ATTR_LOCALE)
public final Locale locale;
/** The users time zone */
@NotNull
@JsonProperty(USER.ATTR_TIMEZONE)
public final DateTimeZone timeZone;
/** The users roles in a unmodifiable set. Is never null */
@NotNull
@NotEmpty(message = "user:roles:notEmpty:_:_:_")
@JsonProperty(USER_ROLE.REFERENCE_NAME)
public final Set<String> roles;
@Size(min = 8, max = 255, message = "user:password:size:{min}:{max}:${validatedValue}")
@JsonProperty(ATTR_NAME_NEW_PASSWORD)
@ -35,47 +82,97 @@ public final class UserMod implements GrantEntity {
@JsonCreator
public UserMod(
@JsonProperty(ATTR_NAME_USER_INFO) final UserInfo userInfo,
@JsonProperty(USER.ATTR_UUID) final String uuid,
@JsonProperty(USER.ATTR_INSTITUTION_ID) final Long institutionId,
@JsonProperty(USER.ATTR_NAME) final String name,
@JsonProperty(USER.ATTR_USER_NAME) final String userName,
@JsonProperty(ATTR_NAME_NEW_PASSWORD) final String newPassword,
@JsonProperty(ATTR_NAME_RETYPED_NEW_PASSWORD) final String retypedNewPassword) {
@JsonProperty(ATTR_NAME_RETYPED_NEW_PASSWORD) final String retypedNewPassword,
@JsonProperty(USER.ATTR_EMAIL) final String email,
@JsonProperty(USER.ATTR_ACTIVE) final Boolean active,
@JsonProperty(USER.ATTR_LOCALE) final Locale locale,
@JsonProperty(USER.ATTR_TIMEZONE) final DateTimeZone timeZone,
@JsonProperty(USER_ROLE.REFERENCE_NAME) final Set<String> roles) {
this.userInfo = userInfo;
this.uuid = uuid;
this.institutionId = institutionId;
this.newPassword = newPassword;
this.retypedNewPassword = retypedNewPassword;
this.name = name;
this.userName = userName;
this.email = email;
this.locale = locale;
this.timeZone = timeZone;
this.roles = (roles != null)
? Collections.unmodifiableSet(roles)
: Collections.emptySet();
}
public UserMod(final UserInfo userInfo, final String newPassword, final String retypedNewPassword) {
this.uuid = userInfo.uuid;
this.institutionId = userInfo.institutionId;
this.newPassword = newPassword;
this.retypedNewPassword = retypedNewPassword;
this.name = userInfo.name;
this.userName = userInfo.userName;
this.email = userInfo.email;
this.locale = userInfo.locale;
this.timeZone = userInfo.timeZone;
this.roles = userInfo.roles;
}
@Override
@JsonIgnore
public String getId() {
return this.userInfo.getId();
return this.uuid;
}
@Override
@JsonIgnore
public EntityType entityType() {
return this.userInfo.entityType();
return EntityType.USER;
}
@Override
@JsonIgnore
public Long getInstitutionId() {
return this.userInfo.getInstitutionId();
return this.institutionId;
}
@Override
@JsonIgnore
public String getOwnerUUID() {
return this.userInfo.getOwnerUUID();
}
public UserInfo getUserInfo() {
return this.userInfo;
return this.uuid;
}
public String getNewPassword() {
return this.newPassword;
}
public String getName() {
return this.name;
}
public String getUserName() {
return this.userName;
}
public String getEmail() {
return this.email;
}
public Locale getLocale() {
return this.locale;
}
public DateTimeZone getTimeZone() {
return this.timeZone;
}
public Set<String> getRoles() {
return this.roles;
}
public String getRetypedNewPassword() {
return this.retypedNewPassword;
}
@ -88,37 +185,13 @@ public final class UserMod implements GrantEntity {
return passwordChangeRequest() && this.newPassword.equals(this.retypedNewPassword);
}
public boolean createNew() {
return this.userInfo.uuid == null;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((this.userInfo == null) ? 0 : this.userInfo.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 UserMod other = (UserMod) obj;
if (this.userInfo == null) {
if (other.userInfo != null)
return false;
} else if (!this.userInfo.equals(other.userInfo))
return false;
return true;
}
@Override
public String toString() {
return "UserMod [userInfo=" + this.userInfo + "]";
return "UserMod [uuid=" + this.uuid + ", institutionId=" + this.institutionId + ", name=" + this.name
+ ", userName="
+ this.userName + ", email=" + this.email + ", locale=" + this.locale + ", timeZone=" + this.timeZone
+ ", roles=" + this.roles
+ ", newPassword=" + this.newPassword + ", retypedNewPassword=" + this.retypedNewPassword + "]";
}
}

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.webservice.servicelayer.activation;
import java.util.Collection;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ActivatableEntityDAO;
@Service
@WebServiceProfile
public class EntityActivationService {
private final Collection<ActivatableEntityDAO<?>> activatableEntityDAOs;
public EntityActivationService(final Collection<ActivatableEntityDAO<?>> activatableEntityDAOs) {
this.activatableEntityDAOs = activatableEntityDAOs;
}
@EventListener(EntityActivationEvent.class)
public void notifyActivationEvent(final EntityActivationEvent event) {
for (final ActivatableEntityDAO<?> dao : this.activatableEntityDAOs) {
if (event.activated) {
dao.notifyActivation(event.entity);
} else {
dao.notifyDeactivation(event.entity);
}
}
}
}

View file

@ -21,7 +21,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.SEBServerUser
/** The Data Access Object for all User related data like get user data within UserInfo,
* save and modify user related data within UserMod and get internal user principal data
* within SEBServerUser. */
public interface UserDAO extends EntityDAO<UserInfo> {
public interface UserDAO extends EntityDAO<UserInfo>, ActivatableEntityDAO<UserInfo> {
/** Use this to get UserInfo by database identifier
*
@ -48,11 +48,6 @@ public interface UserDAO extends EntityDAO<UserInfo> {
* @return a Result of SEBServerUser for specified username. Or an exception result on error case */
Result<SEBServerUser> sebServerUserByUsername(String username);
/** Use this to get a Collection of UserInfo for all active users.
*
* @return a Result of Collection of UserInfo for all active users. Or an exception result on error case */
Result<Collection<UserInfo>> allActive();
/** Use this to get a Collection of UserInfo that matches a given predicate.
*
* NOTE: This first gets all UserRecord from database, for each creates new UserInfo

View file

@ -13,12 +13,34 @@ import java.util.Collection;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.util.Result;
/** Interface for a DAo handling an Entity with relations to an user (account)
*
*
* @param <T> the concrete type of the Entity */
public interface UserRelatedEntityDAO<T extends Entity> extends EntityDAO<T> {
Result<Collection<T>> getAllForUser(String userId);
/** Get all Entity instances that has a relation to the user-account
* of a given user identity (UUID)
*
* @param userUuid the users identity
* @return A Result of all Entity instances that has a relation to the user-account
* of a given user identity (UUID) or a Result with error if happen */
Result<Collection<T>> getAllForUser(String userUuid);
Result<Integer> deleteUserReferences(final String userId);
/** Overwrite all user-references for entities that belongs to the user with the given identity
* to refer to an internal anonymous user-account
*
* @param userUuid the users identity
* @param deactivate indicates if the effected entities should also be deactivated if possible
* @return A Result with the number of overwrite Entity instances or with an error if happen */
Result<Integer> overwriteUserReferences(final String userUuid, boolean deactivate);
Result<Integer> deleteUserEnities(final String userId);
/** Delete all user-references for entities that belongs to the user with the given identity
*
* NOTE: This processes a hard-delete. All effected data will be lost.
*
* @param userUuid the users identity
* @return A Result with the number of deleted Entity instances or with an error if happen */
Result<Integer> deleteUserEnities(final String userUuid);
}

View file

@ -116,8 +116,8 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
@Override
@Transactional(readOnly = true)
public Result<Collection<UserActivityLog>> getAllForUser(final String userId) {
return all(userId, null, null, model -> true);
public Result<Collection<UserActivityLog>> getAllForUser(final String userUuid) {
return all(userUuid, null, null, model -> true);
}
@Override
@ -182,11 +182,11 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
@Override
@Transactional(readOnly = true)
public Result<Integer> deleteUserReferences(final String userId) {
public Result<Integer> overwriteUserReferences(final String userUuid, final boolean deactivate) {
try {
final List<UserActivityLogRecord> records = this.userLogRecordMapper.selectByExample()
.where(UserActivityLogRecordDynamicSqlSupport.userUuid, SqlBuilder.isEqualTo(userId))
.where(UserActivityLogRecordDynamicSqlSupport.userUuid, SqlBuilder.isEqualTo(userUuid))
.build()
.execute();
@ -196,19 +196,19 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
records
.stream()
.forEach(this::overrrideUser);
.forEach(this::overwriteUser);
return Result.of(records.size());
} catch (final Throwable t) {
log.error(
"Unexpected error while trying to delete all user references form activity logs for user with id: {}",
userId);
userUuid);
return Result.ofError(t);
}
}
private void overrrideUser(final UserActivityLogRecord record) {
private void overwriteUser(final UserActivityLogRecord record) {
final UserActivityLogRecord selective = new UserActivityLogRecord(
record.getId(),
this.userService.getAnonymousUser().getUsername(),
@ -218,16 +218,16 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
}
@Override
public Result<Integer> deleteUserEnities(final String userId) {
public Result<Integer> deleteUserEnities(final String userUuid) {
try {
return Result.of(this.userLogRecordMapper.deleteByExample()
.where(UserActivityLogRecordDynamicSqlSupport.userUuid, SqlBuilder.isEqualToWhenPresent(userId))
.where(UserActivityLogRecordDynamicSqlSupport.userUuid, SqlBuilder.isEqualToWhenPresent(userUuid))
.build()
.execute());
} catch (final Throwable t) {
log.error("Unexpected error while trying to delete all activity logs for user with id: {}", userId);
log.error("Unexpected error while trying to delete all activity logs for user with id: {}", userUuid);
return Result.ofError(t);
}
}

View file

@ -34,10 +34,10 @@ import org.springframework.transaction.interceptor.TransactionInterceptor;
import org.springframework.util.CollectionUtils;
import ch.ethz.seb.sebserver.WebSecurityConfig;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.EntityType;
import ch.ethz.seb.sebserver.gbl.model.APIMessage.APIMessageException;
import ch.ethz.seb.sebserver.gbl.model.APIMessage.ErrorMessage;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.EntityType;
import ch.ethz.seb.sebserver.gbl.model.user.UserFilter;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
import ch.ethz.seb.sebserver.gbl.model.user.UserMod;
@ -110,7 +110,7 @@ public class UserDaoImpl implements UserDAO {
@Override
@Transactional(readOnly = true)
public Result<Collection<UserInfo>> allActive() {
return all(UserFilter.ofActive());
return all(new UserFilter(null, null, null, null, true, null));
}
@Override
@ -168,8 +168,7 @@ public class UserDaoImpl implements UserDAO {
try {
final UserInfo userInfo = userMod.getUserInfo();
return (userInfo.uuid != null)
return (userMod.uuid != null)
? updateUser(userMod)
: createNewUser(userMod);
@ -189,6 +188,63 @@ public class UserDaoImpl implements UserDAO {
return Result.ofError(new RuntimeException("TODO"));
}
@Override
@Transactional
public Result<UserInfo> setActive(final String entityId, final boolean active) {
try {
this.userRecordMapper.updateByExampleSelective(
new UserRecord(
null, null, null, null, null, null, null, null, null,
BooleanUtils.toIntegerObject(active)))
.where(UserRecordDynamicSqlSupport.uuid, isEqualTo(entityId))
.build()
.execute();
return byUuid(entityId);
} catch (final Exception e) {
log.error("unexpected error: ", e);
return Result.ofError(e);
}
}
@Override
public void notifyActivation(final Entity source) {
// If an Institution has been deactivated, all its user accounts gets also be deactivated
if (source.entityType() == EntityType.INSTITUTION) {
setAllActiveForInstitution(Long.parseLong(source.getId()), true);
}
}
@Override
public void notifyDeactivation(final Entity source) {
// If an Institution has been deactivated, all its user accounts gets also be deactivated
if (source.entityType() == EntityType.INSTITUTION) {
setAllActiveForInstitution(Long.parseLong(source.getId()), false);
}
}
private void setAllActiveForInstitution(final Long institutionId, final boolean active) {
try {
final UserRecord record = new UserRecord(
null, null, null, null, null, null, null, null, null,
BooleanUtils.toIntegerObject(active));
this.userRecordMapper.updateByExampleSelective(record)
.where(UserRecordDynamicSqlSupport.institutionId, isEqualTo(institutionId))
.build()
.execute();
} catch (final Exception e) {
log.error("Unexpected error while trying to set all active: {} for institution: {}",
active,
institutionId,
e);
}
}
private Result<Collection<UserInfo>> fromRecords(
final List<UserRecord> records,
final Predicate<UserInfo> predicate) {
@ -204,13 +260,8 @@ public class UserDaoImpl implements UserDAO {
}
private Result<UserInfo> updateUser(final UserMod userMod) {
final UserInfo userInfo = userMod.getUserInfo();
return recordByUUID(userInfo.uuid)
return recordByUUID(userMod.uuid)
.flatMap(record -> {
if (record.getInstitutionId().longValue() != userInfo.institutionId.longValue()) {
return Result.ofError(new IllegalArgumentException("The users institution cannot be changed"));
}
final boolean changePWD = userMod.passwordChangeRequest();
if (changePWD && !userMod.newPasswordMatch()) {
return Result.ofError(new APIMessageException(ErrorMessage.PASSWORD_MISSMATCH));
@ -220,23 +271,22 @@ public class UserDaoImpl implements UserDAO {
record.getId(),
null,
null,
userInfo.name,
userInfo.userName,
userMod.name,
userMod.userName,
(changePWD) ? this.userPasswordEncoder.encode(userMod.getNewPassword()) : null,
userInfo.email,
userInfo.locale.toLanguageTag(),
userInfo.timeZone.getID(),
BooleanUtils.toIntegerObject(userInfo.active));
userMod.email,
userMod.locale.toLanguageTag(),
userMod.timeZone.getID(),
null);
this.userRecordMapper.updateByPrimaryKeySelective(newRecord);
updateRolesForUser(record.getId(), userInfo.roles);
updateRolesForUser(record.getId(), userMod.roles);
return byId(record.getId());
});
}
private Result<UserInfo> createNewUser(final UserMod userMod) {
final UserInfo userInfo = userMod.getUserInfo();
if (!userMod.newPasswordMatch()) {
return Result.ofError(new APIMessageException(ErrorMessage.PASSWORD_MISSMATCH));
@ -244,19 +294,19 @@ public class UserDaoImpl implements UserDAO {
final UserRecord newRecord = new UserRecord(
null,
userInfo.institutionId,
userMod.institutionId,
UUID.randomUUID().toString(),
userInfo.name,
userInfo.userName,
userMod.name,
userMod.userName,
this.userPasswordEncoder.encode(userMod.getNewPassword()),
userInfo.email,
userInfo.locale.toLanguageTag(),
userInfo.timeZone.getID(),
BooleanUtils.toIntegerObject(userInfo.active));
userMod.email,
userMod.locale.toLanguageTag(),
userMod.timeZone.getID(),
BooleanUtils.toInteger(false));
this.userRecordMapper.insert(newRecord);
final Long newUserId = newRecord.getId();
insertRolesForUser(newUserId, userInfo.roles);
insertRolesForUser(newUserId, userMod.roles);
return byId(newUserId);
}
@ -349,4 +399,5 @@ public class UserDaoImpl implements UserDAO {
userInfo,
record.getPassword()));
}
}

View file

@ -8,7 +8,6 @@
package ch.ethz.seb.sebserver.webservice.weblayer.api;
import java.security.Principal;
import java.util.Collection;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@ -16,11 +15,11 @@ import java.util.stream.Collectors;
import javax.validation.Valid;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import ch.ethz.seb.sebserver.gbl.model.EntityType;
@ -29,6 +28,7 @@ import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
import ch.ethz.seb.sebserver.gbl.model.user.UserMod;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.activation.EntityActivationEvent;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationGrantService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
@ -64,19 +64,30 @@ public class UserAccountController {
@RequestMapping(method = RequestMethod.GET)
public Collection<UserInfo> getAll(
//@RequestParam(required = false) final UserFilter filter,
@RequestBody(required = false) final UserFilter userFilter,
final Principal principal) {
@RequestParam(required = false) final Long institutionId,
@RequestParam(required = false) final Boolean active,
@RequestParam(required = false) final String name,
@RequestParam(required = false) final String userName,
@RequestParam(required = false) final String email,
@RequestParam(required = false) final String locale) {
// fist check if current user has any privileges for this action
this.authorizationGrantService.checkHasAnyPrivilege(
EntityType.USER,
PrivilegeType.READ_ONLY);
final UserFilter userFilter = ((institutionId != null) ||
(active != null) ||
(name != null) ||
(userName != null) ||
(email != null) ||
(locale != null))
? new UserFilter(institutionId, name, userName, email, active, locale)
: null;
if (this.authorizationGrantService.hasBasePrivilege(
EntityType.USER,
PrivilegeType.READ_ONLY,
principal)) {
PrivilegeType.READ_ONLY)) {
return (userFilter != null)
? this.userDao.all(userFilter).getOrThrow()
@ -86,8 +97,7 @@ public class UserAccountController {
final Predicate<UserInfo> grantFilter = this.authorizationGrantService.getGrantFilter(
EntityType.USER,
PrivilegeType.READ_ONLY,
principal);
PrivilegeType.READ_ONLY);
if (userFilter == null) {
@ -108,14 +118,14 @@ public class UserAccountController {
}
@RequestMapping(value = "/me", method = RequestMethod.GET)
public UserInfo loggedInUser(final Authentication auth) {
public UserInfo loggedInUser() {
return this.userService
.getCurrentUser()
.getUserInfo();
}
@RequestMapping(value = "/{userUUID}", method = RequestMethod.GET)
public UserInfo accountInfo(@PathVariable final String userUUID, final Principal principal) {
public UserInfo accountInfo(@PathVariable final String userUUID) {
return this.userDao
.byUuid(userUUID)
.flatMap(userInfo -> this.authorizationGrantService.checkGrantOnEntity(
@ -126,27 +136,42 @@ public class UserAccountController {
}
@RequestMapping(value = "/create", method = RequestMethod.PUT)
public UserInfo createUser(
@Valid @RequestBody final UserMod userData,
final Principal principal) {
public UserInfo createUser(@Valid @RequestBody final UserMod userData) {
return _saveUser(userData, PrivilegeType.WRITE)
.getOrThrow();
}
@RequestMapping(value = "/save", method = RequestMethod.POST)
public UserInfo saveUser(
@Valid @RequestBody final UserMod userData,
final Principal principal) {
public UserInfo saveUser(@Valid @RequestBody final UserMod userData) {
return _saveUser(userData, PrivilegeType.MODIFY)
.getOrThrow();
}
@RequestMapping(value = "/{userUUID}/activate", method = RequestMethod.POST)
public UserInfo activateUser(@PathVariable final String userUUID) {
return setActivity(userUUID, true);
}
@RequestMapping(value = "/{userUUID}/deactivate", method = RequestMethod.POST)
public UserInfo deactivateUser(@PathVariable final String userUUID) {
return setActivity(userUUID, false);
}
private UserInfo setActivity(final String userUUID, final boolean activity) {
return this.userDao.byUuid(userUUID)
.flatMap(userInfo -> this.authorizationGrantService.checkGrantOnEntity(userInfo, PrivilegeType.WRITE))
.flatMap(userInfo -> this.userDao.setActive(userInfo.uuid, activity))
.map(userInfo -> {
this.applicationEventPublisher.publishEvent(new EntityActivationEvent(userInfo, activity));
return userInfo;
})
.getOrThrow();
}
private Result<UserInfo> _saveUser(final UserMod userData, final PrivilegeType privilegeType) {
final ActivityType actionType = (userData.getUserInfo().uuid == null)
final ActivityType actionType = (userData.uuid == null)
? ActivityType.CREATE
: ActivityType.MODIFY;

View file

@ -30,7 +30,6 @@ import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.model.APIMessage;
import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog;
import ch.ethz.seb.sebserver.gbl.model.user.UserFilter;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
import ch.ethz.seb.sebserver.gbl.model.user.UserMod;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
@ -152,15 +151,10 @@ public class UserAPITest extends AdministrationAPIIntegrationTest {
@Test
public void getAllUserInfoWithSearchInactive() throws Exception {
final UserFilter filter = UserFilter.ofInactive();
final String filterJson = this.jsonMapper.writeValueAsString(filter);
final String token = getSebAdminAccess();
final List<UserInfo> userInfos = this.jsonMapper.readValue(
this.mockMvc.perform(get(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT)
.header("Authorization", "Bearer " + token)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(filterJson))
this.mockMvc.perform(get(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "?active=false")
.header("Authorization", "Bearer " + token))
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString(),
new TypeReference<List<UserInfo>>() {
@ -173,15 +167,10 @@ public class UserAPITest extends AdministrationAPIIntegrationTest {
@Test
public void getAllUserInfoWithSearchUsernameLike() throws Exception {
final UserFilter filter = new UserFilter(null, null, "exam", null, null, null);
final String filterJson = this.jsonMapper.writeValueAsString(filter);
final String token = getSebAdminAccess();
final List<UserInfo> userInfos = this.jsonMapper.readValue(
this.mockMvc.perform(get(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT)
.header("Authorization", "Bearer " + token)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(filterJson))
this.mockMvc.perform(get(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "?userName=exam")
.header("Authorization", "Bearer " + token))
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString(),
new TypeReference<List<UserInfo>>() {
@ -236,6 +225,7 @@ public class UserAPITest extends AdministrationAPIIntegrationTest {
assertNotNull(createdUserGet);
assertEquals(createdUser, createdUserGet);
assertFalse(createdUserGet.isActive());
// check user activity log for newly created user
final List<UserActivityLog> logs = this.jsonMapper.readValue(
@ -497,6 +487,50 @@ public class UserAPITest extends AdministrationAPIIntegrationTest {
assertEquals("1300", messages.get(0).messageCode);
}
@Test
public void deactivateUserAccount() throws Exception {
// only a SEB Administrator or an Institutional administrator should be able to deactivate a user-account
final String examAdminToken = getExamAdmin1();
this.mockMvc.perform(post(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "/user4/deactivate")
.header("Authorization", "Bearer " + examAdminToken))
.andExpect(status().isForbidden());
// With SEB Administrator it should work
final String sebAdminToken = getSebAdminAccess();
final UserInfo deactivatedUser = this.jsonMapper.readValue(
this.mockMvc.perform(post(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "/user4/deactivate")
.header("Authorization", "Bearer " + sebAdminToken))
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString(),
new TypeReference<UserInfo>() {
});
assertNotNull(deactivatedUser);
assertFalse(deactivatedUser.isActive());
}
@Test
public void activateUserAccount() throws Exception {
// only a SEB Administrator or an Institutional administrator should be able to deactivate a user-account
final String examAdminToken = getExamAdmin1();
this.mockMvc.perform(post(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "/user6/activate")
.header("Authorization", "Bearer " + examAdminToken))
.andExpect(status().isForbidden());
// With SEB Administrator it should work
final String sebAdminToken = getSebAdminAccess();
final UserInfo deactivatedUser = this.jsonMapper.readValue(
this.mockMvc.perform(post(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "/user6/activate")
.header("Authorization", "Bearer " + sebAdminToken))
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString(),
new TypeReference<UserInfo>() {
});
assertNotNull(deactivatedUser);
assertTrue(deactivatedUser.isActive());
}
private UserInfo getUserInfo(final String name, final Collection<UserInfo> infos) {
return infos
.stream()