From c9ebeacf1e6fe5e0660a89c1ae1300bd39b06378 Mon Sep 17 00:00:00 2001 From: anhefti Date: Tue, 11 Feb 2020 16:16:27 +0100 Subject: [PATCH] register user, status filter, activity in lists --- .../sebserver/gbl/model/user/UserInfo.java | 3 +- .../gbl/model/user/UserLogActivityType.java | 1 + .../seb/sebserver/gbl/model/user/UserMod.java | 2 +- .../seb/sebserver/gui/content/ExamForm.java | 12 +- .../gui/content/InstitutionList.java | 21 +--- .../sebserver/gui/content/LmsSetupList.java | 2 +- .../seb/sebserver/gui/content/MainPage.java | 8 +- .../content/MonitoringClientConnection.java | 8 +- .../gui/content/MonitoringRunningExam.java | 20 ++- .../sebserver/gui/content/RegisterPage.java | 68 +++++++++-- .../gui/content/SebClientConfigList.java | 2 +- .../gui/content/SebExamConfigList.java | 6 - .../gui/content/SebExamConfigPropForm.java | 10 +- .../gui/content/UserAccountList.java | 2 +- .../gui/content/action/ActionDefinition.java | 10 +- .../gui/content/activity/ActivitiesPane.java | 31 +++-- .../ch/ethz/seb/sebserver/gui/form/Form.java | 6 + .../gui/service/ResourceService.java | 6 + .../sebserver/gui/widget/WidgetFactory.java | 1 + .../impl/AuthorizationServiceImpl.java | 6 +- .../servicelayer/dao/UserActivityLogDAO.java | 47 +++++--- .../dao/impl/UserActivityLogDAOImpl.java | 19 +++ .../weblayer/api/InfoController.java | 20 ++- .../weblayer/api/RegisterUserController.java | 114 +++++------------- src/main/resources/messages.properties | 11 +- src/main/resources/static/css/sebserver.css | 9 ++ 26 files changed, 252 insertions(+), 193 deletions(-) diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserInfo.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserInfo.java index 073287de..a312d606 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserInfo.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserInfo.java @@ -33,7 +33,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import ch.ethz.seb.sebserver.gbl.api.EntityType; -import ch.ethz.seb.sebserver.gbl.model.Activatable; import ch.ethz.seb.sebserver.gbl.model.Domain.USER; import ch.ethz.seb.sebserver.gbl.model.Domain.USER_ROLE; import ch.ethz.seb.sebserver.gbl.model.EntityKey; @@ -46,7 +45,7 @@ import ch.ethz.seb.sebserver.gbl.util.Utils; * * This domain model is immutable and thread-save */ @JsonIgnoreProperties(ignoreUnknown = true) -public final class UserInfo implements UserAccount, Activatable, Serializable { +public final class UserInfo implements UserAccount, Serializable { private static final long serialVersionUID = 2526446136264377808L; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserLogActivityType.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserLogActivityType.java index f8c95388..8015c73f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserLogActivityType.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/user/UserLogActivityType.java @@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.gbl.model.user; /** All activity types */ public enum UserLogActivityType { + REGISTER, CREATE, IMPORT, EXPORT, 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 8d266ccd..e18cd318 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 @@ -43,7 +43,7 @@ public final class UserMod implements UserAccount { public final String uuid; /** The foreign key identifier to the institution where the User belongs to */ - @NotNull + @NotNull(message = "user:institutionId:notNull") @JsonProperty(USER.ATTR_INSTITUTION_ID) public final Long institutionId; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java index 3ea38459..c84df4ef 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java @@ -425,7 +425,7 @@ public class ExamForm implements TemplateComposer { this.resourceService::localizedExamConfigStatusName) .widthProportion(1)) .withDefaultActionIf( - () -> editable, + () -> modifyGrant, this::viewExamConfigPageAction) .compose(pageContext.copyOf(content)); @@ -447,7 +447,7 @@ public class ExamForm implements TemplateComposer { .newAction(ActionDefinition.EXAM_CONFIGURATION_EXAM_CONFIG_VIEW_PROP) .withParentEntityKey(entityKey) .withEntityKey(configMapKey) - .publishIf(() -> modifyGrant && editable && configurationTable.hasAnyContent()) + .publishIf(() -> modifyGrant && configurationTable.hasAnyContent()) .newAction(ActionDefinition.EXAM_CONFIGURATION_DELETE_FROM_LIST) .withEntityKey(entityKey) @@ -514,7 +514,7 @@ public class ExamForm implements TemplateComposer { .asMarkup() .widthProportion(4)) .withDefaultActionIf( - () -> editable, + () -> modifyGrant, () -> actionBuilder .newAction(ActionDefinition.EXAM_INDICATOR_MODIFY_FROM_LIST) .withParentEntityKey(entityKey) @@ -526,7 +526,7 @@ public class ExamForm implements TemplateComposer { .newAction(ActionDefinition.EXAM_INDICATOR_NEW) .withParentEntityKey(entityKey) - .publishIf(() -> modifyGrant && editable) + .publishIf(() -> modifyGrant) .newAction(ActionDefinition.EXAM_INDICATOR_MODIFY_FROM_LIST) .withParentEntityKey(entityKey) @@ -534,7 +534,7 @@ public class ExamForm implements TemplateComposer { indicatorTable::getSelection, PageAction::applySingleSelectionAsEntityKey, INDICATOR_EMPTY_SELECTION_TEXT_KEY) - .publishIf(() -> modifyGrant && indicatorTable.hasAnyContent() && editable) + .publishIf(() -> modifyGrant && indicatorTable.hasAnyContent()) .newAction(ActionDefinition.EXAM_INDICATOR_DELETE_FROM_LIST) .withEntityKey(entityKey) @@ -542,7 +542,7 @@ public class ExamForm implements TemplateComposer { indicatorTable::getSelection, this::deleteSelectedIndicator, INDICATOR_EMPTY_SELECTION_TEXT_KEY) - .publishIf(() -> modifyGrant && indicatorTable.hasAnyContent() && editable); + .publishIf(() -> modifyGrant && indicatorTable.hasAnyContent()); } } 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 025ef8af..fa6b9310 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 @@ -113,14 +113,12 @@ public class InstitutionList implements TemplateComposer { Institution::getUrlSuffix) .sortable() .withFilter(this.urlSuffixFilter)) - .withColumn(new ColumnDefinition( + .withColumn(new ColumnDefinition<>( Domain.INSTITUTION.ATTR_ACTIVE, ACTIVE_TEXT_KEY, - entity -> this.pageService - .getResourceService() - .localizedActivityResource().apply(entity.active)) - .sortable() - .withFilter(this.activityFilter)) + this.pageService.getResourceService(). localizedActivityFunction()) + .sortable() + .withFilter(this.activityFilter)) .withDefaultAction(pageActionBuilder .newAction(ActionDefinition.INSTITUTION_VIEW_FROM_LIST) .create()) @@ -152,16 +150,7 @@ public class InstitutionList implements TemplateComposer { .newAction(ActionDefinition.INSTITUTION_TOGGLE_ACTIVITY) .withExec(this.pageService.activationToggleActionFunction(table, EMPTY_SELECTION_TEXT_KEY)) .withConfirm(this.pageService.confirmDeactivation(table)) - .publishIf(() -> instGrant.m() && table.hasAnyContent(), false) - -// Removed as discussed in SEBSERV-52 -// .newAction(ActionDefinition.INSTITUTION_USER_ACCOUNT_NEW) -// .withSelect( -// table::getSelection, -// PageAction::applySingleSelectionAsParentEntityKey, -// EMPTY_SELECTION_TEXT_KEY) -// .publishIf(() -> table.hasAnyContent() && userGrant.w()) - ; + .publishIf(() -> instGrant.m() && table.hasAnyContent(), false); } private final Consumer> getSelectionPublisher(final PageContext pageContext) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupList.java index f28136a1..32c1e0d2 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupList.java @@ -141,7 +141,7 @@ public class LmsSetupList implements TemplateComposer { .withColumn(new ColumnDefinition<>( Domain.LMS_SETUP.ATTR_ACTIVE, ACTIVITY_TEXT_KEY, - LmsSetup::getActive) + this.pageService.getResourceService(). localizedActivityFunction()) .withFilter(this.activityFilter) .sortable()) .withDefaultAction(actionBuilder 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 1a806db7..786593b0 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 @@ -86,13 +86,13 @@ public class MainPage implements TemplateComposer { final Composite content = PageService.createManagedVScrolledComposite( mainSash, scrolledComposite -> { - final Composite reusult = new Composite(scrolledComposite, SWT.NONE); - reusult.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + final Composite result = new Composite(scrolledComposite, SWT.NONE); + result.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); final GridLayout contentOuterlayout = new GridLayout(); contentOuterlayout.marginHeight = 0; contentOuterlayout.marginWidth = 0; - reusult.setLayout(contentOuterlayout); - return reusult; + result.setLayout(contentOuterlayout); + return result; }, false); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java index 6f1a7b4e..78afce85 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java @@ -22,6 +22,7 @@ import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; +import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; import ch.ethz.seb.sebserver.gbl.model.user.UserRole; @@ -145,6 +146,10 @@ public class MonitoringClientConnection implements TemplateComposer { .withURIVariable(API.PARAM_MODEL_ID, exam.getModelId()) .withURIVariable(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken); + final ClientConnectionData connectionData = getConnectionData + .call() + .getOrThrow(); + final ClientConnectionDetails clientConnectionDetails = new ClientConnectionDetails( this.pageService, pageContext.copyOf(content), @@ -227,7 +232,8 @@ public class MonitoringClientConnection implements TemplateComposer { return action; }) .noEventPropagation() - .publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER)); + .publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER) && + connectionData.clientConnection.status == ConnectionStatus.ACTIVE); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java index 705d1527..2156c827 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java @@ -9,6 +9,8 @@ package ch.ethz.seb.sebserver.gui.content; import java.util.Collection; +import java.util.Collections; +import java.util.Set; import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.function.Function; @@ -29,6 +31,7 @@ import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; +import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; import ch.ethz.seb.sebserver.gbl.model.user.UserRole; @@ -64,6 +67,8 @@ public class MonitoringRunningExam implements TemplateComposer { private static final LocTextKey EMPTY_SELECTION_TEXT_KEY = new LocTextKey("sebserver.monitoring.exam.connection.emptySelection"); + private static final LocTextKey EMPTY_ACTIVE_SELECTION_TEXT_KEY = + new LocTextKey("sebserver.monitoring.exam.connection.emptySelection.active"); private static final LocTextKey CONFIRM_QUIT_SELECTED = new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.selected.confirm"); private static final LocTextKey CONFIRM_QUIT_ALL = @@ -178,9 +183,9 @@ public class MonitoringRunningExam implements TemplateComposer { .withEntityKey(entityKey) .withConfirm(() -> CONFIRM_QUIT_SELECTED) .withSelect( - clientTable::getSelection, + () -> this.selectionForQuitInstruction(clientTable), action -> this.quitSebClients(action, clientTable, false), - EMPTY_SELECTION_TEXT_KEY) + EMPTY_ACTIVE_SELECTION_TEXT_KEY) .noEventPropagation() .publishIf(privilege) @@ -289,6 +294,17 @@ public class MonitoringRunningExam implements TemplateComposer { }; } + private Set selectionForQuitInstruction(final ClientConnectionTable clientTable) { + final Set connectionTokens = clientTable.getConnectionTokens( + ClientConnection.getStatusPredicate(ConnectionStatus.ACTIVE), + true); + if (connectionTokens == null || connectionTokens.isEmpty()) { + return Collections.emptySet(); + } + + return clientTable.getSelection(); + } + private PageAction quitSebClients( final PageAction action, final ClientConnectionTable clientTable, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/RegisterPage.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/RegisterPage.java index 5c04682b..631b9d9c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/RegisterPage.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/RegisterPage.java @@ -31,16 +31,20 @@ import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.EntityName; 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.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Tuple; import ch.ethz.seb.sebserver.gui.InstitutionalAuthenticationEntryPoint; import ch.ethz.seb.sebserver.gui.form.FormBuilder; +import ch.ethz.seb.sebserver.gui.form.FormHandle; import ch.ethz.seb.sebserver.gui.service.ResourceService; 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.PageService; import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitutionInfo; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.RegisterNewUser; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.WebserviceURIService; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; @@ -71,6 +75,14 @@ public class RegisterPage implements TemplateComposer { new LocTextKey("sebserver.useraccount.form.institution"); static final LocTextKey FORM_LANG_TEXT_KEY = new LocTextKey("sebserver.useraccount.form.language"); + static final LocTextKey ACTION_CREATE = + new LocTextKey("sebserver.login.register.do"); + static final LocTextKey ACTION_CANCEL = + new LocTextKey("sebserver.overall.action.cancel"); + static final LocTextKey MESSAGE_SUCCESS_TILE = + new LocTextKey("sebserver.page.message"); + static final LocTextKey MESSAGE_SUCCESS_TEXT = + new LocTextKey("sebserver.login.register.success"); private final PageService pageService; private final ResourceService resourceService; @@ -101,6 +113,20 @@ public class RegisterPage implements TemplateComposer { @Override public void compose(final PageContext pageContext) { + final Composite parent = PageService.createManagedVScrolledComposite( + pageContext.getParent(), + scrolledComposite -> { + final Composite result = new Composite(scrolledComposite, SWT.NONE); + result.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + final GridLayout contentOuterlayout = new GridLayout(); + contentOuterlayout.marginHeight = 0; + contentOuterlayout.marginWidth = 0; + result.setLayout(contentOuterlayout); + result.setData(RWT.CUSTOM_VARIANT, "register"); + return result; + }, + false); + final String institutionId = InstitutionalAuthenticationEntryPoint .extractInstitutionalEndpoint(); @@ -119,14 +145,11 @@ public class RegisterPage implements TemplateComposer { .sorted(ResourceService.RESOURCE_COMPARATOR) .collect(Collectors.toList()); - final Composite content = this.widgetFactory.defaultPageLayout( - pageContext.getParent(), - TITLE_TEXT_KEY); - content.setData(RWT.CUSTOM_VARIANT, WidgetFactory.CustomVariant.LOGIN.key); + this.widgetFactory.labelLocalizedTitle(parent, TITLE_TEXT_KEY); // The UserAccount form - final FormBuilder formBuilder = this.pageService.formBuilder( - pageContext.copyOf(content)) + final FormHandle registerForm = this.pageService.formBuilder( + pageContext.copyOf(parent)) .readonly(false) .putStaticValueIf( () -> !this.multilingual, @@ -169,25 +192,44 @@ public class RegisterPage implements TemplateComposer { .addField(FormBuilder.text( PasswordChange.ATTR_NAME_CONFIRM_NEW_PASSWORD, FORM_PASSWORD_CONFIRM_TEXT_KEY) - .asPasswordField()); + .asPasswordField()) - //formBuilder.formParent.setData(RWT.CUSTOM_VARIANT, WidgetFactory.CustomVariant.LOGIN_BACK.key); - formBuilder.build(); + .build(); - final Composite buttons = new Composite(content, SWT.NONE); + final Composite buttons = new Composite(parent, SWT.NONE); buttons.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); - buttons.setLayout(new GridLayout(2, false)); + final GridLayout gridLayout = new GridLayout(2, false); + gridLayout.marginWidth = 20; + gridLayout.marginTop = 0; + gridLayout.marginBottom = 20; + buttons.setLayout(gridLayout); buttons.setData(RWT.CUSTOM_VARIANT, WidgetFactory.CustomVariant.LOGIN_BACK.key); - final Button registerButton = this.widgetFactory.buttonLocalized(buttons, "sebserver.login.register"); + final Button registerButton = this.widgetFactory.buttonLocalized(buttons, ACTION_CREATE); GridData gridData = new GridData(SWT.LEFT, SWT.TOP, false, false); gridData.verticalIndent = 10; registerButton.setLayoutData(gridData); registerButton.addListener(SWT.Selection, event -> { + registerForm.getForm().clearErrors(); + final Result onError = this.pageService + .getRestService() + .getBuilder(RegisterNewUser.class) + .withRestTemplate(this.restTemplate) + .withFormBinding(registerForm.getForm()) + .call() + .onError(registerForm::handleError); + + if (onError.hasError()) { + return; + } + + pageContext.forwardToLoginPage(); + pageContext.publishPageMessage(MESSAGE_SUCCESS_TILE, MESSAGE_SUCCESS_TEXT); + }); - final Button cancelButton = this.widgetFactory.buttonLocalized(buttons, "sebserver.overall.action.cancel"); + final Button cancelButton = this.widgetFactory.buttonLocalized(buttons, ACTION_CANCEL); gridData = new GridData(SWT.LEFT, SWT.TOP, false, false); gridData.verticalIndent = 10; cancelButton.setLayoutData(gridData); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebClientConfigList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebClientConfigList.java index e405254d..5de41fcf 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebClientConfigList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebClientConfigList.java @@ -145,7 +145,7 @@ public class SebClientConfigList implements TemplateComposer { .withColumn(new ColumnDefinition<>( Domain.SEB_CLIENT_CONFIGURATION.ATTR_ACTIVE, ACTIVE_TEXT_KEY, - SebClientConfig::getActive) + this.pageService.getResourceService(). localizedActivityFunction()) .withFilter(this.activityFilter) .sortable()) .withDefaultAction(pageActionBuilder diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigList.java index 7c4ed06e..ec2783b1 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigList.java @@ -163,12 +163,6 @@ public class SebExamConfigList implements TemplateComposer { PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY) .publishIf(() -> examConfigGrant.im() && configTable.hasAnyContent()) - .newAction(ActionDefinition.SEB_EXAM_CONFIG_MODIFY_FROM_LIST) - .withSelect( - configTable.getGrantedSelection(this.currentUser, NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUION), - PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY) - .publishIf(() -> examConfigGrant.im() && configTable.hasAnyContent()) - .newAction(ActionDefinition.SEB_EXAM_CONFIG_IMPORT_TO_NEW_CONFIG) .withExec(SebExamConfigImportPopup.importFunction(this.pageService, true)) .noEventPropagation() diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigPropForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigPropForm.java index 04b96c4c..f90c31a0 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigPropForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigPropForm.java @@ -216,14 +216,14 @@ public class SebExamConfigPropForm implements TemplateComposer { .withEntityKey(entityKey) .publishIf(() -> modifyGrant && isReadonly) + .newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW) + .withEntityKey(entityKey) + .publishIf(() -> isReadonly) + .newAction(ActionDefinition.SEB_EXAM_CONFIG_MODIFY) .withEntityKey(entityKey) .publishIf(() -> modifyGrant && isReadonly && !settingsReadonly) - .newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW) - .withEntityKey(entityKey) - .publishIf(() -> modifyGrant && isReadonly && settingsReadonly) - .newAction(ActionDefinition.SEB_EXAM_CONFIG_EXPORT_PLAIN_XML) .withEntityKey(entityKey) .withExec(action -> { @@ -286,7 +286,7 @@ public class SebExamConfigPropForm implements TemplateComposer { .withExec(this.pageService.backToCurrentFunction()) .publishIf(() -> !isReadonly); - if (isAttachedToExam) { + if (isAttachedToExam && isReadonly) { widgetFactory.labelSeparator(content); widgetFactory.labelLocalized( 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 01c2ef63..a8a68cbc 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 @@ -182,7 +182,7 @@ public class UserAccountList implements TemplateComposer { .withColumn(new ColumnDefinition<>( Domain.USER.ATTR_ACTIVE, ACTIVE_TEXT_KEY, - UserInfo::getActive) + this.pageService.getResourceService(). localizedActivityFunction()) .sortable() .withFilter(this.activityFilter) .widthProportion(1)) 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 ec26c6b1..c2f9d1dd 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 @@ -74,12 +74,6 @@ public enum ActionDefinition { PageStateDefinitionImpl.INSTITUTION_LIST, ActionCategory.INSTITUTION_LIST), - INSTITUTION_USER_ACCOUNT_NEW( - new LocTextKey("sebserver.useraccount.action.new"), - ImageIcon.USER, - PageStateDefinitionImpl.USER_ACCOUNT_EDIT, - ActionCategory.INSTITUTION_LIST), - USER_ACCOUNT_VIEW_LIST( new LocTextKey("sebserver.useraccount.action.list"), PageStateDefinitionImpl.USER_ACCOUNT_LIST), @@ -272,7 +266,7 @@ public enum ActionDefinition { PageStateDefinitionImpl.EXAM_VIEW, ActionCategory.EXAM_CONFIG_MAPPING_LIST), EXAM_CONFIGURATION_EXAM_CONFIG_VIEW_PROP( - new LocTextKey("sebserver.examconfig.action.view"), + new LocTextKey("sebserver.exam.configuration.action.list.view"), ImageIcon.SHOW, PageStateDefinitionImpl.SEB_EXAM_CONFIG_PROP_VIEW, ActionCategory.EXAM_CONFIG_MAPPING_LIST), @@ -388,7 +382,7 @@ public enum ActionDefinition { PageStateDefinitionImpl.SEB_EXAM_CONFIG_PROP_VIEW, ActionCategory.SEB_EXAM_CONFIG_LIST), SEB_EXAM_CONFIG_VIEW_PROP( - new LocTextKey("sebserver.examconfig.action.view"), + new LocTextKey("sebserver.examconfig.action.view.properties"), ImageIcon.SHOW, PageStateDefinitionImpl.SEB_EXAM_CONFIG_PROP_VIEW, ActionCategory.FORM), 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 35352c1f..a31cccc3 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 @@ -67,6 +67,9 @@ public class ActivitiesPane implements TemplateComposer { final UserInfo userInfo = this.currentUser .getOrHandleError(t -> this.pageService.logoutOnError(t, pageContext)); + final boolean isSupporterOnly = userInfo.hasRole(UserRole.EXAM_SUPPORTER) && + !userInfo.hasAnyRole(UserRole.EXAM_ADMIN, UserRole.INSTITUTIONAL_ADMIN, UserRole.SEB_SERVER_ADMIN); + if (this.pageService.getI18nSupport().hasText(TITLE_KEY)) { final Label activities = this.widgetFactory.labelLocalized( pageContext.getParent(), @@ -185,7 +188,7 @@ public class ActivitiesPane implements TemplateComposer { PrivilegeType.READ, EntityType.CONFIGURATION_NODE); - if (clientConfigRead || examConfigRead) { + if ((clientConfigRead || examConfigRead) && !isSupporterOnly) { final TreeItem sebConfigs = this.widgetFactory.treeItemLocalized( navigation, ActivityDefinition.SEB_CONFIGURATION.displayName); @@ -236,8 +239,9 @@ public class ActivitiesPane implements TemplateComposer { // ---- EXAM ADMINISTRATION ------------------------------------------------------------ final boolean lmsRead = this.currentUser.hasInstitutionalPrivilege(PrivilegeType.READ, EntityType.LMS_SETUP); - final boolean examRead = this.currentUser.get().hasAnyRole(UserRole.EXAM_SUPPORTER, UserRole.EXAM_ADMIN) || + final boolean examRead = userInfo.hasAnyRole(UserRole.EXAM_SUPPORTER, UserRole.EXAM_ADMIN) || this.currentUser.hasInstitutionalPrivilege(PrivilegeType.READ, EntityType.EXAM); + final boolean examWrite = this.currentUser.hasInstitutionalPrivilege(PrivilegeType.WRITE, EntityType.EXAM); // Exam Administration final TreeItem examadmin = this.widgetFactory.treeItemLocalized( @@ -246,7 +250,7 @@ public class ActivitiesPane implements TemplateComposer { if (examRead || lmsRead) { // LMS Setup - if (lmsRead) { + if (lmsRead && !isSupporterOnly) { final TreeItem lmsSetup = this.widgetFactory.treeItemLocalized( examadmin, ActivityDefinition.LMS_SETUP.displayName); @@ -257,18 +261,19 @@ public class ActivitiesPane implements TemplateComposer { .create()); } - // Exam (Quiz Discovery) if (examRead) { - // Quiz Discovery - final TreeItem quizDiscovery = this.widgetFactory.treeItemLocalized( - examadmin, - ActivityDefinition.QUIZ_DISCOVERY.displayName); - injectActivitySelection( - quizDiscovery, - actionBuilder - .newAction(ActionDefinition.QUIZ_DISCOVERY_VIEW_LIST) - .create()); + if (examWrite) { + // Quiz Discovery + final TreeItem quizDiscovery = this.widgetFactory.treeItemLocalized( + examadmin, + ActivityDefinition.QUIZ_DISCOVERY.displayName); + injectActivitySelection( + quizDiscovery, + actionBuilder + .newAction(ActionDefinition.QUIZ_DISCOVERY_VIEW_LIST) + .create()); + } // Exam final TreeItem exam = this.widgetFactory.treeItemLocalized( 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 be75dd1c..fcd4b36f 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 @@ -242,6 +242,12 @@ public final class Form implements FormBinding { .isPresent(); } + public void clearErrors() { + process( + Utils.truePredicate(), + ffa -> ffa.resetError()); + } + public void setFieldError(final String fieldName, final String errorMessage) { final List list = this.formFields.get(fieldName); if (list != null) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java index 76353bb0..3cca59d5 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java @@ -29,6 +29,7 @@ import org.springframework.stereotype.Service; 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.model.Activatable; import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.EntityName; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; @@ -414,6 +415,11 @@ public class ResourceService { .collect(Collectors.toList()); } + public Function localizedActivityFunction() { + final Function localizedActivityResource = localizedActivityResource(); + return activatable -> localizedActivityResource.apply(activatable.isActive()); + } + public Function localizedActivityResource() { return activity -> activity ? this.i18nSupport.getText(ACTIVE_TEXT_KEY) 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 97359fbc..0e01437a 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 @@ -185,6 +185,7 @@ public class WidgetFactory { LOGIN("login"), LOGIN_BACK("login-back"), + SCROLL("scroll"), LIST_NAVIGATION("list-nav") diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/impl/AuthorizationServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/impl/AuthorizationServiceImpl.java index baccaeb8..aa46388d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/impl/AuthorizationServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/impl/AuthorizationServiceImpl.java @@ -123,7 +123,7 @@ public class AuthorizationServiceImpl implements AuthorizationService { .andForRole(UserRole.EXAM_ADMIN) .withInstitutionalPrivilege(PrivilegeType.WRITE) .andForRole(UserRole.EXAM_SUPPORTER) - .withInstitutionalPrivilege(PrivilegeType.MODIFY) + .withInstitutionalPrivilege(PrivilegeType.READ) .create(); // grants for configuration addPrivilege(EntityType.CONFIGURATION) @@ -134,7 +134,7 @@ public class AuthorizationServiceImpl implements AuthorizationService { .andForRole(UserRole.EXAM_ADMIN) .withInstitutionalPrivilege(PrivilegeType.WRITE) .andForRole(UserRole.EXAM_SUPPORTER) - .withInstitutionalPrivilege(PrivilegeType.MODIFY) + .withInstitutionalPrivilege(PrivilegeType.READ) .create(); // grants for configuration value addPrivilege(EntityType.CONFIGURATION_VALUE) @@ -145,7 +145,7 @@ public class AuthorizationServiceImpl implements AuthorizationService { .andForRole(UserRole.EXAM_ADMIN) .withInstitutionalPrivilege(PrivilegeType.WRITE) .andForRole(UserRole.EXAM_SUPPORTER) - .withInstitutionalPrivilege(PrivilegeType.MODIFY) + .withInstitutionalPrivilege(PrivilegeType.READ) .create(); // grants for configuration attributes 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 8521b38b..c65a6c06 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 @@ -13,6 +13,7 @@ import java.util.function.Predicate; import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.model.Entity; +import ch.ethz.seb.sebserver.gbl.model.user.UserAccount; import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog; import ch.ethz.seb.sebserver.gbl.model.user.UserLogActivityType; import ch.ethz.seb.sebserver.gbl.util.Result; @@ -26,52 +27,58 @@ public interface UserActivityLogDAO extends /** Create a user activity log entry for the current user of activity type CREATE * * @param entity the Entity - * @return Result of the Entity or referring to an Error id happened */ - public Result logCreate(E entity); + * @return Result of the Entity or referring to an Error if happened */ + Result logCreate(E entity); + + /** Create a user activity log entry for a user registration event + * + * @param account the UserAccount + * @return Result of the UserAccount or referring to an Error if happened */ + Result logRegisterAccount(UserAccount account); /** Creates a user activity log entry for SEB Exam Configuration save in history action - * + * * @param entity the Entity - * @return Result of the Entity or referring to an Error id happened */ - public Result logSaveToHistory(E entity); + * @return Result of the Entity or referring to an Error if happened */ + Result logSaveToHistory(E entity); - /** Creates a user activity log entry for SEB Exam Configuration undoy action - * + /** Creates a user activity log entry for SEB Exam Configuration undo action + * * @param entity the Entity - * @return Result of the Entity or referring to an Error id happened */ - public Result logUndo(E entity); + * @return Result of the Entity or referring to an Error if happened */ + Result logUndo(E entity); /** Create a user activity log entry for the current user of activity type IMPORT * * @param entity the Entity - * @return Result of the Entity or referring to an Error id happened */ - public Result logImport(E entity); + * @return Result of the Entity or referring to an Error if happened */ + Result logImport(E entity); /** Create a user activity log entry for the current user of activity type EXPORT * * @param entity the Entity - * @return Result of the Entity or referring to an Error id happened */ - public Result logExport(E entity); + * @return Result of the Entity or referring to an Error if happened */ + Result logExport(E entity); /** Create a user activity log entry for the current user of activity type MODIFY * * @param entity the Entity - * @return Result of the Entity or referring to an Error id happened */ - public Result logModify(E entity); + * @return Result of the Entity or referring to an Error if happened */ + Result logModify(E entity); /** Creates a user activity log entry for the current user. * * @param activityType the activity type * @param entity the Entity * @param message an optional message - * @return Result of the Entity or referring to an Error id happened */ + * @return Result of the Entity or referring to an Error if happened */ Result log(UserLogActivityType activityType, E entity, String message); /** Creates a user activity log entry for the current user. * * @param actionType the action type * @param entity the Entity - * @return Result of the Entity or referring to an Error id happened */ + * @return Result of the Entity or referring to an Error if happened */ Result log(UserLogActivityType activityType, E entity); /** Creates a user activity log entry for the current user. @@ -86,7 +93,7 @@ public interface UserActivityLogDAO extends * @param activityType the activity type * @param entityType the EntityType * @param message the message - * @return Result of the Entity or referring to an Error id happened */ + * @return Result of the Entity or referring to an Error if happened */ Result log(UserLogActivityType activityType, EntityType entityType, String entityId, String message, T data); /** Creates a user activity log entry. @@ -95,7 +102,7 @@ public interface UserActivityLogDAO extends * @param activityType the activity type * @param entity the Entity * @param message an optional message - * @return Result of the Entity or referring to an Error id happened */ + * @return Result of the Entity or referring to an Error if happened */ Result log( SEBServerUser user, UserLogActivityType activityType, @@ -108,7 +115,7 @@ public interface UserActivityLogDAO extends * @param activityType the activity type * @param entityType the entity type * @param entityId the entity id (primary key or UUID) - * @return Result of the Entity or referring to an Error id happened */ + * @return Result of the Entity or referring to an Error if happened */ default Result log( final SEBServerUser user, final UserLogActivityType activityType, diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/UserActivityLogDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/UserActivityLogDAOImpl.java index 7b75dfa7..fa8ab2f5 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/UserActivityLogDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/UserActivityLogDAOImpl.java @@ -36,6 +36,7 @@ import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.JSONMapper; import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.EntityKey; +import ch.ethz.seb.sebserver.gbl.model.user.UserAccount; import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog; import ch.ethz.seb.sebserver.gbl.model.user.UserLogActivityType; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; @@ -87,6 +88,24 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO { return log(UserLogActivityType.CREATE, entity); } + @Override + @Transactional + public Result logRegisterAccount(final UserAccount account) { + return Result.tryCatch(() -> { + + this.userLogRecordMapper.insertSelective(new UserActivityLogRecord( + null, + account.getModelId(), + System.currentTimeMillis(), + UserLogActivityType.REGISTER.name(), + EntityType.USER.name(), + account.getModelId(), + toMessage(account))); + + return account; + }); + } + @Override @Transactional public Result logSaveToHistory(final E entity) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/InfoController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/InfoController.java index 2082757c..9251697a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/InfoController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/InfoController.java @@ -20,7 +20,7 @@ import org.springframework.web.bind.annotation.RestController; import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.authorization.Privilege; -import ch.ethz.seb.sebserver.gbl.model.EntityKey; +import ch.ethz.seb.sebserver.gbl.model.EntityName; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.InstitutionDAO; @@ -60,11 +60,25 @@ public class InfoController { .orElse(null); } + @RequestMapping( + path = API.INFO_INST_PATH_SEGMENT, + method = RequestMethod.GET, + produces = MediaType.APPLICATION_JSON_VALUE) + public Collection getInstitutionInfo() { + return this.institutionDAO + .all(null, true) + .getOrThrow() + .stream() + .filter(inst -> BooleanUtils.isTrue(inst.active)) + .map(inst -> new EntityName(inst.getEntityKey(), inst.name)) + .collect(Collectors.toList()); + } + @RequestMapping( path = API.INFO_INST_ENDPOINT, method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) - public Collection getInstitutionInfo(@PathVariable(required = false) final String urlSuffix) { + public Collection getInstitutionInfo(@PathVariable final String urlSuffix) { return this.institutionDAO .all(null, true) .getOrThrow() @@ -72,7 +86,7 @@ public class InfoController { .filter(inst -> BooleanUtils.isTrue(inst.active) && (inst.urlSuffix == null || urlSuffix.equals(inst.urlSuffix))) - .map(inst -> inst.getEntityKey()) + .map(inst -> new EntityName(inst.getEntityKey(), inst.name)) .collect(Collectors.toList()); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/RegisterUserController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/RegisterUserController.java index f32c46e4..667cf524 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/RegisterUserController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/RegisterUserController.java @@ -9,16 +9,12 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; -import java.util.HashSet; -import java.util.Locale; -import org.apache.commons.lang3.StringUtils; -import org.joda.time.DateTimeZone; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.MediaType; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.util.MultiValueMap; import org.springframework.validation.FieldError; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -26,21 +22,20 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import ch.ethz.seb.sebserver.WebSecurityConfig; -import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.APIMessage; import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException; -import ch.ethz.seb.sebserver.gbl.model.Domain; +import ch.ethz.seb.sebserver.gbl.api.POSTMapper; +import ch.ethz.seb.sebserver.gbl.model.Domain.USER_ROLE; import ch.ethz.seb.sebserver.gbl.model.user.PasswordChange; import ch.ethz.seb.sebserver.gbl.model.user.UserInfo; import ch.ethz.seb.sebserver.gbl.model.user.UserMod; import ch.ethz.seb.sebserver.gbl.model.user.UserRole; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; -import ch.ethz.seb.sebserver.gbl.util.Utils; -import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.InstitutionDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService; @WebServiceProfile @RestController @@ -50,105 +45,56 @@ public class RegisterUserController { private final InstitutionDAO institutionDAO; private final UserActivityLogDAO userActivityLogDAO; private final UserDAO userDAO; - private final ClientCredentialService clientCredentialService; + private final BeanValidationService beanValidationService; protected RegisterUserController( final InstitutionDAO institutionDAO, final UserActivityLogDAO userActivityLogDAO, final UserDAO userDAO, - final ClientCredentialService clientCredentialService, + final BeanValidationService beanValidationService, @Qualifier(WebSecurityConfig.USER_PASSWORD_ENCODER_BEAN_NAME) final PasswordEncoder userPasswordEncoder) { this.institutionDAO = institutionDAO; this.userActivityLogDAO = userActivityLogDAO; this.userDAO = userDAO; - this.clientCredentialService = clientCredentialService; + this.beanValidationService = beanValidationService; } @RequestMapping( method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) - public UserInfo registerNewUser( - @RequestParam(name = Domain.USER.ATTR_INSTITUTION_ID, required = true) final String institutionId, - @RequestParam(name = Domain.USER.ATTR_NAME, required = true) final String name, - @RequestParam(name = Domain.USER.ATTR_SURNAME, required = false) final String surname, - @RequestParam(name = Domain.USER.ATTR_USERNAME, required = true) final String username, - @RequestParam(name = Domain.USER.ATTR_EMAIL, required = false) final String email, - @RequestParam( - name = Domain.USER.ATTR_LANGUAGE, - required = false, - defaultValue = Constants.DEFAULT_LANG_CODE) final String lang, - @RequestParam( - name = Domain.USER.ATTR_TIMEZONE, - required = false, - defaultValue = Constants.DEFAULT_TIME_ZONE_CODE) final String timezone, - @RequestParam(name = PasswordChange.ATTR_NAME_NEW_PASSWORD, required = true) final String pwd, - @RequestParam(name = PasswordChange.ATTR_NAME_CONFIRM_NEW_PASSWORD, required = true) final String rpwd) { + public UserInfo registerNewUser(@RequestParam final MultiValueMap allRequestParams) { - final Collection errors = new ArrayList<>(); + final POSTMapper postMap = new POSTMapper(allRequestParams) + .putIfAbsent(USER_ROLE.REFERENCE_NAME, UserRole.EXAM_SUPPORTER.name()); + final UserMod userMod = new UserMod(null, postMap); - // check institution info - Long instId = null; - if (StringUtils.isNotBlank(institutionId)) { - try { - instId = Long.parseLong(institutionId); - } catch (final Exception e) { - instId = this.institutionDAO - .all(null, true) - .getOrThrow() - .stream() - .filter(inst -> inst.urlSuffix != null && institutionId.equals(inst.urlSuffix)) - .findFirst() - .map(inst -> inst.id) - .orElse(null); - } - } + return this.beanValidationService.validateBean(userMod) + .map(userAccount -> { - if (instId == null) { - errors.add(APIMessage.fieldValidationError( - new FieldError( - "user", - Domain.USER.ATTR_INSTITUTION_ID, - "user:institutionId:notNull"))); - } + final Collection errors = new ArrayList<>(); + if (!userAccount.newPasswordMatch()) { + errors.add(APIMessage.fieldValidationError( + new FieldError( + "passwordChange", + PasswordChange.ATTR_NAME_CONFIRM_NEW_PASSWORD, + "user:confirmNewPassword:password.mismatch"))); + } - // check password-match - final CharSequence rawPWD = this.clientCredentialService.decrypt(pwd); - final CharSequence rawRPWD = this.clientCredentialService.decrypt(rpwd); - if (!rawPWD.equals(rawRPWD)) { - errors.add(APIMessage.fieldValidationError( - new FieldError( - "passwordChange", - PasswordChange.ATTR_NAME_CONFIRM_NEW_PASSWORD, - "user:confirmNewPassword:password.mismatch"))); - } + if (!errors.isEmpty()) { + throw new APIMessageException(errors); + } - if (!errors.isEmpty()) { - throw new APIMessageException(errors); - } + return userAccount; - final UserMod user = new UserMod( - null, - instId, - name, - surname, - username, - rawPWD, - rawRPWD, - email, - Locale.forLanguageTag(lang), - DateTimeZone.forID(timezone), - new HashSet<>(Arrays.asList(UserRole.EXAM_SUPPORTER.name()))); - - return this.userDAO.createNew(user) - .flatMap(this.userActivityLogDAO::logCreate) - .map(u -> { - Utils.clear(rawPWD); - Utils.clear(rawRPWD); - return u; }) + .flatMap(this.userDAO::createNew) + .flatMap(account -> this.userDAO.setActive(account, true)) + .flatMap(this.userActivityLogDAO::logRegisterAccount) + .flatMap(account -> this.userDAO.byModelId(account.getModelId())) .getOrThrow(); + } } diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index a2690926..7658ab51 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -35,6 +35,7 @@ sebserver.overall.action.remove=Remove sebserver.overall.action.select=Please Select sebserver.overall.action.toggle-activity=Switch Activity +sebserver.overall.types.activityType.REGISTER=Register new Account sebserver.overall.types.activityType.CREATE=Create New sebserver.overall.types.activityType.IMPORT=Import sebserver.overall.types.activityType.EXPORT=Export @@ -111,7 +112,9 @@ sebserver.login.password.change=Information sebserver.login.password.change.success=The password was successfully changed. Please sign in with your new password sebserver.login.register=Register -sebserver.login.register.form.title=Register As New User +sebserver.login.register.form.title=Create an Account +sebserver.login.register.do=Create Account +sebserver.login.register.success=New account successfully created.
Please log in with your username and password. ################################ @@ -391,6 +394,7 @@ sebserver.exam.configuration.action.noconfig.message=There is currently no SEB e sebserver.exam.configuration.action.list.new=Add Configuration sebserver.exam.configuration.action.list.modify=Edit Configuration +sebserver.exam.configuration.action.list.view=View Configuration sebserver.exam.configuration.action.list.delete=Delete Configuration sebserver.exam.configuration.action.save=Save Configuration sebserver.exam.configuration.action.export-config=Export Configuration @@ -499,11 +503,11 @@ sebserver.examconfig.list.action.no.modify.privilege=No Access: An Exam Configur sebserver.examconfig.action.list.new=Add Exam Configuration sebserver.examconfig.action.list.view=View Configuration sebserver.examconfig.action.list.modify=Edit Settings -sebserver.examconfig.action.list.modify=View Settings sebserver.examconfig.action.list.modify.properties=Edit Configuration -sebserver.examconfig.action.view=View Configuration +sebserver.examconfig.action.view=View Settings sebserver.examconfig.action.modify=Edit Settings sebserver.examconfig.action.modify.properties=Edit Configuration +sebserver.examconfig.action.view.properties=View Configuration sebserver.examconfig.action.save=Save sebserver.examconfig.action.saveToHistory=Save / Publish sebserver.examconfig.action.saveToHistory.success=Successfully saved in history @@ -1101,6 +1105,7 @@ sebserver.monitoring.connection.list.column.examname=Exam sebserver.monitoring.connection.list.column.vdiAddress=IP Address (VDI) sebserver.monitoring.exam.connection.emptySelection=Please select first a Connection from the list +sebserver.monitoring.exam.connection.emptySelection.active=Please select first an active Connection from the list sebserver.monitoring.exam.connection.title=SEB Client Connection sebserver.monitoring.exam.connection.list.actions= sebserver.monitoring.exam.connection.action.view=View Details diff --git a/src/main/resources/static/css/sebserver.css b/src/main/resources/static/css/sebserver.css index c07e6480..4a2020ac 100644 --- a/src/main/resources/static/css/sebserver.css +++ b/src/main/resources/static/css/sebserver.css @@ -139,6 +139,8 @@ Composite { background-color: transparent; } + + Composite.bordered { border: 2px; } @@ -196,6 +198,13 @@ Composite.login { border-radius: 2px; } +Composite.register { + background-color: #EAECEE; + margin: 20px 0 0 0; + padding: 15px 8px 8px 8px; + border: none; +} + Composite.login-back { background-color: #EAECEE; margin: 0px 0 0 0;