Merge branch 'development' of github.com:SafeExamBrowser/seb-server into SEBSLI-9

This commit is contained in:
Nadim Ritter 2024-02-29 16:56:28 +01:00
commit f3a34ab06f
39 changed files with 719 additions and 441 deletions

View file

@ -18,16 +18,16 @@ jobs:
steps: steps:
- -
name: Checkout repository name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v4
- -
name: Set up JDK 8 name: Set up JDK 8
uses: actions/setup-java@v3 uses: actions/setup-java@v4
with: with:
java-version: '8' java-version: '8'
distribution: 'adopt' distribution: 'adopt'
- -
name: Cache Maven packages name: Cache Maven packages
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: ~/.m2 path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
@ -37,7 +37,7 @@ jobs:
run: mvn clean install -e -P let_reporting run: mvn clean install -e -P let_reporting
- -
name: Reporting name: Reporting
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v4
with: with:
flags: unittests flags: unittests
name: SEB Server Build name: SEB Server Build
@ -69,16 +69,16 @@ jobs:
echo ${{ env.TAG_NAME }} echo ${{ env.TAG_NAME }}
- -
name: Checkout repository name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v4
- -
name: Set up JDK 17 name: Set up JDK 17
uses: actions/setup-java@v3 uses: actions/setup-java@v4
with: with:
java-version: '17' java-version: '17'
distribution: 'adopt' distribution: 'adopt'
- -
name: Cache Maven packages name: Cache Maven packages
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: ~/.m2 path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
@ -92,7 +92,7 @@ jobs:
name: Simplify package name name: Simplify package name
run: mv target/seb-server-${{ env.TAG_NAME }}-${{ env.SHA }}.jar target/seb-server.jar run: mv target/seb-server-${{ env.TAG_NAME }}-${{ env.SHA }}.jar target/seb-server.jar
- -
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: Package name: Package
path: target/seb-server.jar path: target/seb-server.jar
@ -132,7 +132,7 @@ jobs:
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- -
name: Checkout repository name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v4
# Install the cosign tool except on PR # Install the cosign tool except on PR
# https://github.com/sigstore/cosign-installer # https://github.com/sigstore/cosign-installer
@ -142,7 +142,7 @@ jobs:
uses: sigstore/cosign-installer@main uses: sigstore/cosign-installer@main
- -
name: Download a single artifact name: Download a single artifact
uses: actions/download-artifact@v3 uses: actions/download-artifact@v4
with: with:
name: Package name: Package
- -

View file

@ -1,3 +1,6 @@
Safe Exam Browser (SEB) Server
--------------------------------
Master: Master:
.. image:: https://github.com/SafeExamBrowser/seb-server/actions/workflows/buildReporting.yml/badge.svg?branch=master .. image:: https://github.com/SafeExamBrowser/seb-server/actions/workflows/buildReporting.yml/badge.svg?branch=master
@ -32,7 +35,7 @@ What is Safe Exam Browser Server (SEB Server)?
While the interaction with SEB is well known in Learning Management Systems (LMS) like `Open edX <https://open.edx.org/>`_, While the interaction with SEB is well known in Learning Management Systems (LMS) like `Open edX <https://open.edx.org/>`_,
`Moodle <https://moodle.org/>`_ etc. the SEB Server is an entirely new component to set up secured online exams. `Moodle <https://moodle.org/>`_ etc. the SEB Server is an entirely new component to set up secured online exams.
It interacts with the assessments system/LMS as well as with SEB on exam clients.It supports exam scenarios on student owned devices (BYOD) It interacts with the assessments system/LMS as well as with SEB on exam clients. It supports exam scenarios on student owned devices (BYOD)
and on managed devices. and on managed devices.
SEB Server is a modern webservice with a REST API and a GUI service on top of it. SEB Server is written in Java and uses Docker for installation and setup. SEB Server is a modern webservice with a REST API and a GUI service on top of it. SEB Server is written in Java and uses Docker for installation and setup.
@ -62,7 +65,7 @@ New Features:
- Security: New Application Signature Key (ASK) integration within SEB Server exams and monitoring - Security: New Application Signature Key (ASK) integration within SEB Server exams and monitoring
- Security: Minimum SEB Client version tracking within SEB Server monitoring - Security: Minimum SEB Client version tracking within SEB Server monitoring
- LMS Integration: Better Moodle integration with new `SEB Server Moodle Plugin <https://github.com/ethz-let/moodle-quizzaccess_sebserver>`_ - LMS Integration: Better Moodle integration with new `SEB Server Moodle Plugin <https://github.com/ethz-let/moodle-quizaccess_sebserver>`_
- Exam Maintenance: Added new SEB grouping functionality for Exam (and Exam Template) and Monitoring to be able to view/manage SEB Clients within defined groups (IP range, SEB client OS, ...) - Exam Maintenance: Added new SEB grouping functionality for Exam (and Exam Template) and Monitoring to be able to view/manage SEB Clients within defined groups (IP range, SEB client OS, ...)
- Exam Maintenance: Batch actions for archive and delete exams - Exam Maintenance: Batch actions for archive and delete exams
- Exam Maintenance: Added SEB log export for finished and archived exams - Exam Maintenance: Added SEB log export for finished and archived exams
@ -98,12 +101,20 @@ Bugfixes:
Docker-Image: Docker-Image:
- Exact release version: docker pull anhefti/seb-server:v1.5.0 (sha256:21d62e24dd5cf697ab5f2b437dc458e6c7492ea294f77a424d39d05164d6c8cc)
- Stable minor version: docker pull anhefti/seb-server:v1.5-stable
Latest Version is 1.5.1 with Docker-Image:
- Exact release version: docker pull anhefti/seb-server:v1.5.1 (sha256:af860f5dd4d99db3e7acaa66d26c3ee72cf0ad08d8ca88febec6d4ecd160b9cf)
- Latest stable minor version with latest patches: docker pull anhefti/seb-server:v1.5-latest
SEB - SEB Server Compatibility SEB - SEB Server Compatibility
------------------------------ ------------------------------
The table below shows available and upcoming SEB client versions that has SEB Server integration support and are compatible with particular The table below shows available and upcoming SEB client versions that has SEB Server integration support and are compatible with particular
SEB Server version. There is an entry for each platform with a beta or testing release date and a official release date. SEB Server versions. There is an entry for each platform with a beta or testing release date and an official release date.
**SEB Server Version 1.5.X** **SEB Server Version 1.5.X**
@ -134,7 +145,61 @@ Getting started with SEB Server
For a complete SEB Server user guide please go to `SEB Server User Guide <https://seb-server.readthedocs.io/en/latest/#>`_ For a complete SEB Server user guide please go to `SEB Server User Guide <https://seb-server.readthedocs.io/en/latest/#>`_
Project Background Project Background
------------------ ------------------
The SEB Server is currently build and maintained by `ETH Zürich <https://ethz.ch/en.html>`_ and by the `Swiss MOOC Service <https://www.swissmooc.ch/>`_ that is founded by leading Swiss universities EPFL, ETH, SUPSI, USI and HES-SO. The Swiss MOOC Service was financially supported from 2018-2020 by the `Swissuniversities´ P5 program <https://www.swissuniversities.ch/themen/digitalisierung/p-5-wissenschaftliche-information>`_. The SEB Server is currently build and maintained by `ETH Zürich <https://ethz.ch/en.html>`_ and by the `Swiss MOOC Service <https://www.swissmooc.ch/>`_ that is founded by leading Swiss universities EPFL, ETH, SUPSI, USI and HES-SO. The Swiss MOOC Service was financially supported from 2018-2020 by the `Swissuniversities´ P5 program <https://www.swissuniversities.ch/themen/digitalisierung/p-5-wissenschaftliche-information>`_.
Contributing to SEB Server
------------------
We want to make contributing to this project as easy and transparent as possible, whether it's:
- Give us a star
- Reporting a bug
- Submitting a fix
- Proposing new features
- Becoming a SEB Alliance member
We use github to host code, to track issues and feature requests, as well as accept pull requests.
And we use `Github issues <https://github.com/SafeExamBrowser/seb-server/issues>`_ to track public bugs.
Report a bug by [opening a new issue]();
**Before enter a new bug-report, ensure the bug was not already reported**
Please fill and provide all the information suggested by the bug-report template
Great Bug Reports tend to have:
- A quick summary and/or background
- Steps to reproduce
- Be specific and give sample code if you can. Can also be Pseudocode.
- What you expected would happen
- What actually happens
- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work)
**We Use Git-Flow for Code Contributions**
Pull requests are the best way to propose changes to the codebase. We use `Github Flow <https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow>`_. We actively welcome your pull requests:
1. Fork the repo and create your branch from `development`. The development branch always has the newest changes.
2. If you've added code that should be tested, add tests.
3. If you introduce new API also add clear documentation.
4. Ensure the test suite passes.
5. Make sure your code lints.
6. Issue that pull request!
**Use a Consistent Coding Style**
Have a close look to the existing code stile that is used within SEB Server and adapt to it as close as possible.
We reserve the right to adapt contributed code to the code style matching SEB Server code style before or after a pull request.
**Any contributions you make will be under the Mozilla Public License Version 2.0**
In short, when you submit code changes, your submissions are understood to be under the same `Mozilla Public License <https://github.com/SafeExamBrowser/seb-server?tab=MPL-2.0-1-ov-file>`_ that covers the project. Feel free to contact the maintainers if that's a concern.
**Becoming a SEB Alliance member**
The `SEB Alliance <https://www.safeexambrowser.org/alliance/members.html>`_ is the body which sustains ongoing funding of the Safe Exam Browser open source project to continue its maintenance, development and support activities. ETH Zurich provides the infrastructure for the management and the software engineering of the SEB project and appoints an alliance manager who will provide administrative support to the SEB Alliance, and ensure the day-to-day running of the SEB Alliance. ETH Zurich leads the Alliance and offers different contribution levels to parties interested in the evolution of the SEB open source project.
More information about `joining <https://www.safeexambrowser.org/alliance/join.html>`_ the Alliance is available in our `benefits <https://www.safeexambrowser.org/alliance/benefits.html>`_ and `documents <https://www.safeexambrowser.org/alliance/documents.html>`_ section.

View file

@ -1,5 +1,5 @@
docutils<0.18 docutils<0.18
sphinx==5.3.0 sphinx==5.3.0
sphinx_rtd_theme==2.0.0 sphinx_rtd_theme==2.0.0
readthedocs-sphinx-search==0.1.1 readthedocs-sphinx-search==0.3.2
urllib3==1.26.13 urllib3==1.26.13

View file

@ -11,6 +11,11 @@ package ch.ethz.seb.sebserver.gbl.model.sebconfig;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size; import javax.validation.constraints.Size;
import java.util.Collection;
import java.util.Set;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.validator.constraints.URL; import org.hibernate.validator.constraints.URL;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.DateTimeZone; import org.joda.time.DateTimeZone;
@ -52,6 +57,7 @@ public final class SEBClientConfig implements GrantEntity, Activatable {
public static final String ATTR_ENCRYPT_CERTIFICATE_ASYM = "cert_encryption_asym"; public static final String ATTR_ENCRYPT_CERTIFICATE_ASYM = "cert_encryption_asym";
public static final String FILTER_ATTR_CREATION_DATE = "creation_date"; public static final String FILTER_ATTR_CREATION_DATE = "creation_date";
public static final String ATTR_EXAM_SELECTION = "exam_selection";
public enum ConfigPurpose { public enum ConfigPurpose {
START_EXAM, START_EXAM,
@ -174,6 +180,8 @@ public final class SEBClientConfig implements GrantEntity, Activatable {
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_LAST_UPDATE_USER) @JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_LAST_UPDATE_USER)
public final String lastUpdateUser; public final String lastUpdateUser;
@JsonProperty(SEBClientConfig.ATTR_EXAM_SELECTION)
public final Set<Long> selectedExams;
@JsonCreator @JsonCreator
public SEBClientConfig( public SEBClientConfig(
@ -204,7 +212,8 @@ public final class SEBClientConfig implements GrantEntity, Activatable {
@JsonProperty(ATTR_ENCRYPT_CERTIFICATE_ASYM) final Boolean encryptCertificateAsym, @JsonProperty(ATTR_ENCRYPT_CERTIFICATE_ASYM) final Boolean encryptCertificateAsym,
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ACTIVE) final Boolean active, @JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ACTIVE) final Boolean active,
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_LAST_UPDATE_TIME) final DateTime lastUpdateTime, @JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_LAST_UPDATE_TIME) final DateTime lastUpdateTime,
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_LAST_UPDATE_USER) final String lastUpdateUser) { @JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_LAST_UPDATE_USER) final String lastUpdateUser,
@JsonProperty(SEBClientConfig.ATTR_EXAM_SELECTION) final Set<Long> selectedExams) {
this.id = id; this.id = id;
this.institutionId = institutionId; this.institutionId = institutionId;
@ -240,6 +249,7 @@ public final class SEBClientConfig implements GrantEntity, Activatable {
this.active = active; this.active = active;
this.lastUpdateTime = lastUpdateTime; this.lastUpdateTime = lastUpdateTime;
this.lastUpdateUser = lastUpdateUser; this.lastUpdateUser = lastUpdateUser;
this.selectedExams = Utils.immutableSetOf(selectedExams);
} }
public SEBClientConfig(final Long institutionId, final POSTMapper postParams) { public SEBClientConfig(final Long institutionId, final POSTMapper postParams) {
@ -281,6 +291,7 @@ public final class SEBClientConfig implements GrantEntity, Activatable {
this.active = false; this.active = false;
this.lastUpdateTime = postParams.getDateTime(CONFIGURATION_NODE.ATTR_LAST_UPDATE_TIME); this.lastUpdateTime = postParams.getDateTime(CONFIGURATION_NODE.ATTR_LAST_UPDATE_TIME);
this.lastUpdateUser = postParams.getString(CONFIGURATION_NODE.ATTR_LAST_UPDATE_USER); this.lastUpdateUser = postParams.getString(CONFIGURATION_NODE.ATTR_LAST_UPDATE_USER);
this.selectedExams = Utils.immutableSetOf(Utils.getIdsFromString(postParams.getString(SEBClientConfig.ATTR_EXAM_SELECTION)));
} }
@Override @Override
@ -424,6 +435,10 @@ public final class SEBClientConfig implements GrantEntity, Activatable {
return this.lastUpdateUser; return this.lastUpdateUser;
} }
public Set<Long> getSelectedExams() {
return selectedExams;
}
@Override @Override
public String toString() { public String toString() {
final StringBuilder builder = new StringBuilder(); final StringBuilder builder = new StringBuilder();
@ -503,7 +518,8 @@ public final class SEBClientConfig implements GrantEntity, Activatable {
this.encryptCertificateAsym, this.encryptCertificateAsym,
this.active, this.active,
this.lastUpdateTime, this.lastUpdateTime,
this.lastUpdateUser); this.lastUpdateUser,
this.selectedExams);
} }
public static SEBClientConfig createNew(final Long institutionId, final long pingIterval) { public static SEBClientConfig createNew(final Long institutionId, final long pingIterval) {
@ -533,6 +549,7 @@ public final class SEBClientConfig implements GrantEntity, Activatable {
false, false,
false, false,
null, null,
null,
null); null);
} }

View file

@ -17,8 +17,12 @@ public class UserFeatures {
public enum Feature { public enum Feature {
ADMIN_INSTITUTION("admin.institution"), ADMIN_INSTITUTION("admin.institution"),
ADMIN_USER_ADMINISTRATION("admin.user.administration"), ADMIN_USER_ADMINISTRATION("admin.user.administration"),
ADMIN_USER_ACCOUNT("admin.user.account"), ADMIN_USER_ACCOUNT("admin.user.account"),
ADMIN_USER_ACCOUNT_SELF_REGISTERING("admin.user.account.self.registering"),
ADMIN_USER_ACCOUNT_SELF_REGISTERING_AUTO_ACTIVATION("admin.user.account.self.registering.autoactivation"),
ADMIN_AUDIT_LOGS("admin.auditlogs"), ADMIN_AUDIT_LOGS("admin.auditlogs"),
CONFIG_CONNECTION_CONFIGURATION("config.connection.configuration"), CONFIG_CONNECTION_CONFIGURATION("config.connection.configuration"),

View file

@ -935,4 +935,22 @@ public final class Utils {
return "Basic " + base64Creds; return "Basic " + base64Creds;
} }
public static Set<Long> getIdsFromString(final String idsString) {
if (StringUtils.isBlank(idsString)) {
return Collections.emptySet();
}
return Arrays
.stream(StringUtils.split(idsString, Constants.LIST_SEPARATOR_CHAR))
.map(s -> {
try {
return Long.valueOf(s);
} catch (final Exception e) {
log.warn("Failed to parse String: {} to Long", s);
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toSet());
}
} }

View file

@ -59,14 +59,16 @@ public class LoginPage implements TemplateComposer {
public LoginPage( public LoginPage(
final PageService pageService, final PageService pageService,
final DefaultRegisterPage defaultRegisterPage, final DefaultRegisterPage defaultRegisterPage,
@Value("${sebserver.gui.registering:false}") final Boolean registeringEnabled) { @Value("${sebserver.gui.registering:false}") final boolean guiRegEnabled,
@Value("${sebserver.gui.self-registering:false}") final boolean guiRegEnabledOld,
@Value("${sebserver.feature.admin.user.account.self.registering:true}") final boolean webRegEnabled) {
this.pageService = pageService; this.pageService = pageService;
this.authorizationContextHolder = pageService.getAuthorizationContextHolder(); this.authorizationContextHolder = pageService.getAuthorizationContextHolder();
this.widgetFactory = pageService.getWidgetFactory(); this.widgetFactory = pageService.getWidgetFactory();
this.i18nSupport = pageService.getI18nSupport(); this.i18nSupport = pageService.getI18nSupport();
this.defaultRegisterPage = defaultRegisterPage; this.defaultRegisterPage = defaultRegisterPage;
this.registeringEnabled = BooleanUtils.toBoolean(registeringEnabled); this.registeringEnabled = webRegEnabled && (guiRegEnabled || guiRegEnabledOld);
} }
@Override @Override

View file

@ -85,6 +85,9 @@ public class RegisterPage implements TemplateComposer {
static final LocTextKey MESSAGE_SUCCESS_TEXT = static final LocTextKey MESSAGE_SUCCESS_TEXT =
new LocTextKey("sebserver.login.register.success"); new LocTextKey("sebserver.login.register.success");
static final LocTextKey MESSAGE_SUCCESS_ACTIVATION_TEXT =
new LocTextKey("sebserver.login.register.success.activate");
private final PageService pageService; private final PageService pageService;
private final ResourceService resourceService; private final ResourceService resourceService;
private final WidgetFactory widgetFactory; private final WidgetFactory widgetFactory;
@ -247,7 +250,9 @@ public class RegisterPage implements TemplateComposer {
} }
pageContext.forwardToLoginPage(); pageContext.forwardToLoginPage();
pageContext.publishPageMessage(MESSAGE_SUCCESS_TILE, MESSAGE_SUCCESS_TEXT); pageContext.publishPageMessage(
MESSAGE_SUCCESS_TILE,
(result.get().active) ? MESSAGE_SUCCESS_TEXT : MESSAGE_SUCCESS_ACTIVATION_TEXT);
}); });

View file

@ -134,6 +134,9 @@ public class SEBClientConfigForm implements TemplateComposer {
private static final LocTextKey FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY = private static final LocTextKey FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY =
new LocTextKey("sebserver.clientconfig.form.encryptSecret.confirm"); new LocTextKey("sebserver.clientconfig.form.encryptSecret.confirm");
private static final LocTextKey FORM_EXAM_SELECTION_TEXT_KEY =
new LocTextKey("sebserver.clientconfig.form.examselection");
private static final LocTextKey DELETE_CONFIRM = private static final LocTextKey DELETE_CONFIRM =
new LocTextKey("sebserver.clientconfig.action.delete.confirm"); new LocTextKey("sebserver.clientconfig.action.delete.confirm");
private static final LocTextKey DELETE_SUCCESS = private static final LocTextKey DELETE_SUCCESS =
@ -409,20 +412,28 @@ public class SEBClientConfigForm implements TemplateComposer {
.mandatory(!isReadonly)) .mandatory(!isReadonly))
.withDefaultSpanEmptyCell(3) .withDefaultSpanEmptyCell(3)
.addField(FormBuilder.multiComboSelection(
SEBClientConfig.ATTR_EXAM_SELECTION,
FORM_EXAM_SELECTION_TEXT_KEY,
StringUtils.join(clientConfig.selectedExams, Constants.LIST_SEPARATOR),
() -> pageService.getResourceService().getExamLogSelectionResources())
.withInputSpan(5))
.withDefaultSpanEmptyCell(1);
// VDI // VDI
.withDefaultSpanInput(2) // .withDefaultSpanInput(2)
.addFieldIf( // .addFieldIf(
() -> false, // TODO skipped for version 1.2 --> 1.3 or 1.4 // () -> false, // TODO skipped for version 1.2 --> 1.3 or 1.4
() -> FormBuilder.singleSelection( // () -> FormBuilder.singleSelection(
SEBClientConfig.ATTR_VDI_TYPE, // SEBClientConfig.ATTR_VDI_TYPE,
VDI_TYPE_TEXT_KEY, // VDI_TYPE_TEXT_KEY,
clientConfig.vdiType != null // clientConfig.vdiType != null
? clientConfig.vdiType.name() // ? clientConfig.vdiType.name()
: SEBClientConfig.VDIType.NO.name(), // : SEBClientConfig.VDIType.NO.name(),
() -> this.pageService.getResourceService().vdiTypeResources()) // () -> this.pageService.getResourceService().vdiTypeResources())
.mandatory(!isReadonly)) // .mandatory(!isReadonly))
.withDefaultSpanEmptyCell(3); // .withDefaultSpanEmptyCell(3);
// VDI Attributes // VDI Attributes
@ -460,7 +471,10 @@ public class SEBClientConfigForm implements TemplateComposer {
FALLBACK_TEXT_KEY, FALLBACK_TEXT_KEY,
clientConfig.fallback != null clientConfig.fallback != null
? clientConfig.fallback.toString() ? clientConfig.fallback.toString()
: Constants.FALSE_STRING)); : Constants.FALSE_STRING))
.withDefaultSpanEmptyCell(3)
;
// Fallback Attributes // Fallback Attributes

View file

@ -137,6 +137,7 @@ public class ExamFormConfigs implements TemplateComposer {
.withSelectionListener(this.pageService.getSelectionPublisher( .withSelectionListener(this.pageService.getSelectionPublisher(
pageContext, pageContext,
ActionDefinition.EXAM_CONFIGURATION_MODIFY_FROM_LIST,
ActionDefinition.EXAM_CONFIGURATION_EXAM_CONFIG_VIEW_PROP, ActionDefinition.EXAM_CONFIGURATION_EXAM_CONFIG_VIEW_PROP,
ActionDefinition.EXAM_CONFIGURATION_DELETE_FROM_LIST, ActionDefinition.EXAM_CONFIGURATION_DELETE_FROM_LIST,
ActionDefinition.EXAM_CONFIGURATION_EXPORT, ActionDefinition.EXAM_CONFIGURATION_EXPORT,
@ -144,7 +145,7 @@ public class ExamFormConfigs implements TemplateComposer {
.compose(pageContext.copyOf(content)); .compose(pageContext.copyOf(content));
final EntityKey configMapKey = (configurationTable.hasAnyContent()) final EntityKey configKey = (configurationTable.hasAnyContent())
? new EntityKey( ? new EntityKey(
configurationTable.getFirstRowData().configurationNodeId, configurationTable.getFirstRowData().configurationNodeId,
EntityType.CONFIGURATION_NODE) EntityType.CONFIGURATION_NODE)
@ -164,9 +165,18 @@ public class ExamFormConfigs implements TemplateComposer {
.newAction(ActionDefinition.EXAM_CONFIGURATION_EXAM_CONFIG_VIEW_PROP) .newAction(ActionDefinition.EXAM_CONFIGURATION_EXAM_CONFIG_VIEW_PROP)
.withParentEntityKey(entityKey) .withParentEntityKey(entityKey)
.withEntityKey(configMapKey) .withEntityKey(configKey)
.publishIf(() -> examConfigEnabled && readGrant && configurationTable.hasAnyContent(), false) .publishIf(() -> examConfigEnabled && readGrant && configurationTable.hasAnyContent(), false)
.newAction(ActionDefinition.EXAM_CONFIGURATION_MODIFY_FROM_LIST)
.withParentEntityKey(entityKey)
.withSelect(
getConfigMappingSelection(configurationTable),
this.examToConfigBindingForm.bindFunction(),
CONFIG_EMPTY_SELECTION_TEXT_KEY)
.noEventPropagation()
.publishIf(() -> examConfigEnabled && editable && configurationTable.hasAnyContent(), false)
.newAction(ActionDefinition.EXAM_CONFIGURATION_DELETE_FROM_LIST) .newAction(ActionDefinition.EXAM_CONFIGURATION_DELETE_FROM_LIST)
.withEntityKey(entityKey) .withEntityKey(entityKey)
.withSelect( .withSelect(
@ -179,7 +189,7 @@ public class ExamFormConfigs implements TemplateComposer {
} }
return null; return null;
}) })
.publishIf(() -> examConfigEnabled && editable && configurationTable.hasAnyContent() && editable, false) .publishIf(() -> examConfigEnabled && editable && configurationTable.hasAnyContent(), false)
.newAction(ActionDefinition.EXAM_CONFIGURATION_GET_CONFIG_KEY) .newAction(ActionDefinition.EXAM_CONFIGURATION_GET_CONFIG_KEY)
.withSelect( .withSelect(

View file

@ -82,7 +82,7 @@ public class ExamToConfigBindingForm {
return action -> { return action -> {
final PageContext pageContext = action.pageContext(); final PageContext pageContext = action.pageContext();
final EntityKey entityKey = pageContext.getEntityKey(); final EntityKey entityKey = action.getSingleSelection();
final boolean isNew = entityKey == null; final boolean isNew = entityKey == null;
if (isNew) { if (isNew) {
@ -103,11 +103,12 @@ public class ExamToConfigBindingForm {
final BindFormContext bindFormContext = new BindFormContext( final BindFormContext bindFormContext = new BindFormContext(
this.pageService, this.pageService,
action.pageContext()); action.pageContext()
.withEntityKey(entityKey));
final Predicate<FormHandle<ExamConfigurationMap>> doBind = formHandle -> doCreate( final Predicate<FormHandle<ExamConfigurationMap>> doBind = formHandle -> doCreate(
this.pageService, this.pageService,
pageContext, bindFormContext.pageContext,
formHandle); formHandle);
// the default page layout // the default page layout
@ -181,14 +182,13 @@ public class ExamToConfigBindingForm {
final EntityKey parentEntityKey = this.pageContext.getParentEntityKey(); final EntityKey parentEntityKey = this.pageContext.getParentEntityKey();
final boolean isNew = entityKey == null; final boolean isNew = entityKey == null;
final Exam exam = (isNew) final Exam exam = restService
? restService .getBuilder(GetExam.class)
.getBuilder(GetExam.class) .withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId)
.withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId) .call()
.call() .onError(error -> this.pageContext.notifyLoadError(EntityType.EXAM, error))
.onError(error -> this.pageContext.notifyLoadError(EntityType.EXAM, error)) .getOrThrow();
.getOrThrow()
: null;
// get data or create new. Handle error if happen // get data or create new. Handle error if happen
final ExamConfigurationMap examConfigurationMap = (isNew) final ExamConfigurationMap examConfigurationMap = (isNew)
@ -217,8 +217,13 @@ public class ExamToConfigBindingForm {
.putStaticValue( .putStaticValue(
Domain.EXAM_CONFIGURATION_MAP.ATTR_EXAM_ID, Domain.EXAM_CONFIGURATION_MAP.ATTR_EXAM_ID,
String.valueOf(examConfigurationMap.examId)) String.valueOf(examConfigurationMap.examId))
.putStaticValueIf(
() -> !isNew,
Domain.EXAM_CONFIGURATION_MAP.ATTR_CONFIGURATION_NODE_ID,
String.valueOf(examConfigurationMap.configurationNodeId))
.addField(FormBuilder.singleSelection( .addFieldIf( () -> isNew,
() -> FormBuilder.singleSelection(
Domain.EXAM_CONFIGURATION_MAP.ATTR_CONFIGURATION_NODE_ID, Domain.EXAM_CONFIGURATION_MAP.ATTR_CONFIGURATION_NODE_ID,
CONFIG_MAPPING_NAME_TEXT_KEY, CONFIG_MAPPING_NAME_TEXT_KEY,
String.valueOf(examConfigurationMap.configurationNodeId), String.valueOf(examConfigurationMap.configurationNodeId),
@ -226,6 +231,13 @@ public class ExamToConfigBindingForm {
.withSelectionListener(form -> updateFormValuesFromConfigSelection(form, resourceService)) .withSelectionListener(form -> updateFormValuesFromConfigSelection(form, resourceService))
.mandatory()) .mandatory())
.addFieldIf( () -> !isNew,
() -> FormBuilder.text(
Domain.EXAM_CONFIGURATION_MAP.ATTR_CONFIGURATION_NODE_ID,
CONFIG_MAPPING_NAME_TEXT_KEY,
examConfigurationMap.configName)
.readonly(true))
.addField(FormBuilder.text( .addField(FormBuilder.text(
Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION, Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
FORM_DESCRIPTION_TEXT_KEY, FORM_DESCRIPTION_TEXT_KEY,

View file

@ -111,6 +111,13 @@ public class ProctoringSettingsPopup {
private final static LocTextKey RESET_SUCCESS_KEY = private final static LocTextKey RESET_SUCCESS_KEY =
new LocTextKey("sebserver.exam.proctoring.form.resetOk"); new LocTextKey("sebserver.exam.proctoring.form.resetOk");
private final static LocTextKey DEPRECATION_MESSAGE =
new LocTextKey("sebserver.exam.proctoring.deprecation.message");
private final static LocTextKey DEPRECATION_NOTE =
new LocTextKey("sebserver.exam.proctoring.deprecation.title");
Function<PageAction, PageAction> settingsFunction(final PageService pageService, final boolean modifyGrant) { Function<PageAction, PageAction> settingsFunction(final PageService pageService, final boolean modifyGrant) {
return action -> { return action -> {
@ -125,7 +132,7 @@ public class ProctoringSettingsPopup {
action.pageContext().getParent().getShell(), action.pageContext().getParent().getShell(),
pageService.getWidgetFactory()) pageService.getWidgetFactory())
.setDialogWidth(860) .setDialogWidth(860)
.setDialogHeight(600); .setDialogHeight(650);
final ResetButtonHandler resetButtonHandler = new ResetButtonHandler(); final ResetButtonHandler resetButtonHandler = new ResetButtonHandler();
if (modifyGrant) { if (modifyGrant) {
@ -326,6 +333,10 @@ public class ProctoringSettingsPopup {
final RestService restService = this.pageService.getRestService(); final RestService restService = this.pageService.getRestService();
final EntityKey entityKey = this.pageContext.getEntityKey(); final EntityKey entityKey = this.pageContext.getEntityKey();
final Composite warningPanel = this.pageService.getWidgetFactory().createWarningPanel(parent);
this.pageService.getWidgetFactory().labelLocalized(warningPanel, WidgetFactory.CustomVariant.TITLE_LABEL, DEPRECATION_NOTE);
this.pageService.getWidgetFactory().labelLocalized(warningPanel, WidgetFactory.CustomVariant.SUBTITLE, DEPRECATION_MESSAGE, null);
final Composite content = this.pageService final Composite content = this.pageService
.getWidgetFactory() .getWidgetFactory()
.createPopupScrollComposite(parent); .createPopupScrollComposite(parent);

View file

@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.gui.form;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -122,6 +123,7 @@ public final class SelectionFieldBuilder extends FieldBuilder<String> {
this.itemsSupplier.get() this.itemsSupplier.get()
.stream() .stream()
.filter(tuple -> keys.contains(tuple._1)) .filter(tuple -> keys.contains(tuple._1))
.sorted((t1, t2) -> String.CASE_INSENSITIVE_ORDER.compare(t1._2, t2._2))
.map(tuple -> tuple._1) .map(tuple -> tuple._1)
.forEach(v -> buildReadonlyLabel(builder, composite, v, 1)); .forEach(v -> buildReadonlyLabel(builder, composite, v, 1));
} }

View file

@ -8,22 +8,17 @@
package ch.ethz.seb.sebserver.gui.widget; package ch.ethz.seb.sebserver.gui.widget;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.eclipse.rap.rwt.widgets.DropDown; import org.eclipse.rap.rwt.widgets.DropDown;
import org.eclipse.swt.SWT; import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.*;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Text;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -33,13 +28,11 @@ import ch.ethz.seb.sebserver.gui.service.page.PageService;
public final class MultiSelectionCombo extends Composite implements Selection { public final class MultiSelectionCombo extends Composite implements Selection {
public static final LocTextKey DESELECT_TOOLTIP = new LocTextKey( "sebserver.form.multiselect.deselect.tooltip" );
private static final Logger log = LoggerFactory.getLogger(MultiSelectionCombo.class); private static final Logger log = LoggerFactory.getLogger(MultiSelectionCombo.class);
private static final long serialVersionUID = -7787134114963647332L; private static final long serialVersionUID = -7787134114963647332L;
private final WidgetFactory widgetFactory;
private final List<Control> selectionControls = new ArrayList<>();
private final List<Tuple<String>> valueMapping = new ArrayList<>(); private final List<Tuple<String>> valueMapping = new ArrayList<>();
private final List<Tuple<String>> availableValues = new ArrayList<>(); private final List<Tuple<String>> availableValues = new ArrayList<>();
private final List<Tuple<String>> selectedValues = new ArrayList<>(); private final List<Tuple<String>> selectedValues = new ArrayList<>();
@ -50,7 +43,10 @@ public final class MultiSelectionCombo extends Composite implements Selection {
private final Composite updateAnchor; private final Composite updateAnchor;
private final String testKey; private final String testKey;
private final Table selectionTable;
private Listener listener = null; private Listener listener = null;
private WidgetFactory widgetFactory;
MultiSelectionCombo( MultiSelectionCombo(
final Composite parent, final Composite parent,
@ -78,7 +74,7 @@ public final class MultiSelectionCombo extends Composite implements Selection {
this.textInput.addListener(SWT.FocusIn, event -> openDropDown()); this.textInput.addListener(SWT.FocusIn, event -> openDropDown());
this.textInput.addListener(SWT.Modify, event -> openDropDown()); this.textInput.addListener(SWT.Modify, event -> openDropDown());
this.textInput.addListener(SWT.MouseUp, event -> openDropDown()); this.textInput.addListener(SWT.MouseUp, event -> openDropDown());
this.dropDown.addListener(SWT.Selection, event -> { this.dropDown.addListener(SWT.DefaultSelection, event -> {
final int selectionIndex = this.dropDown.getSelectionIndex(); final int selectionIndex = this.dropDown.getSelectionIndex();
if (selectionIndex >= 0) { if (selectionIndex >= 0) {
final String selectedItem = this.dropDown.getItems()[selectionIndex]; final String selectedItem = this.dropDown.getItems()[selectionIndex];
@ -86,21 +82,29 @@ public final class MultiSelectionCombo extends Composite implements Selection {
} }
}); });
selectionTable = widgetFactory.tableLocalized(this, SWT.NONE);
final GridLayout tableLayout = new GridLayout(1, true);
selectionTable.setLayout(tableLayout);
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, false);
selectionTable.setLayoutData(gridData);
//selectionTable.setToolTipText();
selectionTable.addListener(SWT.MouseDoubleClick, this::removeComboSelection);
selectionTable.addListener(SWT.Selection, event -> {
selectionTable.setSelection(-1);
});
selectionTable.setHeaderVisible(false);
selectionTable.setLinesVisible(true);
this.updateAnchor = updateAnchor; this.updateAnchor = updateAnchor;
} }
private void openDropDown() { @Override
final String text = this.textInput.getText(); public void setToolTipText(final String tooltipText) {
if (text == null) { if (tooltipText == null) {
this.dropDown.setVisible(false); super.setToolTipText(widgetFactory.getI18nSupport().getText(DESELECT_TOOLTIP));
return; return;
} }
this.dropDown.setItems(this.availableValues super.setToolTipText(tooltipText + "\n\n" + widgetFactory.getI18nSupport().getText(DESELECT_TOOLTIP));
.stream()
.filter(it -> it._2 != null && it._2.startsWith(text))
.map(t -> t._2).toArray(String[]::new));
this.dropDown.setSelectionIndex(0);
this.dropDown.setVisible(true);
} }
@Override @Override
@ -157,9 +161,6 @@ public final class MultiSelectionCombo extends Composite implements Selection {
@Override @Override
public void clear() { public void clear() {
this.selectedValues.clear(); this.selectedValues.clear();
this.selectionControls
.forEach(Control::dispose);
this.selectionControls.clear();
this.availableValues.clear(); this.availableValues.clear();
this.availableValues.addAll(this.valueMapping); this.availableValues.addAll(this.valueMapping);
} }
@ -170,42 +171,39 @@ public final class MultiSelectionCombo extends Composite implements Selection {
} }
this.selectedValues.add(item); this.selectedValues.add(item);
final Label label = this.widgetFactory.label(this, item._2);
label.setData(OPTION_VALUE, item._2);
final GridData textCell = new GridData(SWT.LEFT, SWT.CENTER, true, true);
label.setLayoutData(textCell);
label.addListener(SWT.MouseDoubleClick, this::removeComboSelection);
this.selectionControls.add(label);
WidgetFactory.setARIALabel(label, item._2); sortSelectedTable();
WidgetFactory.setTestId(label, (this.testKey != null) ? this.testKey + "_" + item._1 : item._1);
this.availableValues.remove(item); this.availableValues.remove(item);
PageService.updateScrolledComposite(this); PageService.updateScrolledComposite(this);
this.updateAnchor.layout(true, true); this.updateAnchor.layout(true, true);
} }
private void sortSelectedTable() {
selectionTable.removeAll();
selectedValues.sort((t1, t2) -> String.CASE_INSENSITIVE_ORDER.compare(t1._2, t2._2));
selectedValues.stream().forEach(t -> {
final TableItem tItem = new TableItem(selectionTable, SWT.NONE);
tItem.setText(0, t._2);
tItem.setData("tuple", t);
WidgetFactory.setARIALabel(tItem, t._2);
WidgetFactory.setTestId(tItem, (this.testKey != null) ? this.testKey + "_" + t._1 : t._1);
});
}
private void removeComboSelection(final Event event) { private void removeComboSelection(final Event event) {
if (event.widget == null) { if (event.widget == null) {
return; return;
} }
final String selectionKey = (String) event.widget.getData(OPTION_VALUE); final TableItem item = selectionTable.getItem(new Point(event.x, event.y));
final Optional<Control> findFirst = this.selectionControls.stream() @SuppressWarnings("unchecked")
.filter(t -> selectionKey.equals(t.getData(OPTION_VALUE))) final Tuple<String> value = (Tuple<String>) item.getData("tuple");
.findFirst(); this.selectedValues.remove(value);
if (!findFirst.isPresent()) {
return;
}
final Control control = findFirst.get();
final int indexOf = this.selectionControls.indexOf(control);
this.selectionControls.remove(control);
control.dispose();
final Tuple<String> value = this.selectedValues.remove(indexOf);
this.availableValues.add(value); this.availableValues.add(value);
sortSelectedTable();
PageService.updateScrolledComposite(this); PageService.updateScrolledComposite(this);
this.updateAnchor.layout(true, true); this.updateAnchor.layout(true, true);
if (this.listener != null) { if (this.listener != null) {
@ -238,4 +236,18 @@ public final class MultiSelectionCombo extends Composite implements Selection {
return findFirst.orElse(null); return findFirst.orElse(null);
} }
private void openDropDown() {
final String text = this.textInput.getText();
if (text == null) {
this.dropDown.setVisible(false);
return;
}
this.dropDown.setItems(this.availableValues
.stream()
.filter(it -> it._2 != null && it._2.startsWith(text))
.map(t -> t._2).toArray(String[]::new));
this.dropDown.setSelectionIndex(0);
this.dropDown.setVisible(true);
}
} }

View file

@ -12,10 +12,12 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -44,15 +46,18 @@ class AdminUserInitializer {
private final boolean initializeAdmin; private final boolean initializeAdmin;
private final String adminName; private final String adminName;
private final String orgName; private final String orgName;
private final Environment environment;
public AdminUserInitializer( public AdminUserInitializer(
final UserDAO userDAO, final UserDAO userDAO,
final InstitutionDAO institutionDAO, final InstitutionDAO institutionDAO,
final Environment environment,
@Qualifier(WebSecurityConfig.USER_PASSWORD_ENCODER_BEAN_NAME) final PasswordEncoder passwordEncoder, @Qualifier(WebSecurityConfig.USER_PASSWORD_ENCODER_BEAN_NAME) final PasswordEncoder passwordEncoder,
@Value("${sebserver.init.adminaccount.gen-on-init:false}") final boolean initializeAdmin, @Value("${sebserver.init.adminaccount.gen-on-init:false}") final boolean initializeAdmin,
@Value("${sebserver.init.adminaccount.username:seb-server-admin}") final String adminName, @Value("${sebserver.init.adminaccount.username:seb-server-admin}") final String adminName,
@Value("${sebserver.init.organisation.name:[SET_ORGANIZATION_NAME]}") final String orgName) { @Value("${sebserver.init.organisation.name:[SET_ORGANIZATION_NAME]}") final String orgName) {
this.environment = environment;
this.userDAO = userDAO; this.userDAO = userDAO;
this.institutionDAO = institutionDAO; this.institutionDAO = institutionDAO;
this.passwordEncoder = passwordEncoder; this.passwordEncoder = passwordEncoder;
@ -89,7 +94,11 @@ class AdminUserInitializer {
} }
} }
} else { } else {
final CharSequence generateAdminPassword = this.generateAdminPassword(); final String initPWD = environment.getProperty("sebserver.init.adminaccount.init.pwd", "");
final CharSequence generateAdminPassword = StringUtils.isNotBlank(initPWD)
? initPWD :
this.generateAdminPassword();
Long institutionId = this.institutionDAO.allMatching(new FilterMap()) Long institutionId = this.institutionDAO.allMatching(new FilterMap())
.getOrElse(Collections::emptyList) .getOrElse(Collections::emptyList)
.stream() .stream()

View file

@ -156,21 +156,21 @@ public class WebserviceInfo {
} }
final boolean spsEnabled = BooleanUtils.toBoolean(environment.getProperty( final boolean spsEnabled = BooleanUtils.toBoolean(environment.getProperty(
"sebserver.feature.seb.screenProctoring.enabled", "sebserver.feature.exam.seb.screenProctoring.enabled",
Constants.FALSE_STRING)); Constants.FALSE_STRING));
final boolean spsBundled = BooleanUtils.toBoolean(environment.getProperty( final boolean spsBundled = BooleanUtils.toBoolean(environment.getProperty(
"sebserver.feature.seb.screenProctoring.bundled", "sebserver.feature.exam.seb.screenProctoring.bundled",
Constants.FALSE_STRING)); Constants.FALSE_STRING));
if (spsEnabled && spsBundled) { if (spsEnabled && spsBundled) {
this.screenProctoringServiceBundle = new ScreenProctoringServiceBundle( this.screenProctoringServiceBundle = new ScreenProctoringServiceBundle(
environment.getProperty("sebserver.feature.seb.screenProctoring.bundled.url"), environment.getProperty("sebserver.feature.exam.seb.screenProctoring.bundled.url"),
environment.getProperty("sebserver.feature.seb.screenProctoring.bundled.clientId"), environment.getProperty("sebserver.feature.exam.seb.screenProctoring.bundled.clientId"),
cryptor.encrypt( cryptor.encrypt(
environment.getProperty("sebserver.feature.seb.screenProctoring.bundled.clientPassword")) environment.getProperty("sebserver.feature.exam.seb.screenProctoring.bundled.clientPassword"))
.getOrThrow(), .getOrThrow(),
environment.getProperty("sebserver.feature.seb.screenProctoring.bundled.sebserveraccount.username"), environment.getProperty("sebserver.feature.exam.seb.screenProctoring.bundled.sebserveraccount.username"),
cryptor.encrypt(environment cryptor.encrypt(environment
.getProperty("sebserver.feature.seb.screenProctoring.bundled.sebserveraccount.password")) .getProperty("sebserver.feature.exam.seb.screenProctoring.bundled.sebserveraccount.password"))
.getOrThrow()); .getOrThrow());
} else { } else {
this.screenProctoringServiceBundle = new ScreenProctoringServiceBundle(); this.screenProctoringServiceBundle = new ScreenProctoringServiceBundle();
@ -178,10 +178,10 @@ public class WebserviceInfo {
} }
public Map<String, Boolean> configuredFeatures() { public Map<String, Boolean> configuredFeatures() {
return Arrays.stream(UserFeatures.Feature.values()).collect(Collectors.toMap( return new TreeMap<>( Arrays.stream(UserFeatures.Feature.values()).collect(Collectors.toMap(
f -> f.featureName, f -> f.featureName,
featureService::isEnabledByConfig featureService::isEnabledByConfig
)); )));
} }
public boolean isMaster() { public boolean isMaster() {

View file

@ -76,22 +76,26 @@ public class WebserviceInit implements ApplicationListener<ApplicationReadyEvent
SEBServerInit.INIT_LOGGER.info("----> "); SEBServerInit.INIT_LOGGER.info("----> ");
SEBServerInit.INIT_LOGGER.info("----> Register Webservice: {}", this.webserviceInfo.getWebserviceUUID()); SEBServerInit.INIT_LOGGER.info("----> Register Webservice: {}", this.webserviceInfo.getWebserviceUUID());
if (this.webserviceInfoDAO.isInitialized()) { try {
this.registerWebservice(); if (this.webserviceInfoDAO.isInitialized()) {
this.registerWebservice();
// Apply migration if needed and possible // Apply migration if needed and possible
SEBServerInit.INIT_LOGGER.info("----> "); SEBServerInit.INIT_LOGGER.info("----> ");
this.sebServerMigrationStrategy.applyMigration(); this.sebServerMigrationStrategy.applyMigration();
SEBServerInit.INIT_LOGGER.info("----> "); SEBServerInit.INIT_LOGGER.info("----> ");
} else { } else {
// Apply migration if needed and possible // Apply migration if needed and possible
SEBServerInit.INIT_LOGGER.info("----> "); SEBServerInit.INIT_LOGGER.info("----> ");
this.sebServerMigrationStrategy.applyMigration(); this.sebServerMigrationStrategy.applyMigration();
SEBServerInit.INIT_LOGGER.info("----> "); SEBServerInit.INIT_LOGGER.info("----> ");
this.registerWebservice(); this.registerWebservice();
}
} catch (final Exception e) {
SEBServerInit.INIT_LOGGER.error("Failed to apply data import and migration --> ", e);
} }
SEBServerInit.INIT_LOGGER.info("----> "); SEBServerInit.INIT_LOGGER.info("----> ");

View file

@ -216,7 +216,9 @@ public interface ExamDAO extends ActivatableEntityDAO<Exam, Exam>, BulkActionSup
* *
* @param examId the Exam identifier */ * @param examId the Exam identifier */
@CacheEvict( @CacheEvict(
cacheNames = ExamSessionCacheService.CACHE_NAME_RUNNING_EXAM, cacheNames = {
ExamSessionCacheService.CACHE_NAME_RUNNING_EXAM,
ExamSessionCacheService.CACHE_NAME_SEB_CONFIG_EXAM },
key = "#examId") key = "#examId")
void markUpdate(Long examId); void markUpdate(Long examId);

View file

@ -8,6 +8,7 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl; package ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl;
import static ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamConfigurationMapRecordDynamicSqlSupport.*;
import static org.mybatis.dynamic.sql.SqlBuilder.*; import static org.mybatis.dynamic.sql.SqlBuilder.*;
import java.util.ArrayList; import java.util.ArrayList;
@ -23,6 +24,7 @@ import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.mybatis.dynamic.sql.SqlBuilder; import org.mybatis.dynamic.sql.SqlBuilder;
import org.mybatis.dynamic.sql.update.UpdateDSL;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -260,15 +262,17 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
public Result<ExamConfigurationMap> save(final ExamConfigurationMap data) { public Result<ExamConfigurationMap> save(final ExamConfigurationMap data) {
return Result.tryCatch(() -> { return Result.tryCatch(() -> {
final ExamConfigurationMapRecord newRecord = new ExamConfigurationMapRecord( final String p = (StringUtils.isNotBlank(data.encryptSecret))
data.id, ? getEncryptionPassword(data)
null, : null;
null,
null, UpdateDSL.updateWithMapper(examConfigurationMapRecordMapper::update, examConfigurationMapRecord)
getEncryptionPassword(data), .set(encryptSecret).equalTo(p )
data.clientGroupId); .set(clientGroupId).equalToWhenPresent(data.clientGroupId)
.where(id, isEqualTo(data.id))
.build()
.execute();
this.examConfigurationMapRecordMapper.updateByPrimaryKeySelective(newRecord);
return this.examConfigurationMapRecordMapper.selectByPrimaryKey(data.id); return this.examConfigurationMapRecordMapper.selectByPrimaryKey(data.id);
}) })
.flatMap(this::toDomainModel) .flatMap(this::toDomainModel)

View file

@ -489,7 +489,11 @@ public class SEBClientConfigDAOImpl implements SEBClientConfigDAO {
additionalAttributes.containsKey(SEBClientConfig.ATTR_ENCRYPT_CERTIFICATE_ASYM), additionalAttributes.containsKey(SEBClientConfig.ATTR_ENCRYPT_CERTIFICATE_ASYM),
BooleanUtils.toBooleanObject(record.getActive()), BooleanUtils.toBooleanObject(record.getActive()),
Utils.toDateTimeUTC(record.getLastUpdateTime()), Utils.toDateTimeUTC(record.getLastUpdateTime()),
record.getLastUpdateUser())); record.getLastUpdateUser(),
Utils.getIdsFromString(
additionalAttributes.containsKey(SEBClientConfig.ATTR_EXAM_SELECTION)
? additionalAttributes.get(SEBClientConfig.ATTR_EXAM_SELECTION).getValue()
: null)));
} }
private String getEncryptionPassword(final SEBClientConfig sebClientConfig) { private String getEncryptionPassword(final SEBClientConfig sebClientConfig) {
@ -685,6 +689,21 @@ public class SEBClientConfigDAOImpl implements SEBClientConfigDAO {
configId, configId,
SEBClientConfig.ATTR_ENCRYPT_CERTIFICATE_ASYM); SEBClientConfig.ATTR_ENCRYPT_CERTIFICATE_ASYM);
} }
final Set<Long> selectedExams = sebClientConfig.getSelectedExams();
if (selectedExams != null && !selectedExams.isEmpty()) {
final String ids = StringUtils.join(selectedExams, Constants.LIST_SEPARATOR);
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.SEB_CLIENT_CONFIGURATION,
configId,
SEBClientConfig.ATTR_EXAM_SELECTION,
ids);
} else {
this.additionalAttributesDAO.delete(
EntityType.SEB_CLIENT_CONFIGURATION,
configId,
SEBClientConfig.ATTR_EXAM_SELECTION);
}
} }
private Long disposeSEBClientConfig(final Long pk) { private Long disposeSEBClientConfig(final Long pk) {

View file

@ -57,6 +57,7 @@ public class WebserviceInfoDAOImpl implements WebserviceInfoDAO {
.execute(); .execute();
return true; return true;
} catch (final Exception e) { } catch (final Exception e) {
log.warn("DB Context not initialized yet: {}", e.getMessage());
return false; return false;
} }
} }

View file

@ -63,5 +63,5 @@ public interface ClientConfigService {
boolean checkAccess(SEBClientConfig config); boolean checkAccess(SEBClientConfig config);
@Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME) @Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME)
void initalCheckAccess(SEBClientConfig config); void initialCheckAccess(SEBClientConfig config);
} }

View file

@ -504,7 +504,7 @@ public class ClientConfigServiceImpl implements ClientConfigService {
} }
@Override @Override
public void initalCheckAccess(final SEBClientConfig config) { public void initialCheckAccess(final SEBClientConfig config) {
checkAccess(config); checkAccess(config);
} }

View file

@ -9,6 +9,7 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.session; package ch.ethz.seb.sebserver.webservice.servicelayer.session;
import java.io.OutputStream; import java.io.OutputStream;
import java.security.Principal;
import java.util.Collection; import java.util.Collection;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate; import java.util.function.Predicate;
@ -130,9 +131,10 @@ public interface ExamSessionService {
/** Gets all currently running Exams for a particular Institution. /** Gets all currently running Exams for a particular Institution.
* *
* @param institutionId the Institution identifier * @param institutionId the Institution identifier
* @param examSelectionFilter Exam selection filter from SEB connection configuration
* @return Result referencing the list of all currently running Exams of the institution or to an error if * @return Result referencing the list of all currently running Exams of the institution or to an error if
* happened. */ * happened. */
Result<Collection<Exam>> getRunningExamsForInstitution(Long institutionId); Result<Collection<Exam>> getRunningExams(Long institutionId, Predicate<Long> examSelectionFilter);
/** Gets all currently running Exams for a particular FilterMap. /** Gets all currently running Exams for a particular FilterMap.
* *

View file

@ -13,7 +13,6 @@ import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -251,9 +250,10 @@ public class ExamSessionServiceImpl implements ExamSessionService {
} }
@Override @Override
public Result<Collection<Exam>> getRunningExamsForInstitution(final Long institutionId) { public Result<Collection<Exam>> getRunningExams(final Long institutionId, final Predicate<Long> examSelectionFilter) {
return this.examDAO.allIdsOfRunning(institutionId) return this.examDAO.allIdsOfRunning(institutionId)
.map(col -> col.stream() .map(col -> col.stream()
.filter(examSelectionFilter)
.map(this::getRunningExam) .map(this::getRunningExam)
.filter(Result::hasValue) .filter(Result::hasValue)
.map(Result::get) .map(Result::get)

View file

@ -11,17 +11,17 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.Principal; import java.security.Principal;
import java.util.Arrays; import java.util.*;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -132,7 +132,10 @@ public class ExamAPI_V1_Controller {
// Crate list of running exams // Crate list of running exams
final List<RunningExamInfo> result; final List<RunningExamInfo> result;
if (examId == null) { if (examId == null) {
result = this.examSessionService.getRunningExamsForInstitution(institutionId)
result = this.examSessionService.getRunningExams(
institutionId,
getExamSelectionPredicate(principal.getName()))
.getOrThrow() .getOrThrow()
.stream() .stream()
.map(this::createRunningExamInfo) .map(this::createRunningExamInfo)
@ -161,17 +164,7 @@ public class ExamAPI_V1_Controller {
this.executor); this.executor);
} }
private boolean checkConsistency(final RunningExamInfo info) {
if (StringUtils.isNotBlank(info.name) &&
StringUtils.isNotBlank(info.url) &&
StringUtils.isNotBlank(info.examId)) {
return true;
}
log.warn("Invalid running exam detected. Filter out exam : {}", info);
return false;
}
@RequestMapping( @RequestMapping(
path = API.EXAM_API_HANDSHAKE_ENDPOINT, path = API.EXAM_API_HANDSHAKE_ENDPOINT,
@ -420,4 +413,31 @@ public class ExamAPI_V1_Controller {
.onSuccess(bek -> response.setHeader(API.EXAM_API_EXAM_ALT_BEK, bek)); .onSuccess(bek -> response.setHeader(API.EXAM_API_EXAM_ALT_BEK, bek));
} }
private Predicate<Long> getExamSelectionPredicate(final String clientName) {
return this.sebClientConfigDAO
.byClientName(clientName)
.map(this::getExamSelectionPredicate)
.onError(error -> log.warn("Failed to get SEB connection configuration by name: {}", clientName))
.getOr(Utils.truePredicate());
}
private Predicate<Long> getExamSelectionPredicate(final SEBClientConfig config) {
if (config == null || config.selectedExams.isEmpty()) {
return Utils.truePredicate();
}
return config.getSelectedExams()::contains;
}
private boolean checkConsistency(final RunningExamInfo info) {
if (StringUtils.isNotBlank(info.name) &&
StringUtils.isNotBlank(info.url) &&
StringUtils.isNotBlank(info.examId)) {
return true;
}
log.warn("Invalid running exam detected. Filter out exam : {}", info);
return false;
}
} }

View file

@ -707,6 +707,9 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
examSessionService.hasActiveSEBClientConnections(exam.id)) { examSessionService.hasActiveSEBClientConnections(exam.id)) {
final Exam oldExam = this.examDAO.byPK(exam.id).getOrThrow(); final Exam oldExam = this.examDAO.byPK(exam.id).getOrThrow();
final CharSequence pwd = cryptor.decrypt(oldExam.quitPassword).getOr(oldExam.quitPassword); final CharSequence pwd = cryptor.decrypt(oldExam.quitPassword).getOr(oldExam.quitPassword);
if (StringUtils.isBlank(pwd) && StringUtils.isBlank(exam.quitPassword)) {
return exam;
}
if (!Objects.equals(pwd, exam.quitPassword)) { if (!Objects.equals(pwd, exam.quitPassword)) {
throw new APIMessageException(APIMessage.fieldValidationError( throw new APIMessageException(APIMessage.fieldValidationError(
new FieldError( new FieldError(

View file

@ -13,6 +13,8 @@ import java.util.List;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.SqlTable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import org.springframework.validation.FieldError; import org.springframework.validation.FieldError;
@ -57,6 +59,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe
@RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.EXAM_CONFIGURATION_MAP_ENDPOINT) @RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.EXAM_CONFIGURATION_MAP_ENDPOINT)
public class ExamConfigurationMappingController extends EntityController<ExamConfigurationMap, ExamConfigurationMap> { public class ExamConfigurationMappingController extends EntityController<ExamConfigurationMap, ExamConfigurationMap> {
private static final Logger log = LoggerFactory.getLogger(ExamConfigurationMappingController.class);
private final ExamDAO examDao; private final ExamDAO examDao;
private final ConfigurationNodeDAO configurationNodeDAO; private final ConfigurationNodeDAO configurationNodeDAO;
private final ExamConfigUpdateService examConfigUpdateService; private final ExamConfigUpdateService examConfigUpdateService;
@ -133,6 +137,18 @@ public class ExamConfigurationMappingController extends EntityController<ExamCon
.map(this::checkNoActiveClientConnections); .map(this::checkNoActiveClientConnections);
} }
@Override
protected Result<ExamConfigurationMap> validForSave(final ExamConfigurationMap entity) {
return super.validForSave(entity)
.map(this::checkPasswordMatch);
}
@Override
protected Result<ExamConfigurationMap> notifySaved(final ExamConfigurationMap entity) {
examDao.markUpdate(entity.examId);
return super.notifySaved(entity);
}
@Override @Override
@RequestMapping( @RequestMapping(
method = RequestMethod.POST, method = RequestMethod.POST,
@ -154,7 +170,6 @@ public class ExamConfigurationMappingController extends EntityController<ExamCon
final ExamConfigurationMap requestModel = this.createNew(postMap); final ExamConfigurationMap requestModel = this.createNew(postMap);
return this.checkCreateAccess(requestModel) return this.checkCreateAccess(requestModel)
.flatMap(this::validForCreate) .flatMap(this::validForCreate)
.map(this::checkPasswordMatch)
.flatMap(entity -> this.examConfigUpdateService.processExamConfigurationMappingChange( .flatMap(entity -> this.examConfigUpdateService.processExamConfigurationMappingChange(
entity, entity,
this.entityDAO::createNew)) this.entityDAO::createNew))

View file

@ -10,10 +10,15 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Map;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import ch.ethz.seb.sebserver.gbl.model.user.*;
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
@ -30,10 +35,6 @@ import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper; import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.api.TooManyRequests; import ch.ethz.seb.sebserver.gbl.api.TooManyRequests;
import ch.ethz.seb.sebserver.gbl.model.Domain.USER_ROLE; 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.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.InstitutionDAO; 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.UserActivityLogDAO;
@ -51,6 +52,8 @@ public class RegisterUserController {
private final BeanValidationService beanValidationService; private final BeanValidationService beanValidationService;
private final LocalBucket requestRateLimitBucket; private final LocalBucket requestRateLimitBucket;
private final LocalBucket createRateLimitBucket; private final LocalBucket createRateLimitBucket;
private final boolean registeringEnabled;
private final boolean autoActivation;
protected RegisterUserController( protected RegisterUserController(
final InstitutionDAO institutionDAO, final InstitutionDAO institutionDAO,
@ -58,12 +61,15 @@ public class RegisterUserController {
final UserDAO userDAO, final UserDAO userDAO,
final BeanValidationService beanValidationService, final BeanValidationService beanValidationService,
final RateLimitService rateLimitService, final RateLimitService rateLimitService,
final WebserviceInfo webserviceInfo,
@Qualifier(WebSecurityConfig.USER_PASSWORD_ENCODER_BEAN_NAME) final PasswordEncoder userPasswordEncoder) { @Qualifier(WebSecurityConfig.USER_PASSWORD_ENCODER_BEAN_NAME) final PasswordEncoder userPasswordEncoder) {
final Map<String, Boolean> features = webserviceInfo.configuredFeatures();
this.userActivityLogDAO = userActivityLogDAO; this.userActivityLogDAO = userActivityLogDAO;
this.userDAO = userDAO; this.userDAO = userDAO;
this.beanValidationService = beanValidationService; this.beanValidationService = beanValidationService;
this. registeringEnabled = BooleanUtils.isTrue(features.get(UserFeatures.Feature.ADMIN_USER_ACCOUNT_SELF_REGISTERING.featureName));
this.autoActivation = BooleanUtils.isTrue(features.get(UserFeatures.Feature.ADMIN_USER_ACCOUNT_SELF_REGISTERING_AUTO_ACTIVATION.featureName));
this.requestRateLimitBucket = rateLimitService.createRequestLimitBucker(); this.requestRateLimitBucket = rateLimitService.createRequestLimitBucker();
this.createRateLimitBucket = rateLimitService.createCreationLimitBucker(); this.createRateLimitBucket = rateLimitService.createCreationLimitBucker();
} }
@ -76,6 +82,10 @@ public class RegisterUserController {
@RequestParam final MultiValueMap<String, String> allRequestParams, @RequestParam final MultiValueMap<String, String> allRequestParams,
final HttpServletRequest request) { final HttpServletRequest request) {
if (!registeringEnabled) {
throw new RuntimeException("Registering is not enabled from backend!");
}
if (!this.requestRateLimitBucket.tryConsume(1)) { if (!this.requestRateLimitBucket.tryConsume(1)) {
throw new TooManyRequests(); throw new TooManyRequests();
} }
@ -107,7 +117,7 @@ public class RegisterUserController {
return userAccount; return userAccount;
}) })
.flatMap(this.userDAO::createNew) .flatMap(this.userDAO::createNew)
.flatMap(account -> this.userDAO.setActive(account, true)) .flatMap(account -> this.userDAO.setActive(account, autoActivation))
.flatMap(this.userActivityLogDAO::logRegisterAccount) .flatMap(this.userActivityLogDAO::logRegisterAccount)
.flatMap(account -> this.userDAO.byModelId(account.getModelId())) .flatMap(account -> this.userDAO.byModelId(account.getModelId()))
.getOrThrow(); .getOrThrow();

View file

@ -182,7 +182,7 @@ public class SEBClientConfigController extends ActivatableEntityController<SEBCl
protected Result<SEBClientConfig> notifySaved(final SEBClientConfig entity) { protected Result<SEBClientConfig> notifySaved(final SEBClientConfig entity) {
if (entity.isActive()) { if (entity.isActive()) {
// try to get access token for SEB client // try to get access token for SEB client
this.sebClientConfigService.initalCheckAccess(entity); this.sebClientConfigService.initialCheckAccess(entity);
} }
return super.notifySaved(entity); return super.notifySaved(entity);
} }

View file

@ -10,12 +10,16 @@ package ch.ethz.seb.sebserver.webservice.weblayer.oauth;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.webservice.weblayer.WebServiceUserDetails; import ch.ethz.seb.sebserver.webservice.weblayer.WebServiceUserDetails;
@Lazy
@Component @Component
@WebServiceProfile
public class PreAuthProvider extends PreAuthenticatedAuthenticationProvider { public class PreAuthProvider extends PreAuthenticatedAuthenticationProvider {
private final WebServiceUserDetails webServiceUserDetails; private final WebServiceUserDetails webServiceUserDetails;

View file

@ -72,11 +72,11 @@ springdoc.swagger-ui.oauth.clientSecret=${sebserver.password}
springdoc.paths-to-exclude=/exam-api,/exam-api/discovery,/sebserver/error,/sebserver/check,/oauth,/exam-api/v1/* springdoc.paths-to-exclude=/exam-api,/exam-api/discovery,/sebserver/error,/sebserver/check,/oauth,/exam-api/v1/*
# features # features
sebserver.feature.seb.screenProctoring.enabled=true sebserver.feature.exam.seb.screenProctoring.enabled=true
sebserver.feature.seb.screenProctoring.bundled=true sebserver.feature.exam.seb.screenProctoring.bundled=true
sebserver.feature.seb.screenProctoring.bundled.url=http://localhost:8090 sebserver.feature.exam.seb.screenProctoring.bundled.url=http://localhost:8090
sebserver.feature.seb.screenProctoring.bundled.clientId=sebserverClient sebserver.feature.exam.seb.screenProctoring.bundled.clientId=sebserverClient
sebserver.feature.seb.screenProctoring.bundled.sebserveraccount.username=SEBServerAPIAccount sebserver.feature.exam.seb.screenProctoring.bundled.sebserveraccount.username=SEBServerAPIAccount
#sebserver.feature.admin.user.administration.enabled=false #sebserver.feature.admin.user.administration.enabled=false
#sebserver.feature.admin.user.account.enabled=false #sebserver.feature.admin.user.account.enabled=false

View file

@ -95,15 +95,18 @@ sebserver.webservice.configtemplate.examconfig.default.description=This has auto
# features # features
sebserver.feature.admin.institution.enabled=true sebserver.feature.admin.institution.enabled=true
sebserver.feature.admin.user.account.self.registering.enabled=true
sebserver.feature.admin.user.account.self.registering.autoactivation.enabled=true
sebserver.feature.seb.liveProctoring.enabled=true sebserver.feature.seb.liveProctoring.enabled=true
sebserver.feature.lms.type.MOCKUP.enabled=true sebserver.feature.lms.type.MOCKUP.enabled=true
sebserver.feature.exam.noLMS.enabled=true sebserver.feature.exam.noLMS.enabled=true
sebserver.feature.seb.screenProctoring.enabled=false sebserver.feature.exam.seb.screenProctoring.enabled=false
sebserver.feature.seb.screenProctoring.bundled=true sebserver.feature.exam.seb.screenProctoring.bundled=true
sebserver.feature.seb.screenProctoring.bundled.url=sps-service:8090 sebserver.feature.exam.seb.screenProctoring.bundled.url=sps-service:8090
sebserver.feature.seb.screenProctoring.bundled.clientId=sebserverClient sebserver.feature.exam.seb.screenProctoring.bundled.clientId=sebserverClient
sebserver.feature.seb.screenProctoring.bundled.clientPassword=${sps.sebserver.client.secret} sebserver.feature.exam.seb.screenProctoring.bundled.clientPassword=${sps.sebserver.client.secret}
sebserver.feature.seb.screenProctoring.bundled.sebserveraccount.username=SEBServerAPIAccount sebserver.feature.exam.seb.screenProctoring.bundled.sebserveraccount.username=SEBServerAPIAccount
sebserver.feature.seb.screenProctoring.bundled.sebserveraccount.password=${sps.sebserver.password} sebserver.feature.exam.seb.screenProctoring.bundled.sebserveraccount.password=${sps.sebserver.password}

View file

@ -129,6 +129,7 @@ sebserver.form.mandatory.label={0} mandatory
sebserver.form.confirm.label=confirm {0} sebserver.form.confirm.label=confirm {0}
sebserver.form.tablefilter.label={0} table-column filter sebserver.form.tablefilter.label={0} table-column filter
sebserver.table.column.sort.default.tooltip=Click on the column header to sort the table within this column sebserver.table.column.sort.default.tooltip=Click on the column header to sort the table within this column
sebserver.form.multiselect.deselect.tooltip=Please use double-click to deselect selected items.
sebserver.dialog.confirm.deactivation=Note that there are {0} other entities that belong to this entity.<br/>Those will also be deactivated by deactivating this entity.<br/><br/>Are you sure to deactivate this entity? sebserver.dialog.confirm.deactivation=Note that there are {0} other entities that belong to this entity.<br/>Those will also be deactivated by deactivating this entity.<br/><br/>Are you sure to deactivate this entity?
sebserver.dialog.confirm.deactivation.noDependencies=Are you sure you want to deactivate? sebserver.dialog.confirm.deactivation.noDependencies=Are you sure you want to deactivate?
@ -162,6 +163,7 @@ sebserver.login.register=Register
sebserver.login.register.form.title=Create an Account sebserver.login.register.form.title=Create an Account
sebserver.login.register.do=Create Account sebserver.login.register.do=Create Account
sebserver.login.register.success=New account successfully created.<br/> Please log in with your username and password. sebserver.login.register.success=New account successfully created.<br/> Please log in with your username and password.
sebserver.login.register.success.activate=New account successfully created.<br/> Please contact your system administrator for account activation.
################################ ################################
@ -677,7 +679,7 @@ sebserver.exam.configuration.list.pleaseSelect=At first please select an exam co
sebserver.exam.configuration.action.noconfig.message=There is currently no exam configuration to select.<br/>Please create one in Exam Configurations sebserver.exam.configuration.action.noconfig.message=There is currently no exam configuration to select.<br/>Please create one in Exam Configurations
sebserver.exam.configuration.action.list.new=Add Exam Configuration sebserver.exam.configuration.action.list.new=Add Exam Configuration
sebserver.exam.configuration.action.list.modify=Edit Exam Configuration sebserver.exam.configuration.action.list.modify=Edit Encryption Password
sebserver.exam.configuration.action.list.view=View Exam Configuration sebserver.exam.configuration.action.list.view=View Exam Configuration
sebserver.exam.configuration.action.list.delete=Remove Exam Configuration sebserver.exam.configuration.action.list.delete=Remove Exam Configuration
sebserver.exam.configuration.action.save=Save Exam Configuration sebserver.exam.configuration.action.save=Save Exam Configuration
@ -828,6 +830,8 @@ sebserver.exam.proctoring.form.collect.strategy=Collecting Room Strategy
sebserver.exam.proctoring.form.collect.strategy.tooltip=This specifies the strategy how connecting SEB clients are collected into proctoring rooms sebserver.exam.proctoring.form.collect.strategy.tooltip=This specifies the strategy how connecting SEB clients are collected into proctoring rooms
sebserver.exam.proctoring.form.collectingRoomSize=Collecting Room Size sebserver.exam.proctoring.form.collectingRoomSize=Collecting Room Size
sebserver.exam.proctoring.form.collectingRoomSize.tooltip=The size of proctor rooms to collect connecting SEB clients into. sebserver.exam.proctoring.form.collectingRoomSize.tooltip=The size of proctor rooms to collect connecting SEB clients into.
sebserver.exam.proctoring.deprecation.title=Deprecation Note:
sebserver.exam.proctoring.deprecation.message=The SEB Server live proctoring integration with Zoom and Jitsi Meet has been deprecated since this version of SEB Server.<br/>This means live proctoring is still available for dedicated SEB versions, but not actively maintained and supported any more.
sebserver.exam.proctoring.form.appkey.jitsi=App Key sebserver.exam.proctoring.form.appkey.jitsi=App Key
sebserver.exam.proctoring.form.appkey.jitsi.tooltip=The application key of the proctoring service server sebserver.exam.proctoring.form.appkey.jitsi.tooltip=The application key of the proctoring service server
@ -1033,6 +1037,8 @@ sebserver.clientconfig.form.certificate=Encrypt with Certificate
sebserver.clientconfig.form.certificate.tooltip=Choose identity certificate to be used for encrypting the connection configuration sebserver.clientconfig.form.certificate.tooltip=Choose identity certificate to be used for encrypting the connection configuration
sebserver.clientconfig.form.type.async=Use asymmetric-only encryption sebserver.clientconfig.form.type.async=Use asymmetric-only encryption
sebserver.clientconfig.form.type.async.tooltip=Use old asymmetric-only encryption (for SEB < 2.2) sebserver.clientconfig.form.type.async.tooltip=Use old asymmetric-only encryption (for SEB < 2.2)
sebserver.clientconfig.form.examselection=Exams
sebserver.clientconfig.form.examselection.tooltip=List of Exams selected to work with this Connection Configuration.
sebserver.clientconfig.form.credentials.title=Client Credentials of Connection Configuration sebserver.clientconfig.form.credentials.title=Client Credentials of Connection Configuration
sebserver.clientconfig.form.credentials.info=A SEB client that loads this connection configuration<br/>uses the following credentials to securely connect to the SEB Server. sebserver.clientconfig.form.credentials.info=A SEB client that loads this connection configuration<br/>uses the following credentials to securely connect to the SEB Server.

View file

@ -138,7 +138,8 @@ public class ModelObjectJSONGenerator {
false, false,
true, true,
DateTime.now(), DateTime.now(),
"user123"); "user123",
null);
System.out.println(domainObject.getClass().getSimpleName() + ":"); System.out.println(domainObject.getClass().getSimpleName() + ":");
System.out.println(writerWithDefaultPrettyPrinter.writeValueAsString(domainObject)); System.out.println(writerWithDefaultPrettyPrinter.writeValueAsString(domainObject));

View file

@ -128,6 +128,7 @@ public class ClientConfigTest extends GuiIntegrationTest {
false, false,
null, null,
null, null,
null,
null)) null))
.call(); .call();
@ -161,6 +162,7 @@ public class ClientConfigTest extends GuiIntegrationTest {
false, false,
null, null,
null, null,
null,
null)) null))
.call() .call()
.getOrThrow(); .getOrThrow();

View file

@ -12,6 +12,7 @@ import static org.junit.Assert.*;
import java.util.Collection; import java.util.Collection;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Order;
@ -61,7 +62,7 @@ public class ExamProctoringRoomServiceTest extends AdministrationAPIIntegrationT
@Order(1) @Order(1)
public void test01_checkExamRunning() { public void test01_checkExamRunning() {
final Result<Collection<Exam>> runningExamsForInstitution = final Result<Collection<Exam>> runningExamsForInstitution =
this.examSessionService.getRunningExamsForInstitution(1L); this.examSessionService.getRunningExams(1L, Utils.truePredicate());
assertFalse(runningExamsForInstitution.hasError()); assertFalse(runningExamsForInstitution.hasError());
final Collection<Exam> collection = runningExamsForInstitution.get(); final Collection<Exam> collection = runningExamsForInstitution.get();
assertFalse(collection.isEmpty()); assertFalse(collection.isEmpty());

View file

@ -48,4 +48,4 @@ sebserver.webservice.api.exam.indicator.color=b4b4b4
sebserver.webservice.api.exam.indicator.thresholds=[{"value":5000.0,"color":"22b14c"},{"value":10000.0,"color":"ff7e00"},{"value":15000.0,"color":"ed1c24"}] sebserver.webservice.api.exam.indicator.thresholds=[{"value":5000.0,"color":"22b14c"},{"value":10000.0,"color":"ff7e00"},{"value":15000.0,"color":"ed1c24"}]
sebserver.webservice.master.delay.threshold=1000 sebserver.webservice.master.delay.threshold=1000
sebserver.feature.seb.screenProctoring.bundled=false sebserver.feature.exam.seb.screenProctoring.bundled=false