SEBSERV-13 #general implementation of entity activation service
This commit is contained in:
parent
70d66e6806
commit
41f9f25cd8
10 changed files with 373 additions and 156 deletions
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in a new issue