This commit is contained in:
anhefti 2019-02-20 21:09:49 +01:00
parent 28556afae6
commit bb97e60922
49 changed files with 963 additions and 261 deletions

View file

@ -30,7 +30,7 @@
<profile>
<id>Demo</id>
<activation>
<activeByDefault>true</activeByDefault>
<activeByDefault>false</activeByDefault>
</activation>
<properties>
<java.version>1.8</java.version>
@ -58,7 +58,7 @@
<profile>
<id>Java 11</id>
<activation>
<activeByDefault>false</activeByDefault>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<java.version>11</java.version>

View file

@ -57,6 +57,8 @@ public final class API {
public static final String DEPENDENCY_PATH_SEGMENT = "/dependency";
public static final String PASSWORD_PATH_SEGMENT = "/password";
public static final String PATH_VAR_ACTIVE = MODEL_ID_VAR_PATH_SEGMENT + ACTIVE_PATH_SEGMENT;
public static final String PATH_VAR_INACTIVE = MODEL_ID_VAR_PATH_SEGMENT + INACTIVE_PATH_SEGMENT;

View file

@ -36,7 +36,7 @@ public class APIMessage implements Serializable {
ILLEGAL_API_ARGUMENT("1010", HttpStatus.BAD_REQUEST, "Illegal API request argument"),
UNEXPECTED("1100", HttpStatus.INTERNAL_SERVER_ERROR, "Unexpected intenral server-side error"),
FIELD_VALIDATION("1200", HttpStatus.BAD_REQUEST, "Field validation error"),
PASSWORD_MISSMATCH("1300", HttpStatus.BAD_REQUEST, "new password do not match retyped password")
PASSWORD_MISMATCH("1300", HttpStatus.BAD_REQUEST, "new password do not match retyped password")
;

View file

@ -27,7 +27,11 @@ public interface Entity extends ModelIdAware {
@JsonIgnore
default EntityKey getEntityKey() {
return new EntityKey(getModelId(), entityType());
final String modelId = getModelId();
if (modelId == null) {
return null;
}
return new EntityKey(modelId, entityType());
}
public static EntityName toName(final Entity entity) {

View file

@ -36,13 +36,13 @@ public final class LmsSetup implements GrantEntity, Activatable {
@JsonProperty(LMS_SETUP.ATTR_ID)
public final Long id;
@JsonProperty(LMS_SETUP.ATTR_INSTITUTION_ID)
@NotNull
@JsonProperty(LMS_SETUP.ATTR_INSTITUTION_ID)
public final Long institutionId;
@JsonProperty(LMS_SETUP.ATTR_NAME)
@NotNull(message = "lmsSetup:name:notNull")
@Size(min = 3, max = 255, message = "lmsSetup:name:size:{min}:{max}:${validatedValue}")
@JsonProperty(LMS_SETUP.ATTR_NAME)
public final String name;
@JsonProperty(LMS_SETUP.ATTR_LMS_TYPE)

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2019 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.gbl.model.user;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
public class PasswordChange {
public static final String ATTR_NAME_NEW_PASSWORD = "newPassword";
public static final String ATTR_NAME_RETYPED_NEW_PASSWORD = "retypedNewPassword";
@NotNull(message = "user:password:notNull")
@Size(min = 8, max = 255, message = "user:password:size:{min}:{max}:${validatedValue}")
@JsonProperty(ATTR_NAME_NEW_PASSWORD)
private final String newPassword;
@JsonProperty(ATTR_NAME_RETYPED_NEW_PASSWORD)
private final String retypedNewPassword;
@JsonCreator
public PasswordChange(
@JsonProperty(ATTR_NAME_NEW_PASSWORD) final String newPassword,
@JsonProperty(ATTR_NAME_RETYPED_NEW_PASSWORD) final String retypedNewPassword) {
this.newPassword = newPassword;
this.retypedNewPassword = retypedNewPassword;
}
public String getNewPassword() {
return this.newPassword;
}
public String getRetypedNewPassword() {
return this.retypedNewPassword;
}
public boolean newPasswordMatch() {
return this.newPassword.equals(this.retypedNewPassword);
}
}

View file

@ -0,0 +1,42 @@
/*
* Copyright (c) 2019 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.gbl.model.user;
import java.util.Locale;
import java.util.Set;
import org.joda.time.DateTimeZone;
public interface UserAccount {
String getModelId();
Long getInstitutionId();
String getName();
String getUsername();
String getEmail();
Boolean getActive();
boolean isActive();
Locale getLocale();
DateTimeZone getTimeZone();
Set<String> getRoles();
String getNewPassword();
String getRetypedNewPassword();
}

View file

@ -12,10 +12,15 @@ import java.io.Serializable;
import java.util.Locale;
import java.util.Set;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.apache.commons.lang3.BooleanUtils;
import org.joda.time.DateTimeZone;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
@ -32,7 +37,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, Activatable, Serializable {
public final class UserInfo implements UserAccount, GrantEntity, Activatable, Serializable {
private static final long serialVersionUID = 2526446136264377808L;
@ -45,30 +50,39 @@ public final class UserInfo implements GrantEntity, Activatable, 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(message = "user:name: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(message = "user:username:notNull")
@Size(min = 3, max = 255, message = "user:username:size:{min}:{max}:${validatedValue}")
@JsonProperty(USER.ATTR_USERNAME)
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(message = "user:locale:notNull")
@JsonProperty(USER.ATTR_LOCALE)
public final Locale locale;
/** The users time zone */
@NotNull(message = "user:timeZone:notNull")
@JsonProperty(USER.ATTR_TIMEZONE)
public final DateTimeZone timeZone;
@ -129,14 +143,17 @@ public final class UserInfo implements GrantEntity, Activatable, Serializable {
return this.name;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public String getEmail() {
return this.email;
}
@Override
public Boolean getActive() {
return this.active;
}
@ -146,14 +163,17 @@ public final class UserInfo implements GrantEntity, Activatable, Serializable {
return this.active;
}
@Override
public Locale getLocale() {
return this.locale;
}
@Override
public DateTimeZone getTimeZone() {
return this.timeZone;
}
@Override
public Set<String> getRoles() {
return this.roles;
}
@ -165,6 +185,18 @@ public final class UserInfo implements GrantEntity, Activatable, Serializable {
return this.roles.contains(userRole.name());
}
@JsonIgnore
@Override
public String getNewPassword() {
return null;
}
@JsonIgnore
@Override
public String getRetypedNewPassword() {
return null;
}
@Override
public int hashCode() {
final int prime = 31;

View file

@ -28,7 +28,7 @@ import ch.ethz.seb.sebserver.gbl.model.Domain.USER;
import ch.ethz.seb.sebserver.gbl.model.Domain.USER_ROLE;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.GrantEntity;
public final class UserMod implements GrantEntity {
public final class UserMod implements UserAccount, GrantEntity {
public static final String ATTR_NAME_NEW_PASSWORD = "newPassword";
public static final String ATTR_NAME_RETYPED_NEW_PASSWORD = "retypedNewPassword";
@ -88,7 +88,6 @@ public final class UserMod implements GrantEntity {
@JsonProperty(ATTR_NAME_NEW_PASSWORD) final String newPassword,
@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) {
@ -133,6 +132,19 @@ public final class UserMod implements GrantEntity {
this.roles = postAttrMapper.getStringSet(USER_ROLE.REFERENCE_NAME);
}
public UserMod(final String modelId, final Long institutionId) {
this.uuid = modelId;
this.institutionId = institutionId;
this.newPassword = null;
this.retypedNewPassword = null;
this.name = null;
this.username = null;
this.email = null;
this.locale = null;
this.timeZone = null;
this.roles = null;
}
@Override
public String getModelId() {
return this.uuid;
@ -153,6 +165,7 @@ public final class UserMod implements GrantEntity {
return this.uuid;
}
@Override
public String getNewPassword() {
return this.newPassword;
}
@ -162,26 +175,32 @@ public final class UserMod implements GrantEntity {
return this.name;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public String getEmail() {
return this.email;
}
@Override
public Locale getLocale() {
return this.locale;
}
@Override
public DateTimeZone getTimeZone() {
return this.timeZone;
}
@Override
public Set<String> getRoles() {
return this.roles;
}
@Override
public String getRetypedNewPassword() {
return this.retypedNewPassword;
}
@ -194,6 +213,16 @@ public final class UserMod implements GrantEntity {
return passwordChangeRequest() && this.newPassword.equals(this.retypedNewPassword);
}
@Override
public Boolean getActive() {
return false;
}
@Override
public boolean isActive() {
return false;
}
@Override
public String toString() {
return "UserMod [uuid=" + this.uuid + ", institutionId=" + this.institutionId + ", name=" + this.name

View file

@ -17,11 +17,14 @@ import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
@ -65,6 +68,19 @@ public final class Form implements FormBinding {
}
}
@Override
public MultiValueMap<String, String> getFormAsQueryAttributes() {
final LinkedMultiValueMap<String, String> result = new LinkedMultiValueMap<>();
for (final Map.Entry<String, FormFieldAccessor> entry : this.formFields.entrySet()) {
final String value = entry.getValue().getValue();
if (StringUtils.isNoneBlank(value)) {
result.add(entry.getKey(), value);
}
}
return result;
}
public String getValue(final String name) {
final FormFieldAccessor formFieldAccessor = this.formFields.get(name);
if (formFieldAccessor != null) {
@ -155,7 +171,7 @@ public final class Form implements FormBinding {
.forEach(processor);
}
public void flush() {
private void flush() {
for (final Map.Entry<String, FormFieldAccessor> entry : this.formFields.entrySet()) {
final FormFieldAccessor accessor = entry.getValue();
if (accessor.control.isVisible()) {

View file

@ -9,6 +9,7 @@
package ch.ethz.seb.sebserver.gui.service.form;
import java.util.List;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Function;
@ -186,6 +187,19 @@ public class FormBuilder {
return this;
}
public FormBuilder addImageUploadIf(
final BooleanSupplier condition,
final String name,
final String label,
final String value,
final int span) {
if (condition != null && condition.getAsBoolean()) {
return addImageUpload(name, label, value, span);
}
return this;
}
public FormBuilder addImageUpload(
final String name,
final String label,

View file

@ -36,7 +36,6 @@ public interface PageContext {
public static final String PAGE_TEMPLATE_COMPOSER_NAME = "ATTR_PAGE_TEMPLATE_COMPOSER_NAME";
public static final String READ_ONLY = "READ_ONLY";
public static final String CREATE_NEW = "CREATE_NEW";
public static final String ENTITY_ID = "ENTITY_ID";
public static final String PARENT_ENTITY_ID = "PARENT_ENTITY_ID";
@ -106,6 +105,8 @@ public interface PageContext {
String getAttribute(String name, String def);
boolean isReadonly();
EntityKey getEntityKey();
EntityKey getParentEntityKey();

View file

@ -9,6 +9,7 @@
package ch.ethz.seb.sebserver.gui.service.page.action;
import java.util.Set;
import java.util.function.BooleanSupplier;
import java.util.function.Function;
import java.util.function.Supplier;
@ -113,4 +114,12 @@ public final class Action implements Runnable {
return this.pageContext;
}
public PageContext publishIf(final BooleanSupplier condition) {
if (condition.getAsBoolean()) {
publish();
}
return this.pageContext;
}
}

View file

@ -16,10 +16,14 @@ public enum ActionDefinition {
"sebserver.institution.action.new",
ImageIcon.NEW),
INSTITUTION_VIEW(
"sebserver.institution.action.view",
INSTITUTION_VIEW_FROM_LIST(
"sebserver.institution.action.list.view",
ImageIcon.SHOW),
INSTITUTION_MODIFY_FROM__LIST(
"sebserver.institution.action.list.modify",
ImageIcon.EDIT),
INSTITUTION_MODIFY(
"sebserver.institution.action.modify",
ImageIcon.EDIT),
@ -44,7 +48,38 @@ public enum ActionDefinition {
"sebserver.institution.action.modify",
ImageIcon.DELETE),
;
USER_ACCOUNT_NEW(
"sebserver.useraccount.action.new",
ImageIcon.NEW),
USER_ACCOUNT_VIEW(
"sebserver.useraccount.action.view",
ImageIcon.SHOW),
USER_ACCOUNT_MODIFY(
"sebserver.useraccount.action.modify",
ImageIcon.EDIT),
USER_ACCOUNT_CANCEL_MODIFY(
"sebserver.overall.action.modify.cancel",
ImageIcon.CANCEL),
USER_ACCOUNT_SAVE(
"sebserver.useraccount.action.save",
ImageIcon.SAVE),
USER_ACCOUNT_ACTIVATE(
"sebserver.useraccount.action.activate",
ImageIcon.INACTIVE),
USER_ACCOUNT_DEACTIVATE(
"sebserver.useraccount.action.deactivate",
ImageIcon.ACTIVE),
USER_ACCOUNT_DELETE(
"sebserver.useraccount.action.modify",
ImageIcon.DELETE),
;
public final String name;
public final ImageIcon icon;

View file

@ -40,11 +40,11 @@ public final class InstitutionActions {
}
public static Result<?> viewInstitution(final Action action) {
return fromInstitution(action, false);
return fromSelection(action, false);
}
public static Result<?> editInstitutionFromList(final Action action) {
return fromInstitution(action, true);
return fromSelection(action, true);
}
public static Result<?> editInstitution(final Action action) {
@ -55,10 +55,16 @@ public final class InstitutionActions {
}
public static Result<?> cancelEditInstitution(final Action action) {
return Result.of(goToInstitution(
action.pageContext,
action.pageContext.getAttribute(AttributeKeys.ENTITY_ID),
false));
if (action.pageContext.getEntityKey() == null) {
final ActivitySelection toList = Activity.INSTITUTION_LIST.createSelection();
action.pageContext.publishPageEvent(new ActivitySelectionEvent(toList));
return Result.of(toList);
} else {
return Result.of(goToInstitution(
action.pageContext,
action.pageContext.getAttribute(AttributeKeys.ENTITY_ID),
false));
}
}
public static Result<?> activateInstitution(final Action action) {
@ -81,7 +87,7 @@ public final class InstitutionActions {
.map(report -> goToInstitution(action.pageContext, report.getSingleSource().modelId, false));
}
private static Result<?> fromInstitution(final Action action, final boolean edit) {
private static Result<?> fromSelection(final Action action, final boolean edit) {
final Collection<String> selection = action.selectionSupplier.get();
if (selection.isEmpty()) {
return Result.ofError(new PageMessageException("sebserver.institution.info.pleaseSelect"));
@ -90,13 +96,19 @@ public final class InstitutionActions {
return Result.of(goToInstitution(action.pageContext, selection.iterator().next(), edit));
}
private static ActivitySelection goToInstitution(final PageContext pageContext, final String modelId,
private static ActivitySelection goToInstitution(
final PageContext pageContext,
final String modelId,
final boolean edit) {
final ActivitySelection activitySelection = Activity.INSTITUTION_FORM
.createSelection()
.withEntity(new EntityKey(modelId, EntityType.INSTITUTION))
.withAttribute(AttributeKeys.READ_ONLY, String.valueOf(!edit))
.withAttribute(AttributeKeys.CREATE_NEW, (modelId != null) ? "false" : "true");
.withAttribute(AttributeKeys.READ_ONLY, String.valueOf(!edit));
if (modelId != null) {
activitySelection.withEntity(new EntityKey(modelId, EntityType.INSTITUTION));
}
pageContext.publishPageEvent(new ActivitySelectionEvent(activitySelection));
return activitySelection;
}

View file

@ -0,0 +1,64 @@
/*
* Copyright (c) 2019 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.gui.service.page.action;
import java.util.Collection;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys;
import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
import ch.ethz.seb.sebserver.gui.service.page.activity.ActivitySelection;
import ch.ethz.seb.sebserver.gui.service.page.activity.ActivitySelection.Activity;
import ch.ethz.seb.sebserver.gui.service.page.event.ActivitySelectionEvent;
public final class UserAccountActions {
public static Result<?> newUserAccount(final Action action) {
return Result.of(goToUserAccount(action.pageContext, null, true));
}
public static Result<?> viewUserAccountFromList(final Action action) {
return fromSelection(action, false);
}
public static Result<?> editUserAccountFromList(final Action action) {
return fromSelection(action, true);
}
private static Result<?> fromSelection(final Action action, final boolean edit) {
final Collection<String> selection = action.selectionSupplier.get();
if (selection.isEmpty()) {
return Result.ofError(new PageMessageException("sebserver.useraccount.info.pleaseSelect"));
}
return Result.of(goToUserAccount(action.pageContext, selection.iterator().next(), edit));
}
private static ActivitySelection goToUserAccount(
final PageContext pageContext,
final String modelId,
final boolean edit) {
final ActivitySelection activitySelection = Activity.USER_ACCOUNT_FORM
.createSelection()
.withEntity(new EntityKey(modelId, EntityType.USER))
.withAttribute(AttributeKeys.READ_ONLY, String.valueOf(!edit));
if (modelId != null) {
activitySelection.withEntity(new EntityKey(modelId, EntityType.USER));
}
pageContext.publishPageEvent(new ActivitySelectionEvent(activitySelection));
return activitySelection;
}
}

View file

@ -22,6 +22,7 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
@ -91,6 +92,7 @@ public class ActivitiesPane implements TemplateComposer {
navigation.setLayoutData(navigationGridData);
// Institution
// If current user has SEB Server Admin role, show the Institution list
if (userInfo.hasRole(UserRole.SEB_SERVER_ADMIN)) {
// institutions (list) as root
final TreeItem institutions = this.widgetFactory.treeItemLocalized(
@ -99,7 +101,7 @@ public class ActivitiesPane implements TemplateComposer {
injectActivitySelection(institutions, Activity.INSTITUTION_LIST.createSelection());
} else {
// institution node as none root
// otherwise show the form of the institution for current user
final TreeItem institutions = this.widgetFactory.treeItemLocalized(
navigation,
Activity.INSTITUTION_FORM.title);
@ -111,10 +113,23 @@ public class ActivitiesPane implements TemplateComposer {
}
// User Account
final TreeItem userAccounts = this.widgetFactory.treeItemLocalized(
navigation,
Activity.USER_ACCOUNT_LIST.title);
injectActivitySelection(userAccounts, Activity.USER_ACCOUNT_LIST.createSelection());
// if current user has base read privilege for User Account, show list
if (this.currentUser.hasPrivilege(PrivilegeType.READ_ONLY, EntityType.USER)) {
final TreeItem userAccounts = this.widgetFactory.treeItemLocalized(
navigation,
Activity.USER_ACCOUNT_LIST.title);
injectActivitySelection(userAccounts, Activity.USER_ACCOUNT_LIST.createSelection());
} else {
// otherwise show the user account form for current user
final TreeItem userAccounts = this.widgetFactory.treeItemLocalized(
navigation,
Activity.USER_ACCOUNT_FORM.title);
injectActivitySelection(
userAccounts,
Activity.USER_ACCOUNT_FORM.createSelection()
.withEntity(this.currentUser.get().getEntityKey())
.withAttribute(AttributeKeys.READ_ONLY, "true"));
}
//
// final TreeItem configs = this.widgetFactory.treeItemLocalized(
// navigation,

View file

@ -112,7 +112,6 @@ public class ActivitySelection {
}
return this;
}
public ActivitySelection withParentEntity(final EntityKey parentEntityKey) {

View file

@ -9,10 +9,8 @@
package ch.ethz.seb.sebserver.gui.service.page.content;
import java.util.Set;
import java.util.UUID;
import java.util.function.Supplier;
import org.apache.commons.lang3.BooleanUtils;
import org.eclipse.swt.widgets.Composite;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -31,7 +29,6 @@ import ch.ethz.seb.sebserver.gui.service.form.FormHandle;
import ch.ethz.seb.sebserver.gui.service.form.PageFormService;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys;
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
import ch.ethz.seb.sebserver.gui.service.page.action.ActionDefinition;
import ch.ethz.seb.sebserver.gui.service.page.action.InstitutionActions;
@ -68,44 +65,45 @@ public class InstitutionForm implements TemplateComposer {
public void compose(final PageContext pageContext) {
if (log.isDebugEnabled()) {
log.debug("Compose Institutoion Form");
log.debug("Compose Institutoion Form within PageContext: {}", pageContext);
}
final WidgetFactory widgetFactory = this.pageFormService.getWidgetFactory();
final boolean readonly = BooleanUtils.toBoolean(
pageContext.getAttribute(AttributeKeys.READ_ONLY, "true"));
final boolean createNew = BooleanUtils.toBoolean(
pageContext.getAttribute(AttributeKeys.CREATE_NEW, "false"));
final EntityKey entityKey = pageContext.getEntityKey();
// get data or create new and handle error
Institution institution = null;
PageContext formContext = pageContext;
if (createNew) {
institution = this.restService
.getBuilder(NewInstitution.class)
.withQueryParam(Domain.INSTITUTION.ATTR_NAME, "[NEW-" + UUID.randomUUID() + "]")
.call()
.get(pageContext::notifyError);
formContext = pageContext.withEntityKey(institution.getEntityKey());
} else {
final String instId = pageContext.getAttribute(AttributeKeys.ENTITY_ID);
institution = this.restService
.getBuilder(GetInstitution.class)
.withURIVariable(API.PARAM_MODEL_ID, instId)
.call()
.get(pageContext::notifyError);
}
final Institution institution = (entityKey == null)
? new Institution(null, null, null, null, false)
: this.restService
.getBuilder(GetInstitution.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
.call()
.get(pageContext::notifyError);
if (institution == null) {
// TODO should here be a forward to institution list page for SEB Admin?
log.error("Failed to get Institution. "
+ "Error was notified to the User. "
+ "See previous logs for more infomation");
return;
}
// new PageContext with actual EntityKey
final PageContext formContext = pageContext;
pageContext.withEntityKey(institution.getEntityKey());
if (log.isDebugEnabled()) {
log.debug("Institution Form for Institution {}", institution.name);
}
// the default page layout with interactive title
final LocTextKey titleKey = new LocTextKey(
(entityKey != null)
? "sebserver.institution.form.title"
: "sebserver.institution.form.title.new",
institution.name);
final Composite content = widgetFactory.defaultPageLayout(
formContext.getParent(),
new LocTextKey("sebserver.institution.form.title", institution.name),
titleKey,
ActionDefinition.INSTITUTION_SAVE,
title -> event -> {
final Entity entity = (Entity) event.source;
@ -118,7 +116,7 @@ public class InstitutionForm implements TemplateComposer {
// The Institution form
final FormHandle<Institution> formHandle = this.pageFormService.getBuilder(
formContext.copyOf(content), 4)
.readonly(readonly)
.readonly(pageContext.isReadonly())
.putStaticValue("id", institution.getModelId())
.addTextField(
Domain.INSTITUTION.ATTR_NAME,
@ -130,36 +128,37 @@ public class InstitutionForm implements TemplateComposer {
"sebserver.institution.form.urlSuffix",
institution.urlSuffix, 2)
.addEmptyCell()
.addImageUpload(
.addImageUploadIf(() -> entityKey != null,
Domain.INSTITUTION.ATTR_LOGO_IMAGE,
"sebserver.institution.form.logoImage",
institution.logoImage, 2)
.buildFor(
this.restService.getRestCall(SaveInstitution.class),
.buildFor((entityKey == null)
? this.restService.getRestCall(NewInstitution.class)
: this.restService.getRestCall(SaveInstitution.class),
InstitutionActions.postSaveAdapter(pageContext));
// propagate content actions to action-pane
if (readonly) {
if (this.currentUser.hasPrivilege(PrivilegeType.WRITE, institution)) {
formContext.createAction(ActionDefinition.INSTITUTION_NEW)
.withExec(InstitutionActions::newInstitution)
.publish();
}
if (this.currentUser.hasPrivilege(PrivilegeType.MODIFY, institution)) {
formContext.createAction(ActionDefinition.INSTITUTION_MODIFY)
.withExec(InstitutionActions::editInstitution)
.publish();
if (!institution.isActive()) {
formContext.createAction(ActionDefinition.INSTITUTION_ACTIVATE)
.withExec(InstitutionActions::activateInstitution)
.publish();
} else {
formContext.createAction(ActionDefinition.INSTITUTION_DEACTIVATE)
.withExec(InstitutionActions::deactivateInstitution)
.withConfirm(confirmDeactivation(institution))
.publish();
}
final boolean writeGrant = this.currentUser.hasPrivilege(PrivilegeType.WRITE, institution);
final boolean modifyGrant = this.currentUser.hasPrivilege(PrivilegeType.MODIFY, institution);
if (pageContext.isReadonly()) {
formContext.createAction(ActionDefinition.INSTITUTION_NEW)
.withExec(InstitutionActions::newInstitution)
.publishIf(() -> writeGrant);
formContext.createAction(ActionDefinition.INSTITUTION_MODIFY)
.withExec(InstitutionActions::editInstitution)
.publishIf(() -> modifyGrant);
if (!institution.isActive()) {
formContext.createAction(ActionDefinition.INSTITUTION_ACTIVATE)
.withExec(InstitutionActions::activateInstitution)
.publishIf(() -> modifyGrant);
} else {
formContext.createAction(ActionDefinition.INSTITUTION_DEACTIVATE)
.withExec(InstitutionActions::deactivateInstitution)
.withConfirm(confirmDeactivation(institution))
.publishIf(() -> modifyGrant);
}
} else {
formContext.createAction(ActionDefinition.INSTITUTION_SAVE)
.withExec(formHandle::postChanges)

View file

@ -8,13 +8,14 @@
package ch.ethz.seb.sebserver.gui.service.page.content;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.institution.Institution;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
@ -25,6 +26,7 @@ import ch.ethz.seb.sebserver.gui.service.page.action.ActionDefinition;
import ch.ethz.seb.sebserver.gui.service.page.action.InstitutionActions;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitutions;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
import ch.ethz.seb.sebserver.gui.service.table.ColumnDefinition;
import ch.ethz.seb.sebserver.gui.service.table.EntityTable;
import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory;
@ -34,28 +36,31 @@ import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory;
@GuiProfile
public class InstitutionList implements TemplateComposer {
private static final Logger log = LoggerFactory.getLogger(InstitutionList.class);
private final WidgetFactory widgetFactory;
private final RestService restService;
private final CurrentUser currentUser;
protected InstitutionList(
final WidgetFactory widgetFactory,
final RestService restService) {
final RestService restService,
final CurrentUser currentUser) {
this.widgetFactory = widgetFactory;
this.restService = restService;
this.currentUser = currentUser;
}
@Override
public void compose(final PageContext pageContext) {
final Composite content = new Composite(pageContext.getParent(), SWT.NONE);
final GridLayout contentLayout = new GridLayout();
contentLayout.marginLeft = 10;
content.setLayout(contentLayout);
content.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
// title
this.widgetFactory.labelLocalizedTitle(
content,
if (log.isDebugEnabled()) {
log.debug("Compose Institutoion list within PageContext: {}", pageContext);
}
final Composite content = this.widgetFactory.defaultPageLayout(
pageContext.getParent(),
new LocTextKey("sebserver.institution.list.title"));
// table
@ -82,15 +87,15 @@ public class InstitutionList implements TemplateComposer {
// propagate content actions to action-pane
pageContext.createAction(ActionDefinition.INSTITUTION_NEW)
.withExec(InstitutionActions::newInstitution)
.publish()
.createAction(ActionDefinition.INSTITUTION_VIEW)
.publishIf(() -> this.currentUser.hasPrivilege(PrivilegeType.WRITE, EntityType.INSTITUTION))
.createAction(ActionDefinition.INSTITUTION_VIEW_FROM_LIST)
.withSelectionSupplier(table::getSelection)
.withExec(InstitutionActions::viewInstitution)
.publish()
.createAction(ActionDefinition.INSTITUTION_MODIFY)
.createAction(ActionDefinition.INSTITUTION_MODIFY_FROM__LIST)
.withSelectionSupplier(table::getSelection)
.withExec(InstitutionActions::editInstitutionFromList)
.publish();
.publishIf(() -> this.currentUser.hasPrivilege(PrivilegeType.MODIFY, EntityType.INSTITUTION));
}

View file

@ -8,13 +8,24 @@
package ch.ethz.seb.sebserver.gui.service.page.content;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.user.UserAccount;
import ch.ethz.seb.sebserver.gbl.model.user.UserMod;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.form.PageFormService;
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.GetUserAccount;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory;
@Lazy
@ -22,20 +33,48 @@ import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory;
@GuiProfile
public class UserAccountForm implements TemplateComposer {
private final WidgetFactory widgetFactory;
private static final Logger log = LoggerFactory.getLogger(UserAccountForm.class);
private final PageFormService pageFormService;
private final RestService restService;
private final CurrentUser currentUser;
protected UserAccountForm(
final WidgetFactory widgetFactory,
final RestService restService) {
final PageFormService pageFormService,
final RestService restService,
final CurrentUser currentUser) {
this.widgetFactory = widgetFactory;
this.pageFormService = pageFormService;
this.restService = restService;
this.currentUser = currentUser;
}
@Override
public void compose(final PageContext pageContext) {
// TODO Auto-generated method stub
if (log.isDebugEnabled()) {
log.debug("Compose User Account Form within PageContext: {}", pageContext);
}
final WidgetFactory widgetFactory = this.pageFormService.getWidgetFactory();
final EntityKey entityKey = pageContext.getEntityKey();
// get data or create new and handle error
final UserAccount userAccount = (entityKey == null)
? new UserMod(
UUID.randomUUID().toString(),
this.currentUser.get().institutionId)
: this.restService
.getBuilder(GetUserAccount.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
.call()
.get(pageContext::notifyError);
if (userAccount == null) {
log.error(
"Failed to get UserAccount. Error was notified to the User. See previous logs for more infomation");
return;
}
}

View file

@ -9,18 +9,25 @@
package ch.ethz.seb.sebserver.gui.service.page.content;
import org.eclipse.swt.widgets.Composite;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
import ch.ethz.seb.sebserver.gui.service.page.action.ActionDefinition;
import ch.ethz.seb.sebserver.gui.service.page.action.UserAccountActions;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.GetUserAccounts;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
import ch.ethz.seb.sebserver.gui.service.table.ColumnDefinition;
import ch.ethz.seb.sebserver.gui.service.table.ColumnDefinition.TableFilterAttribute;
import ch.ethz.seb.sebserver.gui.service.table.EntityTable;
@ -32,22 +39,33 @@ import ch.ethz.seb.sebserver.gui.service.widget.WidgetFactory;
@GuiProfile
public class UserAccountList implements TemplateComposer {
private static final Logger log = LoggerFactory.getLogger(UserAccountList.class);
private final WidgetFactory widgetFactory;
private final RestService restService;
private final CurrentUser currentUser;
private final int pageSize;
protected UserAccountList(
final WidgetFactory widgetFactory,
final RestService restService,
final CurrentUser currentUser,
@Value("${sebserver.gui.list.page.size}") final Integer pageSize) {
this.widgetFactory = widgetFactory;
this.restService = restService;
this.currentUser = currentUser;
this.pageSize = (pageSize != null) ? pageSize : 20;
}
@Override
public void compose(final PageContext pageContext) {
if (log.isDebugEnabled()) {
log.debug("Compose User Account list within PageContext: {}", pageContext);
}
// content page layout with title
final Composite content = this.widgetFactory.defaultPageLayout(
pageContext.getParent(),
new LocTextKey("sebserver.useraccount.list.title"));
@ -87,6 +105,18 @@ public class UserAccountList implements TemplateComposer {
true))
.compose(content);
// propagate content actions to action-pane
pageContext.createAction(ActionDefinition.USER_ACCOUNT_NEW)
.withExec(UserAccountActions::newUserAccount)
.publishIf(() -> this.currentUser.hasPrivilege(PrivilegeType.WRITE, EntityType.USER))
.createAction(ActionDefinition.USER_ACCOUNT_VIEW)
.withSelectionSupplier(table::getSelection)
.withExec(UserAccountActions::viewUserAccountFromList)
.publish()
.createAction(ActionDefinition.USER_ACCOUNT_MODIFY)
.withSelectionSupplier(table::getSelection)
.withExec(UserAccountActions::editUserAccountFromList)
.publishIf(() -> this.currentUser.hasPrivilege(PrivilegeType.MODIFY, EntityType.USER));
}
private String getLocaleDisplayText(final UserInfo userInfo) {

View file

@ -14,6 +14,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.BooleanUtils;
import org.eclipse.rap.rwt.widgets.DialogCallback;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
@ -168,6 +169,11 @@ public class PageContextImpl implements PageContext {
}
}
@Override
public boolean isReadonly() {
return BooleanUtils.toBoolean(getAttribute(AttributeKeys.READ_ONLY, "true"));
}
@Override
public EntityKey getEntityKey() {
if (hasAttribute(AttributeKeys.ENTITY_ID) && hasAttribute(AttributeKeys.ENTITY_TYPE)) {
@ -192,6 +198,9 @@ public class PageContextImpl implements PageContext {
@Override
public PageContext withEntityKey(final EntityKey entityKey) {
if (entityKey == null) {
return this;
}
return withAttribute(AttributeKeys.ENTITY_ID, entityKey.modelId)
.withAttribute(AttributeKeys.ENTITY_TYPE, entityKey.entityType.name());
}
@ -254,7 +263,7 @@ public class PageContextImpl implements PageContext {
public void applyConfirmDialog(final LocTextKey confirmMessage, final Runnable onOK) {
final Message messageBox = new Message(
this.root.getShell(),
this.i18nSupport.getText("org.sebserver.dialog.confirm.title"),
this.i18nSupport.getText("sebserver.dialog.confirm.title"),
this.i18nSupport.getText(confirmMessage),
SWT.OK | SWT.CANCEL);
messageBox.setMarkupEnabled(true);

View file

@ -8,6 +8,8 @@
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api;
import org.springframework.util.MultiValueMap;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
public interface FormBinding {
@ -16,4 +18,6 @@ public interface FormBinding {
String getFormAsJson();
MultiValueMap<String, String> getFormAsQueryAttributes();
}

View file

@ -32,6 +32,7 @@ import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService.SortOrder;
@ -123,14 +124,14 @@ public abstract class RestCall<T> {
return new RestCallBuilder();
}
public final class RestCallBuilder {
public class RestCallBuilder {
private final HttpHeaders httpHeaders = new HttpHeaders();
private String body = null;
private final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
private final Map<String, String> uriVariables = new HashMap<>();
RestCallBuilder() {
protected RestCallBuilder() {
this.httpHeaders.set(
HttpHeaders.CONTENT_TYPE,
RestCall.this.contentType.toString());
@ -192,8 +193,13 @@ public abstract class RestCall<T> {
}
public RestCallBuilder withFormBinding(final FormBinding formBinding) {
return withURIVariable(API.PARAM_MODEL_ID, formBinding.entityKey().modelId)
.withBody(formBinding.getFormAsJson());
final EntityKey entityKey = formBinding.entityKey();
if (entityKey != null) {
return withURIVariable(API.PARAM_MODEL_ID, formBinding.entityKey().modelId)
.withBody(formBinding.getFormAsJson());
} else {
return withQueryParams(formBinding.getFormAsQueryAttributes());
}
}
public RestCallBuilder onlyActive(final boolean active) {
@ -205,14 +211,14 @@ public abstract class RestCall<T> {
return RestCall.this.exchange(this);
}
String buildURI() {
protected String buildURI() {
return RestCall.this.restService.getWebserviceURIBuilder()
.path(RestCall.this.path)
.queryParams(this.queryParams)
.toUriString();
}
HttpEntity<?> buildRequestEntity() {
protected HttpEntity<?> buildRequestEntity() {
if (this.body != null) {
return new HttpEntity<>(this.body, this.httpHeaders);
} else {

View file

@ -31,7 +31,7 @@ public class SaveInstitution extends RestCall<Institution> {
},
HttpMethod.PUT,
MediaType.APPLICATION_JSON_UTF8,
API.INSTITUTION_ENDPOINT + API.MODEL_ID_VAR_PATH_SEGMENT);
API.INSTITUTION_ENDPOINT);
}
}

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2019 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.gui.service.remote.webservice.api.useraccount;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class ActivateUserAccount extends RestCall<EntityProcessingReport> {
protected ActivateUserAccount() {
super(
new TypeReference<EntityProcessingReport>() {
},
HttpMethod.POST,
MediaType.APPLICATION_FORM_URLENCODED,
API.USER_ACCOUNT_ENDPOINT + API.PATH_VAR_ACTIVE);
}
}

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2019 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.gui.service.remote.webservice.api.useraccount;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class DeactivateUserAccount extends RestCall<EntityProcessingReport> {
protected DeactivateUserAccount() {
super(
new TypeReference<EntityProcessingReport>() {
},
HttpMethod.POST,
MediaType.APPLICATION_FORM_URLENCODED,
API.USER_ACCOUNT_ENDPOINT + API.PATH_VAR_INACTIVE);
}
}

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2019 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.gui.service.remote.webservice.api.useraccount;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class NewUserAccount extends RestCall<UserInfo> {
protected NewUserAccount() {
super(
new TypeReference<UserInfo>() {
},
HttpMethod.POST,
MediaType.APPLICATION_FORM_URLENCODED,
API.USER_ACCOUNT_ENDPOINT);
}
}

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2019 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.gui.service.remote.webservice.api.useraccount;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class SaveUserAccount extends RestCall<UserInfo> {
protected SaveUserAccount() {
super(
new TypeReference<UserInfo>() {
},
HttpMethod.PUT,
MediaType.APPLICATION_JSON_UTF8,
API.USER_ACCOUNT_ENDPOINT);
}
}

View file

@ -15,6 +15,8 @@ import java.nio.charset.StandardCharsets;
import java.util.Base64;
import org.apache.commons.codec.binary.Base64InputStream;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.rap.fileupload.FileDetails;
import org.eclipse.rap.fileupload.FileUploadHandler;
import org.eclipse.rap.fileupload.FileUploadReceiver;
@ -29,7 +31,6 @@ import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StreamUtils;
import ch.ethz.seb.sebserver.gui.service.push.ServerPushContext;
import ch.ethz.seb.sebserver.gui.service.push.ServerPushService;
@ -68,7 +69,7 @@ public class ImageUpload extends Composite {
final String contentType = details.getContentType();
if (contentType != null && contentType.startsWith("image")) {
ImageUpload.this.imageBase64 = Base64.getEncoder()
.encodeToString(StreamUtils.copyToByteArray(stream));
.encodeToString(IOUtils.toByteArray(stream));
}
} catch (final Exception e) {
log.error("Error while trying to upload image", e);
@ -116,7 +117,7 @@ public class ImageUpload extends Composite {
}
public void setImageBase64(final String imageBase64) {
if (imageBase64 == null) {
if (StringUtils.isBlank(imageBase64)) {
return;
}

View file

@ -73,11 +73,10 @@ public interface EntityDAO<T extends Entity, M extends ModelIdAware> {
/** Use this to save/modify an entity.
*
* @param modelId the model id of the entity to save
* @param data entity instance containing all data that should be saved
* @return A Result of the entity instance where the successfully saved/modified entity data is available or a
* reported exception on error case */
Result<T> save(String modelId, M data);
Result<T> save(T data);
/** Use this to delete a set Entity by a Collection of EntityKey
*

View file

@ -17,30 +17,69 @@ import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.SEBServerUser;
public interface UserActivityLogDAO
extends EntityDAO<UserActivityLog, UserActivityLog>, UserRelatedEntityDAO<UserActivityLog> {
public interface UserActivityLogDAO extends
EntityDAO<UserActivityLog, UserActivityLog>,
UserRelatedEntityDAO<UserActivityLog> {
/** All activity types */
enum ActivityType {
CREATE,
IMPORT,
MODIFY,
DEACTIVATE,
ACTIVATE,
ARCHIVE,
DELETE
}
/** Create a user activity log entry for the current user of activity type CREATE
*
* @param entity the Entity
* @return Result of the Entity or referring to an Error id happened */
public <E extends Entity> Result<E> logCreate(E entity);
/** Create a user activity log entry for the current user of activity type IMPORT
*
* @param entity the Entity
* @return Result of the Entity or referring to an Error id happened */
public <E extends Entity> Result<E> logImport(E entity);
/** Create a user activity log entry for the current user of activity type MODIFY
*
* @param entity the Entity
* @return Result of the Entity or referring to an Error id happened */
public <E extends Entity> Result<E> logModify(E entity);
/** Create a user activity log entry for the current user of activity type ACTIVATE
*
* @param entity the Entity
* @return Result of the Entity or referring to an Error id happened */
public <E extends Entity> Result<E> logActivate(E entity);
/** Create a user activity log entry for the current user of activity type DEACTIVATE
*
* @param entity the Entity
* @return Result of the Entity or referring to an Error id happened */
public <E extends Entity> Result<E> logDeactivate(E entity);
/** Create a user activity log entry for the current user of activity type DELETE
*
* @param entity the Entity
* @return Result of the Entity or referring to an Error id happened */
public <E extends Entity> Result<E> logDelete(E entity);
/** Creates a user activity log entry for the current user.
*
* @param activityType the activity type
* @param entity the Entity
* @param message an optional message */
* @param message an optional message
* @return Result of the Entity or referring to an Error id happened */
<E extends Entity> Result<E> log(ActivityType activityType, E entity, String message);
/** Creates a user activity log entry for the current user.
*
* @param actionType the action type
* @param entity the Entity */
* @param entity the Entity
* @return Result of the Entity or referring to an Error id happened */
<E extends Entity> Result<E> log(ActivityType activityType, E entity);
/** Creates a user activity log entry for the current user.
@ -54,7 +93,8 @@ public interface UserActivityLogDAO
*
* @param activityType the activity type
* @param entityType the EntityType
* @param message the message */
* @param message the message
* @return Result of the Entity or referring to an Error id happened */
<T> Result<T> log(ActivityType activityType, EntityType entityType, String entityId, String message, T data);
/** Creates a user activity log entry.
@ -62,7 +102,8 @@ public interface UserActivityLogDAO
* @param user for specified SEBServerUser instance
* @param activityType the activity type
* @param entity the Entity
* @param message an optional message */
* @param message an optional message
* @return Result of the Entity or referring to an Error id happened */
<E extends Entity> Result<E> log(
SEBServerUser user,
ActivityType activityType,
@ -74,7 +115,8 @@ public interface UserActivityLogDAO
* @param user for specified SEBServerUser instance
* @param activityType the activity type
* @param entityType the entity type
* @param entityId the entity id (primary key or UUID) */
* @param entityId the entity id (primary key or UUID)
* @return Result of the Entity or referring to an Error id happened */
default <E extends Entity> Result<E> log(
final SEBServerUser user,
final ActivityType activityType,

View file

@ -22,11 +22,11 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionSuppor
* within SEBServerUser. */
public interface UserDAO extends ActivatableEntityDAO<UserInfo, UserMod>, BulkActionSupportDAO<UserInfo> {
/** Use this to get the user id (PK) from a given UUID.
/** Use this to get the user id (PK) from a given modelId (users UUID).
*
* @param uuid The UUID of the user
* @return the user id (PK) from a given UUID. */
Result<Long> pkForModelId(String uuid);
Result<Long> pkForModelId(String modelId);
/** Use this to get UserInfo by users username
*
@ -34,20 +34,20 @@ public interface UserDAO extends ActivatableEntityDAO<UserInfo, UserMod>, BulkAc
* @return a Result of UserInfo data from user with the specified username. Or an exception result on error case */
Result<UserInfo> byUsername(String username);
/** Set given password as new password for specified user account.
*
* @param modelId the model id of the user account to change the password
* @param newPassword the new verified password that is encrypted and stored as the new password for the user
* account
* @return a Result of user account information. Or an exception result on error case */
Result<UserInfo> changePassword(String modelId, String newPassword);
/** Use this to get the SEBServerUser principal for a given username.
*
* @param username The username of the user to get SEBServerUser from
* @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 filtered UserInfo. The filter criteria
// * from given UserFilter instance will be translated to SQL query and
// * the filtering happens on data-base level
// *
// * @param filter The UserFilter instance containing all filter criteria
// * @return a Result of Collection of filtered UserInfo. Or an exception result on error case */
// Result<Collection<UserInfo>> allMatching(final UserFilter filter);
/** Use this to get a Collection containing EntityKey's of all entities that belongs to a given User.
*
* @param uuid The UUID of the user

View file

@ -157,12 +157,11 @@ public class ExamDAOImpl implements ExamDAO {
@Override
@Transactional
public Result<Exam> save(final String modelId, final Exam exam) {
public Result<Exam> save(final Exam exam) {
return Result.tryCatch(() -> {
final Long pk = Long.parseLong(modelId);
final ExamRecord examRecord = new ExamRecord(
pk,
exam.id,
null, null, null, null,
(exam.supporter != null)
? StringUtils.join(exam.supporter, Constants.LIST_SEPARATOR_CHAR)
@ -173,7 +172,7 @@ public class ExamDAOImpl implements ExamDAO {
BooleanUtils.toIntegerObject(exam.active));
this.examRecordMapper.updateByPrimaryKeySelective(examRecord);
return this.examRecordMapper.selectByPrimaryKey(pk);
return this.examRecordMapper.selectByPrimaryKey(exam.id);
})
.flatMap(this::toDomainModel)
.onErrorDo(TransactionHandler::rollback);

View file

@ -122,12 +122,11 @@ public class IndicatorDAOImpl implements IndicatorDAO {
@Override
@Transactional
public Result<Indicator> save(final String modelId, final Indicator modified) {
public Result<Indicator> save(final Indicator modified) {
return Result.tryCatch(() -> {
final Long pk = Long.parseLong(modelId);
final IndicatorRecord newRecord = new IndicatorRecord(
pk,
modified.id,
null,
modified.type.name(),
modified.name,
@ -137,7 +136,7 @@ public class IndicatorDAOImpl implements IndicatorDAO {
// update also the thresholds
this.thresholdRecordMapper.deleteByExample()
.where(ThresholdRecordDynamicSqlSupport.indicatorId, isEqualTo(pk))
.where(ThresholdRecordDynamicSqlSupport.indicatorId, isEqualTo(modified.id))
.build()
.execute();
@ -145,12 +144,12 @@ public class IndicatorDAOImpl implements IndicatorDAO {
.stream()
.map(threshold -> new ThresholdRecord(
null,
pk,
modified.id,
new BigDecimal(threshold.value),
threshold.color))
.forEach(this.thresholdRecordMapper::insert);
return this.indicatorRecordMapper.selectByPrimaryKey(pk);
return this.indicatorRecordMapper.selectByPrimaryKey(modified.id);
})
.flatMap(this::toDomainModel)
.onErrorDo(TransactionHandler::rollback);

View file

@ -26,8 +26,8 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.FieldValidationException;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.institution.Institution;
import ch.ethz.seb.sebserver.gbl.util.Result;
@ -129,7 +129,7 @@ public class InstitutionDAOImpl implements InstitutionDAO {
@Override
@Transactional
public Result<Institution> save(final String modelId, final Institution institution) {
public Result<Institution> save(final Institution institution) {
return Result.tryCatch(() -> {
final Long count = this.institutionRecordMapper.countByExample()
@ -142,16 +142,15 @@ public class InstitutionDAOImpl implements InstitutionDAO {
throw new FieldValidationException("name", "institution:name:exists");
}
final Long pk = Long.parseLong(modelId);
final InstitutionRecord newRecord = new InstitutionRecord(
pk,
institution.id,
institution.name,
institution.urlSuffix,
null,
institution.logoImage);
this.institutionRecordMapper.updateByPrimaryKeySelective(newRecord);
return this.institutionRecordMapper.selectByPrimaryKey(pk);
return this.institutionRecordMapper.selectByPrimaryKey(institution.id);
})
.flatMap(InstitutionDAOImpl::toDomainModel)
.onErrorDo(TransactionHandler::rollback);

View file

@ -134,12 +134,11 @@ public class LmsSetupDAOImpl implements LmsSetupDAO {
@Override
@Transactional
public Result<LmsSetup> save(final String modelId, final LmsSetup lmsSetup) {
public Result<LmsSetup> save(final LmsSetup lmsSetup) {
return Result.tryCatch(() -> {
final Long pk = Long.parseLong(modelId);
final LmsSetupRecord newRecord = new LmsSetupRecord(
pk,
lmsSetup.id,
lmsSetup.institutionId,
lmsSetup.name,
(lmsSetup.lmsType != null) ? lmsSetup.lmsType.name() : null,
@ -156,7 +155,7 @@ public class LmsSetupDAOImpl implements LmsSetupDAO {
null);
this.lmsSetupRecordMapper.updateByPrimaryKeySelective(newRecord);
return this.lmsSetupRecordMapper.selectByPrimaryKey(pk);
return this.lmsSetupRecordMapper.selectByPrimaryKey(lmsSetup.id);
})
.flatMap(LmsSetupDAOImpl::toDomainModel)
.onErrorDo(TransactionHandler::rollback);

View file

@ -65,6 +65,42 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
return EntityType.USER_ACTIVITY_LOG;
}
@Override
@Transactional
public <E extends Entity> Result<E> logCreate(final E entity) {
return log(ActivityType.CREATE, entity);
}
@Override
@Transactional
public <E extends Entity> Result<E> logImport(final E entity) {
return log(ActivityType.IMPORT, entity);
}
@Override
@Transactional
public <E extends Entity> Result<E> logModify(final E entity) {
return log(ActivityType.MODIFY, entity);
}
@Override
@Transactional
public <E extends Entity> Result<E> logActivate(final E entity) {
return log(ActivityType.ACTIVATE, entity);
}
@Override
@Transactional
public <E extends Entity> Result<E> logDeactivate(final E entity) {
return log(ActivityType.DEACTIVATE, entity);
}
@Override
@Transactional
public <E extends Entity> Result<E> logDelete(final E entity) {
return log(ActivityType.DELETE, entity);
}
@Override
@Transactional
public <E extends Entity> Result<E> log(
@ -288,7 +324,7 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
@Override
@Transactional
public Result<UserActivityLog> save(final String modelId, final UserActivityLog modified) {
public Result<UserActivityLog> save(final UserActivityLog modified) {
// TODO Auto-generated method stub
return Result.ofTODO();
}

View file

@ -32,9 +32,9 @@ import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import ch.ethz.seb.sebserver.WebSecurityConfig;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
import ch.ethz.seb.sebserver.gbl.model.user.UserMod;
@ -180,7 +180,7 @@ public class UserDaoImpl implements UserDAO {
return Result.tryCatch(() -> {
if (!userMod.newPasswordMatch()) {
throw new APIMessageException(ErrorMessage.PASSWORD_MISSMATCH);
throw new APIMessageException(ErrorMessage.PASSWORD_MISMATCH);
}
final UserRecord recordToSave = new UserRecord(
@ -209,28 +209,46 @@ public class UserDaoImpl implements UserDAO {
@Override
@Transactional
public Result<UserInfo> save(final String modelId, final UserMod userMod) {
public Result<UserInfo> changePassword(final String modelId, final String newPassword) {
return recordByUUID(modelId)
.map(record -> {
final boolean changePWD = userMod.passwordChangeRequest();
if (changePWD && !userMod.newPasswordMatch()) {
throw new APIMessageException(ErrorMessage.PASSWORD_MISSMATCH);
}
final UserRecord newRecord = new UserRecord(
record.getId(),
null,
null,
userMod.name,
userMod.username,
(changePWD) ? this.userPasswordEncoder.encode(userMod.getNewPassword()) : null,
userMod.email,
userMod.locale.toLanguageTag(),
userMod.timeZone.getID(),
null,
null,
this.userPasswordEncoder.encode(newPassword),
null,
null,
null,
null);
this.userRecordMapper.updateByPrimaryKeySelective(newRecord);
return this.userRecordMapper.selectByPrimaryKey(record.getId());
})
.flatMap(this::toDomainModel)
.onErrorDo(TransactionHandler::rollback);
}
@Override
@Transactional
public Result<UserInfo> save(final UserInfo userInfo) {
return recordByUUID(userInfo.uuid)
.map(record -> {
final UserRecord newRecord = new UserRecord(
record.getId(),
null,
null,
userInfo.name,
userInfo.username,
null,
userInfo.email,
userInfo.locale.toLanguageTag(),
userInfo.timeZone.getID(),
null);
this.userRecordMapper.updateByPrimaryKeySelective(newRecord);
updateRolesForUser(record.getId(), userMod.roles);
updateRolesForUser(record.getId(), userInfo.roles);
return this.userRecordMapper.selectByPrimaryKey(record.getId());
})
.flatMap(this::toDomainModel)

View file

@ -47,7 +47,6 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionServic
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.EntityDAO;
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.ActivityType;
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
public abstract class EntityController<T extends GrantEntity, M extends GrantEntity> {
@ -250,8 +249,8 @@ public abstract class EntityController<T extends GrantEntity, M extends GrantEnt
return this.beanValidationService.validateBean(requestModel)
.flatMap(this.entityDAO::createNew)
.flatMap(entity -> this.userActivityLogDAO.log(ActivityType.CREATE, entity))
.flatMap(entity -> this.notifySaved(requestModel, entity))
.flatMap(this.userActivityLogDAO::logCreate)
.flatMap(this::notifyCreated)
.getOrThrow();
}
@ -260,19 +259,16 @@ public abstract class EntityController<T extends GrantEntity, M extends GrantEnt
// ****************
@RequestMapping(
path = "/{modelId}",
method = RequestMethod.PUT,
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public T savePut(
@PathVariable final String modelId,
@Valid @RequestBody final M modifyData) {
public T savePut(@Valid @RequestBody final T modifyData) {
return this.beanValidationService.validateBean(modifyData)
.flatMap(this.authorization::checkModify)
.flatMap(m -> this.entityDAO.save(modelId, m))
.flatMap(e -> this.userActivityLogDAO.log(ActivityType.MODIFY, e))
.flatMap(e -> notifySaved(modifyData, e))
return this.authorization.checkModify(modifyData)
.flatMap(this::validForSave)
.flatMap(this.entityDAO::save)
.flatMap(this.userActivityLogDAO::logModify)
.flatMap(this::notifySaved)
.getOrThrow();
}
@ -339,7 +335,19 @@ public abstract class EntityController<T extends GrantEntity, M extends GrantEnt
this.authorization::hasReadonlyGrant);
}
protected Result<T> notifySaved(final M modifyData, final T entity) {
protected Result<T> notifyCreated(final T entity) {
return Result.of(entity);
}
protected Result<T> validForSave(final T entity) {
if (entity.getModelId() != null) {
return Result.of(entity);
} else {
return Result.ofError(new IllegalAPIArgumentException("Missing model identifier"));
}
}
protected Result<T> notifySaved(final T entity) {
return Result.of(entity);
}

View file

@ -223,7 +223,7 @@ public class ExamAdministrationController extends ActivatableEntityController<Ex
.getOrThrow();
return this.indicatorDAO
.save(id, indicator)
.save(indicator)
.getOrThrow();
}

View file

@ -73,11 +73,7 @@ public class InstitutionController extends ActivatableEntityController<Instituti
@Override
protected Institution createNew(final POSTMapper postParams) {
final Institution institution = new Institution(null, postParams);
if (this.institutionDAO.exists(institution.name)) {
throw new IllegalAPIArgumentException("institution:name:unique:" + institution.name);
}
return institution;
return new Institution(null, postParams);
}
}

View file

@ -8,14 +8,22 @@
package ch.ethz.seb.sebserver.webservice.weblayer.api;
import javax.validation.Valid;
import org.mybatis.dynamic.sql.SqlTable;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.http.MediaType;
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.RestController;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.model.user.PasswordChange;
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;
@ -25,6 +33,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO.ActivityType;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
import ch.ethz.seb.sebserver.webservice.weblayer.oauth.RevokeTokenEndpoint;
@ -35,9 +44,10 @@ import ch.ethz.seb.sebserver.webservice.weblayer.oauth.RevokeTokenEndpoint;
public class UserAccountController extends ActivatableEntityController<UserInfo, UserMod> {
private final ApplicationEventPublisher applicationEventPublisher;
private final UserDAO userDAO;
public UserAccountController(
final UserDAO userDao,
final UserDAO userDAO,
final AuthorizationService authorization,
final UserActivityLogDAO userActivityLogDAO,
final PaginationService paginationService,
@ -47,11 +57,12 @@ public class UserAccountController extends ActivatableEntityController<UserInfo,
super(authorization,
bulkActionService,
userDao,
userDAO,
userActivityLogDAO,
paginationService,
beanValidationService);
this.applicationEventPublisher = applicationEventPublisher;
this.userDAO = userDAO;
}
@RequestMapping(path = "/me", method = RequestMethod.GET)
@ -72,19 +83,39 @@ public class UserAccountController extends ActivatableEntityController<UserInfo,
return UserRecordDynamicSqlSupport.userRecord;
}
@Override
protected Result<UserInfo> notifySaved(final UserMod userData, final UserInfo userInfo) {
// handle password change; revoke access tokens if password has changed
if (userData.passwordChangeRequest() && userData.newPasswordMatch()) {
this.applicationEventPublisher.publishEvent(
new RevokeTokenEndpoint.RevokeTokenEvent(this, userInfo.username));
}
return Result.of(userInfo);
}
@Override
protected UserMod createNew(final POSTMapper postParams) {
return new UserMod(null, postParams);
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT + API.PASSWORD_PATH_SEGMENT,
method = RequestMethod.PUT,
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public UserInfo changePassword(
@PathVariable final String modelId,
@Valid @RequestBody final PasswordChange passwordChange) {
if (!passwordChange.newPasswordMatch()) {
throw new APIMessageException(ErrorMessage.PASSWORD_MISMATCH);
}
return this.userDAO.byModelId(modelId)
.flatMap(this.authorization::checkWrite)
.flatMap(e -> this.userDAO.changePassword(modelId, passwordChange.getNewPassword()))
.flatMap(this::revokeAccessToken)
.flatMap(e -> this.userActivityLogDAO.log(ActivityType.MODIFY, e))
.getOrThrow();
}
private Result<UserInfo> revokeAccessToken(final UserInfo userInfo) {
return Result.tryCatch(() -> {
this.applicationEventPublisher.publishEvent(
new RevokeTokenEndpoint.RevokeTokenEvent(userInfo, userInfo.username));
return userInfo;
});
}
}

View file

@ -13,6 +13,18 @@ sebserver.overall.action.modify.cancel.confirm=Are you sure to cancel? Modificat
sebserver.overall.action.filter=Apply Filter
sebserver.overall.action.filter.clear=Clear Filter Criteria
################################
# Form validation and messages
################################
sebserver.form.validation.fieldError.size=The size must be between {3} and {4}
sebserver.form.validation.fieldError.name=Name is mandatory and must have a size between 3 and 255 character
sebserver.form.validation.fieldError.urlSuffix=URL Suffix must have a size between 3 and 255 character
sebserver.form.validation.fieldError.notNull=This field is mandatory
sebserver.error.unexpected=Unexpected Error
sebserver.page.message=Information
sebserver.dialog.confirm.title=Confirmation
################################
# Login Page
################################
@ -51,15 +63,17 @@ sebserver.institution.list.column.urlSuffix=URL Suffix
sebserver.institution.list.column.active=Active
sebserver.institution.action.new=New Institution
sebserver.institution.action.view=View Institution
sebserver.institution.action.list.view=View Selected
sebserver.institution.action.modify=Edit Institution
sebserver.institution.action.list.modify=Edit Selected
sebserver.institution.action.save=Save Institution
sebserver.institution.action.activate=Active
sebserver.institution.action.deactivate=Active
sebserver.institution.action.delete=Delete Institution
sebserver.institution.info.pleaseSelect=Please Select an Institution from the Table first.
sebserver.institution.form.title=Institution ({0})
sebserver.institution.info.pleaseSelect=Please Select an Institution first.
sebserver.institution.form.title.new=New Institution
sebserver.institution.form.title=Institution : {0}
sebserver.institution.form.name=Name
sebserver.institution.form.urlSuffix=URL Suffix
@ -78,14 +92,15 @@ sebserver.useraccount.list.column.email=Mail
sebserver.useraccount.list.column.language=Language
sebserver.useraccount.list.column.active=Active
sebserver.useraccount.action.new=New User Account
sebserver.useraccount.action.view=View Selected
sebserver.useraccount.action.modify=Edit Selected
sebserver.useraccount.action.save=Save User Account
sebserver.useraccount.action.activate=Active
sebserver.useraccount.action.deactivate=Active
sebserver.useraccount.action.delete=Delete User Account
sebserver.useraccount.info.pleaseSelect=Please Select an User Account first.
################################
# Form validation and messages
################################
sebserver.form.validation.fieldError.size=The size must be between {3} and {4}
sebserver.form.validation.fieldError.name=Name is mandatory and must have a size between 3 and 255 character
sebserver.form.validation.fieldError.urlSuffix=URL Suffix must have a size between 3 and 255 character
sebserver.error.unexpected=Unexpected Error
sebserver.page.message=Information

View file

@ -169,7 +169,7 @@ Text.error {
color: #4a4a4a;
background-repeat: repeat;
background-position: left top;
background-color: #A8322D;
background-color: #82BE1E;
background-image: none;
text-shadow: none;
box-shadow: none;
@ -247,7 +247,7 @@ Shell-Titlebar.message {
background-color: #1f407a;
background-gradient-color: #1f407a;
color: white;
background-image: gradient( linear, left top, left bottom, from( #0069B4 ), to( #0069B4 ) );
background-image: gradient( linear, left top, left bottom, from( #1f407a ), to( #1f407a ) );
padding: 2px 5px 2px;
margin: 0px;
height: 22px;

View file

@ -187,7 +187,7 @@ public class InstitutionAPITest extends AdministrationAPIIntegrationTester {
assertNotNull(errorMessage);
assertTrue(errorMessage.size() > 0);
assertEquals("1010", errorMessage.get(0).messageCode);
assertEquals("1200", errorMessage.get(0).messageCode);
// and predefined id should be ignored
institution = new RestAPITestHelper()
@ -241,10 +241,9 @@ public class InstitutionAPITest extends AdministrationAPIIntegrationTester {
// modify
institution = new RestAPITestHelper()
.withAccessToken(sebAdminAccess)
.withPath(API.INSTITUTION_ENDPOINT).withPath("/")
.withPath(String.valueOf(institution.id))
.withPath(API.INSTITUTION_ENDPOINT)
.withMethod(HttpMethod.PUT)
.withBodyJson(new Institution(null, "testInstitution", "testSuffix", null, null))
.withBodyJson(new Institution(institution.id, "testInstitution", "testSuffix", null, null))
.withExpectedStatus(HttpStatus.OK)
.getAsObject(new TypeReference<Institution>() {
});

View file

@ -68,7 +68,7 @@ public class LmsSetupAPITest extends AdministrationAPIIntegrationTester {
lmsSetup = new RestAPITestHelper()
.withAccessToken(getAdminInstitution1Access())
.withPath(API.LMS_SETUP_ENDPOINT + "/" + lmsSetup.id)
.withPath(API.LMS_SETUP_ENDPOINT)
.withMethod(HttpMethod.PUT)
.withBodyJson(modified)
.withExpectedStatus(HttpStatus.OK)

View file

@ -39,6 +39,7 @@ import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.EntityName;
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.model.user.PasswordChange;
import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
import ch.ethz.seb.sebserver.gbl.model.user.UserMod;
@ -572,8 +573,8 @@ public class UserAPITest extends AdministrationAPIIntegrationTester {
assertEquals("[EXAM_SUPPORTER]", String.valueOf(user.roles));
// change userName, email and roles
final UserMod modifyUser = new UserMod(new UserInfo(
null,
final UserInfo modifyUser = new UserInfo(
user.uuid,
user.getInstitutionId(),
user.getName(),
"newUser1",
@ -581,17 +582,14 @@ public class UserAPITest extends AdministrationAPIIntegrationTester {
user.getActive(),
user.getLocale(),
user.getTimeZone(),
Stream.of(UserRole.EXAM_ADMIN.name(), UserRole.EXAM_SUPPORTER.name()).collect(Collectors.toSet())),
null, null);
Stream.of(UserRole.EXAM_ADMIN.name(), UserRole.EXAM_SUPPORTER.name()).collect(Collectors.toSet()));
final String modifyUserJson = this.jsonMapper.writeValueAsString(modifyUser);
UserInfo modifiedUserResult = this.jsonMapper.readValue(
this.mockMvc
.perform(
put(this.endpoint + API.USER_ACCOUNT_ENDPOINT + "/" + user.getUuid())
.header("Authorization", "Bearer " + token)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(modifyUserJson))
this.mockMvc.perform(put(this.endpoint + API.USER_ACCOUNT_ENDPOINT)
.header("Authorization", "Bearer " + token)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(modifyUserJson))
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString(),
new TypeReference<UserInfo>() {
@ -668,10 +666,9 @@ public class UserAPITest extends AdministrationAPIIntegrationTester {
new TypeReference<UserInfo>() {
});
final UserMod modifiedUser = new UserMod(examAdmin, null, null);
final String modifiedUserJson = this.jsonMapper.writeValueAsString(modifiedUser);
final String modifiedUserJson = this.jsonMapper.writeValueAsString(examAdmin);
this.mockMvc.perform(put(this.endpoint + API.USER_ACCOUNT_ENDPOINT + "/" + modifiedUser.uuid)
this.mockMvc.perform(put(this.endpoint + API.USER_ACCOUNT_ENDPOINT)
.header("Authorization", "Bearer " + examAdminToken1)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(modifiedUserJson))
@ -694,12 +691,11 @@ public class UserAPITest extends AdministrationAPIIntegrationTester {
.andReturn().getResponse().getContentAsString();
final UserInfo userInfo = new UserInfo(
null, 2L, "NewTestUser", "NewTestUser",
"NewTestUser", 2L, "NewTestUser", "NewTestUser",
"", true, Locale.CANADA, DateTimeZone.UTC,
new HashSet<>(Arrays.asList(UserRole.EXAM_ADMIN.name())));
final UserMod newUser = new UserMod(userInfo, "12345678", "12345678");
final String newUserJson = this.jsonMapper.writeValueAsString(newUser);
this.mockMvc.perform(put(this.endpoint + API.USER_ACCOUNT_ENDPOINT + "/NewTestUser")
final String newUserJson = this.jsonMapper.writeValueAsString(userInfo);
this.mockMvc.perform(put(this.endpoint + API.USER_ACCOUNT_ENDPOINT)
.header("Authorization", "Bearer " + token)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(newUserJson))
@ -722,12 +718,12 @@ public class UserAPITest extends AdministrationAPIIntegrationTester {
.andReturn().getResponse().getContentAsString();
final UserInfo userInfo = new UserInfo(
null, 2L, "NewTestUser", "NewTestUser",
"NewTestUser", 2L, "NewTestUser", "NewTestUser",
"", true, Locale.CANADA, DateTimeZone.UTC,
new HashSet<>(Arrays.asList(UserRole.EXAM_ADMIN.name())));
final UserMod newUser = new UserMod(userInfo, "12345678", "12345678");
final String newUserJson = this.jsonMapper.writeValueAsString(newUser);
this.mockMvc.perform(put(this.endpoint + API.USER_ACCOUNT_ENDPOINT + "/NewTestUser")
//final UserMod newUser = new UserMod(userInfo, "12345678", "12345678");
final String newUserJson = this.jsonMapper.writeValueAsString(userInfo);
this.mockMvc.perform(put(this.endpoint + API.USER_ACCOUNT_ENDPOINT)
.header("Authorization", "Bearer " + token)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(newUserJson))
@ -751,16 +747,16 @@ public class UserAPITest extends AdministrationAPIIntegrationTester {
new TypeReference<UserInfo>() {
});
final UserMod modifiedUser = new UserMod(
UserInfo.of(examAdmin1),
final PasswordChange passwordChange = new PasswordChange(
"newPassword",
"newPassword");
final String modifiedUserJson = this.jsonMapper.writeValueAsString(modifiedUser);
final String modifiedUserJson = this.jsonMapper.writeValueAsString(passwordChange);
this.mockMvc.perform(put(this.endpoint + API.USER_ACCOUNT_ENDPOINT + "/" + modifiedUser.uuid)
.header("Authorization", "Bearer " + sebAdminToken)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(modifiedUserJson))
this.mockMvc.perform(
put(this.endpoint + API.USER_ACCOUNT_ENDPOINT + "/" + examAdmin1.uuid + API.PASSWORD_PATH_SEGMENT)
.header("Authorization", "Bearer " + sebAdminToken)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(modifiedUserJson))
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString();
@ -799,18 +795,18 @@ public class UserAPITest extends AdministrationAPIIntegrationTester {
});
// must be longer then 8 chars
UserMod modifiedUser = new UserMod(
UserInfo.of(examAdmin1),
PasswordChange passwordChange = new PasswordChange(
"new",
"new");
String modifiedUserJson = this.jsonMapper.writeValueAsString(modifiedUser);
String modifiedUserJson = this.jsonMapper.writeValueAsString(passwordChange);
List<APIMessage> messages = this.jsonMapper.readValue(
this.mockMvc.perform(
put(this.endpoint + API.USER_ACCOUNT_ENDPOINT + "/" + modifiedUser.uuid)
.header("Authorization", "Bearer " + sebAdminToken)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(modifiedUserJson))
put(this.endpoint + API.USER_ACCOUNT_ENDPOINT + "/" + examAdmin1.uuid
+ API.PASSWORD_PATH_SEGMENT)
.header("Authorization", "Bearer " + sebAdminToken)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(modifiedUserJson))
.andExpect(status().isBadRequest())
.andReturn().getResponse().getContentAsString(),
new TypeReference<List<APIMessage>>() {
@ -822,18 +818,18 @@ public class UserAPITest extends AdministrationAPIIntegrationTester {
assertEquals("[user, password, size, 8, 255, new]", String.valueOf(messages.get(0).getAttributes()));
// wrong password retype
modifiedUser = new UserMod(
UserInfo.of(examAdmin1),
passwordChange = new PasswordChange(
"12345678",
"87654321");
modifiedUserJson = this.jsonMapper.writeValueAsString(modifiedUser);
modifiedUserJson = this.jsonMapper.writeValueAsString(passwordChange);
messages = this.jsonMapper.readValue(
this.mockMvc.perform(
put(this.endpoint + API.USER_ACCOUNT_ENDPOINT + "/" + modifiedUser.uuid)
.header("Authorization", "Bearer " + sebAdminToken)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(modifiedUserJson))
put(this.endpoint + API.USER_ACCOUNT_ENDPOINT + "/" + examAdmin1.uuid
+ API.PASSWORD_PATH_SEGMENT)
.header("Authorization", "Bearer " + sebAdminToken)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(modifiedUserJson))
.andExpect(status().isBadRequest())
.andReturn().getResponse().getContentAsString(),
new TypeReference<List<APIMessage>>() {