From 97bf08e602aa6a0ad9786caf49dae106bb36c52c Mon Sep 17 00:00:00 2001 From: anhefti Date: Mon, 25 Feb 2019 10:13:18 +0100 Subject: [PATCH] SEBSERV-27 & SEBSERV-21 password change and refactoring --- .../gbl/model/user/PasswordChange.java | 40 +++- .../seb/sebserver/gbl/model/user/UserMod.java | 15 +- .../gui/content/InstitutionForm.java | 19 +- .../gui/content/InstitutionList.java | 23 +- .../seb/sebserver/gui/content/MainPage.java | 24 +-- .../UserAccountChangePasswordForm.java | 97 +++++++++ .../gui/content/UserAccountForm.java | 69 +++--- .../gui/content/UserAccountList.java | 13 +- .../gui/content/action/ActionDefinition.java | 200 ++++++++++++++---- .../gui/content/action/ActionPane.java | 2 +- .../content/action/InstitutionActions.java | 87 +++----- .../content/action/UserAccountActions.java | 87 +++----- .../gui/content/activity/ActivitiesPane.java | 182 +++++----------- .../gui/content/activity/Activity.java | 81 ------- .../seb/sebserver/gui/form/FieldBuilder.java | 15 +- .../ch/ethz/seb/sebserver/gui/form/Form.java | 1 - .../seb/sebserver/gui/form/FormBuilder.java | 17 +- .../seb/sebserver/gui/form/FormHandle.java | 25 ++- .../gui/form/ImageUploadFieldBuilder.java | 4 +- .../gui/form/SelectionFieldBuilder.java | 74 ++++--- .../sebserver/gui/form/TextFieldBuilder.java | 4 +- .../gui/service/page/PageContext.java | 14 +- .../sebserver/gui/service/page/PageUtils.java | 2 + .../gui/service/page/action/Action.java | 70 +++++- .../page/activity/ActivityActionHandler.java | 26 --- .../page/activity/ActivitySelection.java | 83 -------- .../gui/service/page/event/ActionEvent.java | 16 +- .../page/event/ActionEventListener.java | 79 ++++--- .../page/event/ActivitySelectionEvent.java | 21 -- .../page/event/ActivitySelectionListener.java | 18 -- .../gui/service/page/impl/MainPageState.java | 7 +- .../service/page/impl/PageContextImpl.java | 46 ++-- .../remote/webservice/api/RestCall.java | 1 - .../api/useraccount/ChangePassword.java | 37 ++++ .../auth/SEBServerAuthorizationContext.java | 31 +++ .../seb/sebserver/gui/table/EntityTable.java | 9 + .../sebserver/gui/widget/MultiSelection.java | 27 ++- .../sebserver/gui/widget/WidgetFactory.java | 13 +- .../bulkaction/BulkActionService.java | 6 +- .../dao/ActivatableEntityDAO.java | 6 + .../servicelayer/dao/UserActivityLogDAO.java | 1 + .../webservice/servicelayer/dao/UserDAO.java | 1 + .../servicelayer/dao/impl/ExamDAOImpl.java | 15 ++ .../dao/impl/InstitutionDAOImpl.java | 16 +- .../dao/impl/LmsSetupDAOImpl.java | 15 ++ .../servicelayer/dao/impl/UserDaoImpl.java | 27 ++- .../validation/BeanValidationService.java | 27 ++- .../weblayer/WebServiceUserDetails.java | 1 + .../api/ActivatableEntityController.java | 9 +- .../weblayer/api/EntityController.java | 1 + .../weblayer/api/UserAccountController.java | 67 ++++-- src/main/resources/data-demo.sql | 24 ++- src/main/resources/messages.properties | 17 +- src/main/resources/static/css/sebserver.css | 63 ++++-- .../api/admin/InstitutionAPITest.java | 6 +- .../integration/api/admin/UserAPITest.java | 90 ++++++-- 56 files changed, 1135 insertions(+), 836 deletions(-) create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountChangePasswordForm.java delete mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/content/activity/Activity.java delete mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/page/activity/ActivityActionHandler.java delete mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/page/activity/ActivitySelection.java delete mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActivitySelectionEvent.java delete mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActivitySelectionListener.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/useraccount/ChangePassword.java diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/PasswordChange.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/PasswordChange.java index 47fb306a..8d61819d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/PasswordChange.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/PasswordChange.java @@ -14,13 +14,26 @@ import javax.validation.constraints.Size; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -public class PasswordChange { +import ch.ethz.seb.sebserver.gbl.api.EntityType; +import ch.ethz.seb.sebserver.gbl.model.Domain.USER; +import ch.ethz.seb.sebserver.gbl.model.Entity; +public class PasswordChange implements Entity { + + public static final String ATTR_NAME_OLD_PASSWORD = "oldPassword"; public static final String ATTR_NAME_NEW_PASSWORD = "newPassword"; public static final String ATTR_NAME_RETYPED_NEW_PASSWORD = "retypedNewPassword"; + @NotNull + @JsonProperty(USER.ATTR_UUID) + public final String userId; + @NotNull(message = "user:password:notNull") - @Size(min = 8, max = 255, message = "user:password:size:{min}:{max}:${validatedValue}") + @JsonProperty(ATTR_NAME_OLD_PASSWORD) + private final String oldPassword; + + @NotNull(message = "user:password:notNull") + @Size(min = 8, max = 255, message = "user:newPassword:size:{min}:{max}:${validatedValue}") @JsonProperty(ATTR_NAME_NEW_PASSWORD) private final String newPassword; @@ -29,13 +42,21 @@ public class PasswordChange { @JsonCreator public PasswordChange( + @JsonProperty(USER.ATTR_UUID) final String userId, + @JsonProperty(ATTR_NAME_OLD_PASSWORD) final String oldPassword, @JsonProperty(ATTR_NAME_NEW_PASSWORD) final String newPassword, @JsonProperty(ATTR_NAME_RETYPED_NEW_PASSWORD) final String retypedNewPassword) { + this.userId = userId; + this.oldPassword = oldPassword; this.newPassword = newPassword; this.retypedNewPassword = retypedNewPassword; } + public String getOldPassword() { + return this.oldPassword; + } + public String getNewPassword() { return this.newPassword; } @@ -48,4 +69,19 @@ public class PasswordChange { return this.newPassword.equals(this.retypedNewPassword); } + @Override + public String getModelId() { + return this.userId; + } + + @Override + public EntityType entityType() { + return EntityType.USER; + } + + @Override + public String getName() { + return "PasswordChange"; + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserMod.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserMod.java index 4178766f..d6157a0f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserMod.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserMod.java @@ -32,9 +32,6 @@ import ch.ethz.seb.sebserver.gbl.model.EntityKey; public final class UserMod implements UserAccount { - public static final String ATTR_NAME_NEW_PASSWORD = "newPassword"; - public static final String ATTR_NAME_RETYPED_NEW_PASSWORD = "retypedNewPassword"; - public final String uuid; /** The foreign key identifier to the institution where the User belongs to */ @@ -75,11 +72,11 @@ public final class UserMod implements UserAccount { @NotNull(message = "user:newPassword:notNull") @Size(min = 8, max = 255, message = "user:password:size:{min}:{max}:${validatedValue}") - @JsonProperty(ATTR_NAME_NEW_PASSWORD) + @JsonProperty(PasswordChange.ATTR_NAME_NEW_PASSWORD) private final String newPassword; @NotNull(message = "user:retypedNewPassword:notNull") - @JsonProperty(ATTR_NAME_RETYPED_NEW_PASSWORD) + @JsonProperty(PasswordChange.ATTR_NAME_RETYPED_NEW_PASSWORD) private final String retypedNewPassword; @JsonCreator @@ -89,8 +86,8 @@ public final class UserMod implements UserAccount { @JsonProperty(USER.ATTR_INSTITUTION_ID) final Long institutionId, @JsonProperty(USER.ATTR_NAME) final String name, @JsonProperty(USER.ATTR_USERNAME) final String username, - @JsonProperty(ATTR_NAME_NEW_PASSWORD) final String newPassword, - @JsonProperty(ATTR_NAME_RETYPED_NEW_PASSWORD) final String retypedNewPassword, + @JsonProperty(PasswordChange.ATTR_NAME_NEW_PASSWORD) final String newPassword, + @JsonProperty(PasswordChange.ATTR_NAME_RETYPED_NEW_PASSWORD) final String retypedNewPassword, @JsonProperty(USER.ATTR_EMAIL) final String email, @JsonProperty(USER.ATTR_LOCALE) final Locale locale, @JsonProperty(USER.ATTR_TIMEZONE) final DateTimeZone timeZone, @@ -126,8 +123,8 @@ public final class UserMod implements UserAccount { public UserMod(final String modelId, final POSTMapper postAttrMapper) { this.uuid = modelId; this.institutionId = postAttrMapper.getLong(USER.ATTR_INSTITUTION_ID); - this.newPassword = postAttrMapper.getString(ATTR_NAME_NEW_PASSWORD); - this.retypedNewPassword = postAttrMapper.getString(ATTR_NAME_RETYPED_NEW_PASSWORD); + this.newPassword = postAttrMapper.getString(PasswordChange.ATTR_NAME_NEW_PASSWORD); + this.retypedNewPassword = postAttrMapper.getString(PasswordChange.ATTR_NAME_RETYPED_NEW_PASSWORD); this.name = postAttrMapper.getString(USER.ATTR_NAME); this.username = postAttrMapper.getString(USER.ATTR_USERNAME); this.email = postAttrMapper.getString(USER.ATTR_EMAIL); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/InstitutionForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/InstitutionForm.java index 3265f411..9b7c827b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/InstitutionForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/InstitutionForm.java @@ -15,6 +15,7 @@ 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.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.EntityKey; @@ -22,11 +23,13 @@ import ch.ethz.seb.sebserver.gbl.model.institution.Institution; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition; import ch.ethz.seb.sebserver.gui.content.action.InstitutionActions; +import ch.ethz.seb.sebserver.gui.content.action.UserAccountActions; import ch.ethz.seb.sebserver.gui.form.FormBuilder; import ch.ethz.seb.sebserver.gui.form.FormHandle; import ch.ethz.seb.sebserver.gui.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.PageUtils; import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; @@ -60,10 +63,6 @@ public class InstitutionForm implements TemplateComposer { @Override public void compose(final PageContext pageContext) { - if (log.isDebugEnabled()) { - log.debug("Compose Institutoion Form within PageContext: {}", pageContext); - } - final WidgetFactory widgetFactory = this.pageFormService.getWidgetFactory(); final EntityKey entityKey = pageContext.getEntityKey(); @@ -123,15 +122,14 @@ public class InstitutionForm implements TemplateComposer { .withCondition(() -> entityKey != null)) .buildFor((entityKey == null) ? this.restService.getRestCall(NewInstitution.class) - : this.restService.getRestCall(SaveInstitution.class), - InstitutionActions.postSaveAdapter(pageContext)); + : this.restService.getRestCall(SaveInstitution.class)); // propagate content actions to action-pane 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) + .withAttribute(AttributeKeys.READ_ONLY, "false") .publishIf(() -> writeGrant); formContext.createAction(ActionDefinition.INSTITUTION_MODIFY) .withExec(InstitutionActions::editInstitution) @@ -145,7 +143,12 @@ public class InstitutionForm implements TemplateComposer { formContext.createAction(ActionDefinition.INSTITUTION_DEACTIVATE) .withExec(InstitutionActions::deactivateInstitution) .withConfirm(PageUtils.confirmDeactivation(institution, this.restService)) - .publishIf(() -> modifyGrant); + .publishIf(() -> modifyGrant) + .withParentEntityKey(entityKey) + .createAction(ActionDefinition.USER_ACCOUNT_NEW) + .withExec(UserAccountActions::newUserAccount) + .withParentEntity(institution.getEntityKey()) + .publishIf(() -> this.currentUser.hasPrivilege(PrivilegeType.WRITE, EntityType.USER)); } } else { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/InstitutionList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/InstitutionList.java index 9e3b063b..a93f1d3c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/InstitutionList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/InstitutionList.java @@ -9,8 +9,6 @@ package ch.ethz.seb.sebserver.gui.content; 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; @@ -21,6 +19,7 @@ import ch.ethz.seb.sebserver.gbl.model.institution.Institution; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition; import ch.ethz.seb.sebserver.gui.content.action.InstitutionActions; +import ch.ethz.seb.sebserver.gui.content.action.UserAccountActions; 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; @@ -36,8 +35,6 @@ import ch.ethz.seb.sebserver.gui.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; @@ -54,11 +51,6 @@ public class InstitutionList implements TemplateComposer { @Override public void compose(final PageContext pageContext) { - - 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")); @@ -86,16 +78,21 @@ public class InstitutionList implements TemplateComposer { // propagate content actions to action-pane pageContext.createAction(ActionDefinition.INSTITUTION_NEW) - .withExec(InstitutionActions::newInstitution) + .readonly(false) .publishIf(() -> this.currentUser.hasPrivilege(PrivilegeType.WRITE, EntityType.INSTITUTION)) .createAction(ActionDefinition.INSTITUTION_VIEW_FROM_LIST) .withSelectionSupplier(table::getSelection) - .withExec(InstitutionActions::viewInstitution) + .withExec(InstitutionActions::viewInstitutionFromList) .publish() - .createAction(ActionDefinition.INSTITUTION_MODIFY_FROM__LIST) + .createAction(ActionDefinition.INSTITUTION_MODIFY_FROM_LIST) .withSelectionSupplier(table::getSelection) .withExec(InstitutionActions::editInstitutionFromList) - .publishIf(() -> this.currentUser.hasPrivilege(PrivilegeType.MODIFY, EntityType.INSTITUTION)); + .readonly(false) + .publishIf(() -> this.currentUser.hasPrivilege(PrivilegeType.MODIFY, EntityType.INSTITUTION)) + .createAction(ActionDefinition.USER_ACCOUNT_NEW) + .withExec(UserAccountActions::newUserAccount) + .publishIf(() -> this.currentUser.hasPrivilege(PrivilegeType.WRITE, EntityType.USER)); + ; } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/MainPage.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/MainPage.java index 19a188a0..77dd19c9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/MainPage.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/MainPage.java @@ -25,8 +25,8 @@ import ch.ethz.seb.sebserver.gui.content.activity.ActivitiesPane; 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.event.ActivitySelectionEvent; -import ch.ethz.seb.sebserver.gui.service.page.event.ActivitySelectionListener; +import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent; +import ch.ethz.seb.sebserver.gui.service.page.event.ActionEventListener; import ch.ethz.seb.sebserver.gui.service.page.event.PageEventListener; import ch.ethz.seb.sebserver.gui.service.page.impl.MainPageState; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; @@ -119,19 +119,17 @@ public class MainPage implements TemplateComposer { contentObjectslayout.marginWidth = 0; contentObjects.setLayout(contentObjectslayout); contentObjects.setData(PageEventListener.LISTENER_ATTRIBUTE_KEY, - new ActivitySelectionListener() { + new ActionEventListener() { @Override public int priority() { return 2; } @Override - public void notify(final ActivitySelectionEvent event) { + public void notify(final ActionEvent event) { pageContext.composerService().compose( - event.selection.activity.contentPaneComposer, - pageContext - .copyOf(contentObjects) - .withSelection(event.selection)); + event.action.definition.contentPaneComposer, + event.action.pageContext().copyOf(contentObjects)); } }); @@ -141,19 +139,17 @@ public class MainPage implements TemplateComposer { actionPane.setLayout(actionPaneGrid); actionPane.setData(RWT.CUSTOM_VARIANT, "actionPane"); actionPane.setData(PageEventListener.LISTENER_ATTRIBUTE_KEY, - new ActivitySelectionListener() { + new ActionEventListener() { @Override public int priority() { return 1; } @Override - public void notify(final ActivitySelectionEvent event) { + public void notify(final ActionEvent event) { pageContext.composerService().compose( - event.selection.activity.actionPaneComposer, - pageContext - .copyOf(actionPane) - .withSelection(event.selection)); + event.action.definition.actionPaneComposer, + event.action.pageContext().copyOf(actionPane)); } }); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountChangePasswordForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountChangePasswordForm.java new file mode 100644 index 00000000..f8e4d0c2 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountChangePasswordForm.java @@ -0,0 +1,97 @@ +/* + * 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.content; + +import org.eclipse.swt.widgets.Composite; +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.Domain; +import ch.ethz.seb.sebserver.gbl.model.EntityKey; +import ch.ethz.seb.sebserver.gbl.model.user.PasswordChange; +import ch.ethz.seb.sebserver.gbl.model.user.UserInfo; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition; +import ch.ethz.seb.sebserver.gui.content.action.UserAccountActions; +import ch.ethz.seb.sebserver.gui.form.FormBuilder; +import ch.ethz.seb.sebserver.gui.form.FormHandle; +import ch.ethz.seb.sebserver.gui.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.TemplateComposer; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.ChangePassword; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.GetUserAccount; +import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; + +@Lazy +@Component +@GuiProfile +public class UserAccountChangePasswordForm implements TemplateComposer { + + private final PageFormService pageFormService; + private final RestService restService; + + protected UserAccountChangePasswordForm( + final PageFormService pageFormService, + final RestService restService) { + + this.pageFormService = pageFormService; + this.restService = restService; + } + + @Override + public void compose(final PageContext pageContext) { + + final WidgetFactory widgetFactory = this.pageFormService.getWidgetFactory(); + final EntityKey entityKey = pageContext.getEntityKey(); + + final UserInfo userInfo = this.restService + .getBuilder(GetUserAccount.class) + .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) + .call() + .get(pageContext::notifyError); + + final Composite content = widgetFactory.defaultPageLayout( + pageContext.getParent(), + new LocTextKey("sebserver.useraccount.form.pwchange.title", userInfo.username)); + + // The Password Change form + final FormHandle formHandle = this.pageFormService.getBuilder( + pageContext.copyOf(content), 4) + .readonly(pageContext.isReadonly()) + .putStaticValueIf(() -> entityKey != null, + Domain.USER.ATTR_ID, + entityKey.getModelId()) + .addField(FormBuilder.text( + PasswordChange.ATTR_NAME_OLD_PASSWORD, + "sebserver.useraccount.form.institution.password.old") + .asPasswordField()) + .addField(FormBuilder.text( + PasswordChange.ATTR_NAME_NEW_PASSWORD, + "sebserver.useraccount.form.institution.password.new") + .asPasswordField()) + .addField(FormBuilder.text( + PasswordChange.ATTR_NAME_RETYPED_NEW_PASSWORD, + "sebserver.useraccount.form.institution.password.retyped") + .asPasswordField() + .withCondition(() -> entityKey != null)) + .buildFor(this.restService.getRestCall(ChangePassword.class)); + + pageContext.createAction(ActionDefinition.USER_ACCOUNT_CHANGE_PASSOWRD_SAVE) + .withExec(formHandle::postChanges) + .publish() + .createAction(ActionDefinition.USER_ACCOUNT_CANCEL_MODIFY) + .withExec(UserAccountActions::cancelEditUserAccount) + .withConfirm("sebserver.overall.action.modify.cancel.confirm") + .publish(); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountForm.java index 77b95048..c05d63db 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountForm.java @@ -20,10 +20,13 @@ import org.springframework.stereotype.Component; import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.api.API; +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.Domain.USER_ROLE; import ch.ethz.seb.sebserver.gbl.model.EntityKey; +import ch.ethz.seb.sebserver.gbl.model.institution.Institution; +import ch.ethz.seb.sebserver.gbl.model.user.PasswordChange; import ch.ethz.seb.sebserver.gbl.model.user.UserAccount; import ch.ethz.seb.sebserver.gbl.model.user.UserInfo; import ch.ethz.seb.sebserver.gbl.model.user.UserMod; @@ -39,6 +42,7 @@ import ch.ethz.seb.sebserver.gui.service.page.PageContext; import ch.ethz.seb.sebserver.gui.service.page.PageUtils; 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.institution.GetInstitution; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.GetUserAccount; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.NewUserAccount; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.SaveUserAccount; @@ -69,12 +73,9 @@ public class UserAccountForm implements TemplateComposer { @Override public void compose(final PageContext pageContext) { - if (log.isDebugEnabled()) { - log.debug("Compose User Account Form within PageContext: {}", pageContext); - } - final WidgetFactory widgetFactory = this.pageFormService.getWidgetFactory(); final EntityKey entityKey = pageContext.getEntityKey(); + final EntityKey parentEntityKey = pageContext.getParentEntityKey(); final BooleanSupplier isNew = () -> entityKey == null; final BooleanSupplier isNotNew = () -> !isNew.getAsBoolean(); final BooleanSupplier isSEBAdmin = () -> this.currentUser.get().hasRole(UserRole.SEB_SERVER_ADMIN); @@ -126,9 +127,12 @@ public class UserAccountForm implements TemplateComposer { .addField(FormBuilder.singleSelection( Domain.USER.ATTR_INSTITUTION_ID, "sebserver.useraccount.form.institution", - String.valueOf(userAccount.getInstitutionId()), + (parentEntityKey != null && parentEntityKey.entityType == EntityType.INSTITUTION) + ? parentEntityKey.modelId + : String.valueOf(userAccount.getInstitutionId()), () -> PageUtils.getInstitutionSelectionResource(this.restService)) - .withCondition(isSEBAdmin)) + .withCondition(isSEBAdmin) + .readonlyIf(isNotNew)) .addField(FormBuilder.text( Domain.USER.ATTR_NAME, "sebserver.useraccount.form.name", @@ -157,44 +161,53 @@ public class UserAccountForm implements TemplateComposer { StringUtils.join(userAccount.getRoles(), Constants.LIST_SEPARATOR_CHAR), widgetFactory.getI18nSupport().localizedUserRoleResources())) .addField(FormBuilder.text( - UserMod.ATTR_NAME_NEW_PASSWORD, - "sebserver.useraccount.form.password", - null) + PasswordChange.ATTR_NAME_NEW_PASSWORD, + "sebserver.useraccount.form.password") .asPasswordField() .withCondition(isNew)) .addField(FormBuilder.text( - UserMod.ATTR_NAME_RETYPED_NEW_PASSWORD, - "sebserver.useraccount.form.password.retyped", - null) + PasswordChange.ATTR_NAME_RETYPED_NEW_PASSWORD, + "sebserver.useraccount.form.password.retyped") .asPasswordField() .withCondition(isNew)) .buildFor((entityKey == null) ? this.restService.getRestCall(NewUserAccount.class) - : this.restService.getRestCall(SaveUserAccount.class), - UserAccountActions.postSaveAdapter(pageContext)); + : this.restService.getRestCall(SaveUserAccount.class)); // propagate content actions to action-pane final boolean writeGrant = this.currentUser.hasPrivilege(PrivilegeType.WRITE, userAccount); final boolean modifyGrant = this.currentUser.hasPrivilege(PrivilegeType.MODIFY, userAccount); if (pageContext.isReadonly()) { - formContext.createAction(ActionDefinition.USER_ACCOUNT_NEW) - .withExec(UserAccountActions::newUserAccount) + + formContext.createAction(ActionDefinition.USER_ACCOUNT_CHANGE_PASSOWRD) + .withEntity(userAccount.getEntityKey()) .publishIf(() -> writeGrant); - formContext.createAction(ActionDefinition.USER_ACCOUNT_MODIFY) - .withExec(UserAccountActions::editUserAccount) - .publishIf(() -> modifyGrant); - if (!userAccount.isActive()) { - formContext.createAction(ActionDefinition.USER_ACCOUNT_ACTIVATE) - .withExec(UserAccountActions::activateUserAccount) - .publishIf(() -> modifyGrant); - } else { - formContext.createAction(ActionDefinition.USER_ACCOUNT_DEACTIVATE) - .withExec(UserAccountActions::deactivateUserAccount) - .withConfirm(PageUtils.confirmDeactivation(userAccount, this.restService)) + // modifying an UserAccount is not possible if the root institution is inactive + final Institution inst = this.restService.getBuilder(GetInstitution.class) + .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(userAccount.getInstitutionId())) + .call() + .getOrThrow(); + + if (inst.isActive()) { + formContext.createAction(ActionDefinition.USER_ACCOUNT_NEW) + .withExec(UserAccountActions::newUserAccount) + .publishIf(() -> writeGrant); + formContext.createAction(ActionDefinition.USER_ACCOUNT_MODIFY) + .withExec(UserAccountActions::editUserAccount) .publishIf(() -> modifyGrant); + + if (!userAccount.isActive()) { + formContext.createAction(ActionDefinition.USER_ACCOUNT_ACTIVATE) + .withExec(UserAccountActions::activateUserAccount) + .publishIf(() -> modifyGrant); + } else { + formContext.createAction(ActionDefinition.USER_ACCOUNT_DEACTIVATE) + .withExec(UserAccountActions::deactivateUserAccount) + .withConfirm(PageUtils.confirmDeactivation(userAccount, this.restService)) + .publishIf(() -> modifyGrant); + } } - } else { formContext.createAction(ActionDefinition.USER_ACCOUNT_SAVE) .withExec(formHandle::postChanges) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountList.java index 963efe34..98a0334a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountList.java @@ -9,8 +9,6 @@ package ch.ethz.seb.sebserver.gui.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; @@ -29,8 +27,8 @@ 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.table.ColumnDefinition; -import ch.ethz.seb.sebserver.gui.table.EntityTable; import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute; +import ch.ethz.seb.sebserver.gui.table.EntityTable; import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; @@ -39,8 +37,6 @@ import ch.ethz.seb.sebserver.gui.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; @@ -60,11 +56,6 @@ public class UserAccountList implements TemplateComposer { @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(), @@ -113,7 +104,7 @@ public class UserAccountList implements TemplateComposer { .withSelectionSupplier(table::getSelection) .withExec(UserAccountActions::viewUserAccountFromList) .publish() - .createAction(ActionDefinition.USER_ACCOUNT_MODIFY) + .createAction(ActionDefinition.USER_ACCOUNT_MODIFY_FROM__LIST) .withSelectionSupplier(table::getSelection) .withExec(UserAccountActions::editUserAccountFromList) .publishIf(() -> this.currentUser.hasPrivilege(PrivilegeType.MODIFY, EntityType.USER)); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java index 5c037877..d692f83d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java @@ -8,85 +8,205 @@ package ch.ethz.seb.sebserver.gui.content.action; +import ch.ethz.seb.sebserver.gui.content.InstitutionForm; +import ch.ethz.seb.sebserver.gui.content.InstitutionList; +import ch.ethz.seb.sebserver.gui.content.UserAccountChangePasswordForm; +import ch.ethz.seb.sebserver.gui.content.UserAccountForm; +import ch.ethz.seb.sebserver.gui.content.UserAccountList; +import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; +import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.ImageIcon; public enum ActionDefinition { + INSTITUTION_VIEW_LIST( + new LocTextKey("sebserver.institution.action.list"), + InstitutionList.class), + INSTITUTION_VIEW_FORM( + new LocTextKey("sebserver.institution.action.form"), + InstitutionForm.class, + INSTITUTION_VIEW_LIST), INSTITUTION_NEW( - "sebserver.institution.action.new", - ImageIcon.NEW), - + new LocTextKey("sebserver.institution.action.new"), + ImageIcon.NEW, + InstitutionForm.class, + INSTITUTION_VIEW_LIST), INSTITUTION_VIEW_FROM_LIST( - "sebserver.institution.action.list.view", - ImageIcon.SHOW), + new LocTextKey("sebserver.institution.action.list.view"), + ImageIcon.SHOW, + InstitutionForm.class, + INSTITUTION_VIEW_LIST), - INSTITUTION_MODIFY_FROM__LIST( - "sebserver.institution.action.list.modify", - ImageIcon.EDIT), + INSTITUTION_MODIFY_FROM_LIST( + new LocTextKey("sebserver.institution.action.list.modify"), + ImageIcon.EDIT, + InstitutionForm.class, + INSTITUTION_VIEW_LIST), INSTITUTION_MODIFY( - "sebserver.institution.action.modify", - ImageIcon.EDIT), + new LocTextKey("sebserver.institution.action.modify"), + ImageIcon.EDIT, + InstitutionForm.class, + INSTITUTION_VIEW_LIST), INSTITUTION_CANCEL_MODIFY( - "sebserver.overall.action.modify.cancel", - ImageIcon.CANCEL), + new LocTextKey("sebserver.overall.action.modify.cancel"), + ImageIcon.CANCEL, + InstitutionForm.class, + INSTITUTION_VIEW_LIST), INSTITUTION_SAVE( - "sebserver.institution.action.save", - ImageIcon.SAVE), + new LocTextKey("sebserver.institution.action.save"), + ImageIcon.SAVE, + InstitutionForm.class, + INSTITUTION_VIEW_LIST), INSTITUTION_ACTIVATE( - "sebserver.institution.action.activate", - ImageIcon.INACTIVE), + new LocTextKey("sebserver.institution.action.activate"), + ImageIcon.INACTIVE, + InstitutionForm.class, + INSTITUTION_VIEW_LIST), INSTITUTION_DEACTIVATE( - "sebserver.institution.action.deactivate", - ImageIcon.ACTIVE), + new LocTextKey("sebserver.institution.action.deactivate"), + ImageIcon.ACTIVE, + InstitutionForm.class, + INSTITUTION_VIEW_LIST), INSTITUTION_DELETE( - "sebserver.institution.action.modify", - ImageIcon.DELETE), + new LocTextKey("sebserver.institution.action.modify"), + ImageIcon.DELETE, + InstitutionList.class, + INSTITUTION_VIEW_LIST), + USER_ACCOUNT_VIEW_LIST( + new LocTextKey("sebserver.useraccount.action.list"), + UserAccountList.class), + USER_ACCOUNT_VIEW_FORM( + new LocTextKey("sebserver.useraccount.action.form"), + InstitutionForm.class, + USER_ACCOUNT_VIEW_LIST), USER_ACCOUNT_NEW( - "sebserver.useraccount.action.new", - ImageIcon.NEW), + new LocTextKey("sebserver.useraccount.action.new"), + ImageIcon.NEW, + UserAccountForm.class, + USER_ACCOUNT_VIEW_LIST), USER_ACCOUNT_VIEW( - "sebserver.useraccount.action.view", - ImageIcon.SHOW), + new LocTextKey("sebserver.useraccount.action.view"), + ImageIcon.SHOW, + UserAccountForm.class, + USER_ACCOUNT_VIEW_LIST), + + USER_ACCOUNT_MODIFY_FROM__LIST( + new LocTextKey("sebserver.useraccount.action.list.modify"), + ImageIcon.EDIT, + UserAccountForm.class, + USER_ACCOUNT_VIEW_LIST), USER_ACCOUNT_MODIFY( - "sebserver.useraccount.action.modify", - ImageIcon.EDIT), + new LocTextKey("sebserver.useraccount.action.modify"), + ImageIcon.EDIT, + UserAccountForm.class, + USER_ACCOUNT_VIEW_LIST), USER_ACCOUNT_CANCEL_MODIFY( - "sebserver.overall.action.modify.cancel", - ImageIcon.CANCEL), + new LocTextKey("sebserver.overall.action.modify.cancel"), + ImageIcon.CANCEL, + UserAccountForm.class, + USER_ACCOUNT_VIEW_LIST), USER_ACCOUNT_SAVE( - "sebserver.useraccount.action.save", - ImageIcon.SAVE), + new LocTextKey("sebserver.useraccount.action.save"), + ImageIcon.SAVE, + UserAccountForm.class, + USER_ACCOUNT_VIEW_LIST), USER_ACCOUNT_ACTIVATE( - "sebserver.useraccount.action.activate", - ImageIcon.INACTIVE), + new LocTextKey("sebserver.useraccount.action.activate"), + ImageIcon.INACTIVE, + UserAccountForm.class, + USER_ACCOUNT_VIEW_LIST), USER_ACCOUNT_DEACTIVATE( - "sebserver.useraccount.action.deactivate", - ImageIcon.ACTIVE), + new LocTextKey("sebserver.useraccount.action.deactivate"), + ImageIcon.ACTIVE, + UserAccountForm.class, + USER_ACCOUNT_VIEW_LIST), USER_ACCOUNT_DELETE( - "sebserver.useraccount.action.modify", - ImageIcon.DELETE), - ; + new LocTextKey("sebserver.useraccount.action.modify"), + ImageIcon.DELETE, + UserAccountList.class, + USER_ACCOUNT_VIEW_LIST), - public final String name; + USER_ACCOUNT_CHANGE_PASSOWRD( + new LocTextKey("sebserver.useraccount.action.change.password"), + ImageIcon.EDIT, + UserAccountChangePasswordForm.class, + USER_ACCOUNT_VIEW_LIST), + USER_ACCOUNT_CHANGE_PASSOWRD_SAVE( + new LocTextKey("sebserver.useraccount.action.change.password.save"), + ImageIcon.SAVE, + UserAccountForm.class, + USER_ACCOUNT_VIEW_LIST), + + ; + + public final LocTextKey title; public final ImageIcon icon; + public final Class contentPaneComposer; + public final Class actionPaneComposer; + public final ActionDefinition activityAlias; - private ActionDefinition(final String name, final ImageIcon icon) { - this.name = name; + private ActionDefinition( + final LocTextKey title, + final Class contentPaneComposer) { + + this.title = title; + this.icon = null; + this.contentPaneComposer = contentPaneComposer; + this.actionPaneComposer = ActionPane.class; + this.activityAlias = null; + } + + private ActionDefinition( + final LocTextKey title, + final Class contentPaneComposer, + final ActionDefinition activityAlias) { + + this.title = title; + this.icon = null; + this.contentPaneComposer = contentPaneComposer; + this.actionPaneComposer = ActionPane.class; + this.activityAlias = activityAlias; + } + + private ActionDefinition( + final LocTextKey title, + final ImageIcon icon, + final Class contentPaneComposer, + final ActionDefinition activityAlias) { + + this.title = title; this.icon = icon; + this.contentPaneComposer = contentPaneComposer; + this.actionPaneComposer = ActionPane.class; + this.activityAlias = activityAlias; + } + + private ActionDefinition( + final LocTextKey title, + final ImageIcon icon, + final Class contentPaneComposer, + final Class actionPaneComposer, + final ActionDefinition activityAlias) { + + this.title = title; + this.icon = icon; + this.contentPaneComposer = contentPaneComposer; + this.actionPaneComposer = actionPaneComposer; + this.activityAlias = activityAlias; } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionPane.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionPane.java index f747827f..d74c2071 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionPane.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionPane.java @@ -90,7 +90,7 @@ public class ActionPane implements TemplateComposer { final TreeItem actionItem = ActionPane.this.widgetFactory.treeItemLocalized( actions, - event.action.definition.name); + event.action.definition.title); actionItem.setImage(event.action.definition.icon.getImage( pageContext.getParent().getDisplay())); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/InstitutionActions.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/InstitutionActions.java index 0f5e0335..3fe423fd 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/InstitutionActions.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/InstitutionActions.java @@ -9,111 +9,86 @@ package ch.ethz.seb.sebserver.gui.content.action; import java.util.Collection; -import java.util.function.Function; import ch.ethz.seb.sebserver.gbl.api.API; 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; -import ch.ethz.seb.sebserver.gui.content.activity.Activity; 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.action.Action; -import ch.ethz.seb.sebserver.gui.service.page.activity.ActivitySelection; -import ch.ethz.seb.sebserver.gui.service.page.event.ActivitySelectionEvent; +import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.ActivateInstitution; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.DeactivateInstitution; /** Defines the action execution functions for all Institution action. */ public final class InstitutionActions { - public static Function postSaveAdapter(final PageContext pageContext) { - return inst -> { - goToInstitution(pageContext, inst.getModelId(), false); - return inst; - }; - } - - public static Result newInstitution(final Action action) { - return Result.of(goToInstitution(action.pageContext, null, true)); - } - - public static Result viewInstitution(final Action action) { + public static Action viewInstitutionFromList(final Action action) { return fromSelection(action, false); } - public static Result editInstitutionFromList(final Action action) { + public static Action editInstitutionFromList(final Action action) { return fromSelection(action, true); } - public static Result editInstitution(final Action action) { - return Result.of(goToInstitution( - action.pageContext, - action.pageContext.getAttribute(AttributeKeys.ENTITY_ID), - true)); + public static Action editInstitution(final Action action) { + return goToInstitution(action, null, true); } - public static Result cancelEditInstitution(final Action action) { - if (action.pageContext.getEntityKey() == null) { - final ActivitySelection toList = Activity.INSTITUTION_LIST.createSelection(); - action.pageContext.publishPageEvent(new ActivitySelectionEvent(toList)); - return Result.of(toList); + public static Action cancelEditInstitution(final Action action) { + if (action.getEntityKey() == null) { + final PageContext pageContext = action.pageContext(); + final Action toList = pageContext.createAction(ActionDefinition.INSTITUTION_VIEW_LIST); + pageContext.publishPageEvent(new ActionEvent(toList, false)); + return toList; } else { - return Result.of(goToInstitution( - action.pageContext, - action.pageContext.getAttribute(AttributeKeys.ENTITY_ID), - false)); + return goToInstitution(action, null, false); } } - public static Result activateInstitution(final Action action) { + public static Action activateInstitution(final Action action) { return action.restService .getBuilder(ActivateInstitution.class) .withURIVariable( API.PARAM_MODEL_ID, - action.pageContext.getAttribute(AttributeKeys.ENTITY_ID)) + action.pageContext().getAttribute(AttributeKeys.ENTITY_ID)) .call() - .map(report -> goToInstitution(action.pageContext, report.getSingleSource().modelId, false)); + .map(report -> goToInstitution(action, report.getSingleSource().modelId, false)) + .getOrThrow(); } - public static Result deactivateInstitution(final Action action) { + public static Action deactivateInstitution(final Action action) { return action.restService .getBuilder(DeactivateInstitution.class) .withURIVariable( API.PARAM_MODEL_ID, - action.pageContext.getAttribute(AttributeKeys.ENTITY_ID)) + action.pageContext().getAttribute(AttributeKeys.ENTITY_ID)) .call() - .map(report -> goToInstitution(action.pageContext, report.getSingleSource().modelId, false)); + .map(report -> goToInstitution(action, report.getSingleSource().modelId, false)) + .getOrThrow(); } - private static Result fromSelection(final Action action, final boolean edit) { - return Result.tryCatch(() -> { - final Collection selection = action.getSelectionSupplier().get(); - if (selection.isEmpty()) { - throw new PageMessageException("sebserver.institution.info.pleaseSelect"); - } + private static Action fromSelection(final Action action, final boolean edit) { + final Collection selection = action.getSelectionSupplier().get(); + if (selection.isEmpty()) { + throw new PageMessageException("sebserver.institution.info.pleaseSelect"); + } - return goToInstitution(action.pageContext, selection.iterator().next(), edit); - }); + return goToInstitution(action, selection.iterator().next(), edit); } - private static ActivitySelection goToInstitution( - final PageContext pageContext, + private static Action goToInstitution( + final Action action, final String modelId, final boolean edit) { - final ActivitySelection activitySelection = Activity.INSTITUTION_FORM - .createSelection() - .withAttribute(AttributeKeys.READ_ONLY, String.valueOf(!edit)); - + action.withAttribute(AttributeKeys.READ_ONLY, String.valueOf(!edit)); if (modelId != null) { - activitySelection.withEntity(new EntityKey(modelId, EntityType.INSTITUTION)); + action.withEntity(new EntityKey(modelId, EntityType.INSTITUTION)); } - pageContext.publishPageEvent(new ActivitySelectionEvent(activitySelection)); - return activitySelection; + return action; } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/UserAccountActions.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/UserAccountActions.java index 5434f372..cd10e0ef 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/UserAccountActions.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/UserAccountActions.java @@ -9,110 +9,87 @@ package ch.ethz.seb.sebserver.gui.content.action; import java.util.Collection; -import java.util.function.Function; import ch.ethz.seb.sebserver.gbl.api.API; 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.util.Result; -import ch.ethz.seb.sebserver.gui.content.activity.Activity; -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.action.Action; -import ch.ethz.seb.sebserver.gui.service.page.activity.ActivitySelection; -import ch.ethz.seb.sebserver.gui.service.page.event.ActivitySelectionEvent; +import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.ActivateUserAccount; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.DeactivateUserAccount; public final class UserAccountActions { - public static Function postSaveAdapter(final PageContext pageContext) { - return userAccount -> { - goToUserAccount(pageContext, userAccount.getModelId(), false); - return userAccount; - }; + public static Action newUserAccount(final Action action) { + return goToUserAccount(action, null, true); } - public static Result newUserAccount(final Action action) { - return Result.of(goToUserAccount(action.pageContext, null, true)); - } - - public static Result viewUserAccountFromList(final Action action) { + public static Action viewUserAccountFromList(final Action action) { return fromSelection(action, false); } - public static Result editUserAccountFromList(final Action action) { + public static Action editUserAccountFromList(final Action action) { return fromSelection(action, true); } - public static Result editUserAccount(final Action action) { - return Result.of(goToUserAccount( - action.pageContext, - action.pageContext.getAttribute(AttributeKeys.ENTITY_ID), - true)); + public static Action editUserAccount(final Action action) { + return goToUserAccount(action, null, true); } - public static Result cancelEditUserAccount(final Action action) { - if (action.pageContext.getEntityKey() == null) { - final ActivitySelection toList = Activity.USER_ACCOUNT_LIST.createSelection(); - action.pageContext.publishPageEvent(new ActivitySelectionEvent(toList)); - return Result.of(toList); + public static Action cancelEditUserAccount(final Action action) { + if (action.pageContext().getEntityKey() == null) { + final Action toList = action.pageContext().createAction(ActionDefinition.USER_ACCOUNT_VIEW_LIST); + action.pageContext().publishPageEvent(new ActionEvent(toList, false)); + return toList; } else { - return Result.of(goToUserAccount( - action.pageContext, - action.pageContext.getAttribute(AttributeKeys.ENTITY_ID), - false)); + return goToUserAccount(action, null, false); } } - public static Result activateUserAccount(final Action action) { + public static Action activateUserAccount(final Action action) { return action.restService .getBuilder(ActivateUserAccount.class) .withURIVariable( API.PARAM_MODEL_ID, - action.pageContext.getAttribute(AttributeKeys.ENTITY_ID)) + action.pageContext().getAttribute(AttributeKeys.ENTITY_ID)) .call() - .map(report -> goToUserAccount(action.pageContext, report.getSingleSource().modelId, false)); + .map(report -> goToUserAccount(action, report.getSingleSource().modelId, false)) + .getOrThrow(); } - public static Result deactivateUserAccount(final Action action) { + public static Action deactivateUserAccount(final Action action) { return action.restService .getBuilder(DeactivateUserAccount.class) .withURIVariable( API.PARAM_MODEL_ID, - action.pageContext.getAttribute(AttributeKeys.ENTITY_ID)) + action.pageContext().getAttribute(AttributeKeys.ENTITY_ID)) .call() - .map(report -> goToUserAccount(action.pageContext, report.getSingleSource().modelId, false)); + .map(report -> goToUserAccount(action, report.getSingleSource().modelId, false)) + .getOrThrow(); } - private static Result fromSelection(final Action action, final boolean edit) { - return Result.tryCatch(() -> { - final Collection selection = action.getSelectionSupplier().get(); - if (selection.isEmpty()) { - throw new PageMessageException("sebserver.useraccount.info.pleaseSelect"); - } + private static Action fromSelection(final Action action, final boolean edit) { + final Collection selection = action.getSelectionSupplier().get(); + if (selection.isEmpty()) { + throw new PageMessageException("sebserver.useraccount.info.pleaseSelect"); + } - return goToUserAccount(action.pageContext, selection.iterator().next(), edit); - }); + return goToUserAccount(action, selection.iterator().next(), edit); } - private static ActivitySelection goToUserAccount( - final PageContext pageContext, + private static Action goToUserAccount( + final Action action, final String modelId, final boolean edit) { - final ActivitySelection activitySelection = Activity.USER_ACCOUNT_FORM - .createSelection() - .withAttribute(AttributeKeys.READ_ONLY, String.valueOf(!edit)); - + action.withAttribute(AttributeKeys.READ_ONLY, String.valueOf(!edit)); if (modelId != null) { - activitySelection.withEntity(new EntityKey(modelId, EntityType.USER)); + action.withEntity(new EntityKey(modelId, EntityType.USER)); } - pageContext.publishPageEvent(new ActivitySelectionEvent(activitySelection)); - return activitySelection; + return action; } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivitiesPane.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivitiesPane.java index 046137a7..5753ae31 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivitiesPane.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivitiesPane.java @@ -8,10 +8,6 @@ package ch.ethz.seb.sebserver.gui.content.activity; -import java.util.Collection; -import java.util.EnumMap; -import java.util.Map; - import org.eclipse.swt.SWT; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Event; @@ -30,15 +26,12 @@ import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition; 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.activity.ActivityActionHandler; -import ch.ethz.seb.sebserver.gui.service.page.activity.ActivitySelection; import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; +import ch.ethz.seb.sebserver.gui.service.page.action.Action; import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent; import ch.ethz.seb.sebserver.gui.service.page.event.ActionEventListener; -import ch.ethz.seb.sebserver.gui.service.page.event.ActivitySelectionEvent; import ch.ethz.seb.sebserver.gui.service.page.event.PageEventListener; import ch.ethz.seb.sebserver.gui.service.page.impl.MainPageState; -import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant; @@ -50,26 +43,14 @@ public class ActivitiesPane implements TemplateComposer { private static final String ATTR_ACTIVITY_SELECTION = "ACTIVITY_SELECTION"; private final WidgetFactory widgetFactory; - private final RestService restService; private final CurrentUser currentUser; - // TODO are those really needed? - private final Map activityActionHandler = - new EnumMap<>(ActionDefinition.class); - public ActivitiesPane( final WidgetFactory widgetFactory, - final RestService restService, - final CurrentUser currentUser, - final Collection activityActionHandler) { + final CurrentUser currentUser) { this.widgetFactory = widgetFactory; - this.restService = restService; this.currentUser = currentUser; - - for (final ActivityActionHandler aah : activityActionHandler) { - this.activityActionHandler.put(aah.handlesAction(), aah); - } } @Override @@ -89,7 +70,7 @@ public class ActivitiesPane implements TemplateComposer { pageContext.getParent(), SWT.SINGLE | SWT.FULL_SELECTION); final GridData navigationGridData = new GridData(SWT.FILL, SWT.FILL, true, true); - navigationGridData.horizontalIndent = 10; + //navigationGridData.horizontalIndent = 10; navigation.setLayoutData(navigationGridData); // Institution @@ -98,18 +79,20 @@ public class ActivitiesPane implements TemplateComposer { // institutions (list) as root final TreeItem institutions = this.widgetFactory.treeItemLocalized( navigation, - Activity.INSTITUTION_LIST.title); - injectActivitySelection(institutions, Activity.INSTITUTION_LIST.createSelection()); + ActionDefinition.INSTITUTION_VIEW_LIST.title); + injectActivitySelection( + institutions, + pageContext.createAction(ActionDefinition.INSTITUTION_VIEW_LIST)); } else { // otherwise show the form of the institution for current user final TreeItem institutions = this.widgetFactory.treeItemLocalized( navigation, - Activity.INSTITUTION_FORM.title); + ActionDefinition.INSTITUTION_VIEW_FORM.title); injectActivitySelection( institutions, - Activity.INSTITUTION_FORM.createSelection() - .withEntity(new EntityKey(userInfo.institutionId, EntityType.INSTITUTION)) + pageContext.createAction(ActionDefinition.INSTITUTION_VIEW_FORM) + .withEntity(userInfo.institutionId, EntityType.INSTITUTION) .withAttribute(AttributeKeys.READ_ONLY, "true")); } @@ -118,73 +101,40 @@ public class ActivitiesPane implements TemplateComposer { 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()); + ActionDefinition.USER_ACCOUNT_VIEW_LIST.title); + injectActivitySelection( + userAccounts, + pageContext.createAction(ActionDefinition.USER_ACCOUNT_VIEW_LIST)); } else { // otherwise show the user account form for current user final TreeItem userAccounts = this.widgetFactory.treeItemLocalized( navigation, - Activity.USER_ACCOUNT_FORM.title); + ActionDefinition.USER_ACCOUNT_VIEW_FORM.title); injectActivitySelection( userAccounts, - Activity.USER_ACCOUNT_FORM.createSelection() + pageContext.createAction(ActionDefinition.USER_ACCOUNT_VIEW_FORM) .withEntity(this.currentUser.get().getEntityKey()) .withAttribute(AttributeKeys.READ_ONLY, "true")); } -// -// final TreeItem configs = this.widgetFactory.treeItemLocalized( -// navigation, -// "org.sebserver.activities.sebconfigs"); -// ActivitySelection.set(configs, Activity.SEB_CONFIGS.createSelection()); -// -// final TreeItem config = this.widgetFactory.treeItemLocalized( -// configs, -// "org.sebserver.activities.sebconfig"); -// ActivitySelection.set(config, Activity.SEB_CONFIG.createSelection()); -// -// final TreeItem configTemplates = this.widgetFactory.treeItemLocalized( -// configs, -// "org.sebserver.activities.sebconfig.templates"); -// ActivitySelection.set(configTemplates, Activity.SEB_CONFIG_TEMPLATES.createSelection()); -// -// final TreeItem exams = this.widgetFactory.treeItemLocalized( -// navigation, -// "org.sebserver.activities.exam"); -// ActivitySelection.set(exams, Activity.EXAMS.createSelection()); -// -// final TreeItem monitoring = this.widgetFactory.treeItemLocalized( -// navigation, -// "org.sebserver.activities.monitoring"); -// ActivitySelection.set(monitoring, Activity.MONITORING.createSelection()); -// -// final TreeItem runningExams = this.widgetFactory.treeItemLocalized( -// monitoring, -// "org.sebserver.activities.runningExams"); -// ActivitySelection.set(runningExams, Activity.RUNNING_EXAMS.createSelection() -// .withExpandFunction(this::runningExamExpand)); -// runningExams.setItemCount(1); -// -// final TreeItem logs = this.widgetFactory.treeItemLocalized( -// monitoring, -// "org.sebserver.activities.logs"); -// ActivitySelection.set(logs, Activity.LOGS.createSelection()); - navigation.addListener(SWT.Expand, this::handleExpand); navigation.addListener(SWT.Selection, event -> handleSelection(pageContext, event)); navigation.setData( PageEventListener.LISTENER_ATTRIBUTE_KEY, new ActionEventListener() { @Override public void notify(final ActionEvent event) { -// final ActivityActionHandler aah = -// ActivitiesPane.this.activityActionHandler.get(event.actionDefinition); -// if (aah != null) { -// aah.notifyAction(event, navigation, pageContext); -// } - // on case of an Action with ActivitySelection, reset the MainPageState - if (event.source instanceof ActivitySelection) { - final MainPageState mainPageState = MainPageState.get(); - mainPageState.activitySelection = (ActivitySelection) event.source; + final MainPageState mainPageState = MainPageState.get(); + mainPageState.action = event.action; + if (!event.activity) { + final EntityKey entityKey = event.action.getEntityKey(); + final String modelId = (entityKey != null) ? entityKey.modelId : null; + final TreeItem item = findItemByActionDefinition( + navigation.getItems(), + event.action.definition, + modelId); + if (item != null) { + navigation.select(item); + } } } }); @@ -192,79 +142,47 @@ public class ActivitiesPane implements TemplateComposer { // page-selection on (re)load final MainPageState mainPageState = MainPageState.get(); - if (mainPageState.activitySelection == null || - mainPageState.activitySelection.activity == Activity.NONE) { - mainPageState.activitySelection = getActivitySelection(navigation.getItem(0)); + if (mainPageState.action == null) { + mainPageState.action = getActivitySelection(navigation.getItem(0)); } pageContext.publishPageEvent( - new ActivitySelectionEvent(mainPageState.activitySelection)); + new ActionEvent(mainPageState.action, false)); navigation.select(navigation.getItem(0)); } -// private void runningExamExpand(final TreeItem item) { -// item.removeAll(); -// final List runningExamNames = this.restService -// .sebServerCall(GetRunningExamNames.class) -// .onError(t -> { -// throw new RuntimeException(t); -// }); -// -// if (runningExamNames != null) { -// for (final EntityName runningExamName : runningExamNames) { -// final TreeItem runningExams = this.widgetFactory.treeItemLocalized( -// item, -// runningExamName.name); -// ActivitySelection.set(runningExams, Activity.RUNNING_EXAM.createSelection(runningExamName)); -// } -// } -// } - - private void handleExpand(final Event event) { - final TreeItem treeItem = (TreeItem) event.item; - - System.out.println("opened: " + treeItem); - - final ActivitySelection activity = getActivitySelection(treeItem); - if (activity != null) { - activity.processExpand(treeItem); - } - } - private void handleSelection(final PageContext composerCtx, final Event event) { final TreeItem treeItem = (TreeItem) event.item; System.out.println("selected: " + treeItem); final MainPageState mainPageState = MainPageState.get(); - final ActivitySelection activitySelection = getActivitySelection(treeItem); - if (mainPageState.activitySelection == null) { - mainPageState.activitySelection = Activity.NONE.createSelection(); - } - if (!mainPageState.activitySelection.equals(activitySelection)) { - mainPageState.activitySelection = activitySelection; + final Action action = getActivitySelection(treeItem); + if (mainPageState.action.definition != action.definition) { + mainPageState.action = action; composerCtx.publishPageEvent( - new ActivitySelectionEvent(mainPageState.activitySelection)); + new ActionEvent(action, true)); } } - static final TreeItem findItemByActivity( + static final TreeItem findItemByActionDefinition( final TreeItem[] items, - final Activity activity, - final String objectId) { + final ActionDefinition actionDefinition, + final String modelId) { if (items == null) { return null; } for (final TreeItem item : items) { - final ActivitySelection activitySelection = getActivitySelection(item); - final String id = activitySelection.getEntityId(); - if (activitySelection != null && activitySelection.activity == activity && - (id == null || (objectId != null && objectId.equals(id)))) { + final Action action = getActivitySelection(item); + final EntityKey entityKey = action.getEntityKey(); + if (action != null + && (action.definition == actionDefinition || action.definition == actionDefinition.activityAlias) && + (entityKey == null || (modelId != null && modelId.equals(entityKey.modelId)))) { return item; } - final TreeItem _item = findItemByActivity(item.getItems(), activity, objectId); + final TreeItem _item = findItemByActionDefinition(item.getItems(), actionDefinition, modelId); if (_item != null) { return _item; } @@ -273,8 +191,8 @@ public class ActivitiesPane implements TemplateComposer { return null; } - static final TreeItem findItemByActivity(final TreeItem[] items, final Activity activity) { - return findItemByActivity(items, activity, null); + static final TreeItem findItemByActionDefinition(final TreeItem[] items, final ActionDefinition actionDefinition) { + return findItemByActionDefinition(items, actionDefinition, null); } static final void expand(final TreeItem item) { @@ -286,12 +204,12 @@ public class ActivitiesPane implements TemplateComposer { expand(item.getParentItem()); } - public static ActivitySelection getActivitySelection(final TreeItem item) { - return (ActivitySelection) item.getData(ATTR_ACTIVITY_SELECTION); + public static Action getActivitySelection(final TreeItem item) { + return (Action) item.getData(ATTR_ACTIVITY_SELECTION); } - public static void injectActivitySelection(final TreeItem item, final ActivitySelection selection) { - item.setData(ATTR_ACTIVITY_SELECTION, selection); + public static void injectActivitySelection(final TreeItem item, final Action action) { + item.setData(ATTR_ACTIVITY_SELECTION, action); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/Activity.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/Activity.java deleted file mode 100644 index adbefcc7..00000000 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/Activity.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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.content.activity; - -import ch.ethz.seb.sebserver.gui.content.InstitutionForm; -import ch.ethz.seb.sebserver.gui.content.InstitutionList; -import ch.ethz.seb.sebserver.gui.content.UserAccountForm; -import ch.ethz.seb.sebserver.gui.content.UserAccountList; -import ch.ethz.seb.sebserver.gui.content.action.ActionPane; -import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; -import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; -import ch.ethz.seb.sebserver.gui.service.page.activity.ActivitySelection; -import ch.ethz.seb.sebserver.gui.service.page.impl.TODOTemplate; - -public enum Activity { - NONE(TODOTemplate.class, TODOTemplate.class), - INSTITUTION_LIST( - InstitutionList.class, - ActionPane.class, - new LocTextKey("sebserver.activities.institution")), - INSTITUTION_FORM( - InstitutionForm.class, - ActionPane.class, - new LocTextKey("sebserver.activities.institution")), - - USER_ACCOUNT_LIST( - UserAccountList.class, - ActionPane.class, - new LocTextKey("sebserver.activities.useraccount")), - - USER_ACCOUNT_FORM( - UserAccountForm.class, - ActionPane.class, - new LocTextKey("sebserver.activities.useraccount")), - -// USERS(UserAccountsForm.class, ActionPane.class), -// -// EXAMS(ExamsListPage.class, ActionPane.class), -// SEB_CONFIGS(SEBConfigurationForm.class, ActionPane.class), -// SEB_CONFIG(SEBConfigurationPage.class, ActionPane.class), -// SEB_CONFIG_TEMPLATES(TODOTemplate.class, ActionPane.class), -// MONITORING(MonitoringForm.class, ActionPane.class), -// RUNNING_EXAMS(RunningExamForm.class, ActionPane.class), -// RUNNING_EXAM(RunningExamPage.class, ActionPane.class, AttributeKeys.EXAM_ID), -// LOGS(TODOTemplate.class, ActionPane.class), - ; - - public final LocTextKey title; - public final Class contentPaneComposer; - public final Class actionPaneComposer; - //public final String modelIdAttribute; - - private Activity( - final Class objectPaneComposer, - final Class selectionPaneComposer, - final LocTextKey title) { - - this.title = title; - this.contentPaneComposer = objectPaneComposer; - this.actionPaneComposer = selectionPaneComposer; - } - - private Activity( - final Class objectPaneComposer, - final Class selectionPaneComposer) { - - this.title = null; - this.contentPaneComposer = objectPaneComposer; - this.actionPaneComposer = selectionPaneComposer; - } - - public final ActivitySelection createSelection() { - return new ActivitySelection(this); - } - } \ No newline at end of file diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/form/FieldBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/form/FieldBuilder.java index 48ea7f47..bf4bf6ba 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/form/FieldBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/form/FieldBuilder.java @@ -1,6 +1,6 @@ /* * 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/. @@ -10,13 +10,14 @@ package ch.ethz.seb.sebserver.gui.form; import java.util.function.BooleanSupplier; -abstract class FieldBuilder { +public abstract class FieldBuilder { int spanLabel = -1; int spanInput = -1; int spanEmptyCell = -1; boolean autoEmptyCellSeparation = false; String group = null; BooleanSupplier condition = null; + boolean readonly = false; final String name; final String label; @@ -58,6 +59,16 @@ abstract class FieldBuilder { return this; } + public FieldBuilder readonly(final boolean readonly) { + this.readonly = readonly; + return this; + } + + public FieldBuilder readonlyIf(final BooleanSupplier readonly) { + this.readonly = readonly != null && readonly.getAsBoolean(); + return this; + } + abstract void build(FormBuilder builder); } \ No newline at end of file diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/form/Form.java b/src/main/java/ch/ethz/seb/sebserver/gui/form/Form.java index e196745d..481ad5ef 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/form/Form.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/form/Form.java @@ -44,7 +44,6 @@ public final class Form implements FormBinding { private final Map staticValues = new LinkedHashMap<>(); private final MultiValueMap formFields = new LinkedMultiValueMap<>(); - //private final Map> formFields = new LinkedHashMap<>(); private final Map subForms = new LinkedHashMap<>(); private final Map> subLists = new LinkedHashMap<>(); private final Map> groups = new LinkedHashMap<>(); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/form/FormBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/form/FormBuilder.java index a47e7e89..f2d221b8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/form/FormBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/form/FormBuilder.java @@ -10,10 +10,10 @@ package ch.ethz.seb.sebserver.gui.form; import java.util.List; import java.util.function.BooleanSupplier; -import java.util.function.Function; import java.util.function.Supplier; import org.apache.commons.lang3.StringUtils; +import org.eclipse.rap.rwt.RWT; import org.eclipse.swt.SWT; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; @@ -25,6 +25,7 @@ import org.slf4j.LoggerFactory; import ch.ethz.seb.sebserver.gbl.Constants; 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.util.Tuple; import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService; @@ -159,15 +160,13 @@ public class FormBuilder { return this; } - public FormHandle buildFor( - final RestCall post, - final Function postPostHandle) { + public FormHandle buildFor( + final RestCall post) { return new FormHandle<>( this.pageContext, this.form, post, - (postPostHandle == null) ? Function.identity() : postPostHandle, this.polyglotPageService.getI18nSupport()); } @@ -177,6 +176,10 @@ public class FormBuilder { empty.setText(""); } + public static TextFieldBuilder text(final String name, final String label) { + return new TextFieldBuilder(name, label, null); + } + public static TextFieldBuilder text(final String name, final String label, final String value) { return new TextFieldBuilder(name, label, value); } @@ -207,8 +210,9 @@ public class FormBuilder { Label labelLocalized(final Composite parent, final String locTextKey, final int hspan) { final Label label = this.widgetFactory.labelLocalized(parent, locTextKey); final GridData gridData = new GridData(SWT.RIGHT, SWT.TOP, true, false, hspan, 1); - gridData.verticalIndent = 5; + gridData.verticalIndent = 4; label.setLayoutData(gridData); + label.setData(RWT.CUSTOM_VARIANT, "head"); return label; } @@ -216,6 +220,7 @@ public class FormBuilder { final Label label = new Label(parent, SWT.NONE); label.setText((StringUtils.isNoneBlank(value)) ? value : Constants.EMPTY_NOTE); final GridData gridData = new GridData(SWT.LEFT, SWT.CENTER, true, false, hspan, 1); + gridData.verticalIndent = 4; label.setLayoutData(gridData); return label; } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/form/FormHandle.java b/src/main/java/ch/ethz/seb/sebserver/gui/form/FormHandle.java index 359fd478..d2699dfc 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/form/FormHandle.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/form/FormHandle.java @@ -9,11 +9,11 @@ package ch.ethz.seb.sebserver.gui.form; import java.util.function.Consumer; -import java.util.function.Function; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition; import ch.ethz.seb.sebserver.gui.form.Form.FormFieldAccessor; @@ -21,12 +21,13 @@ import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.page.FieldValidationError; 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.action.Action; import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCallError; -public class FormHandle { +public class FormHandle { private static final Logger log = LoggerFactory.getLogger(FormHandle.class); @@ -35,28 +36,26 @@ public class FormHandle { private final PageContext pageContext; private final Form form; private final RestCall post; - private final Function postPostHandle; private final I18nSupport i18nSupport; FormHandle( final PageContext pageContext, final Form form, final RestCall post, - final Function postPostHandle, final I18nSupport i18nSupport) { this.pageContext = pageContext; this.form = form; this.post = post; - this.postPostHandle = postPostHandle; this.i18nSupport = i18nSupport; } - public final Result postChanges(final Action action) { - return doAPIPost(action.definition); + public final Action postChanges(final Action action) { + return doAPIPost(action.definition) + .getOrThrow(); } - public Result doAPIPost(final ActionDefinition action) { + public Result doAPIPost(final ActionDefinition actionDefinition) { this.form.process( name -> true, fieldAccessor -> fieldAccessor.resetError()); @@ -66,11 +65,15 @@ public class FormHandle { .withFormBinding(this.form) .call() .map(result -> { - this.pageContext.publishPageEvent(new ActionEvent(action, result)); - return result; + final Action action = this.pageContext.createAction(actionDefinition) + .withAttribute(AttributeKeys.READ_ONLY, "true") + .withEntity(result.getEntityKey()); + this.pageContext.publishPageEvent(new ActionEvent(action, false)); + return action; }) .onErrorDo(this::handleError) - .map(this.postPostHandle); + //.map(this.postPostHandle) + ; } private void handleError(final Throwable error) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/form/ImageUploadFieldBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/form/ImageUploadFieldBuilder.java index 5ea2aa24..e4d5ce68 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/form/ImageUploadFieldBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/form/ImageUploadFieldBuilder.java @@ -1,6 +1,6 @@ /* * 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/. @@ -27,7 +27,7 @@ public final class ImageUploadFieldBuilder extends FieldBuilder { final ImageUpload imageUpload = builder.widgetFactory.imageUploadLocalized( builder.formParent, new LocTextKey("sebserver.overall.upload"), - builder.readonly); + builder.readonly || this.readonly); final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false, this.spanInput, 1); imageUpload.setLayoutData(gridData); imageUpload.setImageBase64(this.value); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/form/SelectionFieldBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/form/SelectionFieldBuilder.java index 47692f6c..93d12286 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/form/SelectionFieldBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/form/SelectionFieldBuilder.java @@ -13,11 +13,13 @@ import java.util.Collection; import java.util.List; import java.util.function.Consumer; import java.util.function.Supplier; -import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; +import org.eclipse.rap.rwt.RWT; 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.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; @@ -26,6 +28,7 @@ import ch.ethz.seb.sebserver.gbl.util.Tuple; import ch.ethz.seb.sebserver.gui.widget.MultiSelection; import ch.ethz.seb.sebserver.gui.widget.Selection; import ch.ethz.seb.sebserver.gui.widget.SingleSelection; +import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant; public final class SelectionFieldBuilder extends FieldBuilder { @@ -56,7 +59,7 @@ public final class SelectionFieldBuilder extends FieldBuilder { @Override void build(final FormBuilder builder) { final Label lab = builder.labelLocalized(builder.formParent, this.label, this.spanLabel); - if (builder.readonly) { + if (builder.readonly || this.readonly) { buildReadOnly(builder, lab); } else { buildInput(builder, lab); @@ -85,38 +88,45 @@ public final class SelectionFieldBuilder extends FieldBuilder { /* Build the read-only representation of the selection field */ private void buildReadOnly(final FormBuilder builder, final Label lab) { - builder.form.putField( - this.name, lab, - builder.valueLabel( - builder.formParent, - getSelectionValue(this.value, this.multi), - this.spanInput)); - } + if (this.multi) { + final Composite composite = new Composite(builder.formParent, SWT.NONE); + final GridLayout gridLayout = new GridLayout(1, true); + gridLayout.verticalSpacing = 1; + gridLayout.marginLeft = 0; + gridLayout.marginHeight = 0; + gridLayout.marginWidth = 0; + composite.setLayout(gridLayout); + if (StringUtils.isBlank(this.value)) { + createMuliSelectionReadonlyLabel(composite, Constants.EMPTY_NOTE); + } else { + final Collection keys = Arrays.asList(StringUtils.split(this.value, Constants.LIST_SEPARATOR)); + this.itemsSupplier.get() + .stream() + .filter(tuple -> keys.contains(tuple._1)) + .map(tuple -> tuple._2) + .forEach(v -> createMuliSelectionReadonlyLabel(composite, v)); + } - /* - * For Single selection just the selected value, for multi selection a comma - * separated list of values within a String value. - * - * @param key the key or keys, in case of multi selection a comma separated String of keys - * - * @param multi indicates multi seleciton - * - * @return selected value or comma separated String list of selected values - */ - private String getSelectionValue(final String key, final boolean multi) { - if (multi) { - final Collection keys = Arrays.asList(StringUtils.split(key, Constants.LIST_SEPARATOR)); - return StringUtils.join(this.itemsSupplier.get().stream() - .filter(tuple -> keys.contains(tuple._1)) - .map(tuple -> tuple._2) - .collect(Collectors.toList()), - Constants.LIST_SEPARATOR); } else { - return this.itemsSupplier.get().stream() - .filter(tuple -> key.equals(tuple._1)) - .findFirst() - .map(tuple -> tuple._2) - .orElse(null); + builder.form.putField( + this.name, lab, + builder.valueLabel( + builder.formParent, + this.itemsSupplier.get().stream() + .filter(tuple -> this.value.equals(tuple._1)) + .findFirst() + .map(tuple -> tuple._2) + .orElse(null), + this.spanInput)); } } + + private void createMuliSelectionReadonlyLabel(final Composite composite, final String value) { + final Label label = new Label(composite, SWT.NONE); + final GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, true); + label.setLayoutData(gridData); + label.setData(RWT.CUSTOM_VARIANT, CustomVariant.SELECTION_READONLY.key); + label.setText(value); + } + } \ No newline at end of file diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/form/TextFieldBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/form/TextFieldBuilder.java index 197fb546..6decf374 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/form/TextFieldBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/form/TextFieldBuilder.java @@ -1,6 +1,6 @@ /* * 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/. @@ -33,7 +33,7 @@ public final class TextFieldBuilder extends FieldBuilder { } final Label lab = builder.labelLocalized(builder.formParent, this.label, this.spanLabel); - if (builder.readonly) { + if (builder.readonly || this.readonly) { builder.form.putField(this.name, lab, builder.valueLabel(builder.formParent, this.value, this.spanInput)); } else { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageContext.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageContext.java index cc00a230..d14136cc 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageContext.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageContext.java @@ -15,7 +15,6 @@ import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.page.action.Action; -import ch.ethz.seb.sebserver.gui.service.page.activity.ActivitySelection; import ch.ethz.seb.sebserver.gui.service.page.event.PageEvent; public interface PageContext { @@ -75,6 +74,8 @@ public interface PageContext { * @return the parent Component */ Composite getParent(); + PageContext copy(); + /** Create a copy of this PageContext with a new parent Composite. * * @param parent the new parent Composite @@ -95,11 +96,11 @@ public interface PageContext { * @return this PageContext instance (builder pattern) */ PageContext withAttribute(String key, String value); - default PageContext withSelection(final ActivitySelection selection) { - return withSelection(selection, true); - } - - PageContext withSelection(ActivitySelection selection, boolean clearAttributes); +// default PageContext withSelection(final ActivitySelection selection) { +// return withSelection(selection, true); +// } +// +// PageContext withSelection(ActivitySelection selection, boolean clearAttributes); String getAttribute(String name); @@ -157,5 +158,4 @@ public interface PageContext { void publishPageMessage(LocTextKey title, LocTextKey message); void publishPageMessage(PageMessageException pme); - } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageUtils.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageUtils.java index 27ff9201..c588c6fc 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageUtils.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageUtils.java @@ -19,6 +19,7 @@ import org.slf4j.LoggerFactory; import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType; +import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.util.Tuple; @@ -57,6 +58,7 @@ public final class PageUtils { public static List> getInstitutionSelectionResource(final RestService restService) { return restService.getBuilder(GetInstitutionNames.class) + .withQueryParam(Domain.INSTITUTION.ATTR_ACTIVE, "true") .call() .getOr(Collections.emptyList()) .stream() diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/Action.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/Action.java index fb21cb68..ef2c9020 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/Action.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/action/Action.java @@ -16,10 +16,12 @@ import java.util.function.Supplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.ethz.seb.sebserver.gbl.util.Result; +import ch.ethz.seb.sebserver.gbl.api.EntityType; +import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition; 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.PageMessageException; import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent; import ch.ethz.seb.sebserver.gui.service.page.event.ActionPublishEvent; @@ -31,7 +33,6 @@ public final class Action implements Runnable { private static final Logger log = LoggerFactory.getLogger(Action.class); public final RestService restService; - public final PageContext pageContext; public final ActionDefinition definition; Supplier confirm; LocTextKey successMessage; @@ -39,7 +40,8 @@ public final class Action implements Runnable { Supplier> selectionSupplier; - private Function> exec; + private PageContext pageContext; + private Function exec = Function.identity(); public Action( final ActionDefinition definition, @@ -66,13 +68,15 @@ public final class Action implements Runnable { private void exec() { try { - this.exec.apply(this) - .map(value -> { - this.pageContext.publishPageEvent( - new ActionEvent(this.definition, value)); - return value; - }) - .getOrThrow(); + this.pageContext.publishPageEvent(new ActionEvent(this.exec.apply(this), false)); + +// this.exec.apply(this) +// .map(action -> { +// this.pageContext.publishPageEvent( +// new ActionEvent(action, false)); +// return action; +// }) +// .getOrThrow(); } catch (final PageMessageException pme) { Action.this.pageContext.publishPageMessage(pme); @@ -96,7 +100,7 @@ public final class Action implements Runnable { return this.selectionSupplier; } - public Action withExec(final Function> exec) { + public Action withExec(final Function exec) { this.exec = exec; return this; } @@ -126,6 +130,38 @@ public final class Action implements Runnable { return this; } + public Action withEntity(final EntityKey entityKey) { + this.pageContext = this.pageContext.withEntityKey(entityKey); + return this; + } + + public Action withEntity(final Long modelId, final EntityType entityType) { + if (modelId != null) { + return withEntity(String.valueOf(modelId), entityType); + } + + return this; + } + + public Action withEntity(final String modelId, final EntityType entityType) { + if (modelId == null || entityType == null) { + return this; + } + + this.pageContext = this.pageContext.withEntityKey(new EntityKey(modelId, entityType)); + return this; + } + + public Action withParentEntity(final EntityKey entityKey) { + this.pageContext = this.pageContext.withParentEntityKey(entityKey); + return this; + } + + public Action withAttribute(final String name, final String value) { + this.pageContext = this.pageContext.withAttribute(name, value); + return this; + } + public PageContext publish() { this.pageContext.publishPageEvent(new ActionPublishEvent(this)); return this.pageContext; @@ -139,4 +175,16 @@ public final class Action implements Runnable { return this.pageContext; } + public EntityKey getEntityKey() { + return this.pageContext.getEntityKey(); + } + + public PageContext pageContext() { + return this.pageContext; + } + + public Action readonly(final boolean b) { + return this.withAttribute(AttributeKeys.READ_ONLY, "false"); + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/activity/ActivityActionHandler.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/activity/ActivityActionHandler.java deleted file mode 100644 index 66eab226..00000000 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/activity/ActivityActionHandler.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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.gui.service.page.activity; - -import org.eclipse.swt.widgets.Tree; - -import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition; -import ch.ethz.seb.sebserver.gui.service.page.PageContext; -import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent; - -public interface ActivityActionHandler { - - public ActionDefinition handlesAction(); - - void notifyAction( - final ActionEvent event, - final Tree navigation, - final PageContext composerCtx); - -} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/activity/ActivitySelection.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/activity/ActivitySelection.java deleted file mode 100644 index 1c4c19f9..00000000 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/activity/ActivitySelection.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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.gui.service.page.activity; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Consumer; - -import org.eclipse.swt.widgets.TreeItem; - -import ch.ethz.seb.sebserver.gbl.model.EntityKey; -import ch.ethz.seb.sebserver.gui.content.activity.Activity; -import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys; - -public class ActivitySelection { - - public static final Consumer EMPTY_FUNCTION = ti -> { - }; - public static final Consumer COLLAPSE_NONE_EMPTY = ti -> { - ti.removeAll(); - ti.setItemCount(1); - }; - - public final Activity activity; - final Map attributes; - Consumer expandFunction = EMPTY_FUNCTION; - - public ActivitySelection(final Activity activity) { - this.activity = activity; - this.attributes = new HashMap<>(); - } - - public ActivitySelection withEntity(final EntityKey entityKey) { - if (entityKey != null) { - this.attributes.put(AttributeKeys.ENTITY_ID, entityKey.modelId); - this.attributes.put(AttributeKeys.ENTITY_TYPE, entityKey.entityType.name()); - } - - return this; - } - - public ActivitySelection withParentEntity(final EntityKey parentEntityKey) { - if (parentEntityKey != null) { - this.attributes.put(AttributeKeys.PARENT_ENTITY_ID, parentEntityKey.modelId); - this.attributes.put(AttributeKeys.PARENT_ENTITY_TYPE, parentEntityKey.entityType.name()); - } - - return this; - } - - public ActivitySelection withAttribute(final String name, final String value) { - this.attributes.put(name, value); - return this; - } - - public Map getAttributes() { - return Collections.unmodifiableMap(this.attributes); - } - - public ActivitySelection withExpandFunction(final Consumer expandFunction) { - if (expandFunction == null) { - this.expandFunction = EMPTY_FUNCTION; - } - this.expandFunction = expandFunction; - return this; - } - - public void processExpand(final TreeItem item) { - this.expandFunction.accept(item); - } - - public String getEntityId() { - return this.attributes.get(AttributeKeys.ENTITY_ID); - } - -} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActionEvent.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActionEvent.java index 8000f156..06f78316 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActionEvent.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActionEvent.java @@ -8,21 +8,19 @@ package ch.ethz.seb.sebserver.gui.service.page.event; -import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition; +import ch.ethz.seb.sebserver.gui.service.page.action.Action; /** This Event is used to propagate a user-action to the GUI system. * Potentially every component can listen to an Event and react on the user-action */ public final class ActionEvent implements PageEvent { - public final ActionDefinition actionDefinition; - public final Object source; + public final Action action; + public final boolean activity; - public ActionEvent( - final ActionDefinition actionDefinition, - final Object source) { - - this.actionDefinition = actionDefinition; - this.source = source; + public ActionEvent(final Action action, final boolean activity) { + super(); + this.action = action; + this.activity = activity; } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActionEventListener.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActionEventListener.java index ac4844ad..b9a1ce0b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActionEventListener.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActionEventListener.java @@ -8,13 +8,6 @@ package ch.ethz.seb.sebserver.gui.service.page.event; -import java.util.function.Consumer; -import java.util.function.Predicate; - -import org.eclipse.swt.widgets.Widget; - -import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition; - public interface ActionEventListener extends PageEventListener { @Override @@ -32,42 +25,42 @@ public interface ActionEventListener extends PageEventListener { // } // - static ActionEventListener of( - final Predicate predicate, - final Consumer eventConsumer) { +// static ActionEventListener of( +// final Predicate predicate, +// final Consumer eventConsumer) { +// +// return new ActionEventListener() { +// @Override +// public void notify(final ActionEvent event) { +// if (predicate.test(event)) { +// eventConsumer.accept(event); +// } +// } +// }; +// } - return new ActionEventListener() { - @Override - public void notify(final ActionEvent event) { - if (predicate.test(event)) { - eventConsumer.accept(event); - } - } - }; - } - - static ActionEventListener of( - final ActionDefinition actionDefinition, - final Consumer eventConsumer) { - - return new ActionEventListener() { - @Override - public void notify(final ActionEvent event) { - if (event.actionDefinition == actionDefinition) { - eventConsumer.accept(event); - } - } - }; - } - - static void injectListener( - final Widget widget, - final ActionDefinition actionDefinition, - final Consumer eventConsumer) { - - widget.setData( - PageEventListener.LISTENER_ATTRIBUTE_KEY, - of(actionDefinition, eventConsumer)); - } +// static ActionEventListener of( +// final ActionDefinition actionDefinition, +// final Consumer eventConsumer) { +// +// return new ActionEventListener() { +// @Override +// public void notify(final ActionEvent event) { +// if (event.actionDefinition == actionDefinition) { +// eventConsumer.accept(event); +// } +// } +// }; +// } +// +// static void injectListener( +// final Widget widget, +// final ActionDefinition actionDefinition, +// final Consumer eventConsumer) { +// +// widget.setData( +// PageEventListener.LISTENER_ATTRIBUTE_KEY, +// of(actionDefinition, eventConsumer)); +// } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActivitySelectionEvent.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActivitySelectionEvent.java deleted file mode 100644 index cdf4829e..00000000 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActivitySelectionEvent.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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.gui.service.page.event; - -import ch.ethz.seb.sebserver.gui.service.page.activity.ActivitySelection; - -public class ActivitySelectionEvent implements PageEvent { - - public final ActivitySelection selection; - - public ActivitySelectionEvent(final ActivitySelection selection) { - this.selection = selection; - } - -} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActivitySelectionListener.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActivitySelectionListener.java deleted file mode 100644 index c637ded2..00000000 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/event/ActivitySelectionListener.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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.gui.service.page.event; - -public interface ActivitySelectionListener extends PageEventListener { - - @Override - default boolean match(final Class eventType) { - return eventType == ActivitySelectionEvent.class; - } - -} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/MainPageState.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/MainPageState.java index 5bb33174..a550af76 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/MainPageState.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/MainPageState.java @@ -15,14 +15,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.ethz.seb.sebserver.gui.content.MainPage; -import ch.ethz.seb.sebserver.gui.content.activity.Activity; -import ch.ethz.seb.sebserver.gui.service.page.activity.ActivitySelection; +import ch.ethz.seb.sebserver.gui.service.page.action.Action; public final class MainPageState { private static final Logger log = LoggerFactory.getLogger(MainPageState.class); - public ActivitySelection activitySelection = Activity.NONE.createSelection(); + public Action action = null; private MainPageState() { } @@ -49,6 +48,6 @@ public final class MainPageState { public static void clear() { final MainPageState mainPageState = get(); - mainPageState.activitySelection = Activity.NONE.createSelection(); + mainPageState.action = null; } } \ No newline at end of file diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageContextImpl.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageContextImpl.java index d191f9a8..fc7eddb1 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageContextImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageContextImpl.java @@ -36,7 +36,6 @@ import ch.ethz.seb.sebserver.gui.service.page.PageContext; import ch.ethz.seb.sebserver.gui.service.page.PageDefinition; import ch.ethz.seb.sebserver.gui.service.page.PageMessageException; import ch.ethz.seb.sebserver.gui.service.page.action.Action; -import ch.ethz.seb.sebserver.gui.service.page.activity.ActivitySelection; import ch.ethz.seb.sebserver.gui.service.page.event.PageEvent; import ch.ethz.seb.sebserver.gui.service.page.event.PageEventListener; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; @@ -95,6 +94,11 @@ public class PageContextImpl implements PageContext { return this.parent; } + @Override + public PageContext copy() { + return copyOf(this.parent); + } + @Override public PageContext copyOf(final Composite parent) { return new PageContextImpl( @@ -134,26 +138,26 @@ public class PageContextImpl implements PageContext { attrs); } - @Override - public PageContext withSelection(final ActivitySelection selection, final boolean clearAttributes) { - if (selection == null) { - return this; - } - - final Map attrs = new HashMap<>(); - if (!clearAttributes) { - attrs.putAll(this.attributes); - } - attrs.putAll(selection.getAttributes()); - - return new PageContextImpl( - this.restService, - this.i18nSupport, - this.composerService, - this.root, - this.parent, - attrs); - } +// @Override +// public PageContext withSelection(final ActivitySelection selection, final boolean clearAttributes) { +// if (selection == null) { +// return this; +// } +// +// final Map attrs = new HashMap<>(); +// if (!clearAttributes) { +// attrs.putAll(this.attributes); +// } +// attrs.putAll(selection.getAttributes()); +// +// return new PageContextImpl( +// this.restService, +// this.i18nSupport, +// this.composerService, +// this.root, +// this.parent, +// attrs); +// } @Override public String getAttribute(final String name) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java index e947f6ea..34b5cbb6 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java @@ -206,7 +206,6 @@ public abstract class RestCall { } else { this.body = formBinding.getFormUrlEncoded(); return this; - //return withHeaders(formBinding.getFormAsQueryAttributes()); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/useraccount/ChangePassword.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/useraccount/ChangePassword.java new file mode 100644 index 00000000..e90c45e1 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/useraccount/ChangePassword.java @@ -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 ChangePassword extends RestCall { + + protected ChangePassword() { + super( + new TypeReference() { + }, + HttpMethod.PUT, + MediaType.APPLICATION_JSON_UTF8, + API.USER_ACCOUNT_ENDPOINT + API.PASSWORD_PATH_SEGMENT); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/SEBServerAuthorizationContext.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/SEBServerAuthorizationContext.java index cdc26284..e38cfa18 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/SEBServerAuthorizationContext.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/SEBServerAuthorizationContext.java @@ -14,20 +14,51 @@ import ch.ethz.seb.sebserver.gbl.model.user.UserInfo; import ch.ethz.seb.sebserver.gbl.model.user.UserRole; import ch.ethz.seb.sebserver.gbl.util.Result; +/** Defines functionality for the SEB Server webservice authorization context used to + * manage a user session on GUI service. */ public interface SEBServerAuthorizationContext { + /** Indicates if this authorization context is still valid + * + * @return true if the SEBServerAuthorizationContext is valid. False of not. */ boolean isValid(); + /** Indicated whether a user is logged in within this authorization context or not. + * + * @return whether a user is logged in within this authorization context or not */ boolean isLoggedIn(); + /** Requests a login with username and password on SEB Server webservice. + * This uses OAuth 2 and Springs OAuth2RestTemplate to exchange user/client credentials + * with an access and refresh token. + * + * @param username the username for login + * @param password the password for login + * @return */ boolean login(String username, String password); + /** Requests a logout on SEB Server webservice if a user is currently logged in + * This uses OAuth 2 and Springs OAuth2RestTemplate to make a revoke token request for the + * currently logged in user and also invalidates this SEBServerAuthorizationContext + * + * @return true if logout was successful */ boolean logout(); + /** Gets a Result of the UserInfo data of currently logged in user or of an error if no user is logged in + * or there was an unexpected error while trying to get the user information. + * + * @return Result of logged in user data or of an error on fail */ Result getLoggedInUser(); + /** Returns true if a current logged in user has the specified role. + * + * @param role the UserRole to check + * @return true if a current logged in user has the specified role */ public boolean hasRole(UserRole role); + /** Get the underling RestTemplate to connect and communicate with the SEB Server webservice. + * + * @return the underling RestTemplate to connect and communicate with the SEB Server webservice */ RestTemplate getRestTemplate(); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/table/EntityTable.java b/src/main/java/ch/ethz/seb/sebserver/gui/table/EntityTable.java index fe305a10..d7ffe8e2 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/table/EntityTable.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/table/EntityTable.java @@ -176,6 +176,15 @@ public class EntityTable extends Composite { this.sortOrder); } + public String getSingleSelection() { + final TableItem[] selection = this.table.getSelection(); + if (selection == null || selection.length == 0) { + return null; + } + + return getRowDataId(selection[0]); + } + public Set getSelection() { final TableItem[] selection = this.table.getSelection(); if (selection == null) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/MultiSelection.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/MultiSelection.java index 2335fbf3..9e432864 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/widget/MultiSelection.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/MultiSelection.java @@ -23,6 +23,7 @@ import org.eclipse.swt.widgets.Label; import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.util.Tuple; +import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant; public class MultiSelection extends Composite implements Selection { @@ -37,6 +38,8 @@ public class MultiSelection extends Composite implements Selection { final GridLayout gridLayout = new GridLayout(1, true); gridLayout.verticalSpacing = 1; gridLayout.marginLeft = 0; + gridLayout.marginHeight = 0; + gridLayout.marginWidth = 0; setLayout(gridLayout); } @@ -44,25 +47,29 @@ public class MultiSelection extends Composite implements Selection { public void applyNewMapping(final List> mapping) { final String selectionValue = getSelectionValue(); this.selected.clear(); + this.labels.clear(); for (final Tuple tuple : mapping) { final Label label = new Label(this, SWT.NONE); final GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, true); label.setLayoutData(gridData); label.setData(OPTION_VALUE, tuple._1); - label.setData(RWT.CUSTOM_VARIANT, "selection"); - label.setText(" " + tuple._2); + label.setData(RWT.CUSTOM_VARIANT, CustomVariant.SELECTION.key); + label.setText(tuple._2); label.addListener(SWT.MouseDown, event -> { final Label l = (Label) event.widget; if (this.selected.contains(l)) { - l.setData(RWT.CUSTOM_VARIANT, "selection"); + l.setData(RWT.CUSTOM_VARIANT, CustomVariant.SELECTION.key); this.selected.remove(l); } else { - l.setData(RWT.CUSTOM_VARIANT, "selected"); + l.setData(RWT.CUSTOM_VARIANT, CustomVariant.SELECTED.key); this.selected.add(l); } }); + this.labels.add(label); + } + if (StringUtils.isNoneBlank(selectionValue)) { + select(selectionValue); } - select(selectionValue); } public void selectOne(final String key) { @@ -70,7 +77,7 @@ public class MultiSelection extends Composite implements Selection { .filter(label -> key.equals(label.getData(OPTION_VALUE))) .findFirst() .ifPresent(label -> { - label.setData(RWT.CUSTOM_VARIANT, "selected"); + label.setData(RWT.CUSTOM_VARIANT, CustomVariant.SELECTED.key); this.selected.add(label); }); } @@ -80,14 +87,14 @@ public class MultiSelection extends Composite implements Selection { .filter(label -> key.equals(label.getData(OPTION_VALUE))) .findFirst() .ifPresent(label -> { - label.setData(RWT.CUSTOM_VARIANT, "selection"); + label.setData(RWT.CUSTOM_VARIANT, CustomVariant.SELECTION.key); this.selected.remove(label); }); } public void deselectAll() { for (final Label label : this.selected) { - label.setData(RWT.CUSTOM_VARIANT, "selection"); + label.setData(RWT.CUSTOM_VARIANT, CustomVariant.SELECTION.key); } this.selected.clear(); } @@ -96,10 +103,10 @@ public class MultiSelection extends Composite implements Selection { public void select(final String keys) { this.selected.clear(); if (StringUtils.isNotBlank(keys)) { - final List split = Arrays.asList(keys.split(keys, Constants.LIST_SEPARATOR_CHAR)); + final List split = Arrays.asList(StringUtils.split(keys, Constants.LIST_SEPARATOR)); for (final Label label : this.labels) { if (split.contains(label.getData(OPTION_VALUE))) { - label.setData(RWT.CUSTOM_VARIANT, "selected"); + label.setData(RWT.CUSTOM_VARIANT, CustomVariant.SELECTED.key); this.selected.add(label); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java index c3aef307..d73eb632 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java @@ -49,7 +49,6 @@ import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService; import ch.ethz.seb.sebserver.gui.service.page.PageContext; import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent; -import ch.ethz.seb.sebserver.gui.service.page.event.ActionEventListener; import ch.ethz.seb.sebserver.gui.service.push.ServerPushService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; import ch.ethz.seb.sebserver.gui.table.TableBuilder; @@ -105,6 +104,10 @@ public class WidgetFactory { IMAGE_BUTTON("imageButton"), TEXT_ACTION("action"), + SELECTION("selection"), + SELECTED("selected"), + SELECTION_READONLY("selectionReadonly"), + FOOTER("footer"), ; @@ -158,10 +161,10 @@ public class WidgetFactory { final Composite defaultPageLayout = defaultPageLayout(parent); final Label labelLocalizedTitle = labelLocalizedTitle(defaultPageLayout, title); labelLocalizedTitle.setLayoutData(new GridData(SWT.TOP, SWT.LEFT, true, false)); - ActionEventListener.injectListener( - labelLocalizedTitle, - actionDefinition, - eventFunction.apply(labelLocalizedTitle)); +// ActionEventListener.injectListener( +// labelLocalizedTitle, +// actionDefinition, +// eventFunction.apply(labelLocalizedTitle)); return defaultPageLayout; } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/BulkActionService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/BulkActionService.java index 92a11f29..7bf8b3de 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/BulkActionService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/BulkActionService.java @@ -14,6 +14,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.springframework.stereotype.Service; @@ -135,7 +136,10 @@ public class BulkActionService { final List> dependantSupporterInHierarchicalOrder = getDependantSupporterInHierarchicalOrder(action); Collections.reverse(dependantSupporterInHierarchicalOrder); - return dependantSupporterInHierarchicalOrder; + return dependantSupporterInHierarchicalOrder + .stream() + .filter(v -> v != null) + .collect(Collectors.toList()); } default: return getDependantSupporterInHierarchicalOrder(action); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ActivatableEntityDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ActivatableEntityDAO.java index 4747807e..cd47fcff 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ActivatableEntityDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ActivatableEntityDAO.java @@ -39,4 +39,10 @@ public interface ActivatableEntityDAO * @return The Collection of Results refer to the EntityKey instance or refer to an error if happened */ Result> setActive(Set all, boolean active); + /** Indicates if the activatable entity with specified model identifier is currently active + * + * @param modelId the model identifier of the entity + * @return true if the entity is active, false otherwise */ + boolean isActive(String modelId); + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/UserActivityLogDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/UserActivityLogDAO.java index f0b70917..05c574ef 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/UserActivityLogDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/UserActivityLogDAO.java @@ -26,6 +26,7 @@ public interface UserActivityLogDAO extends CREATE, IMPORT, MODIFY, + PASSWORD_CHANGE, DEACTIVATE, ACTIVATE, DELETE diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/UserDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/UserDAO.java index 73faa44a..cc7b45e9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/UserDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/UserDAO.java @@ -43,6 +43,7 @@ public interface UserDAO extends ActivatableEntityDAO, BulkAc Result changePassword(String modelId, String newPassword); /** Use this to get the SEBServerUser principal for a given username. + * This should be used for internal authorization and consider only active user accounts * * @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 */ diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java index a9e24671..f41bc74f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java @@ -235,6 +235,21 @@ public class ExamDAOImpl implements ExamDAO { }); } + @Override + @Transactional(readOnly = true) + public boolean isActive(final String modelId) { + if (StringUtils.isBlank(modelId)) { + return false; + } + + return this.examRecordMapper.countByExample() + .where(ExamRecordDynamicSqlSupport.id, isEqualTo(Long.valueOf(modelId))) + .and(ExamRecordDynamicSqlSupport.active, isEqualTo(BooleanUtils.toInteger(true))) + .build() + .execute() + .longValue() > 0; + } + @Override @Transactional public Result> delete(final Set all) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/InstitutionDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/InstitutionDAOImpl.java index 96645062..b32bffe5 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/InstitutionDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/InstitutionDAOImpl.java @@ -204,6 +204,21 @@ public class InstitutionDAOImpl implements InstitutionDAO { }); } + @Override + @Transactional(readOnly = true) + public boolean isActive(final String modelId) { + if (StringUtils.isBlank(modelId)) { + return false; + } + + return this.institutionRecordMapper.countByExample() + .where(InstitutionRecordDynamicSqlSupport.id, isEqualTo(Long.valueOf(modelId))) + .and(InstitutionRecordDynamicSqlSupport.active, isEqualTo(BooleanUtils.toInteger(true))) + .build() + .execute() + .longValue() > 0; + } + @Override @Transactional public Result> delete(final Set all) { @@ -266,5 +281,4 @@ public class InstitutionDAOImpl implements InstitutionDAO { record.getLogoImage(), BooleanUtils.toBooleanObject(record.getActive()))); } - } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/LmsSetupDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/LmsSetupDAOImpl.java index 1a5a5d0d..ddb1553c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/LmsSetupDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/LmsSetupDAOImpl.java @@ -207,6 +207,21 @@ public class LmsSetupDAOImpl implements LmsSetupDAO { }); } + @Override + @Transactional(readOnly = true) + public boolean isActive(final String modelId) { + if (StringUtils.isBlank(modelId)) { + return false; + } + + return this.lmsSetupRecordMapper.countByExample() + .where(LmsSetupRecordDynamicSqlSupport.id, isEqualTo(Long.valueOf(modelId))) + .and(LmsSetupRecordDynamicSqlSupport.active, isEqualTo(BooleanUtils.toInteger(true))) + .build() + .execute() + .longValue() > 0; + } + @Override @Transactional public Result> delete(final Set all) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/UserDaoImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/UserDaoImpl.java index 600e2844..417b9e6f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/UserDaoImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/UserDaoImpl.java @@ -22,6 +22,7 @@ import java.util.stream.Collectors; import javax.validation.constraints.NotNull; import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTimeZone; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,6 +33,7 @@ import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import ch.ethz.seb.sebserver.WebSecurityConfig; +import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType; 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; @@ -276,6 +278,21 @@ public class UserDaoImpl implements UserDAO { }); } + @Override + @Transactional(readOnly = true) + public boolean isActive(final String modelId) { + if (StringUtils.isBlank(modelId)) { + return false; + } + + return this.userRecordMapper.countByExample() + .where(UserRecordDynamicSqlSupport.id, isEqualTo(Long.valueOf(modelId))) + .and(UserRecordDynamicSqlSupport.active, isEqualTo(BooleanUtils.toInteger(true))) + .build() + .execute() + .longValue() > 0; + } + @Override @Transactional public Result> delete(final Set all) { @@ -307,7 +324,9 @@ public class UserDaoImpl implements UserDAO { @Transactional(readOnly = true) public Set getDependencies(final BulkAction bulkAction) { // all of institution - if (bulkAction.sourceType == EntityType.INSTITUTION) { + if (bulkAction.sourceType == EntityType.INSTITUTION && + (bulkAction.type == BulkActionType.DEACTIVATE || bulkAction.type == BulkActionType.HARD_DELETE)) { + return getDependencies(bulkAction, this::allIdsOfInstitution); } @@ -356,13 +375,13 @@ public class UserDaoImpl implements UserDAO { private Result> allIdsOfInstitution(final EntityKey institutionKey) { return Result.tryCatch(() -> { - return this.userRecordMapper.selectIdsByExample() + return this.userRecordMapper.selectByExample() .where(UserRecordDynamicSqlSupport.institutionId, isEqualTo(Long.valueOf(institutionKey.modelId))) .build() .execute() .stream() - .map(id -> new EntityKey(id, EntityType.USER)) + .map(rec -> new EntityKey(rec.getUuid(), EntityType.USER)) .collect(Collectors.toList()); }); } @@ -390,7 +409,7 @@ public class UserDaoImpl implements UserDAO { .selectByExample() .where(UserRecordDynamicSqlSupport.username, isEqualTo(username)) .and(UserRecordDynamicSqlSupport.active, - isEqualToWhenPresent(BooleanUtils.toInteger(true))) + isEqualTo(BooleanUtils.toInteger(true))) .build() .execute()); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/validation/BeanValidationService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/validation/BeanValidationService.java index 779b49d4..176e43d9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/validation/BeanValidationService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/validation/BeanValidationService.java @@ -8,21 +8,37 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.validation; +import java.util.Collection; +import java.util.Map; +import java.util.stream.Collectors; + import org.springframework.stereotype.Service; import org.springframework.validation.DirectFieldBindingResult; import org.springframework.validation.Validator; +import ch.ethz.seb.sebserver.gbl.api.EntityType; +import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Result; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ActivatableEntityDAO; @Service @WebServiceProfile public class BeanValidationService { private final Validator validator; + private final Map> activatableDAOs; + + public BeanValidationService( + final Validator validator, + final Collection> activatableDAOs) { - public BeanValidationService(final Validator validator) { this.validator = validator; + this.activatableDAOs = activatableDAOs + .stream() + .collect(Collectors.toUnmodifiableMap( + dao -> dao.entityType(), + dao -> dao)); } public Result validateBean(final T bean) { @@ -35,4 +51,13 @@ public class BeanValidationService { return Result.of(bean); } + public boolean isActive(final EntityKey entityKey) { + final ActivatableEntityDAO activatableEntityDAO = this.activatableDAOs.get(entityKey.entityType); + if (activatableEntityDAO == null) { + return false; + } + + return activatableEntityDAO.isActive(entityKey.modelId); + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/WebServiceUserDetails.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/WebServiceUserDetails.java index 3b9c7f3d..b1293f21 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/WebServiceUserDetails.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/WebServiceUserDetails.java @@ -28,6 +28,7 @@ public class WebServiceUserDetails implements UserDetailsService { this.userDAO = userDAO; } + // TODO do we need an institution id here? otherwise username must be unique thought all institutions! @Override public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException { return this.userDAO.sebServerUserByUsername(username) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ActivatableEntityController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ActivatableEntityController.java index 5b0ae119..1f82fd56 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ActivatableEntityController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ActivatableEntityController.java @@ -15,8 +15,8 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import ch.ethz.seb.sebserver.gbl.api.API; -import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType; +import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport; @@ -121,15 +121,16 @@ public abstract class ActivatableEntityController setActive(final String id, final boolean active) { + private Result setActive(final String modelId, final boolean active) { final EntityType entityType = this.entityDAO.entityType(); final BulkAction bulkAction = new BulkAction( (active) ? BulkActionType.ACTIVATE : BulkActionType.DEACTIVATE, entityType, - new EntityKey(id, entityType)); + new EntityKey(modelId, entityType)); - return this.entityDAO.byModelId(id) + return this.entityDAO.byModelId(modelId) .flatMap(this.authorization::checkWrite) + .flatMap(this::validForSave) .flatMap(entity -> this.bulkActionService.createReport(bulkAction)); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/EntityController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/EntityController.java index c594f4bc..1811244f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/EntityController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/EntityController.java @@ -249,6 +249,7 @@ public abstract class EntityController validForSave(final UserInfo userInfo) { + return Result.tryCatch(() -> { + // check of institution of UserInfo is active. Otherwise save is not valid + if (!this.beanValidationService.isActive(new EntityKey(userInfo.institutionId, EntityType.INSTITUTION))) { + throw new IllegalAPIArgumentException( + "User within an inactive institution cannot be created nor modified"); + } + return userInfo; + }); + } + @RequestMapping( - path = API.MODEL_ID_VAR_PATH_SEGMENT + API.PASSWORD_PATH_SEGMENT, + path = 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); - } + public UserInfo changePassword(@Valid @RequestBody final PasswordChange passwordChange) { + final String modelId = passwordChange.getModelId(); return this.userDAO.byModelId(modelId) .flatMap(this.authorization::checkWrite) + .map(ui -> checkPasswordChange(ui, passwordChange)) .flatMap(e -> this.userDAO.changePassword(modelId, passwordChange.getNewPassword())) .flatMap(this::revokeAccessToken) - .flatMap(e -> this.userActivityLogDAO.log(ActivityType.MODIFY, e)) - + .flatMap(e -> this.userActivityLogDAO.log(ActivityType.PASSWORD_CHANGE, e)) .getOrThrow(); } + private UserInfo checkPasswordChange(final UserInfo info, final PasswordChange passwordChange) { + final SEBServerUser authUser = this.userDAO.sebServerUserByUsername(info.username) + .getOrThrow(); + + if (!this.userPasswordEncoder.matches(passwordChange.getOldPassword(), authUser.getPassword())) { + throw new APIMessageException(APIMessage.fieldValidationError( + new FieldError( + "passwordChange", + PasswordChange.ATTR_NAME_OLD_PASSWORD, + "old password is wrong"))); + } + + if (!passwordChange.newPasswordMatch()) { + + throw new APIMessageException(APIMessage.fieldValidationError( + new FieldError( + "passwordChange", + PasswordChange.ATTR_NAME_RETYPED_NEW_PASSWORD, + "old password is wrong"))); + } + + return info; + + } + private Result revokeAccessToken(final UserInfo userInfo) { return Result.tryCatch(() -> { this.applicationEventPublisher.publishEvent( diff --git a/src/main/resources/data-demo.sql b/src/main/resources/data-demo.sql index af09838f..9ba3e97e 100644 --- a/src/main/resources/data-demo.sql +++ b/src/main/resources/data-demo.sql @@ -1,13 +1,31 @@ INSERT INTO institution VALUES - (1, 'ETH Zürich', 'ethz', null, 1) + (1, 'ETH Zürich', 'ethz', null, 1), + (2, 'Institution 2', 'inst2', null, 1), + (3, 'Institution 3', 'inst3', null, 0), + (4, 'Institution 4', 'inst4', null, 0), + (5, 'Institution 5', 'inst5', null, 0), + (6, 'Institution 6', 'inst6', null, 0), + (7, 'Institution 7', 'inst7', null, 0), + (8, 'Institution 8', 'inst8', null, 0) ; INSERT INTO user VALUES - (1, 1, 'internalDemoAdmin', 'Admin1', 'admin', '$2a$08$c2GKYEYoUVXH1Yb8GXVXVu66ltPvbZgLMcVSXRH.LgZNF/YeaYB8m', 'admin@nomail.nomail', 'en', 'UTC', 1) + (1, 1, 'super-admin', 'super-admin', 'super-admin', '$2a$08$c2GKYEYoUVXH1Yb8GXVXVu66ltPvbZgLMcVSXRH.LgZNF/YeaYB8m', 'super-admin@nomail.nomail', 'en', 'UTC', 1), + (2, 1, 'internalDemoAdmin', 'Admin1', 'admin', '$2a$08$c2GKYEYoUVXH1Yb8GXVXVu66ltPvbZgLMcVSXRH.LgZNF/YeaYB8m', 'admin@nomail.nomail', 'en', 'UTC', 1), + (3, 1, 'inst1Admin', 'Institutional1 Admin', 'inst1Admin', '$2a$08$c2GKYEYoUVXH1Yb8GXVXVu66ltPvbZgLMcVSXRH.LgZNF/YeaYB8m', 'admin@nomail.nomail', 'de', 'UTC', 1), + (4, 2, 'inst2Admin', 'Institutional2 Admin', 'inst2Admin', '$2a$08$c2GKYEYoUVXH1Yb8GXVXVu66ltPvbZgLMcVSXRH.LgZNF/YeaYB8m', 'admin@nomail.nomail', 'en', 'UTC', 1), + (5, 2, 'examAdminInst2', 'Exam Admin 2', 'examAdmin2', '$2a$08$c2GKYEYoUVXH1Yb8GXVXVu66ltPvbZgLMcVSXRH.LgZNF/YeaYB8m', 'admin@nomail.nomail', 'de', 'UTC', 1) ; INSERT INTO user_role VALUES - (1, 1, 'SEB_SERVER_ADMIN') + (1, 1, 'SEB_SERVER_ADMIN'), + (2, 1, 'INSTITUTIONAL_ADMIN'), + (3, 1, 'EXAM_ADMIN'), + (4, 1, 'EXAM_SUPPORTER'), + (5, 2, 'SEB_SERVER_ADMIN'), + (6, 3, 'INSTITUTIONAL_ADMIN'), + (7, 4, 'INSTITUTIONAL_ADMIN'), + (8, 5, 'EXAM_ADMIN') ; diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 4839702e..677b1b78 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -52,12 +52,6 @@ sebserver.mainpage.minimize.tooltip=Minimize sebserver.activitiespane.title=Activities sebserver.actionpane.title=Actions -# Activities -sebserver.activities.institution=Institution -sebserver.activities.useraccount=User Account - - - ################################ # Institution ################################ @@ -67,6 +61,7 @@ sebserver.institution.list.column.name=Name sebserver.institution.list.column.urlSuffix=URL Suffix sebserver.institution.list.column.active=Active +sebserver.institution.action.list=Institution sebserver.institution.action.new=New Institution sebserver.institution.action.list.view=View Selected sebserver.institution.action.modify=Edit Institution @@ -101,13 +96,16 @@ sebserver.useraccount.list.column.email=Mail sebserver.useraccount.list.column.language=Language sebserver.useraccount.list.column.active=Active +sebserver.useraccount.action.list=User Account sebserver.useraccount.action.new=New User Account sebserver.useraccount.action.view=View Selected -sebserver.useraccount.action.modify=Edit Selected +sebserver.useraccount.action.list.modify=Edit Selected +sebserver.useraccount.action.modify=Edit 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.action.change.password=Change Password sebserver.useraccount.info.pleaseSelect=Please Select an User Account first. @@ -123,5 +121,10 @@ sebserver.useraccount.form.roles=User Roles sebserver.useraccount.form.password=Password sebserver.useraccount.form.password.retyped=Retyped Password +sebserver.useraccount.form.pwchange.title=Change Password : {0} +sebserver.useraccount.form.institution.password.old=Old Password +sebserver.useraccount.form.institution.password.new=New Password +sebserver.useraccount.form.institution.password.retyped=Retyped New Password + diff --git a/src/main/resources/static/css/sebserver.css b/src/main/resources/static/css/sebserver.css index b75b3d6d..9f930623 100644 --- a/src/main/resources/static/css/sebserver.css +++ b/src/main/resources/static/css/sebserver.css @@ -26,6 +26,10 @@ Label { text-shadow: none; } +Label.head { + font: bold 12px Verdana, "Lucida Sans", Arial, Helvetica, sans-serif; +} + Label.action { font: 12px Verdana, "Lucida Sans", Arial, Helvetica, sans-serif; color: #82BE1E; @@ -71,6 +75,10 @@ Label.selection { padding: 4px 6px 3px 6px; } +Label.selectionReadonly { + padding: 4px 6px 3px 6px; +} + Label:hover.selection { color: #4a4a4a; background-color: #b5b5b5; @@ -80,8 +88,8 @@ Label:hover.selection { Label.selected { color: #4a4a4a; - background-color: #b5b5b5; - background-image: gradient(linear, left top, left bottom, from(#b5b5b5),to(#b5b5b5)); + background-color: #c5c5c5; + background-image: gradient(linear, left top, left bottom, from(#c5c5c5),to(#c5c5c5)); padding: 4px 6px 3px 6px; } @@ -368,7 +376,7 @@ Tree { border: none; color: #1f407a; margin: 0px 0px 0px 0px; - padding: 0px 0px 0px 0px; + padding: 0px 0px 0px 40px; } Tree[BORDER] { @@ -383,13 +391,14 @@ TreeItem { text-shadow: none; background-image: none; margin: 20px 20px 20px 20px; - padding: 20px 20px 20px 20px; + padding: 20px 20px 20px 40px; } TreeItem:linesvisible:even { background-color: #f3f3f4; } + Tree-RowOverlay { background-color: transparent; color: inherit; @@ -402,12 +411,12 @@ Tree-RowOverlay:hover { } Tree-RowOverlay:selected { - background-color: #b5b5b5; + background-color: #82be1e; color: #1F407A; } Tree-RowOverlay:selected:unfocused { - background-color: #b5b5b5; + background-color: #82be1e; color: #1f407a; } @@ -438,6 +447,7 @@ TreeItem.actions { margin: 0 0 0 0; } + Tree-RowOverlay:hover.actions { background-color: #82be1e; color: #4a4a4a; @@ -448,6 +458,31 @@ Tree-RowOverlay:selected.actions { color: #4a4a4a; } +Tree-Indent { + width: 16px; + background-image: none; +} + +Tree-Indent:collapsed { + background-image: none; +} + +Tree-Indent:collapsed:hover { + background-image: none; +} + +Tree-Indent:expanded { + background-image: none; +} + +Tree-Indent:expanded:hover { + background-image: none; +} + +Tree-Indent:line { + background-image: none; +} + /* TabFolder default theme */ TabFolder { @@ -665,14 +700,14 @@ Table-RowOverlay:hover { Table-RowOverlay:selected { color: #4a4a4a; - background-color: #b5b5b5; - background-image: gradient(linear, left top, left bottom, from(#b5b5b5),to(#b5b5b5)); + background-color: #c5c5c5; + background-image: gradient(linear, left top, left bottom, from(#c5c5c5),to(#c5c5c5)); } Table-RowOverlay:selected:unfocused { color: #4a4a4a; - background-color: #b5b5b5; - background-image: gradient(linear, left top, left bottom, from(#b5b5b5),to(#b5b5b5)); + background-color: #c5c5c5; + background-image: gradient(linear, left top, left bottom, from(#c5c5c5),to(#c5c5c5)); } Table-RowOverlay:linesvisible:even:hover { @@ -683,13 +718,13 @@ Table-RowOverlay:linesvisible:even:hover { Table-RowOverlay:linesvisible:even:selected { color: #4a4a4a; - background-color: #b5b5b5; - background-image: gradient(linear, left top, left bottom, from(#b5b5b5),to(#b5b5b5)); + background-color: #c5c5c5; + background-image: gradient(linear, left top, left bottom, from(#c5c5c5),to(#c5c5c5)); } Table-RowOverlay:linesvisible:even:selected:unfocused { - background-color: #b5b5b5; - background-image: gradient(linear, left top, left bottom, from(#b5b5b5),to(#b5b5b5)); + background-color: #c5c5c5; + background-image: gradient(linear, left top, left bottom, from(#c5c5c5),to(#c5c5c5)); color: #4a4a4a; } diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/InstitutionAPITest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/InstitutionAPITest.java index 8433edf7..a388e359 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/InstitutionAPITest.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/InstitutionAPITest.java @@ -417,9 +417,9 @@ public class InstitutionAPITest extends AdministrationAPIIntegrationTester { assertNotNull(dependencies); assertTrue(dependencies.size() == 3); - assertTrue(dependencies.contains(new EntityKey("1", EntityType.USER))); - assertTrue(dependencies.contains(new EntityKey("2", EntityType.USER))); - assertTrue(dependencies.contains(new EntityKey("5", EntityType.USER))); + assertTrue(dependencies.contains(new EntityKey("user1", EntityType.USER))); + assertTrue(dependencies.contains(new EntityKey("user2", EntityType.USER))); + assertTrue(dependencies.contains(new EntityKey("user5", EntityType.USER))); } static void assertContainsInstitution(final String name, final Collection institutions) { diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/UserAPITest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/UserAPITest.java index bb42ebfc..0c9c3de6 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/UserAPITest.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/UserAPITest.java @@ -24,6 +24,7 @@ import java.util.stream.Stream; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.junit.Test; +import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.test.context.jdbc.Sql; @@ -39,10 +40,10 @@ 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.institution.Institution; 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; import ch.ethz.seb.sebserver.gbl.model.user.UserRole; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO.ActivityType; @@ -449,8 +450,8 @@ public class UserAPITest extends AdministrationAPIIntegrationTester { .param(Domain.USER.ATTR_USERNAME, "NewTestUser") .param(Domain.USER.ATTR_LOCALE, Locale.ENGLISH.toLanguageTag()) .param(Domain.USER.ATTR_TIMEZONE, DateTimeZone.UTC.getID()) - .param(UserMod.ATTR_NAME_NEW_PASSWORD, "12345678") - .param(UserMod.ATTR_NAME_RETYPED_NEW_PASSWORD, "12345678")) + .param(PasswordChange.ATTR_NAME_NEW_PASSWORD, "12345678") + .param(PasswordChange.ATTR_NAME_RETYPED_NEW_PASSWORD, "12345678")) .andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(), new TypeReference() { @@ -621,6 +622,51 @@ public class UserAPITest extends AdministrationAPIIntegrationTester { assertEquals("[EXAM_ADMIN, EXAM_SUPPORTER]", String.valueOf(modifiedUserResult.roles)); } + @Test + public void testModifyUserOnInactiveInstitutionNotAllowed() throws Exception { + // create new institution with seb-admin that is not active + final Institution institution = new RestAPITestHelper() + .withAccessToken(getSebAdminAccess()) + .withPath(API.INSTITUTION_ENDPOINT) + .withMethod(HttpMethod.POST) + .withAttribute("name", "new institution") + .withAttribute("urlSuffix", "new_inst") + .withAttribute("active", "false") + .withExpectedStatus(HttpStatus.OK) + .getAsObject(new TypeReference() { + }); + + assertNotNull(institution); + assertNotNull(institution.id); + assertEquals("new institution", institution.name); + + // try to create a user for this institution should not be possible + final Collection errors = this.jsonMapper.readValue( + this.mockMvc.perform(post(this.endpoint + API.USER_ACCOUNT_ENDPOINT) + .header("Authorization", "Bearer " + getSebAdminAccess()) + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .param(Domain.USER.ATTR_INSTITUTION_ID, String.valueOf(institution.id)) + .param(Domain.USER.ATTR_NAME, "NewTestUser") + .param(Domain.USER.ATTR_USERNAME, "NewTestUser") + .param(Domain.USER.ATTR_LOCALE, Locale.ENGLISH.toLanguageTag()) + .param(Domain.USER.ATTR_TIMEZONE, DateTimeZone.UTC.getID()) + .param(PasswordChange.ATTR_NAME_NEW_PASSWORD, "12345678") + .param(PasswordChange.ATTR_NAME_RETYPED_NEW_PASSWORD, "12345678")) + .andExpect(status().isBadRequest()) + .andReturn().getResponse().getContentAsString(), + new TypeReference>() { + }); + + assertNotNull(errors); + assertTrue(errors.size() == 1); + assertEquals( + "Illegal API request argument", + errors.iterator().next().systemMessage); + assertEquals( + "User within an inactive institution cannot be created nor modified", + errors.iterator().next().details); + } + // @Test // public void modifyUserWithPOSTMethod() throws Exception { // final String token = getSebAdminAccess(); @@ -685,8 +731,8 @@ public class UserAPITest extends AdministrationAPIIntegrationTester { .contentType(MediaType.APPLICATION_FORM_URLENCODED) .param(Domain.USER.ATTR_INSTITUTION_ID, "2") .param(Domain.USER.ATTR_NAME, "NewTestUser") - .param(UserMod.ATTR_NAME_NEW_PASSWORD, "12345678") - .param(UserMod.ATTR_NAME_RETYPED_NEW_PASSWORD, "12345678")) + .param(PasswordChange.ATTR_NAME_NEW_PASSWORD, "12345678") + .param(PasswordChange.ATTR_NAME_RETYPED_NEW_PASSWORD, "12345678")) .andExpect(status().isForbidden()) .andReturn().getResponse().getContentAsString(); @@ -712,8 +758,8 @@ public class UserAPITest extends AdministrationAPIIntegrationTester { .contentType(MediaType.APPLICATION_FORM_URLENCODED) .param(Domain.USER.ATTR_INSTITUTION_ID, "2") .param(Domain.USER.ATTR_NAME, "NewTestUser") - .param(UserMod.ATTR_NAME_NEW_PASSWORD, "12345678") - .param(UserMod.ATTR_NAME_RETYPED_NEW_PASSWORD, "12345678")) + .param(PasswordChange.ATTR_NAME_NEW_PASSWORD, "12345678") + .param(PasswordChange.ATTR_NAME_RETYPED_NEW_PASSWORD, "12345678")) .andExpect(status().isForbidden()) .andReturn().getResponse().getContentAsString(); @@ -748,12 +794,14 @@ public class UserAPITest extends AdministrationAPIIntegrationTester { }); final PasswordChange passwordChange = new PasswordChange( + examAdmin1.uuid, + "admin", "newPassword", "newPassword"); final String modifiedUserJson = this.jsonMapper.writeValueAsString(passwordChange); this.mockMvc.perform( - put(this.endpoint + API.USER_ACCOUNT_ENDPOINT + "/" + examAdmin1.uuid + API.PASSWORD_PATH_SEGMENT) + put(this.endpoint + API.USER_ACCOUNT_ENDPOINT + API.PASSWORD_PATH_SEGMENT) .header("Authorization", "Bearer " + sebAdminToken) .contentType(MediaType.APPLICATION_JSON_UTF8) .content(modifiedUserJson)) @@ -796,17 +844,18 @@ public class UserAPITest extends AdministrationAPIIntegrationTester { // must be longer then 8 chars PasswordChange passwordChange = new PasswordChange( + examAdmin1.uuid, + "admin", "new", "new"); String modifiedUserJson = this.jsonMapper.writeValueAsString(passwordChange); List messages = this.jsonMapper.readValue( 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)) + put(this.endpoint + API.USER_ACCOUNT_ENDPOINT + API.PASSWORD_PATH_SEGMENT) + .header("Authorization", "Bearer " + sebAdminToken) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(modifiedUserJson)) .andExpect(status().isBadRequest()) .andReturn().getResponse().getContentAsString(), new TypeReference>() { @@ -815,21 +864,22 @@ public class UserAPITest extends AdministrationAPIIntegrationTester { assertNotNull(messages); assertTrue(1 == messages.size()); assertEquals("1200", messages.get(0).messageCode); - assertEquals("[user, password, size, 8, 255, new]", String.valueOf(messages.get(0).getAttributes())); + assertEquals("[user, newPassword, size, 8, 255, new]", String.valueOf(messages.get(0).getAttributes())); // wrong password retype passwordChange = new PasswordChange( + examAdmin1.uuid, + "admin", "12345678", "87654321"); modifiedUserJson = this.jsonMapper.writeValueAsString(passwordChange); messages = this.jsonMapper.readValue( 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)) + put(this.endpoint + API.USER_ACCOUNT_ENDPOINT + API.PASSWORD_PATH_SEGMENT) + .header("Authorization", "Bearer " + sebAdminToken) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(modifiedUserJson)) .andExpect(status().isBadRequest()) .andReturn().getResponse().getContentAsString(), new TypeReference>() { @@ -837,7 +887,7 @@ public class UserAPITest extends AdministrationAPIIntegrationTester { assertNotNull(messages); assertTrue(1 == messages.size()); - assertEquals("1300", messages.get(0).messageCode); + assertEquals("1200", messages.get(0).messageCode); } @Test