register user, status filter, activity in lists

This commit is contained in:
anhefti 2020-02-11 16:16:27 +01:00
parent fa3b327180
commit c9ebeacf1e
26 changed files with 252 additions and 193 deletions

View file

@ -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;

View file

@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.gbl.model.user;
/** All activity types */
public enum UserLogActivityType {
REGISTER,
CREATE,
IMPORT,
EXPORT,

View file

@ -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;

View file

@ -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());
}
}

View file

@ -113,14 +113,12 @@ public class InstitutionList implements TemplateComposer {
Institution::getUrlSuffix)
.sortable()
.withFilter(this.urlSuffixFilter))
.withColumn(new ColumnDefinition<Institution>(
.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().<Institution> 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<Set<Institution>> getSelectionPublisher(final PageContext pageContext) {

View file

@ -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().<LmsSetup> localizedActivityFunction())
.withFilter(this.activityFilter)
.sortable())
.withDefaultAction(actionBuilder

View file

@ -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);

View file

@ -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);
}

View file

@ -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<EntityKey> selectionForQuitInstruction(final ClientConnectionTable clientTable) {
final Set<String> 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,

View file

@ -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<UserInfo> 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<UserInfo> 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);

View file

@ -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().<SebClientConfig> localizedActivityFunction())
.withFilter(this.activityFilter)
.sortable())
.withDefaultAction(pageActionBuilder

View file

@ -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()

View file

@ -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(

View file

@ -182,7 +182,7 @@ public class UserAccountList implements TemplateComposer {
.withColumn(new ColumnDefinition<>(
Domain.USER.ATTR_ACTIVE,
ACTIVE_TEXT_KEY,
UserInfo::getActive)
this.pageService.getResourceService().<UserInfo> localizedActivityFunction())
.sortable()
.withFilter(this.activityFilter)
.widthProportion(1))

View file

@ -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),

View file

@ -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(

View file

@ -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<FormFieldAccessor> list = this.formFields.get(fieldName);
if (list != null) {

View file

@ -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 <T extends Activatable> Function<T, String> localizedActivityFunction() {
final Function<Boolean, String> localizedActivityResource = localizedActivityResource();
return activatable -> localizedActivityResource.apply(activatable.isActive());
}
public Function<Boolean, String> localizedActivityResource() {
return activity -> activity
? this.i18nSupport.getText(ACTIVE_TEXT_KEY)

View file

@ -185,6 +185,7 @@ public class WidgetFactory {
LOGIN("login"),
LOGIN_BACK("login-back"),
SCROLL("scroll"),
LIST_NAVIGATION("list-nav")

View file

@ -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

View file

@ -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 <E extends Entity> Result<E> logCreate(E entity);
* @return Result of the Entity or referring to an Error if happened */
<E extends Entity> Result<E> 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<UserAccount> 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 <E extends Entity> Result<E> logSaveToHistory(E entity);
* @return Result of the Entity or referring to an Error if happened */
<E extends Entity> Result<E> 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 <E extends Entity> Result<E> logUndo(E entity);
* @return Result of the Entity or referring to an Error if happened */
<E extends Entity> Result<E> 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 <E extends Entity> Result<E> logImport(E entity);
* @return Result of the Entity or referring to an Error if happened */
<E extends Entity> Result<E> 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 <E extends Entity> Result<E> logExport(E entity);
* @return Result of the Entity or referring to an Error if happened */
<E extends Entity> Result<E> 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 <E extends Entity> Result<E> logModify(E entity);
* @return Result of the Entity or referring to an Error if happened */
<E extends Entity> Result<E> 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 */
<E extends Entity> Result<E> 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 */
<E extends Entity> Result<E> 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 */
<T> Result<T> 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 */
<E extends Entity> Result<E> 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 <E extends Entity> Result<E> log(
final SEBServerUser user,
final UserLogActivityType activityType,

View file

@ -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<UserAccount> 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 <E extends Entity> Result<E> logSaveToHistory(final E entity) {

View file

@ -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<EntityName> 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<EntityKey> getInstitutionInfo(@PathVariable(required = false) final String urlSuffix) {
public Collection<EntityName> 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());
}

View file

@ -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<String, String> allRequestParams) {
final Collection<APIMessage> 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<APIMessage> 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();
}
}

View file

@ -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.<br/> 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

View file

@ -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;