SEBSERV-160 implemented reset from template

This commit is contained in:
anhefti 2022-04-12 15:05:46 +02:00
parent 0921b713f5
commit 55baa2d518
45 changed files with 446 additions and 89 deletions

View file

@ -20,7 +20,7 @@ public final class API {
public enum BatchActionType { public enum BatchActionType {
EXAM_CONFIG_STATE_CHANGE(EntityType.CONFIGURATION_NODE), EXAM_CONFIG_STATE_CHANGE(EntityType.CONFIGURATION_NODE),
EXAM_CONFIG_APPLY_TEMPLATE_VALUES(EntityType.CONFIGURATION_NODE); EXAM_CONFIG_REST_TEMPLATE_SETTINGS(EntityType.CONFIGURATION_NODE);
public final EntityType entityType; public final EntityType entityType;

View file

@ -246,7 +246,7 @@ public class APIMessage implements Serializable {
} }
public APIMessageException(final APIMessage apiMessage) { public APIMessageException(final APIMessage apiMessage) {
super(); super(apiMessage.systemMessage + " " + apiMessage.details);
this.apiMessages = Arrays.asList(apiMessage); this.apiMessages = Arrays.asList(apiMessage);
} }

View file

@ -18,6 +18,7 @@ public enum UserLogActivityType {
PASSWORD_CHANGE, PASSWORD_CHANGE,
DEACTIVATE, DEACTIVATE,
ACTIVATE, ACTIVATE,
FINISHED,
DELETE, DELETE,
LOGIN, LOGIN,
LOGOUT LOGOUT

View file

@ -533,6 +533,12 @@ public enum ActionDefinition {
PageStateDefinitionImpl.SEB_EXAM_CONFIG_LIST, PageStateDefinitionImpl.SEB_EXAM_CONFIG_LIST,
ActionCategory.SEB_EXAM_CONFIG_LIST), ActionCategory.SEB_EXAM_CONFIG_LIST),
SEB_EXAM_CONFIG_BULK_RESET_TO_TEMPLATE(
new LocTextKey("sebserver.examconfig.list.action.reset"),
ImageIcon.EXPORT,
PageStateDefinitionImpl.SEB_EXAM_CONFIG_LIST,
ActionCategory.SEB_EXAM_CONFIG_LIST),
SEB_EXAM_CONFIG_MODIFY_PROP_FROM_LIST( SEB_EXAM_CONFIG_MODIFY_PROP_FROM_LIST(
new LocTextKey("sebserver.examconfig.action.list.modify.properties"), new LocTextKey("sebserver.examconfig.action.list.modify.properties"),
ImageIcon.EDIT, ImageIcon.EDIT,

View file

@ -138,14 +138,14 @@ public class InstitutionList implements TemplateComposer {
.newAction(ActionDefinition.INSTITUTION_VIEW_FROM_LIST) .newAction(ActionDefinition.INSTITUTION_VIEW_FROM_LIST)
.withSelect( .withSelect(
table::getSelection, table::getMultiSelection,
PageAction::applySingleSelectionAsEntityKey, PageAction::applySingleSelectionAsEntityKey,
EMPTY_SELECTION_TEXT_KEY) EMPTY_SELECTION_TEXT_KEY)
.publish(false) .publish(false)
.newAction(ActionDefinition.INSTITUTION_MODIFY_FROM_LIST) .newAction(ActionDefinition.INSTITUTION_MODIFY_FROM_LIST)
.withSelect( .withSelect(
table::getSelection, table::getMultiSelection,
PageAction::applySingleSelectionAsEntityKey, PageAction::applySingleSelectionAsEntityKey,
EMPTY_SELECTION_TEXT_KEY) EMPTY_SELECTION_TEXT_KEY)
.publishIf(() -> instGrant.m(), false) .publishIf(() -> instGrant.m(), false)

View file

@ -222,11 +222,12 @@ public class UserAccountList implements TemplateComposer {
.publishIf(userGrant::iw) .publishIf(userGrant::iw)
.newAction(ActionDefinition.USER_ACCOUNT_VIEW_FROM_LIST) .newAction(ActionDefinition.USER_ACCOUNT_VIEW_FROM_LIST)
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY) .withSelect(table::getMultiSelection, PageAction::applySingleSelectionAsEntityKey,
EMPTY_SELECTION_TEXT_KEY)
.publish(false) .publish(false)
.newAction(ActionDefinition.USER_ACCOUNT_MODIFY_FROM_LIST) .newAction(ActionDefinition.USER_ACCOUNT_MODIFY_FROM_LIST)
.withSelect(table::getSelection, this::editAction, EMPTY_SELECTION_TEXT_KEY) .withSelect(table::getMultiSelection, this::editAction, EMPTY_SELECTION_TEXT_KEY)
.publishIf(() -> userGrant.im(), false) .publishIf(() -> userGrant.im(), false)
.newAction(ActionDefinition.USER_ACCOUNT_TOGGLE_ACTIVITY) .newAction(ActionDefinition.USER_ACCOUNT_TOGGLE_ACTIVITY)

View file

@ -244,7 +244,7 @@ public class UserActivityLogs implements TemplateComposer {
actionBuilder actionBuilder
.newAction(ActionDefinition.LOGS_USER_ACTIVITY_SHOW_DETAILS) .newAction(ActionDefinition.LOGS_USER_ACTIVITY_SHOW_DETAILS)
.withSelect( .withSelect(
table::getSelection, table::getMultiSelection,
action -> this.showDetails(action, table.getSingleSelectedROWData()), action -> this.showDetails(action, table.getSingleSelectedROWData()),
EMPTY_SELECTION_TEXT) EMPTY_SELECTION_TEXT)
.noEventPropagation() .noEventPropagation()

View file

@ -159,7 +159,7 @@ public class CertificateList implements TemplateComposer {
.newAction(ActionDefinition.SEB_CERTIFICATE_REMOVE) .newAction(ActionDefinition.SEB_CERTIFICATE_REMOVE)
.withConfirm(() -> FORM_ACTION_MESSAGE_REMOVE_CONFIRM_TEXT_KEY) .withConfirm(() -> FORM_ACTION_MESSAGE_REMOVE_CONFIRM_TEXT_KEY)
.withSelect( .withSelect(
table::getSelection, table::getMultiSelection,
this::removeCertificate, this::removeCertificate,
EMPTY_SELECTION_TEXT_KEY) EMPTY_SELECTION_TEXT_KEY)
.publishIf(() -> grantCheck.iw(), false); .publishIf(() -> grantCheck.iw(), false);

View file

@ -249,7 +249,7 @@ public class ConfigTemplateForm implements TemplateComposer {
.newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_ATTR_EDIT) .newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_ATTR_EDIT)
.withParentEntityKey(entityKey) .withParentEntityKey(entityKey)
.withSelect( .withSelect(
attrTable::getSelection, attrTable::getMultiSelection,
PageAction::applySingleSelectionAsEntityKey, PageAction::applySingleSelectionAsEntityKey,
EMPTY_ATTRIBUTE_SELECTION_TEXT_KEY) EMPTY_ATTRIBUTE_SELECTION_TEXT_KEY)
.publishIf(() -> modifyGrant, false) .publishIf(() -> modifyGrant, false)
@ -257,7 +257,7 @@ public class ConfigTemplateForm implements TemplateComposer {
.newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_ATTR_SET_DEFAULT) .newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_ATTR_SET_DEFAULT)
.withParentEntityKey(entityKey) .withParentEntityKey(entityKey)
.withSelect( .withSelect(
attrTable::getSelection, attrTable::getMultiSelection,
action -> this.resetToDefaults(action, attrTable), action -> this.resetToDefaults(action, attrTable),
EMPTY_ATTRIBUTE_SELECTION_TEXT_KEY) EMPTY_ATTRIBUTE_SELECTION_TEXT_KEY)
.noEventPropagation() .noEventPropagation()
@ -266,7 +266,7 @@ public class ConfigTemplateForm implements TemplateComposer {
.newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_ATTR_LIST_REMOVE_VIEW) .newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_ATTR_LIST_REMOVE_VIEW)
.withParentEntityKey(entityKey) .withParentEntityKey(entityKey)
.withSelect( .withSelect(
attrTable::getSelection, attrTable::getMultiSelection,
action -> this.removeFormView(action, attrTable), action -> this.removeFormView(action, attrTable),
EMPTY_ATTRIBUTE_SELECTION_TEXT_KEY) EMPTY_ATTRIBUTE_SELECTION_TEXT_KEY)
.noEventPropagation() .noEventPropagation()
@ -275,7 +275,7 @@ public class ConfigTemplateForm implements TemplateComposer {
.newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_ATTR_LIST_ATTACH_DEFAULT_VIEW) .newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_ATTR_LIST_ATTACH_DEFAULT_VIEW)
.withParentEntityKey(entityKey) .withParentEntityKey(entityKey)
.withSelect( .withSelect(
attrTable::getSelection, attrTable::getMultiSelection,
action -> this.attachView(action, attrTable), action -> this.attachView(action, attrTable),
EMPTY_ATTRIBUTE_SELECTION_TEXT_KEY) EMPTY_ATTRIBUTE_SELECTION_TEXT_KEY)
.noEventPropagation() .noEventPropagation()

View file

@ -147,7 +147,7 @@ public class ConfigTemplateList implements TemplateComposer {
.publishIf(examConfigGrant::iw) .publishIf(examConfigGrant::iw)
.newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_VIEW_FROM_LIST) .newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_VIEW_FROM_LIST)
.withSelect(templateTable::getSelection, PageAction::applySingleSelectionAsEntityKey, .withSelect(templateTable::getMultiSelection, PageAction::applySingleSelectionAsEntityKey,
EMPTY_TEMPLATE_SELECTION_TEXT_KEY) EMPTY_TEMPLATE_SELECTION_TEXT_KEY)
.publish(false) .publish(false)

View file

@ -173,7 +173,8 @@ public class SEBClientConfigList implements TemplateComposer {
.publishIf(clientConfigGrant::iw) .publishIf(clientConfigGrant::iw)
.newAction(ActionDefinition.SEB_CLIENT_CONFIG_VIEW_FROM_LIST) .newAction(ActionDefinition.SEB_CLIENT_CONFIG_VIEW_FROM_LIST)
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY) .withSelect(table::getMultiSelection, PageAction::applySingleSelectionAsEntityKey,
EMPTY_SELECTION_TEXT_KEY)
.publish(false) .publish(false)
.newAction(ActionDefinition.SEB_CLIENT_CONFIG_MODIFY_FROM_LIST) .newAction(ActionDefinition.SEB_CLIENT_CONFIG_MODIFY_FROM_LIST)

View file

@ -0,0 +1,95 @@
/*
* Copyright (c) 2022 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.configs;
import java.util.function.Supplier;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.api.API.BatchActionType;
import ch.ethz.seb.sebserver.gbl.model.BatchAction;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
import ch.ethz.seb.sebserver.gui.form.FormHandle;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.AbstractBatchActionWizard;
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.push.ServerPushService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class SEBExamConfigBatchResetToTemplatePopup extends AbstractBatchActionWizard {
private final static LocTextKey FORM_TITLE =
new LocTextKey("sebserver.examconfig.list.batch.reset.title");
private final static LocTextKey ACTION_DO_RESET =
new LocTextKey("sebserver.examconfig.list.batch.action.reset");
private final static LocTextKey FORM_INFO =
new LocTextKey("sebserver.examconfig.list.batch.action.reset.info");
protected SEBExamConfigBatchResetToTemplatePopup(
final PageService pageService,
final ServerPushService serverPushService) {
super(pageService, serverPushService);
}
@Override
protected LocTextKey getTitle() {
return FORM_TITLE;
}
@Override
protected LocTextKey getBatchActionInfo() {
return FORM_INFO;
}
@Override
protected LocTextKey getBatchActionTitle() {
return ACTION_DO_RESET;
}
@Override
protected BatchActionType getBatchActionType() {
return BatchActionType.EXAM_CONFIG_REST_TEMPLATE_SETTINGS;
}
@Override
protected Supplier<PageContext> createResultPageSupplier(
final PageContext pageContext,
final FormHandle<ConfigurationNode> formHandle) {
// No specific fields for this action
return () -> pageContext;
}
@Override
protected void extendBatchActionRequest(
final PageContext pageContext,
final RestCall<BatchAction>.RestCallBuilder batchActionRequestBuilder) {
// Nothing to do here
}
@Override
protected FormBuilder buildSpecificFormFields(
final PageContext formContext,
final FormBuilder formHead,
final boolean readonly) {
// No specific fields for this action
return formHead;
}
}

View file

@ -32,7 +32,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy @Lazy
@Component @Component
@GuiProfile @GuiProfile
public class SEBExamConfigStateChangePopup extends AbstractBatchActionWizard { public class SEBExamConfigBatchStateChangePopup extends AbstractBatchActionWizard {
private static final String ATTR_SELECTED_TARGET_STATE = "selectedTargetState"; private static final String ATTR_SELECTED_TARGET_STATE = "selectedTargetState";
@ -45,7 +45,7 @@ public class SEBExamConfigStateChangePopup extends AbstractBatchActionWizard {
private final static LocTextKey FORM_STATUS_TEXT_KEY = private final static LocTextKey FORM_STATUS_TEXT_KEY =
new LocTextKey("sebserver.examconfig.list.batch.action.status"); new LocTextKey("sebserver.examconfig.list.batch.action.status");
protected SEBExamConfigStateChangePopup( protected SEBExamConfigBatchStateChangePopup(
final PageService pageService, final PageService pageService,
final ServerPushService serverPushService) { final ServerPushService serverPushService) {

View file

@ -70,7 +70,8 @@ public class SEBExamConfigList implements TemplateComposer {
private final PageService pageService; private final PageService pageService;
private final SEBExamConfigImportPopup sebExamConfigImportPopup; private final SEBExamConfigImportPopup sebExamConfigImportPopup;
private final SEBExamConfigCreationPopup sebExamConfigCreationPopup; private final SEBExamConfigCreationPopup sebExamConfigCreationPopup;
private final SEBExamConfigStateChangePopup sebExamConfigStateChangePopup; private final SEBExamConfigBatchStateChangePopup sebExamConfigBatchStateChangePopup;
private final SEBExamConfigBatchResetToTemplatePopup sebExamConfigBatchResetToTemplatePopup;
private final CurrentUser currentUser; private final CurrentUser currentUser;
private final ResourceService resourceService; private final ResourceService resourceService;
private final int pageSize; private final int pageSize;
@ -79,13 +80,15 @@ public class SEBExamConfigList implements TemplateComposer {
final PageService pageService, final PageService pageService,
final SEBExamConfigImportPopup sebExamConfigImportPopup, final SEBExamConfigImportPopup sebExamConfigImportPopup,
final SEBExamConfigCreationPopup sebExamConfigCreationPopup, final SEBExamConfigCreationPopup sebExamConfigCreationPopup,
final SEBExamConfigStateChangePopup sebExamConfigStateChangePopup, final SEBExamConfigBatchStateChangePopup sebExamConfigBatchStateChangePopup,
final SEBExamConfigBatchResetToTemplatePopup sebExamConfigBatchResetToTemplatePopup,
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) { @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
this.pageService = pageService; this.pageService = pageService;
this.sebExamConfigImportPopup = sebExamConfigImportPopup; this.sebExamConfigImportPopup = sebExamConfigImportPopup;
this.sebExamConfigCreationPopup = sebExamConfigCreationPopup; this.sebExamConfigCreationPopup = sebExamConfigCreationPopup;
this.sebExamConfigStateChangePopup = sebExamConfigStateChangePopup; this.sebExamConfigBatchStateChangePopup = sebExamConfigBatchStateChangePopup;
this.sebExamConfigBatchResetToTemplatePopup = sebExamConfigBatchResetToTemplatePopup;
this.currentUser = pageService.getCurrentUser(); this.currentUser = pageService.getCurrentUser();
this.resourceService = pageService.getResourceService(); this.resourceService = pageService.getResourceService();
this.pageSize = pageSize; this.pageSize = pageSize;
@ -159,7 +162,8 @@ public class SEBExamConfigList implements TemplateComposer {
ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP_FROM_LIST, ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP_FROM_LIST,
ActionDefinition.SEB_EXAM_CONFIG_MODIFY_PROP_FROM_LIST, ActionDefinition.SEB_EXAM_CONFIG_MODIFY_PROP_FROM_LIST,
ActionDefinition.SEB_EXAM_CONFIG_COPY_CONFIG_FROM_LIST, ActionDefinition.SEB_EXAM_CONFIG_COPY_CONFIG_FROM_LIST,
ActionDefinition.SEB_EXAM_CONFIG_BULK_STATE_CHANGE)) ActionDefinition.SEB_EXAM_CONFIG_BULK_STATE_CHANGE,
ActionDefinition.SEB_EXAM_CONFIG_BULK_RESET_TO_TEMPLATE))
.compose(pageContext.copyOf(content)); .compose(pageContext.copyOf(content));
@ -171,7 +175,7 @@ public class SEBExamConfigList implements TemplateComposer {
.newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP_FROM_LIST) .newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP_FROM_LIST)
.withSelect( .withSelect(
configTable::getSelection, configTable::getMultiSelection,
PageAction::applySingleSelectionAsEntityKey, PageAction::applySingleSelectionAsEntityKey,
EMPTY_SELECTION_TEXT_KEY) EMPTY_SELECTION_TEXT_KEY)
.publish(false) .publish(false)
@ -205,8 +209,16 @@ public class SEBExamConfigList implements TemplateComposer {
.newAction(ActionDefinition.SEB_EXAM_CONFIG_BULK_STATE_CHANGE) .newAction(ActionDefinition.SEB_EXAM_CONFIG_BULK_STATE_CHANGE)
.withSelect( .withSelect(
configTable.getGrantedSelection(this.currentUser, NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUTION), configTable::getMultiSelection,
this.sebExamConfigStateChangePopup.popupCreationFunction(pageContext), this.sebExamConfigBatchStateChangePopup.popupCreationFunction(pageContext),
EMPTY_SELECTION_TEXT_KEY)
.noEventPropagation()
.publishIf(() -> examConfigGrant.im(), false)
.newAction(ActionDefinition.SEB_EXAM_CONFIG_BULK_RESET_TO_TEMPLATE)
.withSelect(
configTable::getMultiSelection,
this.sebExamConfigBatchResetToTemplatePopup.popupCreationFunction(pageContext),
EMPTY_SELECTION_TEXT_KEY) EMPTY_SELECTION_TEXT_KEY)
.noEventPropagation() .noEventPropagation()
.publishIf(() -> examConfigGrant.im(), false) .publishIf(() -> examConfigGrant.im(), false)

View file

@ -133,7 +133,7 @@ public class ExamFormIndicators implements TemplateComposer {
.newAction(ActionDefinition.EXAM_INDICATOR_MODIFY_FROM_LIST) .newAction(ActionDefinition.EXAM_INDICATOR_MODIFY_FROM_LIST)
.withParentEntityKey(entityKey) .withParentEntityKey(entityKey)
.withSelect( .withSelect(
indicatorTable::getSelection, indicatorTable::getMultiSelection,
PageAction::applySingleSelectionAsEntityKey, PageAction::applySingleSelectionAsEntityKey,
INDICATOR_EMPTY_SELECTION_TEXT_KEY) INDICATOR_EMPTY_SELECTION_TEXT_KEY)
.publishIf(() -> editable && indicatorTable.hasAnyContent(), false) .publishIf(() -> editable && indicatorTable.hasAnyContent(), false)
@ -141,7 +141,7 @@ public class ExamFormIndicators implements TemplateComposer {
.newAction(ActionDefinition.EXAM_INDICATOR_DELETE_FROM_LIST) .newAction(ActionDefinition.EXAM_INDICATOR_DELETE_FROM_LIST)
.withEntityKey(entityKey) .withEntityKey(entityKey)
.withSelect( .withSelect(
indicatorTable::getSelection, indicatorTable::getMultiSelection,
this::deleteSelectedIndicator, this::deleteSelectedIndicator,
INDICATOR_EMPTY_SELECTION_TEXT_KEY) INDICATOR_EMPTY_SELECTION_TEXT_KEY)
.publishIf(() -> editable && indicatorTable.hasAnyContent(), false) .publishIf(() -> editable && indicatorTable.hasAnyContent(), false)

View file

@ -210,7 +210,8 @@ public class ExamList implements TemplateComposer {
final GrantCheck userGrant = currentUser.grantCheck(EntityType.EXAM); final GrantCheck userGrant = currentUser.grantCheck(EntityType.EXAM);
actionBuilder actionBuilder
.newAction(ActionDefinition.EXAM_VIEW_FROM_LIST) .newAction(ActionDefinition.EXAM_VIEW_FROM_LIST)
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY) .withSelect(table::getMultiSelection, PageAction::applySingleSelectionAsEntityKey,
EMPTY_SELECTION_TEXT_KEY)
.publish(false) .publish(false)
.newAction(ActionDefinition.EXAM_MODIFY_FROM_LIST) .newAction(ActionDefinition.EXAM_MODIFY_FROM_LIST)

View file

@ -268,7 +268,7 @@ public class ExamTemplateForm implements TemplateComposer {
.newAction(ActionDefinition.INDICATOR_TEMPLATE_MODIFY_FROM_LIST) .newAction(ActionDefinition.INDICATOR_TEMPLATE_MODIFY_FROM_LIST)
.withParentEntityKey(entityKey) .withParentEntityKey(entityKey)
.withSelect( .withSelect(
indicatorTable::getSelection, indicatorTable::getMultiSelection,
PageAction::applySingleSelectionAsEntityKey, PageAction::applySingleSelectionAsEntityKey,
INDICATOR_EMPTY_SELECTION_TEXT_KEY) INDICATOR_EMPTY_SELECTION_TEXT_KEY)
.publishIf(() -> userGrant.im() && indicatorTable.hasAnyContent(), false) .publishIf(() -> userGrant.im() && indicatorTable.hasAnyContent(), false)
@ -276,7 +276,7 @@ public class ExamTemplateForm implements TemplateComposer {
.newAction(ActionDefinition.INDICATOR_TEMPLATE_DELETE_FROM_LIST) .newAction(ActionDefinition.INDICATOR_TEMPLATE_DELETE_FROM_LIST)
.withEntityKey(entityKey) .withEntityKey(entityKey)
.withSelect( .withSelect(
indicatorTable::getSelection, indicatorTable::getMultiSelection,
this::deleteSelectedIndicator, this::deleteSelectedIndicator,
INDICATOR_EMPTY_SELECTION_TEXT_KEY) INDICATOR_EMPTY_SELECTION_TEXT_KEY)
.publishIf(() -> userGrant.im() && indicatorTable.hasAnyContent(), false) .publishIf(() -> userGrant.im() && indicatorTable.hasAnyContent(), false)

View file

@ -175,11 +175,13 @@ public class ExamTemplateList implements TemplateComposer {
.publishIf(userGrant::iw) .publishIf(userGrant::iw)
.newAction(ActionDefinition.EXAM_TEMPLATE_VIEW_FROM_LIST) .newAction(ActionDefinition.EXAM_TEMPLATE_VIEW_FROM_LIST)
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY) .withSelect(table::getMultiSelection, PageAction::applySingleSelectionAsEntityKey,
EMPTY_SELECTION_TEXT_KEY)
.publish(false) .publish(false)
.newAction(ActionDefinition.EXAM_TEMPLATE_MODIFY_FROM_LIST) .newAction(ActionDefinition.EXAM_TEMPLATE_MODIFY_FROM_LIST)
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY) .withSelect(table::getMultiSelection, PageAction::applySingleSelectionAsEntityKey,
EMPTY_SELECTION_TEXT_KEY)
.publishIf(() -> userGrant.im(), false); .publishIf(() -> userGrant.im(), false);
} }

View file

@ -169,7 +169,10 @@ public class LmsSetupList implements TemplateComposer {
.publishIf(userGrant::iw) .publishIf(userGrant::iw)
.newAction(ActionDefinition.LMS_SETUP_VIEW_FROM_LIST) .newAction(ActionDefinition.LMS_SETUP_VIEW_FROM_LIST)
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY) .withSelect(
table::getMultiSelection,
PageAction::applySingleSelectionAsEntityKey,
EMPTY_SELECTION_TEXT_KEY)
.publish(false) .publish(false)
.newAction(ActionDefinition.LMS_SETUP_MODIFY_FROM_LIST) .newAction(ActionDefinition.LMS_SETUP_MODIFY_FROM_LIST)

View file

@ -236,7 +236,7 @@ public class QuizLookupList implements TemplateComposer {
actionBuilder actionBuilder
.newAction(ActionDefinition.QUIZ_DISCOVERY_SHOW_DETAILS) .newAction(ActionDefinition.QUIZ_DISCOVERY_SHOW_DETAILS)
.withSelect( .withSelect(
table::getSelection, table::getMultiSelection,
action -> this.showDetails( action -> this.showDetails(
action, action,
table.getSingleSelectedROWData(), table.getSingleSelectedROWData(),

View file

@ -170,7 +170,10 @@ public class FinishedExam implements TemplateComposer {
.newAction(ActionDefinition.VIEW_FINISHED_EXAM_CLIENT_CONNECTION) .newAction(ActionDefinition.VIEW_FINISHED_EXAM_CLIENT_CONNECTION)
.withParentEntityKey(examKey) .withParentEntityKey(examKey)
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY) .withSelect(
table::getMultiSelection,
PageAction::applySingleSelectionAsEntityKey,
EMPTY_SELECTION_TEXT_KEY)
.publishIf(isExamSupporter, false); .publishIf(isExamSupporter, false);
} }

View file

@ -141,7 +141,10 @@ public class FinishedExamList implements TemplateComposer {
actionBuilder actionBuilder
.newAction(ActionDefinition.VIEW_FINISHED_EXAM_FROM_LIST) .newAction(ActionDefinition.VIEW_FINISHED_EXAM_FROM_LIST)
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY) .withSelect(
table::getMultiSelection,
PageAction::applySingleSelectionAsEntityKey,
EMPTY_SELECTION_TEXT_KEY)
.publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER), false); .publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER), false);
} }

View file

@ -264,7 +264,7 @@ public class MonitoringClientConnection implements TemplateComposer {
.withParentEntityKey(parentEntityKey) .withParentEntityKey(parentEntityKey)
.withConfirm(() -> NOTIFICATION_LIST_CONFIRM_TEXT_KEY) .withConfirm(() -> NOTIFICATION_LIST_CONFIRM_TEXT_KEY)
.withSelect( .withSelect(
() -> notificationTable.getSelection(), () -> notificationTable.getMultiSelection(),
action -> this.confirmNotification(action, connectionData, notificationTable), action -> this.confirmNotification(action, connectionData, notificationTable),
NOTIFICATION_LIST_NO_SELECTION_KEY) NOTIFICATION_LIST_NO_SELECTION_KEY)

View file

@ -13,6 +13,7 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Set; import java.util.Set;
import java.util.function.BooleanSupplier; import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import org.eclipse.swt.SWT; import org.eclipse.swt.SWT;
@ -172,7 +173,7 @@ public class MonitoringRunningExam implements TemplateComposer {
.withParentEntityKey(entityKey) .withParentEntityKey(entityKey)
.create(), .create(),
this.pageService) this.pageService)
.withSelectionListener(this.pageService.getSelectionPublisher( .withSelectionListener(this.getSelectionPublisherClientConnectionTable(
pageContext, pageContext,
ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION, ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION,
ActionDefinition.MONITOR_EXAM_QUIT_SELECTED, ActionDefinition.MONITOR_EXAM_QUIT_SELECTED,
@ -505,4 +506,13 @@ public class MonitoringRunningExam implements TemplateComposer {
return action; return action;
} }
private Consumer<ClientConnectionTable> getSelectionPublisherClientConnectionTable(
final PageContext pageContext,
final ActionDefinition... actionDefinitions) {
return table -> this.pageService.firePageEvent(
new ActionActivationEvent(table.getSingleSelection() != null, actionDefinitions),
pageContext);
}
} }

View file

@ -141,7 +141,10 @@ public class MonitoringRunningExamList implements TemplateComposer {
actionBuilder actionBuilder
.newAction(ActionDefinition.MONITOR_EXAM_FROM_LIST) .newAction(ActionDefinition.MONITOR_EXAM_FROM_LIST)
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY) .withSelect(
table::getMultiSelection,
PageAction::applySingleSelectionAsEntityKey,
EMPTY_SELECTION_TEXT_KEY)
.publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER), false); .publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER), false);
} }

View file

@ -227,7 +227,7 @@ public class SEBClientEvents implements TemplateComposer {
actionBuilder actionBuilder
.newAction(ActionDefinition.LOGS_SEB_CLIENT_SHOW_DETAILS) .newAction(ActionDefinition.LOGS_SEB_CLIENT_SHOW_DETAILS)
.withSelect( .withSelect(
table::getSelection, table::getMultiSelection,
action -> this.sebClientEventDetailsPopup.showDetails(action, table.getSingleSelectedROWData()), action -> this.sebClientEventDetailsPopup.showDetails(action, table.getSingleSelectedROWData()),
EMPTY_SELECTION_TEXT) EMPTY_SELECTION_TEXT)
.noEventPropagation() .noEventPropagation()

View file

@ -172,7 +172,7 @@ public interface PageService {
* @return a message supplier to notify deactivation dependencies to the user */ * @return a message supplier to notify deactivation dependencies to the user */
default <T extends Entity & Activatable> Supplier<LocTextKey> confirmDeactivation(final EntityTable<T> table) { default <T extends Entity & Activatable> Supplier<LocTextKey> confirmDeactivation(final EntityTable<T> table) {
return () -> { return () -> {
final List<EntityKey> multiSelection = table.getMultiSelection(); final Set<EntityKey> multiSelection = table.getMultiSelection();
if (multiSelection.size() > 1) { if (multiSelection.size() > 1) {
throw new PageMessageException(MESSAGE_NO_MULTISELECTION); throw new PageMessageException(MESSAGE_NO_MULTISELECTION);
} }
@ -204,12 +204,12 @@ public interface PageService {
* @param pageContext the current PageContext * @param pageContext the current PageContext
* @param actionDefinitions list of action definitions that activity should be toggled on table selection * @param actionDefinitions list of action definitions that activity should be toggled on table selection
* @return the selection publisher that handles this defines action activation on table selection */ * @return the selection publisher that handles this defines action activation on table selection */
default <T> Consumer<Set<T>> getSelectionPublisher( default <T extends ModelIdAware> Consumer<EntityTable<T>> getSelectionPublisher(
final PageContext pageContext, final PageContext pageContext,
final ActionDefinition... actionDefinitions) { final ActionDefinition... actionDefinitions) {
return rows -> firePageEvent( return table -> firePageEvent(
new ActionActivationEvent(!rows.isEmpty(), actionDefinitions), new ActionActivationEvent(table.hasSelection(), actionDefinitions),
pageContext); pageContext);
} }
@ -225,15 +225,15 @@ public interface PageService {
* @param pageContext the current PageContext * @param pageContext the current PageContext
* @param actionDefinitions list of action definitions that activity should be toggled on table selection * @param actionDefinitions list of action definitions that activity should be toggled on table selection
* @return the selection publisher that handles this defines action activation on table selection */ * @return the selection publisher that handles this defines action activation on table selection */
default <T extends Activatable> Consumer<Set<T>> getSelectionPublisher( default <T extends Activatable & ModelIdAware> Consumer<EntityTable<T>> getSelectionPublisher(
final ActionDefinition toggle, final ActionDefinition toggle,
final ActionDefinition activate, final ActionDefinition activate,
final ActionDefinition deactivate, final ActionDefinition deactivate,
final PageContext pageContext, final PageContext pageContext,
final ActionDefinition... actionDefinitions) { final ActionDefinition... actionDefinitions) {
return rows -> { return table -> {
final Set<T> rows = table.getPageSelectionData();
if (!rows.isEmpty()) { if (!rows.isEmpty()) {
firePageEvent( firePageEvent(
new ActionActivationEvent( new ActionActivationEvent(

View file

@ -171,7 +171,6 @@ public final class PageAction {
// if selection is needed, check selection fist, before confirm dialog // if selection is needed, check selection fist, before confirm dialog
if (this.selectionSupplier != null) { if (this.selectionSupplier != null) {
getMultiSelection(); getMultiSelection();
} }
final LocTextKey confirmMessage = this.confirm.apply(this); final LocTextKey confirmMessage = this.confirm.apply(this);

View file

@ -249,7 +249,7 @@ public class PageServiceImpl implements PageService {
final Function<PageAction, PageAction> testBeforeActivation) { final Function<PageAction, PageAction> testBeforeActivation) {
return action -> { return action -> {
final List<EntityKey> multiSelection = table.getMultiSelection(); final Set<EntityKey> multiSelection = table.getMultiSelection();
if (multiSelection == null || multiSelection.isEmpty()) { if (multiSelection == null || multiSelection.isEmpty()) {
throw new PageMessageException(noSelectionText); throw new PageMessageException(noSelectionText);
} }

View file

@ -88,7 +88,7 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate
private final Table table; private final Table table;
private final ColorData colorData; private final ColorData colorData;
private final Function<ClientConnectionData, String> localizedClientConnectionStatusNameFunction; private final Function<ClientConnectionData, String> localizedClientConnectionStatusNameFunction;
private Consumer<Set<EntityKey>> selectionListener; private Consumer<ClientConnectionTable> selectionListener;
private int tableWidth; private int tableWidth;
private boolean needsSort = false; private boolean needsSort = false;
@ -238,7 +238,7 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate
} }
} }
public ClientConnectionTable withSelectionListener(final Consumer<Set<EntityKey>> selectionListener) { public ClientConnectionTable withSelectionListener(final Consumer<ClientConnectionTable> selectionListener) {
this.selectionListener = selectionListener; this.selectionListener = selectionListener;
return this; return this;
} }
@ -402,7 +402,7 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate
return; return;
} }
this.selectionListener.accept(this.getSelection()); this.selectionListener.accept(this);
} }
private void notifyTableInfoClick(final Event event) { private void notifyTableInfoClick(final Event event) {

View file

@ -95,7 +95,7 @@ public class EntityTable<ROW extends ModelIdAware> {
private final MultiValueMap<String, String> staticQueryParams; private final MultiValueMap<String, String> staticQueryParams;
private final BiConsumer<TableItem, ROW> rowDecorator; private final BiConsumer<TableItem, ROW> rowDecorator;
private final Consumer<Set<ROW>> selectionListener; private final Consumer<EntityTable<ROW>> selectionListener;
private final Consumer<Integer> contentChangeListener; private final Consumer<Integer> contentChangeListener;
private final Set<String> multiselection; private final Set<String> multiselection;
@ -125,7 +125,7 @@ public class EntityTable<ROW extends ModelIdAware> {
final boolean hideNavigation, final boolean hideNavigation,
final MultiValueMap<String, String> staticQueryParams, final MultiValueMap<String, String> staticQueryParams,
final BiConsumer<TableItem, ROW> rowDecorator, final BiConsumer<TableItem, ROW> rowDecorator,
final Consumer<Set<ROW>> selectionListener, final Consumer<EntityTable<ROW>> selectionListener,
final Consumer<Integer> contentChangeListener, final Consumer<Integer> contentChangeListener,
final String defaultSortColumn, final String defaultSortColumn,
final PageSortOrder defaultSortOrder) { final PageSortOrder defaultSortOrder) {
@ -263,6 +263,10 @@ public class EntityTable<ROW extends ModelIdAware> {
return this.table.getItemCount() > 0; return this.table.getItemCount() > 0;
} }
public boolean hasSelection() {
return (this.multiselection != null && !this.multiselection.isEmpty()) || this.table.getSelectionCount() > 0;
}
public void setPageSize(final int pageSize) { public void setPageSize(final int pageSize) {
this.pageSize = pageSize; this.pageSize = pageSize;
updateTableRows( updateTableRows(
@ -363,15 +367,18 @@ public class EntityTable<ROW extends ModelIdAware> {
return getEntityKey(selection[0]); return getEntityKey(selection[0]);
} }
public List<EntityKey> getMultiSelection() { public Set<EntityKey> getMultiSelection() {
if (this.multiselection == null) { if (this.multiselection == null) {
return Collections.emptyList(); return getPageSelectionData()
.stream()
.map(row -> new EntityKey(row.getModelId(), getEntityType()))
.collect(Collectors.toSet());
} }
return this.multiselection return this.multiselection
.stream() .stream()
.map(modelId -> new EntityKey(modelId, getEntityType())) .map(modelId -> new EntityKey(modelId, getEntityType()))
.collect(Collectors.toList()); .collect(Collectors.toSet());
} }
public ROW getFirstRowData() { public ROW getFirstRowData() {
@ -396,7 +403,7 @@ public class EntityTable<ROW extends ModelIdAware> {
return getRowData(selection[0]); return getRowData(selection[0]);
} }
private Set<ROW> getPageSelectionData() { public Set<ROW> getPageSelectionData() {
final TableItem[] selection = this.table.getSelection(); final TableItem[] selection = this.table.getSelection();
if (selection == null || selection.length == 0) { if (selection == null || selection.length == 0) {
return Collections.emptySet(); return Collections.emptySet();
@ -407,10 +414,6 @@ public class EntityTable<ROW extends ModelIdAware> {
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
public Set<EntityKey> getSelection() {
return getSelection(null);
}
public Set<EntityKey> getSelection(final Predicate<ROW> grantCheck) { public Set<EntityKey> getSelection(final Predicate<ROW> grantCheck) {
final TableItem[] selection = this.table.getSelection(); final TableItem[] selection = this.table.getSelection();
if (selection == null) { if (selection == null) {
@ -685,7 +688,7 @@ public class EntityTable<ROW extends ModelIdAware> {
return; return;
} }
this.selectionListener.accept(this.getPageSelectionData()); this.selectionListener.accept(this);
} }
private void updateCurrentPageAttr() { private void updateCurrentPageAttr() {
@ -807,6 +810,9 @@ public class EntityTable<ROW extends ModelIdAware> {
this.multiselection.remove(modelId); this.multiselection.remove(modelId);
} else { } else {
this.multiselection.add(modelId); this.multiselection.add(modelId);
Arrays.asList(this.table.getSelection())
.stream()
.forEach(i -> this.multiselection.add(getModelId(i)));
} }
multiselectFromPage(); multiselectFromPage();
} }

View file

@ -10,7 +10,6 @@ package ch.ethz.seb.sebserver.gui.table;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.BooleanSupplier; import java.util.function.BooleanSupplier;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -48,7 +47,7 @@ public class TableBuilder<ROW extends ModelIdAware> {
private boolean hideNavigation = false; private boolean hideNavigation = false;
private Function<PageSupplier.Builder<ROW>, PageSupplier.Builder<ROW>> restCallAdapter; private Function<PageSupplier.Builder<ROW>, PageSupplier.Builder<ROW>> restCallAdapter;
private BiConsumer<TableItem, ROW> rowDecorator; private BiConsumer<TableItem, ROW> rowDecorator;
private Consumer<Set<ROW>> selectionListener; private Consumer<EntityTable<ROW>> selectionListener;
private Consumer<Integer> contentChangeListener; private Consumer<Integer> contentChangeListener;
private boolean markupEnabled = false; private boolean markupEnabled = false;
private String defaultSortColumn = null; private String defaultSortColumn = null;
@ -152,7 +151,7 @@ public class TableBuilder<ROW extends ModelIdAware> {
return this; return this;
} }
public TableBuilder<ROW> withSelectionListener(final Consumer<Set<ROW>> selectionListener) { public TableBuilder<ROW> withSelectionListener(final Consumer<EntityTable<ROW>> selectionListener) {
this.selectionListener = selectionListener; this.selectionListener = selectionListener;
return this; return this;
} }

View file

@ -201,6 +201,7 @@ public class PaginationServiceImpl implements PaginationService {
final PageSortOrder sortOrder = PageSortOrder.getSortOrder(sort); final PageSortOrder sortOrder = PageSortOrder.getSortOrder(sort);
final String sortColumnName = verifySortColumnName(sort, sortMappingName); final String sortColumnName = verifySortColumnName(sort, sortMappingName);
if (StringUtils.isNotBlank(sortColumnName)) { if (StringUtils.isNotBlank(sortColumnName)) {
startPage.setOrderByOnly(false);
switch (sortOrder) { switch (sortOrder) {
case DESCENDING: { case DESCENDING: {
PageHelper.orderBy(sortColumnName + " DESC"); PageHelper.orderBy(sortColumnName + " DESC");
@ -211,6 +212,7 @@ public class PaginationServiceImpl implements PaginationService {
break; break;
} }
} }
PageHelper.orderBy("id");
} }
} }

View file

@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.authorization;
import java.security.Principal; import java.security.Principal;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.WebDataBinder;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.impl.SEBServerUser; import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.impl.SEBServerUser;
@ -51,4 +52,9 @@ public interface UserService {
* @param binder Springs WebDataBinder is injected on controller side */ * @param binder Springs WebDataBinder is injected on controller side */
void addUsersInstitutionDefaultPropertySupport(final WebDataBinder binder); void addUsersInstitutionDefaultPropertySupport(final WebDataBinder binder);
/** Used to set authentication on different thread.
*
* @param authentication */
void setAuthenticationIfAbsent(Authentication authentication);
} }

View file

@ -13,6 +13,7 @@ import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.CredentialsContainer; import org.springframework.security.core.CredentialsContainer;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority;
@ -26,7 +27,7 @@ import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
* *
* This implements Spring's UserDetails and CredentialsContainer to act as a principal * This implements Spring's UserDetails and CredentialsContainer to act as a principal
* within internal authentication and authorization processes. */ * within internal authentication and authorization processes. */
public final class SEBServerUser implements UserDetails, CredentialsContainer { public final class SEBServerUser implements UserDetails, CredentialsContainer, Authentication {
private static final long serialVersionUID = 5726250141482925769L; private static final long serialVersionUID = 5726250141482925769L;
@ -160,4 +161,34 @@ public final class SEBServerUser implements UserDetails, CredentialsContainer {
return new SEBServerUser(user.id, UserInfo.of(user.userInfo), user.password); return new SEBServerUser(user.id, UserInfo.of(user.userInfo), user.password);
} }
@Override
public String getName() {
return this.userInfo.username;
}
@Override
public Object getCredentials() {
return this;
}
@Override
public Object getDetails() {
return this;
}
@Override
public Object getPrincipal() {
return this;
}
@Override
public boolean isAuthenticated() {
return isEnabled();
}
@Override
public void setAuthenticated(final boolean isAuthenticated) throws IllegalArgumentException {
}
} }

View file

@ -94,6 +94,13 @@ public class UserServiceImpl implements UserService {
binder.registerCustomEditor(Long.class, usersInstitutionDefaultEditor); binder.registerCustomEditor(Long.class, usersInstitutionDefaultEditor);
} }
@Override
public void setAuthenticationIfAbsent(final Authentication authentication) {
if (SecurityContextHolder.getContext().getAuthentication() == null) {
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
// 1. OAuth2Authentication strategy // 1. OAuth2Authentication strategy
@Lazy @Lazy
@Component @Component
@ -115,6 +122,21 @@ public class UserServiceImpl implements UserService {
} }
} }
// 2. Separated thread strategy
@Lazy
@Component
public static class OtherThreadUserExtractStrategy implements ExtractUserFromAuthenticationStrategy {
@Override
public SEBServerUser extract(final Principal principal) {
if (principal instanceof SEBServerUser) {
return (SEBServerUser) principal;
}
return null;
}
}
private static final SEBServerUser ANONYMOUS_USER = new SEBServerUser( private static final SEBServerUser ANONYMOUS_USER = new SEBServerUser(
-1L, -1L,
new UserInfo("SEB_SERVER_ANONYMOUS_USER", -2L, null, "anonymous", "anonymous", "anonymous", null, false, new UserInfo("SEB_SERVER_ANONYMOUS_USER", -2L, null, "anonymous", "anonymous", "anonymous", null, false,

View file

@ -22,6 +22,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.API;
@ -38,6 +40,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BatchActionServi
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.BatchActionDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.BatchActionDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO;
@Service @Service
@WebServiceProfile @WebServiceProfile
@ -46,17 +50,23 @@ public class BatchActionServiceImpl implements BatchActionService {
private static final Logger log = LoggerFactory.getLogger(BatchActionServiceImpl.class); private static final Logger log = LoggerFactory.getLogger(BatchActionServiceImpl.class);
private final BatchActionDAO batchActionDAO; private final BatchActionDAO batchActionDAO;
private final UserDAO userDAO;
private final TaskScheduler taskScheduler; private final TaskScheduler taskScheduler;
private final UserActivityLogDAO userActivityLogDAO;
private final EnumMap<BatchActionType, BatchActionExec> batchExecutions; private final EnumMap<BatchActionType, BatchActionExec> batchExecutions;
private ScheduledFuture<?> runningBatchProcess = null; private ScheduledFuture<?> runningBatchProcess = null;
public BatchActionServiceImpl( public BatchActionServiceImpl(
final BatchActionDAO batchActionDAO, final BatchActionDAO batchActionDAO,
final UserDAO userDAO,
final UserActivityLogDAO userActivityLogDAO,
final Collection<BatchActionExec> batchExecutions, final Collection<BatchActionExec> batchExecutions,
final TaskScheduler taskScheduler) { final TaskScheduler taskScheduler) {
this.batchActionDAO = batchActionDAO; this.batchActionDAO = batchActionDAO;
this.userDAO = userDAO;
this.userActivityLogDAO = userActivityLogDAO;
this.taskScheduler = taskScheduler; this.taskScheduler = taskScheduler;
this.batchExecutions = new EnumMap<>(BatchActionType.class); this.batchExecutions = new EnumMap<>(BatchActionType.class);
@ -158,7 +168,8 @@ public class BatchActionServiceImpl implements BatchActionService {
new BatchActionProcess( new BatchActionProcess(
new BatchActionHandlerImpl(action), new BatchActionHandlerImpl(action),
this.batchExecutions.get(action.actionType), this.batchExecutions.get(action.actionType),
action), action,
getAuthentication(action)),
Instant.now()); Instant.now());
}) })
.onError(error -> { .onError(error -> {
@ -174,22 +185,43 @@ public class BatchActionServiceImpl implements BatchActionService {
} }
} }
private Authentication getAuthentication(final BatchAction action) {
try {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
return authentication;
}
return this.userDAO.byModelId(action.ownerId)
.flatMap(userInfo -> this.userDAO.sebServerUserByUsername(userInfo.username))
.onError(error -> log.error("Failed to get batch action owner user -> ", error))
.getOr(null);
} catch (final Exception e) {
log.error("Failed to get authentication: ", e);
return null;
}
}
private final static class BatchActionProcess implements Runnable { private final static class BatchActionProcess implements Runnable {
private final BatchActionHandler batchActionHandler; private final BatchActionHandler batchActionHandler;
private final BatchActionExec batchActionExec; private final BatchActionExec batchActionExec;
private final BatchAction batchAction; private final BatchAction batchAction;
private final Authentication authentication;
private Set<String> processingIds; private Set<String> processingIds;
public BatchActionProcess( public BatchActionProcess(
final BatchActionHandler batchActionHandler, final BatchActionHandler batchActionHandler,
final BatchActionExec batchActionExec, final BatchActionExec batchActionExec,
final BatchAction batchAction) { final BatchAction batchAction,
final Authentication authentication) {
this.batchActionHandler = batchActionHandler; this.batchActionHandler = batchActionHandler;
this.batchActionExec = batchActionExec; this.batchActionExec = batchActionExec;
this.batchAction = batchAction; this.batchAction = batchAction;
this.authentication = authentication;
} }
@Override @Override
@ -198,6 +230,13 @@ public class BatchActionServiceImpl implements BatchActionService {
log.info("Starting or continuing batch action - {}", this.batchAction); log.info("Starting or continuing batch action - {}", this.batchAction);
if (SecurityContextHolder.getContext().getAuthentication() == null) {
if (this.authentication == null) {
throw new IllegalStateException("No authentication found within batch context");
}
SecurityContextHolder.getContext().setAuthentication(this.authentication);
}
this.processingIds = new HashSet<>(this.batchAction.sourceIds); this.processingIds = new HashSet<>(this.batchAction.sourceIds);
this.processingIds.removeAll(this.batchAction.successful); this.processingIds.removeAll(this.batchAction.successful);
@ -256,10 +295,10 @@ public class BatchActionServiceImpl implements BatchActionService {
@Override @Override
public void handleError(final String modelId, final Exception error) { public void handleError(final String modelId, final Exception error) {
log.error( log.error(
"Failed to process single entity on batch action. ModelId: {}, action: ", "Failed to process single entity on batch action. ModelId: {}, action: {}, errorMessage: {}",
modelId, modelId,
this.batchAction, this.batchAction,
error); error.getMessage());
BatchActionServiceImpl.this.batchActionDAO.setFailure( BatchActionServiceImpl.this.batchActionDAO.setFailure(
this.batchAction.id, this.batchAction.id,
@ -276,6 +315,10 @@ public class BatchActionServiceImpl implements BatchActionService {
.onError(error -> log.error( .onError(error -> log.error(
"Failed to mark batch action as finished: {}", "Failed to mark batch action as finished: {}",
this.batchAction, error)); this.batchAction, error));
BatchActionServiceImpl.this.batchActionDAO.byPK(this.batchAction.id)
.flatMap(BatchActionServiceImpl.this.userActivityLogDAO::logFinished)
.onError(error -> log.error("Failed to put audit log for batch action finish: ", error));
} }
} }

View file

@ -0,0 +1,86 @@
/*
* Copyright (c) 2022 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl;
import java.util.Map;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.api.API.BatchActionType;
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.gbl.model.BatchAction;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BatchActionExec;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationNodeDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ExamConfigService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamConfigUpdateService;
@Lazy
@Component
@WebServiceProfile
public class ExamConfigResetToTemplate implements BatchActionExec {
private final ConfigurationNodeDAO configurationNodeDAO;
private final ExamConfigService sebExamConfigService;
private final ExamConfigUpdateService examConfigUpdateService;
private final AuthorizationService authorizationService;
public ExamConfigResetToTemplate(
final ConfigurationNodeDAO configurationNodeDAO,
final ExamConfigService sebExamConfigService,
final ExamConfigUpdateService examConfigUpdateService,
final AuthorizationService authorizationService) {
this.configurationNodeDAO = configurationNodeDAO;
this.sebExamConfigService = sebExamConfigService;
this.examConfigUpdateService = examConfigUpdateService;
this.authorizationService = authorizationService;
}
@Override
public BatchActionType actionType() {
return BatchActionType.EXAM_CONFIG_REST_TEMPLATE_SETTINGS;
}
@Override
public APIMessage checkConsistency(final Map<String, String> actionAttributes) {
// no additional check here
return null;
}
@Override
public Result<EntityKey> doSingleAction(final String modelId, final BatchAction batchAction) {
return this.configurationNodeDAO
.byModelId(modelId)
.flatMap(node -> this.authorizationService.check(PrivilegeType.MODIFY, node))
.map(this::checkConsistency)
.flatMap(this.sebExamConfigService::resetToTemplateSettings)
.map(node -> {
this.examConfigUpdateService
.processExamConfigurationChange(node.id)
.getOrThrow();
return node;
})
.map(Entity::getEntityKey);
}
private ConfigurationNode checkConsistency(final ConfigurationNode configurationNode) {
return configurationNode;
}
}

View file

@ -21,36 +21,31 @@ import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService; import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BatchActionExec; import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BatchActionExec;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationNodeDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationNodeDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ExamConfigService; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ExamConfigService;
import io.micrometer.core.instrument.util.StringUtils; import io.micrometer.core.instrument.util.StringUtils;
@Lazy @Lazy
@Component @Component
@WebServiceProfile @WebServiceProfile
public class SingleExamConfigStateChange implements BatchActionExec { public class ExamConfigStateChange implements BatchActionExec {
private final ExamConfigService sebExamConfigService; private final ExamConfigService sebExamConfigService;
private final ConfigurationNodeDAO configurationNodeDAO; private final ConfigurationNodeDAO configurationNodeDAO;
private final AuthorizationService authorizationService; private final AuthorizationService authorizationService;
private final UserDAO userDAO;
public SingleExamConfigStateChange( public ExamConfigStateChange(
final ExamConfigService sebExamConfigService, final ExamConfigService sebExamConfigService,
final ConfigurationNodeDAO configurationNodeDAO, final ConfigurationNodeDAO configurationNodeDAO,
final AuthorizationService authorizationService, final AuthorizationService authorizationService) {
final UserDAO userDAO) {
this.sebExamConfigService = sebExamConfigService; this.sebExamConfigService = sebExamConfigService;
this.configurationNodeDAO = configurationNodeDAO; this.configurationNodeDAO = configurationNodeDAO;
this.authorizationService = authorizationService; this.authorizationService = authorizationService;
this.userDAO = userDAO;
} }
@Override @Override
@ -71,13 +66,9 @@ public class SingleExamConfigStateChange implements BatchActionExec {
@Override @Override
public Result<EntityKey> doSingleAction(final String modelId, final BatchAction batchAction) { public Result<EntityKey> doSingleAction(final String modelId, final BatchAction batchAction) {
final UserInfo user = this.userDAO
.byModelId(batchAction.ownerId)
.getOrThrow();
return this.configurationNodeDAO return this.configurationNodeDAO
.byModelId(modelId) .byModelId(modelId)
.map(node -> this.authorizationService.check(PrivilegeType.MODIFY, user, node)) .flatMap(node -> this.authorizationService.check(PrivilegeType.MODIFY, node))
.map(node -> new ConfigurationNode( .map(node -> new ConfigurationNode(
node.id, null, null, null, null, null, null, node.id, null, null, null, null, null, null,
getTargetState(batchAction.attributes))) getTargetState(batchAction.attributes)))

View file

@ -77,6 +77,12 @@ public interface UserActivityLogDAO extends
* @return Result of the Entity or referring to an Error if happened */ * @return Result of the Entity or referring to an Error if happened */
<E extends Entity> Result<E> logModify(E entity); <E extends Entity> Result<E> logModify(E entity);
/** Create a user activity log entry for the current user of activity type FINISHED
*
* @param entity the Entity
* @return Result of the Entity or referring to an Error if happened */
<E extends Entity> Result<E> logFinished(E entity);
/** Create a user activity log entry for the current user of activity type DELETE /** Create a user activity log entry for the current user of activity type DELETE
* *
* @param entity the Entity * @param entity the Entity

View file

@ -160,6 +160,11 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
return log(UserLogActivityType.MODIFY, entity); return log(UserLogActivityType.MODIFY, entity);
} }
@Override
public <E extends Entity> Result<E> logFinished(final E entity) {
return log(UserLogActivityType.FINISHED, entity);
}
@Override @Override
@Transactional @Transactional
public <E extends Entity> Result<E> logDelete(final E entity) { public <E extends Entity> Result<E> logDelete(final E entity) {

View file

@ -14,6 +14,7 @@ import java.io.OutputStream;
import java.io.PipedInputStream; import java.io.PipedInputStream;
import java.io.PipedOutputStream; import java.io.PipedOutputStream;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -414,6 +415,18 @@ public class ExamConfigServiceImpl implements ExamConfigService {
"The Type of ConfigurationNode cannot change after creation"); "The Type of ConfigurationNode cannot change after creation");
} }
// if configuration is in use, "Ready to Use" is not possible
if (configurationNode.status == ConfigurationStatus.READY_TO_USE) {
if (!this.examConfigurationMapDAO
.getExamIdsForConfigNodeId(configurationNode.id)
.getOr(Collections.emptyList())
.isEmpty()) {
throw new APIMessageException(
APIMessage.ErrorMessage.INTEGRITY_VALIDATION
.of("Exam configuration has references to at least one exam."));
}
}
// if changing to archived check possibility // if changing to archived check possibility
if (configurationNode.status == ConfigurationStatus.ARCHIVED) { if (configurationNode.status == ConfigurationStatus.ARCHIVED) {
if (existingNode.status != ConfigurationStatus.ARCHIVED) { if (existingNode.status != ConfigurationStatus.ARCHIVED) {

View file

@ -49,6 +49,7 @@ sebserver.overall.types.activityType.ACTIVATE=Activate
sebserver.overall.types.activityType.DELETE=Delete sebserver.overall.types.activityType.DELETE=Delete
sebserver.overall.types.activityType.LOGIN=Login sebserver.overall.types.activityType.LOGIN=Login
sebserver.overall.types.activityType.LOGOUT=Logout sebserver.overall.types.activityType.LOGOUT=Logout
sebserver.overall.types.activityType.FINISHED=Finished
sebserver.overall.types.entityType.CONFIGURATION_ATTRIBUTE=Configuration Attribute sebserver.overall.types.entityType.CONFIGURATION_ATTRIBUTE=Configuration Attribute
sebserver.overall.types.entityType.CONFIGURATION_VALUE=Configuration Value sebserver.overall.types.entityType.CONFIGURATION_VALUE=Configuration Value
@ -70,6 +71,7 @@ sebserver.overall.types.entityType.EXAM_SEB_RESTRICTION=SEB Exam Restriction
sebserver.overall.types.entityType.CERTIFICATE=Certificate sebserver.overall.types.entityType.CERTIFICATE=Certificate
sebserver.overall.types.entityType.EXAM_TEMPLATE=Exam Template sebserver.overall.types.entityType.EXAM_TEMPLATE=Exam Template
sebserver.overall.types.entityType.EXAM_PROCTOR_DATA=Exam Proctoring Settings sebserver.overall.types.entityType.EXAM_PROCTOR_DATA=Exam Proctoring Settings
sebserver.overall.types.entityType.BATCH_ACTION=Batch Action
sebserver.overall.activity.title.serveradmin=SEB Server Administration sebserver.overall.activity.title.serveradmin=SEB Server Administration
sebserver.overall.activity.title.sebconfig=Configurations sebserver.overall.activity.title.sebconfig=Configurations
@ -814,13 +816,16 @@ sebserver.examconfig.list.action.no.modify.privilege=No Access: An Exam Configur
sebserver.examconfig.action.list.new=Add Exam Configuration sebserver.examconfig.action.list.new=Add Exam Configuration
sebserver.examconfig.action.list.view=View Exam Configuration sebserver.examconfig.action.list.view=View Exam Configuration
sebserver.examconfig.list.action.statechange=State Change sebserver.examconfig.list.action.statechange=State Change
sebserver.examconfig.list.batch.statechange.title=Bulk State Change sebserver.examconfig.list.batch.statechange.title=State Change All
sebserver.examconfig.list.batch.action.statechange=Change States sebserver.examconfig.list.batch.action.statechange=Change States
sebserver.examconfig.list.batch.progress=Bulk State Change in Progress: {0}% sebserver.examconfig.list.batch.progress=Bulk State Change in Progress: {0}%
sebserver.examconfig.list.batch.finished=Bulk State Change finished. Successful: {0}.Failed: {1} sebserver.examconfig.list.batch.finished=Bulk State Change finished. Successful: {0}.Failed: {1}
sebserver.examconfig.list.batch.action.statechange.info=This action changes all selected exam configurations to a defined target state.<br/>Please note that this will be done only for those that can be changed to the defined target state in regard to its current state. sebserver.examconfig.list.batch.action.statechange.info=This action changes all selected exam configurations to a defined target state.<br/>Please note that this will be done only for those that can be changed to the defined target state in regard to its current state.
sebserver.examconfig.list.batch.action.status=Target Status sebserver.examconfig.list.batch.action.status=Target Status
sebserver.examconfig.list.action.reset=Reset To Template Settings
sebserver.examconfig.list.batch.reset.title=Reset To Template Settings
sebserver.examconfig.list.batch.action.reset=Reset All
sebserver.examconfig.list.batch.action.reset.info=This action process a reset of the SEB settings to its template settings for all selected configurations.<br/>Please note this will be done only for those that has an origin template and are not in state "Used". <br/>Please note also that this action tries to directly publish the changes and if not possible (active SEB client connections) it will report an error even if the settings has been reseted.
sebserver.examconfig.action.list.modify.properties=Edit Exam Configuration sebserver.examconfig.action.list.modify.properties=Edit Exam Configuration
sebserver.examconfig.action.delete=Delete Exam Configuration sebserver.examconfig.action.delete=Delete Exam Configuration

View file

@ -15,6 +15,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -248,11 +249,12 @@ public abstract class AdministrationAPIIntegrationTester {
} }
protected String getOrderedUUIDs(final Collection<? extends Entity> list) { protected String getOrderedUUIDs(final Collection<? extends Entity> list) {
return list final List<String> l = list
.stream() .stream()
.map(userInfo -> userInfo.getModelId()) .map(userInfo -> userInfo.getModelId())
.collect(Collectors.toList()) .collect(Collectors.toList());
.toString(); l.sort((s1, s2) -> s1.compareTo(s2));
return l.toString();
} }
} }

View file

@ -268,7 +268,7 @@ public class UserAPITest extends AdministrationAPIIntegrationTester {
assertTrue(userInfos.numberOfPages == 1); assertTrue(userInfos.numberOfPages == 1);
assertNotNull(userInfos.content); assertNotNull(userInfos.content);
assertTrue(userInfos.content.size() == 3); assertTrue(userInfos.content.size() == 3);
assertEquals("[user5, user2, user1]", getOrderedUUIDs(userInfos.content)); assertEquals("[user1, user2, user5]", getOrderedUUIDs(userInfos.content));
} }
@Test @Test
@ -347,7 +347,7 @@ public class UserAPITest extends AdministrationAPIIntegrationTester {
assertTrue(userInfos.numberOfPages == 2); assertTrue(userInfos.numberOfPages == 2);
assertNotNull(userInfos.content); assertNotNull(userInfos.content);
assertTrue(userInfos.content.size() == 3); assertTrue(userInfos.content.size() == 3);
assertEquals("[user7, user6, user4]", getOrderedUUIDs(userInfos.content)); assertEquals("[user3, user4, user6]", getOrderedUUIDs(userInfos.content));
} }
@Test @Test