Merge branch 'development' of github.com:SafeExamBrowser/seb-server into SEBSLI-9
This commit is contained in:
commit
f3a34ab06f
39 changed files with 719 additions and 441 deletions
20
.github/workflows/buildReporting.yml
vendored
20
.github/workflows/buildReporting.yml
vendored
|
@ -18,16 +18,16 @@ jobs:
|
|||
steps:
|
||||
-
|
||||
name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up JDK 8
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '8'
|
||||
distribution: 'adopt'
|
||||
-
|
||||
name: Cache Maven packages
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.m2
|
||||
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
|
||||
|
@ -37,7 +37,7 @@ jobs:
|
|||
run: mvn clean install -e -P let_reporting
|
||||
-
|
||||
name: Reporting
|
||||
uses: codecov/codecov-action@v3
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
flags: unittests
|
||||
name: SEB Server Build
|
||||
|
@ -69,16 +69,16 @@ jobs:
|
|||
echo ${{ env.TAG_NAME }}
|
||||
-
|
||||
name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'adopt'
|
||||
-
|
||||
name: Cache Maven packages
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.m2
|
||||
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
|
||||
|
@ -92,7 +92,7 @@ jobs:
|
|||
name: Simplify package name
|
||||
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:
|
||||
name: Package
|
||||
path: target/seb-server.jar
|
||||
|
@ -132,7 +132,7 @@ jobs:
|
|||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
-
|
||||
name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Install the cosign tool except on PR
|
||||
# https://github.com/sigstore/cosign-installer
|
||||
|
@ -142,7 +142,7 @@ jobs:
|
|||
uses: sigstore/cosign-installer@main
|
||||
-
|
||||
name: Download a single artifact
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: Package
|
||||
-
|
||||
|
|
73
README.rst
73
README.rst
|
@ -1,4 +1,7 @@
|
|||
Master:
|
||||
Safe Exam Browser (SEB) Server
|
||||
--------------------------------
|
||||
|
||||
Master:
|
||||
|
||||
.. image:: https://github.com/SafeExamBrowser/seb-server/actions/workflows/buildReporting.yml/badge.svg?branch=master
|
||||
:target: https://github.com/SafeExamBrowser/seb-server/actions
|
||||
|
@ -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/>`_,
|
||||
`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.
|
||||
|
||||
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: 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: Batch actions for archive and delete exams
|
||||
- Exam Maintenance: Added SEB log export for finished and archived exams
|
||||
|
@ -98,12 +101,20 @@ Bugfixes:
|
|||
|
||||
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
|
||||
------------------------------
|
||||
|
||||
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**
|
||||
|
||||
|
@ -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/#>`_
|
||||
|
||||
|
||||
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>`_.
|
||||
|
||||
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.
|
||||
|
||||
|
|
|
@ -1,265 +1,265 @@
|
|||
.. _lms-setup-label:
|
||||
|
||||
Learning Management System Setup
|
||||
================================
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
To be able to connect to a learning management system (LMS), to view and manage the courses provided by a LMS is an essential feature of the SEB Server.
|
||||
To setup an exam or e-assessment for SEB on SEB Server that is based on a course from a LMS, we have to make a binding to the course on the LMS.
|
||||
This is also used to always get the actual course data from LMS like start- end-time, name and others.
|
||||
Another feature of SEB Server that needs a LMS communication is the SEB restriction. A SEB restriction will restrict course access on the LMS only
|
||||
for connection with Safe Exam Browser and will also check if a Safe Exam Browser of trust is used and the right configuration is used by the
|
||||
Safe Exam Browser that was defines for the exam on the SEB Server.
|
||||
|
||||
**Course API**
|
||||
|
||||
This API, provided by the LMS, is used by the SEB Server to query the available courses and the needed data for each course. This API
|
||||
is needed to be able to import a course from the LMS as an exam into SEB Server and configure the course as an e-assessment with SEB.
|
||||
Usually this API comes as a REST or SOAP API with the core LMS implementation or a plugin.
|
||||
|
||||
SEB Server supports this course API's so far:
|
||||
- Open edX: The standard system `Open edX REST API <https://courses.edx.org/api-docs/>`_. The SEB Server uses the "courses" endpoints to get course data.
|
||||
- Moodle (Course Access): The standard system `Moodle REST API <https://docs.moodle.org/dev/Web_service_API_functions>`_. The SEB Server uses the standard Moodle rest endpoints to get course data.
|
||||
Please note that a second Moodle integration part for SEB access restriction with Config-Key will follow together with a Moodle plugin in a future version of SEB Server
|
||||
|
||||
|
||||
**SEB Restriction API**
|
||||
|
||||
If the automated SEB restriction functionality is available for a LMS depends on the following requirements:
|
||||
|
||||
- There must exist a SEB integration plugin that offers an API to put and pull SEB restrictions in the form of Config-Keys and/or Browser-Exam-Keys
|
||||
To the LMS and a specific course on the LMS to restrict the access. Such a plugin may also offer additional restriction features like restricting
|
||||
on course section or course components or only for specified user roles.
|
||||
- The SEB integration plugin must be installed on the LMS that is used by the SEB Server.
|
||||
|
||||
For more information about known SEB integration plugins that are supported by the SEB Server see :ref:`lms-setup-rest-plugin-label`
|
||||
|
||||
Regardless if a supported LMS is missing the SEB integration plugin installation, the LMS can be used with the Course API and an exam
|
||||
setup will be possible but without automated SEB restriction feature.
|
||||
|
||||
To be able to connect to an LMS from SEB Server, we need to create an API access account on the LMS side that can be used by the SEB Server to
|
||||
access the API of the LMS. How to do this for the different supported types of LMS see :ref:`lms-api-account-label`.
|
||||
After such an account was created, the account credentials, username and password, can be used by the SEB Server to connect to the LMS.
|
||||
Therefore we need to create a LMS Setup on the SEB Server.
|
||||
|
||||
.. image:: images/lmssetup/new.png
|
||||
:align: center
|
||||
:target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/master/docs/images/lmssetup/new.png
|
||||
|
||||
A SEB Server administrator role will be able to see the institution to which the LMS Setup belongs to while an institutional administrator
|
||||
is only able to see and create LMS Setup for its own institution. The name of the LMS Setup should be unique and is to identify a LMS
|
||||
SEB Server internally. Use the **"Type"** selector to specify the type of the LMS to bind to the SEB Server within the LMS Setup. Currently supported are:
|
||||
|
||||
- **Testing**: This is for testing purposes only and can be used to mock a LMS to test exam settings. This type provides some mock-up courses within the
|
||||
LMS API of the SEB Server that can be seen in the LMS Exam Lookup once the LMS text setup is active. This mock-up courses can be imported and configured
|
||||
as exams like they would exist. But note the a SEB client that is trying to connect to such a course would not be able to connect to the LMS since it
|
||||
is not existing. But a SEB client is able to download the defined exam configuration for testing.
|
||||
- **Open edX**: This type is to bind an existing `Open edX <https://open.edx.org/>`_ LMS system that is available on the Internet or intranet. The SEB
|
||||
Server tries to make use of the above described API's of the Open edX system.
|
||||
|
||||
.. note::
|
||||
If you want to use the automated SEB restriction feature too, the `Open edX SEB Plugin <https://seb-server.readthedocs.io/en/latest/lmssetup.html#lms-setup-rest-plugin-label>`_ must be installed properly on the LMS.
|
||||
|
||||
- **Moodle**: This type is to bind an existing `Moodle <https://moodle.org//>`_ LMS system that is available on the Internet or intranet. The SEB
|
||||
Server tries to make use of the described API's of the Moodle system but there is currently no SEB restriction plugin available that works
|
||||
with SEB Server. Note that Moodle integration is implemented partially within SEB Server version 1.1.x. Only the course access feature is implemented and the course restriction feature will come with a future SEB Server release
|
||||
|
||||
- **Moodle with SEB Server Plugin**: The `SEB Server Plugin for Moodle <https://github.com/ethz-let/moodle-quizzaccess_sebserver>`_ is new and supported by SEB Server since version 1.5.
|
||||
With this plugin installed on Moodle side, SEB Server is able to more efficiently communicate with Moodle to fetch course data as well as restricting the quiz on Moodle side
|
||||
For SEB only access, using a auto-generated Browser Exam Key (BEK) for SEB restriction. Also the Moodle user name resolving for SEB Server monitoring is less error prone especially
|
||||
if Single Sign On some kind of login provider for Moodle is involved. Furthermore the new SEB Server Plugin for Moodle will be constantly extended and improved with new features in the future.
|
||||
|
||||
- **Ans Delft**: This type is to bind SEB Server with an `Ans Delft <https://ans.app/>`_ LMS. With the API credentials from an Ans Delft instance, SEB Server is able
|
||||
to connect to the Ans LMS and provide the common features for Course-Access, SEB Restriction and LMS User Session resolving.
|
||||
|
||||
- **Open Olat**: This type is to bind SEB Server with an `Open Olat <https://www.openolat.com/>`_ LMS. With the API credentials from an Open Olat instance, SEB Server is able
|
||||
to connect to the Olat LMS and provide the common features for Course-Access, SEB Restriction and LMS User Session resolving. For more information please contact the Olat Development-Team at `OpenOLAT UZH <https://www.zi.uzh.ch/en/teaching-and-research/software-elearning/olat.html>`_
|
||||
|
||||
The **"LMS Server Address"** is the root URL to connect to the LMS server with HTTP over the Internet or intranet. This is usually the URL that is
|
||||
also used with the Browser to connect to the main page of the LMS system. And additionally the credentials that have been created with the creation of the :ref:`lms-api-account-label` has to be set in the LMS Setup the make the SEB Server
|
||||
able to securely connect to the LMS. The API credentials that consists of a client-name and a client-secret must be used with the **"LMS Server Username"**
|
||||
and the **"LMS Server Password"** fields of the LMS Setup form on SEB Server.
|
||||
|
||||
Alternatively to **"LMS Server Username"** and **"LMS Server Password"** you can use a direct **Access Token** to connect to the LMS API if the respective LMS allows to
|
||||
generate and use an access token directly.
|
||||
|
||||
If the SEB Server running behind a proxy server or a firewall between SEB Server den LMS, the additional proxy settings can be used to setup the proxy-connection.
|
||||
|
||||
.. note::
|
||||
To Setup a Test LMS Setup (of type "Test") only a correct URL pattern must be set like "http://test" for example. And API credentials can be anything but must be set.
|
||||
|
||||
After all the settings for a LMS Setup have been set, one can use either the "Save LMS Setup" action to save the LMS Setup without activation or the
|
||||
"Activate LMS Setup" action to also activate the settings right after they has been successfully saved. Anyway, for both action there is an initial test
|
||||
that, additionally to the usual field validation that takes place first, tries to connect to the LMS with the given API details. If the connection
|
||||
wasn't successful, the SEB Server will inform the user about a possible reason of failure. Otherwise SEB Server shows a success message and the created
|
||||
LMS Setup can be used.
|
||||
|
||||
Use the "Activate / Deactivate LMS Setup" action to activate an inactive LMS Setup or the deactivate an active LMS Setup.
|
||||
|
||||
.. note::
|
||||
On deactivation of an LMS Setup, the system checks on depending object and will show a confirmation to the user asking that all depending
|
||||
objects will also been deactivated. Depending objects of an LMS Setup are exams that has been imported from the specified LMS Setup in the past.
|
||||
|
||||
|
||||
Use Cases
|
||||
---------
|
||||
|
||||
**Create a new LMS Setup for Open edX**
|
||||
|
||||
A new Open edX system has been installed within your institution and to be able to use the system also for e-assessments with SEB and SEB Server,
|
||||
you have to bind the LMS to the SEB Server.
|
||||
|
||||
- If not already done, install the `Open edX SEB Plugin <https://seb-server.readthedocs.io/en/latest/lmssetup.html#lms-setup-rest-plugin-label>`_ on the Open edX system first.
|
||||
- If you don't already have an API access account on Open edX side, `create one <https://seb-server.readthedocs.io/en/latest/lmssetup.html#lms-api-account-edx-label>`_
|
||||
- Sign into SEB Server with your institutional administrator role account.
|
||||
- Navigate to "Exam Administration" / "LMS Setup" within the navigation on the left hand side.
|
||||
- Use the "Add LMS Setup" action from the right action pane to open a LMS Setup creation form.
|
||||
- Give a unique name to the new LMS Setup for internally identification.
|
||||
- Set the main URL that points to the new LMS system. This is usually the URL that is also used with the Browser to connect to the main page of the LMS system
|
||||
- Set the API credentials that has been creates within step two (client-id, secret).
|
||||
- Use the "Activate LMS Setup" action on the right action pane to test, save and activate the new LMS Setup within one step.
|
||||
|
||||
.. note::
|
||||
If some form attributes are missing or not correct, the SEB Server system will respond with the usual form validation errors.
|
||||
If the connection to the LMS is failing because of missing or wrong credentials or for any other reason the system is not able to connect to the LMS
|
||||
the SEB Server will notify an error dialog to the user.
|
||||
|
||||
|
||||
**Change API Credentials of an Active LMS Setup**
|
||||
|
||||
The API access account on the LMS has been expired and you have to create a new one or update the old one which both result in new API access credentials
|
||||
that has to be set on the existing LMS Setup on the SEB Server.
|
||||
|
||||
.. image:: images/lmssetup/list.png
|
||||
:align: center
|
||||
:target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/master/docs/images/lmssetup/list.png
|
||||
|
||||
- Sign into SEB Server with your institutional administrator role account.
|
||||
- Navigate to "Exam Administration" / "LMS Setup" within the navigation on the left hand side.
|
||||
- Use the Filter above the list to find the specified LMS Setup.
|
||||
- Select the LMS Setup from the list and use the "Edit LMS Setup" action from the right action pane to open the LMS Setup in edit mode.
|
||||
- Set the new credentials and make sure, the LMS Setup is still active.
|
||||
- Use the "Save LMS Setup" action form the right action pane to save the changes and test the connection.
|
||||
|
||||
.. note::
|
||||
If some form attributes are missing or not correct, the SEB Server system will respond with the usual form validation errors.
|
||||
If the connection to the LMS is failing because of missing or wrong credentials or for any other reason the system is not able to connect to the LMS
|
||||
the SEB Server will notify an error dialog to the user.
|
||||
|
||||
**Deactivate LMS Setup**
|
||||
|
||||
A LMS system that was running on your campus to provide e-assessment with SEB and SEB Server has been shut down and you need to also deactivate
|
||||
the setup and exams on the SEB Server for this LMS.
|
||||
|
||||
- Sign into SEB Server with your institutional administrator role account.
|
||||
- Navigate to "Exam Administration" / "LMS Setup" within the navigation on the left hand side.
|
||||
- Use the Filter above the list to find the specified LMS Setup.
|
||||
- Select the specified LMS Setup from the list and use the "Deactivate LMS Setup" action from the right action pane.
|
||||
- Alternatively you can also double-click on the LMS Setup to fist go into the detailed view of the LMS setup and use the "Deactivate LMS Setup" action there.
|
||||
- The system informs you about the number of depending exams that also will be deactivated within the deactivation of the LMS Setup.
|
||||
- Confirm the deactivation and notify that the LMS Setup now is listed as "Inactive" in the list.
|
||||
- Navigate to "LMS Exam Lookup" to make sure the courses form the deactivated LMS Setup are not available anymore.
|
||||
- Navigate also to "Exam" and make sure that all previously imported exams from the deactivated LMS Setup are not available anymore.
|
||||
|
||||
.. _lms-api-account-label:
|
||||
|
||||
API Access Account on LMS
|
||||
--------------------------
|
||||
|
||||
.. _lms-api-account-edx-label:
|
||||
|
||||
**Create Open edX API Access Account**
|
||||
|
||||
To be able to create an API access-account on Open edX you need a user-account with staff and administration privileges.
|
||||
|
||||
**For Open edX Hawthorn and Ironwood versions following the steps below::**
|
||||
|
||||
- Login to Open edX LMS Administration with an appropriate user-account that has administration rights. And find the Users section:
|
||||
|
||||
.. image:: images/lmssetup/openEdxAPIAccess1.bmp
|
||||
:align: center
|
||||
:target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/master/docs/images/lmssetup/openEdxAPIAccess1.bmp
|
||||
|
||||
- Create a new User-Account that acts as an API account. The account must at least have the permissions to query the course API of Open edX and to access the seb_openedx plugin permission.
|
||||
- Make sure that "Staff" status is checked for the account.
|
||||
|
||||
.. image:: images/lmssetup/openEdxAPIAccess2.bmp
|
||||
:align: center
|
||||
:target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/master/docs/images/lmssetup/openEdxAPIAccess2.bmp
|
||||
|
||||
- Back in the administration homepage, find the OAUT2 - Client section and create a new API Client Access for the given User-Account. The Client id and Client secret are automatically generated by Open edx.
|
||||
|
||||
.. image:: images/lmssetup/openEdxAPIAccess3.bmp
|
||||
:align: center
|
||||
:target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/master/docs/images/lmssetup/openEdxAPIAccess3.bmp
|
||||
|
||||
.. image:: images/lmssetup/openEdxAPIAccess4.bmp
|
||||
:align: center
|
||||
:target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/master/docs/images/lmssetup/openEdxAPIAccess4.bmp
|
||||
|
||||
Once the client registration was successful the client id and client secret can be used within the SEB Server to access the course- and SEB-restriction API of Open edX as described in the next step section
|
||||
|
||||
.. note::
|
||||
Since Open edX Juniper is using Django Oauth Toolkit instead of Django Oauth Provider the last step in the above guide looks slightly different. Please see below the last step for setting up on an Open edX Juniper version.
|
||||
|
||||
- Back in the administration homepage, find the DJANGO OAUTH TOOLKIT - Applications section and create a new API Application Access for the given User-Account. The Client id can be defined and the Client secret is automatically be generated by Open edx.
|
||||
|
||||
.. image:: images/lmssetup/openEdxAPIAccess5.png
|
||||
:align: center
|
||||
:target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/documentation/docs/images/lmssetup/openEdxAPIAccess5.png
|
||||
|
||||
.. image:: images/lmssetup/openEdxAPIAccess6.png
|
||||
:align: center
|
||||
:target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/documentation/docs/images/lmssetup/openEdxAPIAccess6.png
|
||||
|
||||
|
||||
|
||||
**Create Moodle API Access Account**
|
||||
|
||||
To be able to create an LMS Setup for Moodle you need a Moodle administrator or manager account. You can then use this account in the LMS Setup to connect to the LMS.
|
||||
|
||||
Since SEB Server uses some functions from the Moodle's mobile API, you have to make sure the web services for mobile apps are enabled within your Moodle setup.
|
||||
To do so please login to Moodle with an administrator account and go to "Side Administration", scroll down to "Mobile App" and choose "Mobile Settings.
|
||||
|
||||
.. image:: images/lmssetup/moodle_mobile.png
|
||||
:align: center
|
||||
:target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/documentation/docs/images/lmssetup/moodle_mobile.png
|
||||
|
||||
If you have a restrictive Moodle setup and troubles with the Moodle API account to use with SEB Server, please try to import the following
|
||||
Moodle role profile within your Moodle instance. This profile will create a SEB Server role within Moodle that can be used to apply to an
|
||||
API account to be used with SEB Server. The role defines only the necessary privileges and functions needed for SEB Server communication.
|
||||
|
||||
Moodle role and account settings: :download:`XML <files/webservice_seb-server.xml>`
|
||||
|
||||
.. note::
|
||||
If you want to use Moodle with SEB Server, we recomend to install the new Moodle Plugin for SEB Server for better integration with Moodle.
|
||||
This plugin comes with the common SEB Server integration features and improved Moodle bining. For more information see :ref:`lms-setup-moodle-plugin-label`
|
||||
|
||||
|
||||
|
||||
.. _lms-setup-rest-plugin-label:
|
||||
|
||||
Install SEB restriction API plugin
|
||||
----------------------------------
|
||||
|
||||
.. _lms-setup-edx-plugin-label:
|
||||
|
||||
**Open edX SEB Plugin**
|
||||
|
||||
There is a SEB integration plugin developed and supported by `eduNEXT <https://www.edunext.co/>`_.
|
||||
- `Documentation <https://seb-openedx.readthedocs.io/en/latest/>`_
|
||||
- `Repository <https://github.com/eduNEXT/seb-openedx>`_
|
||||
|
||||
|
||||
.. _lms-setup-moodle-plugin-label:
|
||||
|
||||
**Moodle Plugin for SEB Server**
|
||||
|
||||
There is a new SEB Server integration plugin for Moodle available since SEB Server 1.5 that can be used with the LMS Setup type Moodle with SEB Server Plugin.
|
||||
This Plugin supports and improves all common SEB Server LMS binding features such as Course-Access, SEB Restriction and LMS Session Name Resolving.
|
||||
It is also planed to extend and improve this plugin with new Moodle specific feature for further releases of SEB Server.
|
||||
- `Documentation <https://github.com/ethz-let/moodle-quizzaccess_sebserver>`_
|
||||
- `Repository <https://github.com/ethz-let/moodle-quizzaccess_sebserver>`_
|
||||
|
||||
.. _lms-setup-label:
|
||||
|
||||
Learning Management System Setup
|
||||
================================
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
To be able to connect to a learning management system (LMS), to view and manage the courses provided by a LMS is an essential feature of the SEB Server.
|
||||
To setup an exam or e-assessment for SEB on SEB Server that is based on a course from a LMS, we have to make a binding to the course on the LMS.
|
||||
This is also used to always get the actual course data from LMS like start- end-time, name and others.
|
||||
Another feature of SEB Server that needs a LMS communication is the SEB restriction. A SEB restriction will restrict course access on the LMS only
|
||||
for connection with Safe Exam Browser and will also check if a Safe Exam Browser of trust is used and the right configuration is used by the
|
||||
Safe Exam Browser that was defines for the exam on the SEB Server.
|
||||
|
||||
**Course API**
|
||||
|
||||
This API, provided by the LMS, is used by the SEB Server to query the available courses and the needed data for each course. This API
|
||||
is needed to be able to import a course from the LMS as an exam into SEB Server and configure the course as an e-assessment with SEB.
|
||||
Usually this API comes as a REST or SOAP API with the core LMS implementation or a plugin.
|
||||
|
||||
SEB Server supports this course API's so far:
|
||||
- Open edX: The standard system `Open edX REST API <https://courses.edx.org/api-docs/>`_. The SEB Server uses the "courses" endpoints to get course data.
|
||||
- Moodle (Course Access): The standard system `Moodle REST API <https://docs.moodle.org/dev/Web_service_API_functions>`_. The SEB Server uses the standard Moodle rest endpoints to get course data.
|
||||
Please note that a second Moodle integration part for SEB access restriction with Config-Key will follow together with a Moodle plugin in a future version of SEB Server
|
||||
|
||||
|
||||
**SEB Restriction API**
|
||||
|
||||
If the automated SEB restriction functionality is available for a LMS depends on the following requirements:
|
||||
|
||||
- There must exist a SEB integration plugin that offers an API to put and pull SEB restrictions in the form of Config-Keys and/or Browser-Exam-Keys
|
||||
To the LMS and a specific course on the LMS to restrict the access. Such a plugin may also offer additional restriction features like restricting
|
||||
on course section or course components or only for specified user roles.
|
||||
- The SEB integration plugin must be installed on the LMS that is used by the SEB Server.
|
||||
|
||||
For more information about known SEB integration plugins that are supported by the SEB Server see :ref:`lms-setup-rest-plugin-label`
|
||||
|
||||
Regardless if a supported LMS is missing the SEB integration plugin installation, the LMS can be used with the Course API and an exam
|
||||
setup will be possible but without automated SEB restriction feature.
|
||||
|
||||
To be able to connect to an LMS from SEB Server, we need to create an API access account on the LMS side that can be used by the SEB Server to
|
||||
access the API of the LMS. How to do this for the different supported types of LMS see :ref:`lms-api-account-label`.
|
||||
After such an account was created, the account credentials, username and password, can be used by the SEB Server to connect to the LMS.
|
||||
Therefore we need to create a LMS Setup on the SEB Server.
|
||||
|
||||
.. image:: images/lmssetup/new.png
|
||||
:align: center
|
||||
:target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/master/docs/images/lmssetup/new.png
|
||||
|
||||
A SEB Server administrator role will be able to see the institution to which the LMS Setup belongs to while an institutional administrator
|
||||
is only able to see and create LMS Setup for its own institution. The name of the LMS Setup should be unique and is to identify a LMS
|
||||
SEB Server internally. Use the **"Type"** selector to specify the type of the LMS to bind to the SEB Server within the LMS Setup. Currently supported are:
|
||||
|
||||
- **Testing**: This is for testing purposes only and can be used to mock a LMS to test exam settings. This type provides some mock-up courses within the
|
||||
LMS API of the SEB Server that can be seen in the LMS Exam Lookup once the LMS text setup is active. This mock-up courses can be imported and configured
|
||||
as exams like they would exist. But note the a SEB client that is trying to connect to such a course would not be able to connect to the LMS since it
|
||||
is not existing. But a SEB client is able to download the defined exam configuration for testing.
|
||||
- **Open edX**: This type is to bind an existing `Open edX <https://open.edx.org/>`_ LMS system that is available on the Internet or intranet. The SEB
|
||||
Server tries to make use of the above described API's of the Open edX system.
|
||||
|
||||
.. note::
|
||||
If you want to use the automated SEB restriction feature too, the `Open edX SEB Plugin <https://seb-server.readthedocs.io/en/latest/lmssetup.html#lms-setup-rest-plugin-label>`_ must be installed properly on the LMS.
|
||||
|
||||
- **Moodle**: This type is to bind an existing `Moodle <https://moodle.org//>`_ LMS system that is available on the Internet or intranet. The SEB
|
||||
Server tries to make use of the described API's of the Moodle system but there is currently no SEB restriction plugin available that works
|
||||
with SEB Server. Note that Moodle integration is implemented partially within SEB Server version 1.1.x. Only the course access feature is implemented and the course restriction feature will come with a future SEB Server release
|
||||
|
||||
- **Moodle with SEB Server Plugin**: The `SEB Server Plugin for Moodle <https://github.com/ethz-let/moodle-quizzaccess_sebserver>`_ is new and supported by SEB Server since version 1.5.
|
||||
With this plugin installed on Moodle side, SEB Server is able to more efficiently communicate with Moodle to fetch course data as well as restricting the quiz on Moodle side
|
||||
For SEB only access, using a auto-generated Browser Exam Key (BEK) for SEB restriction. Also the Moodle user name resolving for SEB Server monitoring is less error prone especially
|
||||
if Single Sign On some kind of login provider for Moodle is involved. Furthermore the new SEB Server Plugin for Moodle will be constantly extended and improved with new features in the future.
|
||||
|
||||
- **Ans Delft**: This type is to bind SEB Server with an `Ans Delft <https://ans.app/>`_ LMS. With the API credentials from an Ans Delft instance, SEB Server is able
|
||||
to connect to the Ans LMS and provide the common features for Course-Access, SEB Restriction and LMS User Session resolving.
|
||||
|
||||
- **Open Olat**: This type is to bind SEB Server with an `Open Olat <https://www.openolat.com/>`_ LMS. With the API credentials from an Open Olat instance, SEB Server is able
|
||||
to connect to the Olat LMS and provide the common features for Course-Access, SEB Restriction and LMS User Session resolving. For more information please contact the Olat Development-Team at `OpenOLAT UZH <https://www.zi.uzh.ch/en/teaching-and-research/software-elearning/olat.html>`_
|
||||
|
||||
The **"LMS Server Address"** is the root URL to connect to the LMS server with HTTP over the Internet or intranet. This is usually the URL that is
|
||||
also used with the Browser to connect to the main page of the LMS system. And additionally the credentials that have been created with the creation of the :ref:`lms-api-account-label` has to be set in the LMS Setup the make the SEB Server
|
||||
able to securely connect to the LMS. The API credentials that consists of a client-name and a client-secret must be used with the **"LMS Server Username"**
|
||||
and the **"LMS Server Password"** fields of the LMS Setup form on SEB Server.
|
||||
|
||||
Alternatively to **"LMS Server Username"** and **"LMS Server Password"** you can use a direct **Access Token** to connect to the LMS API if the respective LMS allows to
|
||||
generate and use an access token directly.
|
||||
|
||||
If the SEB Server running behind a proxy server or a firewall between SEB Server den LMS, the additional proxy settings can be used to setup the proxy-connection.
|
||||
|
||||
.. note::
|
||||
To Setup a Test LMS Setup (of type "Test") only a correct URL pattern must be set like "http://test" for example. And API credentials can be anything but must be set.
|
||||
|
||||
After all the settings for a LMS Setup have been set, one can use either the "Save LMS Setup" action to save the LMS Setup without activation or the
|
||||
"Activate LMS Setup" action to also activate the settings right after they has been successfully saved. Anyway, for both action there is an initial test
|
||||
that, additionally to the usual field validation that takes place first, tries to connect to the LMS with the given API details. If the connection
|
||||
wasn't successful, the SEB Server will inform the user about a possible reason of failure. Otherwise SEB Server shows a success message and the created
|
||||
LMS Setup can be used.
|
||||
|
||||
Use the "Activate / Deactivate LMS Setup" action to activate an inactive LMS Setup or the deactivate an active LMS Setup.
|
||||
|
||||
.. note::
|
||||
On deactivation of an LMS Setup, the system checks on depending object and will show a confirmation to the user asking that all depending
|
||||
objects will also been deactivated. Depending objects of an LMS Setup are exams that has been imported from the specified LMS Setup in the past.
|
||||
|
||||
|
||||
Use Cases
|
||||
---------
|
||||
|
||||
**Create a new LMS Setup for Open edX**
|
||||
|
||||
A new Open edX system has been installed within your institution and to be able to use the system also for e-assessments with SEB and SEB Server,
|
||||
you have to bind the LMS to the SEB Server.
|
||||
|
||||
- If not already done, install the `Open edX SEB Plugin <https://seb-server.readthedocs.io/en/latest/lmssetup.html#lms-setup-rest-plugin-label>`_ on the Open edX system first.
|
||||
- If you don't already have an API access account on Open edX side, `create one <https://seb-server.readthedocs.io/en/latest/lmssetup.html#lms-api-account-edx-label>`_
|
||||
- Sign into SEB Server with your institutional administrator role account.
|
||||
- Navigate to "Exam Administration" / "LMS Setup" within the navigation on the left hand side.
|
||||
- Use the "Add LMS Setup" action from the right action pane to open a LMS Setup creation form.
|
||||
- Give a unique name to the new LMS Setup for internally identification.
|
||||
- Set the main URL that points to the new LMS system. This is usually the URL that is also used with the Browser to connect to the main page of the LMS system
|
||||
- Set the API credentials that has been creates within step two (client-id, secret).
|
||||
- Use the "Activate LMS Setup" action on the right action pane to test, save and activate the new LMS Setup within one step.
|
||||
|
||||
.. note::
|
||||
If some form attributes are missing or not correct, the SEB Server system will respond with the usual form validation errors.
|
||||
If the connection to the LMS is failing because of missing or wrong credentials or for any other reason the system is not able to connect to the LMS
|
||||
the SEB Server will notify an error dialog to the user.
|
||||
|
||||
|
||||
**Change API Credentials of an Active LMS Setup**
|
||||
|
||||
The API access account on the LMS has been expired and you have to create a new one or update the old one which both result in new API access credentials
|
||||
that has to be set on the existing LMS Setup on the SEB Server.
|
||||
|
||||
.. image:: images/lmssetup/list.png
|
||||
:align: center
|
||||
:target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/master/docs/images/lmssetup/list.png
|
||||
|
||||
- Sign into SEB Server with your institutional administrator role account.
|
||||
- Navigate to "Exam Administration" / "LMS Setup" within the navigation on the left hand side.
|
||||
- Use the Filter above the list to find the specified LMS Setup.
|
||||
- Select the LMS Setup from the list and use the "Edit LMS Setup" action from the right action pane to open the LMS Setup in edit mode.
|
||||
- Set the new credentials and make sure, the LMS Setup is still active.
|
||||
- Use the "Save LMS Setup" action form the right action pane to save the changes and test the connection.
|
||||
|
||||
.. note::
|
||||
If some form attributes are missing or not correct, the SEB Server system will respond with the usual form validation errors.
|
||||
If the connection to the LMS is failing because of missing or wrong credentials or for any other reason the system is not able to connect to the LMS
|
||||
the SEB Server will notify an error dialog to the user.
|
||||
|
||||
**Deactivate LMS Setup**
|
||||
|
||||
A LMS system that was running on your campus to provide e-assessment with SEB and SEB Server has been shut down and you need to also deactivate
|
||||
the setup and exams on the SEB Server for this LMS.
|
||||
|
||||
- Sign into SEB Server with your institutional administrator role account.
|
||||
- Navigate to "Exam Administration" / "LMS Setup" within the navigation on the left hand side.
|
||||
- Use the Filter above the list to find the specified LMS Setup.
|
||||
- Select the specified LMS Setup from the list and use the "Deactivate LMS Setup" action from the right action pane.
|
||||
- Alternatively you can also double-click on the LMS Setup to fist go into the detailed view of the LMS setup and use the "Deactivate LMS Setup" action there.
|
||||
- The system informs you about the number of depending exams that also will be deactivated within the deactivation of the LMS Setup.
|
||||
- Confirm the deactivation and notify that the LMS Setup now is listed as "Inactive" in the list.
|
||||
- Navigate to "LMS Exam Lookup" to make sure the courses form the deactivated LMS Setup are not available anymore.
|
||||
- Navigate also to "Exam" and make sure that all previously imported exams from the deactivated LMS Setup are not available anymore.
|
||||
|
||||
.. _lms-api-account-label:
|
||||
|
||||
API Access Account on LMS
|
||||
--------------------------
|
||||
|
||||
.. _lms-api-account-edx-label:
|
||||
|
||||
**Create Open edX API Access Account**
|
||||
|
||||
To be able to create an API access-account on Open edX you need a user-account with staff and administration privileges.
|
||||
|
||||
**For Open edX Hawthorn and Ironwood versions following the steps below::**
|
||||
|
||||
- Login to Open edX LMS Administration with an appropriate user-account that has administration rights. And find the Users section:
|
||||
|
||||
.. image:: images/lmssetup/openEdxAPIAccess1.bmp
|
||||
:align: center
|
||||
:target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/master/docs/images/lmssetup/openEdxAPIAccess1.bmp
|
||||
|
||||
- Create a new User-Account that acts as an API account. The account must at least have the permissions to query the course API of Open edX and to access the seb_openedx plugin permission.
|
||||
- Make sure that "Staff" status is checked for the account.
|
||||
|
||||
.. image:: images/lmssetup/openEdxAPIAccess2.bmp
|
||||
:align: center
|
||||
:target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/master/docs/images/lmssetup/openEdxAPIAccess2.bmp
|
||||
|
||||
- Back in the administration homepage, find the OAUT2 - Client section and create a new API Client Access for the given User-Account. The Client id and Client secret are automatically generated by Open edx.
|
||||
|
||||
.. image:: images/lmssetup/openEdxAPIAccess3.bmp
|
||||
:align: center
|
||||
:target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/master/docs/images/lmssetup/openEdxAPIAccess3.bmp
|
||||
|
||||
.. image:: images/lmssetup/openEdxAPIAccess4.bmp
|
||||
:align: center
|
||||
:target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/master/docs/images/lmssetup/openEdxAPIAccess4.bmp
|
||||
|
||||
Once the client registration was successful the client id and client secret can be used within the SEB Server to access the course- and SEB-restriction API of Open edX as described in the next step section
|
||||
|
||||
.. note::
|
||||
Since Open edX Juniper is using Django Oauth Toolkit instead of Django Oauth Provider the last step in the above guide looks slightly different. Please see below the last step for setting up on an Open edX Juniper version.
|
||||
|
||||
- Back in the administration homepage, find the DJANGO OAUTH TOOLKIT - Applications section and create a new API Application Access for the given User-Account. The Client id can be defined and the Client secret is automatically be generated by Open edx.
|
||||
|
||||
.. image:: images/lmssetup/openEdxAPIAccess5.png
|
||||
:align: center
|
||||
:target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/documentation/docs/images/lmssetup/openEdxAPIAccess5.png
|
||||
|
||||
.. image:: images/lmssetup/openEdxAPIAccess6.png
|
||||
:align: center
|
||||
:target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/documentation/docs/images/lmssetup/openEdxAPIAccess6.png
|
||||
|
||||
|
||||
|
||||
**Create Moodle API Access Account**
|
||||
|
||||
To be able to create an LMS Setup for Moodle you need a Moodle administrator or manager account. You can then use this account in the LMS Setup to connect to the LMS.
|
||||
|
||||
Since SEB Server uses some functions from the Moodle's mobile API, you have to make sure the web services for mobile apps are enabled within your Moodle setup.
|
||||
To do so please login to Moodle with an administrator account and go to "Side Administration", scroll down to "Mobile App" and choose "Mobile Settings.
|
||||
|
||||
.. image:: images/lmssetup/moodle_mobile.png
|
||||
:align: center
|
||||
:target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/documentation/docs/images/lmssetup/moodle_mobile.png
|
||||
|
||||
If you have a restrictive Moodle setup and troubles with the Moodle API account to use with SEB Server, please try to import the following
|
||||
Moodle role profile within your Moodle instance. This profile will create a SEB Server role within Moodle that can be used to apply to an
|
||||
API account to be used with SEB Server. The role defines only the necessary privileges and functions needed for SEB Server communication.
|
||||
|
||||
Moodle role and account settings: :download:`XML <files/webservice_seb-server.xml>`
|
||||
|
||||
.. note::
|
||||
If you want to use Moodle with SEB Server, we recomend to install the new Moodle Plugin for SEB Server for better integration with Moodle.
|
||||
This plugin comes with the common SEB Server integration features and improved Moodle bining. For more information see :ref:`lms-setup-moodle-plugin-label`
|
||||
|
||||
|
||||
|
||||
.. _lms-setup-rest-plugin-label:
|
||||
|
||||
Install SEB restriction API plugin
|
||||
----------------------------------
|
||||
|
||||
.. _lms-setup-edx-plugin-label:
|
||||
|
||||
**Open edX SEB Plugin**
|
||||
|
||||
There is a SEB integration plugin developed and supported by `eduNEXT <https://www.edunext.co/>`_.
|
||||
- `Documentation <https://seb-openedx.readthedocs.io/en/latest/>`_
|
||||
- `Repository <https://github.com/eduNEXT/seb-openedx>`_
|
||||
|
||||
|
||||
.. _lms-setup-moodle-plugin-label:
|
||||
|
||||
**Moodle Plugin for SEB Server**
|
||||
|
||||
There is a new SEB Server integration plugin for Moodle available since SEB Server 1.5 that can be used with the LMS Setup type Moodle with SEB Server Plugin.
|
||||
This Plugin supports and improves all common SEB Server LMS binding features such as Course-Access, SEB Restriction and LMS Session Name Resolving.
|
||||
It is also planed to extend and improve this plugin with new Moodle specific feature for further releases of SEB Server.
|
||||
- `Documentation <https://github.com/ethz-let/moodle-quizzaccess_sebserver>`_
|
||||
- `Repository <https://github.com/ethz-let/moodle-quizzaccess_sebserver>`_
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
docutils<0.18
|
||||
sphinx==5.3.0
|
||||
sphinx_rtd_theme==2.0.0
|
||||
readthedocs-sphinx-search==0.1.1
|
||||
readthedocs-sphinx-search==0.3.2
|
||||
urllib3==1.26.13
|
|
@ -11,6 +11,11 @@ package ch.ethz.seb.sebserver.gbl.model.sebconfig;
|
|||
import javax.validation.constraints.NotNull;
|
||||
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.joda.time.DateTime;
|
||||
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 FILTER_ATTR_CREATION_DATE = "creation_date";
|
||||
public static final String ATTR_EXAM_SELECTION = "exam_selection";
|
||||
|
||||
public enum ConfigPurpose {
|
||||
START_EXAM,
|
||||
|
@ -174,6 +180,8 @@ public final class SEBClientConfig implements GrantEntity, Activatable {
|
|||
|
||||
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_LAST_UPDATE_USER)
|
||||
public final String lastUpdateUser;
|
||||
@JsonProperty(SEBClientConfig.ATTR_EXAM_SELECTION)
|
||||
public final Set<Long> selectedExams;
|
||||
|
||||
@JsonCreator
|
||||
public SEBClientConfig(
|
||||
|
@ -204,7 +212,8 @@ public final class SEBClientConfig implements GrantEntity, Activatable {
|
|||
@JsonProperty(ATTR_ENCRYPT_CERTIFICATE_ASYM) final Boolean encryptCertificateAsym,
|
||||
@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_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.institutionId = institutionId;
|
||||
|
@ -240,6 +249,7 @@ public final class SEBClientConfig implements GrantEntity, Activatable {
|
|||
this.active = active;
|
||||
this.lastUpdateTime = lastUpdateTime;
|
||||
this.lastUpdateUser = lastUpdateUser;
|
||||
this.selectedExams = Utils.immutableSetOf(selectedExams);
|
||||
}
|
||||
|
||||
public SEBClientConfig(final Long institutionId, final POSTMapper postParams) {
|
||||
|
@ -281,6 +291,7 @@ public final class SEBClientConfig implements GrantEntity, Activatable {
|
|||
this.active = false;
|
||||
this.lastUpdateTime = postParams.getDateTime(CONFIGURATION_NODE.ATTR_LAST_UPDATE_TIME);
|
||||
this.lastUpdateUser = postParams.getString(CONFIGURATION_NODE.ATTR_LAST_UPDATE_USER);
|
||||
this.selectedExams = Utils.immutableSetOf(Utils.getIdsFromString(postParams.getString(SEBClientConfig.ATTR_EXAM_SELECTION)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -424,6 +435,10 @@ public final class SEBClientConfig implements GrantEntity, Activatable {
|
|||
return this.lastUpdateUser;
|
||||
}
|
||||
|
||||
public Set<Long> getSelectedExams() {
|
||||
return selectedExams;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
|
@ -503,7 +518,8 @@ public final class SEBClientConfig implements GrantEntity, Activatable {
|
|||
this.encryptCertificateAsym,
|
||||
this.active,
|
||||
this.lastUpdateTime,
|
||||
this.lastUpdateUser);
|
||||
this.lastUpdateUser,
|
||||
this.selectedExams);
|
||||
}
|
||||
|
||||
public static SEBClientConfig createNew(final Long institutionId, final long pingIterval) {
|
||||
|
@ -533,6 +549,7 @@ public final class SEBClientConfig implements GrantEntity, Activatable {
|
|||
false,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,8 +17,12 @@ public class UserFeatures {
|
|||
public enum Feature {
|
||||
ADMIN_INSTITUTION("admin.institution"),
|
||||
|
||||
|
||||
|
||||
ADMIN_USER_ADMINISTRATION("admin.user.administration"),
|
||||
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"),
|
||||
|
||||
CONFIG_CONNECTION_CONFIGURATION("config.connection.configuration"),
|
||||
|
|
|
@ -935,4 +935,22 @@ public final class Utils {
|
|||
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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,14 +59,16 @@ public class LoginPage implements TemplateComposer {
|
|||
public LoginPage(
|
||||
final PageService pageService,
|
||||
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.authorizationContextHolder = pageService.getAuthorizationContextHolder();
|
||||
this.widgetFactory = pageService.getWidgetFactory();
|
||||
this.i18nSupport = pageService.getI18nSupport();
|
||||
this.defaultRegisterPage = defaultRegisterPage;
|
||||
this.registeringEnabled = BooleanUtils.toBoolean(registeringEnabled);
|
||||
this.registeringEnabled = webRegEnabled && (guiRegEnabled || guiRegEnabledOld);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -85,6 +85,9 @@ public class RegisterPage implements TemplateComposer {
|
|||
static final LocTextKey MESSAGE_SUCCESS_TEXT =
|
||||
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 ResourceService resourceService;
|
||||
private final WidgetFactory widgetFactory;
|
||||
|
@ -247,7 +250,9 @@ public class RegisterPage implements TemplateComposer {
|
|||
}
|
||||
|
||||
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);
|
||||
|
||||
});
|
||||
|
||||
|
|
|
@ -134,6 +134,9 @@ public class SEBClientConfigForm implements TemplateComposer {
|
|||
private static final LocTextKey FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY =
|
||||
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 =
|
||||
new LocTextKey("sebserver.clientconfig.action.delete.confirm");
|
||||
private static final LocTextKey DELETE_SUCCESS =
|
||||
|
@ -409,20 +412,28 @@ public class SEBClientConfigForm implements TemplateComposer {
|
|||
.mandatory(!isReadonly))
|
||||
.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
|
||||
|
||||
.withDefaultSpanInput(2)
|
||||
.addFieldIf(
|
||||
() -> false, // TODO skipped for version 1.2 --> 1.3 or 1.4
|
||||
() -> FormBuilder.singleSelection(
|
||||
SEBClientConfig.ATTR_VDI_TYPE,
|
||||
VDI_TYPE_TEXT_KEY,
|
||||
clientConfig.vdiType != null
|
||||
? clientConfig.vdiType.name()
|
||||
: SEBClientConfig.VDIType.NO.name(),
|
||||
() -> this.pageService.getResourceService().vdiTypeResources())
|
||||
.mandatory(!isReadonly))
|
||||
.withDefaultSpanEmptyCell(3);
|
||||
// .withDefaultSpanInput(2)
|
||||
// .addFieldIf(
|
||||
// () -> false, // TODO skipped for version 1.2 --> 1.3 or 1.4
|
||||
// () -> FormBuilder.singleSelection(
|
||||
// SEBClientConfig.ATTR_VDI_TYPE,
|
||||
// VDI_TYPE_TEXT_KEY,
|
||||
// clientConfig.vdiType != null
|
||||
// ? clientConfig.vdiType.name()
|
||||
// : SEBClientConfig.VDIType.NO.name(),
|
||||
// () -> this.pageService.getResourceService().vdiTypeResources())
|
||||
// .mandatory(!isReadonly))
|
||||
// .withDefaultSpanEmptyCell(3);
|
||||
|
||||
// VDI Attributes
|
||||
|
||||
|
@ -460,7 +471,10 @@ public class SEBClientConfigForm implements TemplateComposer {
|
|||
FALLBACK_TEXT_KEY,
|
||||
clientConfig.fallback != null
|
||||
? clientConfig.fallback.toString()
|
||||
: Constants.FALSE_STRING));
|
||||
: Constants.FALSE_STRING))
|
||||
.withDefaultSpanEmptyCell(3)
|
||||
|
||||
;
|
||||
|
||||
// Fallback Attributes
|
||||
|
||||
|
|
|
@ -137,6 +137,7 @@ public class ExamFormConfigs implements TemplateComposer {
|
|||
|
||||
.withSelectionListener(this.pageService.getSelectionPublisher(
|
||||
pageContext,
|
||||
ActionDefinition.EXAM_CONFIGURATION_MODIFY_FROM_LIST,
|
||||
ActionDefinition.EXAM_CONFIGURATION_EXAM_CONFIG_VIEW_PROP,
|
||||
ActionDefinition.EXAM_CONFIGURATION_DELETE_FROM_LIST,
|
||||
ActionDefinition.EXAM_CONFIGURATION_EXPORT,
|
||||
|
@ -144,7 +145,7 @@ public class ExamFormConfigs implements TemplateComposer {
|
|||
|
||||
.compose(pageContext.copyOf(content));
|
||||
|
||||
final EntityKey configMapKey = (configurationTable.hasAnyContent())
|
||||
final EntityKey configKey = (configurationTable.hasAnyContent())
|
||||
? new EntityKey(
|
||||
configurationTable.getFirstRowData().configurationNodeId,
|
||||
EntityType.CONFIGURATION_NODE)
|
||||
|
@ -164,9 +165,18 @@ public class ExamFormConfigs implements TemplateComposer {
|
|||
|
||||
.newAction(ActionDefinition.EXAM_CONFIGURATION_EXAM_CONFIG_VIEW_PROP)
|
||||
.withParentEntityKey(entityKey)
|
||||
.withEntityKey(configMapKey)
|
||||
.withEntityKey(configKey)
|
||||
.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)
|
||||
.withEntityKey(entityKey)
|
||||
.withSelect(
|
||||
|
@ -179,7 +189,7 @@ public class ExamFormConfigs implements TemplateComposer {
|
|||
}
|
||||
return null;
|
||||
})
|
||||
.publishIf(() -> examConfigEnabled && editable && configurationTable.hasAnyContent() && editable, false)
|
||||
.publishIf(() -> examConfigEnabled && editable && configurationTable.hasAnyContent(), false)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_CONFIGURATION_GET_CONFIG_KEY)
|
||||
.withSelect(
|
||||
|
|
|
@ -82,7 +82,7 @@ public class ExamToConfigBindingForm {
|
|||
return action -> {
|
||||
|
||||
final PageContext pageContext = action.pageContext();
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final EntityKey entityKey = action.getSingleSelection();
|
||||
final boolean isNew = entityKey == null;
|
||||
|
||||
if (isNew) {
|
||||
|
@ -103,11 +103,12 @@ public class ExamToConfigBindingForm {
|
|||
|
||||
final BindFormContext bindFormContext = new BindFormContext(
|
||||
this.pageService,
|
||||
action.pageContext());
|
||||
action.pageContext()
|
||||
.withEntityKey(entityKey));
|
||||
|
||||
final Predicate<FormHandle<ExamConfigurationMap>> doBind = formHandle -> doCreate(
|
||||
this.pageService,
|
||||
pageContext,
|
||||
bindFormContext.pageContext,
|
||||
formHandle);
|
||||
|
||||
// the default page layout
|
||||
|
@ -181,14 +182,13 @@ public class ExamToConfigBindingForm {
|
|||
final EntityKey parentEntityKey = this.pageContext.getParentEntityKey();
|
||||
final boolean isNew = entityKey == null;
|
||||
|
||||
final Exam exam = (isNew)
|
||||
? restService
|
||||
.getBuilder(GetExam.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId)
|
||||
.call()
|
||||
.onError(error -> this.pageContext.notifyLoadError(EntityType.EXAM, error))
|
||||
.getOrThrow()
|
||||
: null;
|
||||
final Exam exam = restService
|
||||
.getBuilder(GetExam.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId)
|
||||
.call()
|
||||
.onError(error -> this.pageContext.notifyLoadError(EntityType.EXAM, error))
|
||||
.getOrThrow();
|
||||
|
||||
|
||||
// get data or create new. Handle error if happen
|
||||
final ExamConfigurationMap examConfigurationMap = (isNew)
|
||||
|
@ -217,8 +217,13 @@ public class ExamToConfigBindingForm {
|
|||
.putStaticValue(
|
||||
Domain.EXAM_CONFIGURATION_MAP.ATTR_EXAM_ID,
|
||||
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,
|
||||
CONFIG_MAPPING_NAME_TEXT_KEY,
|
||||
String.valueOf(examConfigurationMap.configurationNodeId),
|
||||
|
@ -226,6 +231,13 @@ public class ExamToConfigBindingForm {
|
|||
.withSelectionListener(form -> updateFormValuesFromConfigSelection(form, resourceService))
|
||||
.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(
|
||||
Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
|
||||
FORM_DESCRIPTION_TEXT_KEY,
|
||||
|
|
|
@ -111,6 +111,13 @@ public class ProctoringSettingsPopup {
|
|||
private final static LocTextKey RESET_SUCCESS_KEY =
|
||||
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) {
|
||||
|
||||
return action -> {
|
||||
|
@ -125,7 +132,7 @@ public class ProctoringSettingsPopup {
|
|||
action.pageContext().getParent().getShell(),
|
||||
pageService.getWidgetFactory())
|
||||
.setDialogWidth(860)
|
||||
.setDialogHeight(600);
|
||||
.setDialogHeight(650);
|
||||
|
||||
final ResetButtonHandler resetButtonHandler = new ResetButtonHandler();
|
||||
if (modifyGrant) {
|
||||
|
@ -326,6 +333,10 @@ public class ProctoringSettingsPopup {
|
|||
final RestService restService = this.pageService.getRestService();
|
||||
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
|
||||
.getWidgetFactory()
|
||||
.createPopupScrollComposite(parent);
|
||||
|
|
|
@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.gui.form;
|
|||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
@ -122,6 +123,7 @@ public final class SelectionFieldBuilder extends FieldBuilder<String> {
|
|||
this.itemsSupplier.get()
|
||||
.stream()
|
||||
.filter(tuple -> keys.contains(tuple._1))
|
||||
.sorted((t1, t2) -> String.CASE_INSENSITIVE_ORDER.compare(t1._2, t2._2))
|
||||
.map(tuple -> tuple._1)
|
||||
.forEach(v -> buildReadonlyLabel(builder, composite, v, 1));
|
||||
}
|
||||
|
|
|
@ -8,22 +8,17 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.gui.widget;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.*;
|
||||
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.eclipse.rap.rwt.widgets.DropDown;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.graphics.Point;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Control;
|
||||
import org.eclipse.swt.widgets.Event;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.eclipse.swt.widgets.Listener;
|
||||
import org.eclipse.swt.widgets.Text;
|
||||
import org.eclipse.swt.widgets.*;
|
||||
import org.slf4j.Logger;
|
||||
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 static final LocTextKey DESELECT_TOOLTIP = new LocTextKey( "sebserver.form.multiselect.deselect.tooltip" );
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(MultiSelectionCombo.class);
|
||||
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>> availableValues = 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 String testKey;
|
||||
|
||||
private final Table selectionTable;
|
||||
|
||||
private Listener listener = null;
|
||||
private WidgetFactory widgetFactory;
|
||||
|
||||
MultiSelectionCombo(
|
||||
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.Modify, 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();
|
||||
if (selectionIndex >= 0) {
|
||||
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;
|
||||
}
|
||||
|
||||
private void openDropDown() {
|
||||
final String text = this.textInput.getText();
|
||||
if (text == null) {
|
||||
this.dropDown.setVisible(false);
|
||||
@Override
|
||||
public void setToolTipText(final String tooltipText) {
|
||||
if (tooltipText == null) {
|
||||
super.setToolTipText(widgetFactory.getI18nSupport().getText(DESELECT_TOOLTIP));
|
||||
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);
|
||||
super.setToolTipText(tooltipText + "\n\n" + widgetFactory.getI18nSupport().getText(DESELECT_TOOLTIP));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -157,9 +161,6 @@ public final class MultiSelectionCombo extends Composite implements Selection {
|
|||
@Override
|
||||
public void clear() {
|
||||
this.selectedValues.clear();
|
||||
this.selectionControls
|
||||
.forEach(Control::dispose);
|
||||
this.selectionControls.clear();
|
||||
this.availableValues.clear();
|
||||
this.availableValues.addAll(this.valueMapping);
|
||||
}
|
||||
|
@ -170,42 +171,39 @@ public final class MultiSelectionCombo extends Composite implements Selection {
|
|||
}
|
||||
|
||||
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);
|
||||
WidgetFactory.setTestId(label, (this.testKey != null) ? this.testKey + "_" + item._1 : item._1);
|
||||
sortSelectedTable();
|
||||
|
||||
this.availableValues.remove(item);
|
||||
PageService.updateScrolledComposite(this);
|
||||
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) {
|
||||
if (event.widget == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String selectionKey = (String) event.widget.getData(OPTION_VALUE);
|
||||
final Optional<Control> findFirst = this.selectionControls.stream()
|
||||
.filter(t -> selectionKey.equals(t.getData(OPTION_VALUE)))
|
||||
.findFirst();
|
||||
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);
|
||||
final TableItem item = selectionTable.getItem(new Point(event.x, event.y));
|
||||
@SuppressWarnings("unchecked")
|
||||
final Tuple<String> value = (Tuple<String>) item.getData("tuple");
|
||||
this.selectedValues.remove(value);
|
||||
this.availableValues.add(value);
|
||||
|
||||
sortSelectedTable();
|
||||
|
||||
PageService.updateScrolledComposite(this);
|
||||
this.updateAnchor.layout(true, true);
|
||||
if (this.listener != null) {
|
||||
|
@ -238,4 +236,18 @@ public final class MultiSelectionCombo extends Composite implements Selection {
|
|||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,10 +12,12 @@ import java.util.Arrays;
|
|||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
@ -44,15 +46,18 @@ class AdminUserInitializer {
|
|||
private final boolean initializeAdmin;
|
||||
private final String adminName;
|
||||
private final String orgName;
|
||||
private final Environment environment;
|
||||
|
||||
public AdminUserInitializer(
|
||||
final UserDAO userDAO,
|
||||
final InstitutionDAO institutionDAO,
|
||||
final Environment environment,
|
||||
@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.username:seb-server-admin}") final String adminName,
|
||||
@Value("${sebserver.init.organisation.name:[SET_ORGANIZATION_NAME]}") final String orgName) {
|
||||
|
||||
this.environment = environment;
|
||||
this.userDAO = userDAO;
|
||||
this.institutionDAO = institutionDAO;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
|
@ -89,7 +94,11 @@ class AdminUserInitializer {
|
|||
}
|
||||
}
|
||||
} 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())
|
||||
.getOrElse(Collections::emptyList)
|
||||
.stream()
|
||||
|
|
|
@ -156,21 +156,21 @@ public class WebserviceInfo {
|
|||
}
|
||||
|
||||
final boolean spsEnabled = BooleanUtils.toBoolean(environment.getProperty(
|
||||
"sebserver.feature.seb.screenProctoring.enabled",
|
||||
"sebserver.feature.exam.seb.screenProctoring.enabled",
|
||||
Constants.FALSE_STRING));
|
||||
final boolean spsBundled = BooleanUtils.toBoolean(environment.getProperty(
|
||||
"sebserver.feature.seb.screenProctoring.bundled",
|
||||
"sebserver.feature.exam.seb.screenProctoring.bundled",
|
||||
Constants.FALSE_STRING));
|
||||
if (spsEnabled && spsBundled) {
|
||||
this.screenProctoringServiceBundle = new ScreenProctoringServiceBundle(
|
||||
environment.getProperty("sebserver.feature.seb.screenProctoring.bundled.url"),
|
||||
environment.getProperty("sebserver.feature.seb.screenProctoring.bundled.clientId"),
|
||||
environment.getProperty("sebserver.feature.exam.seb.screenProctoring.bundled.url"),
|
||||
environment.getProperty("sebserver.feature.exam.seb.screenProctoring.bundled.clientId"),
|
||||
cryptor.encrypt(
|
||||
environment.getProperty("sebserver.feature.seb.screenProctoring.bundled.clientPassword"))
|
||||
environment.getProperty("sebserver.feature.exam.seb.screenProctoring.bundled.clientPassword"))
|
||||
.getOrThrow(),
|
||||
environment.getProperty("sebserver.feature.seb.screenProctoring.bundled.sebserveraccount.username"),
|
||||
environment.getProperty("sebserver.feature.exam.seb.screenProctoring.bundled.sebserveraccount.username"),
|
||||
cryptor.encrypt(environment
|
||||
.getProperty("sebserver.feature.seb.screenProctoring.bundled.sebserveraccount.password"))
|
||||
.getProperty("sebserver.feature.exam.seb.screenProctoring.bundled.sebserveraccount.password"))
|
||||
.getOrThrow());
|
||||
} else {
|
||||
this.screenProctoringServiceBundle = new ScreenProctoringServiceBundle();
|
||||
|
@ -178,10 +178,10 @@ public class WebserviceInfo {
|
|||
}
|
||||
|
||||
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,
|
||||
featureService::isEnabledByConfig
|
||||
));
|
||||
)));
|
||||
}
|
||||
|
||||
public boolean isMaster() {
|
||||
|
|
|
@ -76,22 +76,26 @@ public class WebserviceInit implements ApplicationListener<ApplicationReadyEvent
|
|||
SEBServerInit.INIT_LOGGER.info("----> ");
|
||||
SEBServerInit.INIT_LOGGER.info("----> Register Webservice: {}", this.webserviceInfo.getWebserviceUUID());
|
||||
|
||||
if (this.webserviceInfoDAO.isInitialized()) {
|
||||
this.registerWebservice();
|
||||
try {
|
||||
if (this.webserviceInfoDAO.isInitialized()) {
|
||||
this.registerWebservice();
|
||||
|
||||
// Apply migration if needed and possible
|
||||
SEBServerInit.INIT_LOGGER.info("----> ");
|
||||
this.sebServerMigrationStrategy.applyMigration();
|
||||
SEBServerInit.INIT_LOGGER.info("----> ");
|
||||
// Apply migration if needed and possible
|
||||
SEBServerInit.INIT_LOGGER.info("----> ");
|
||||
this.sebServerMigrationStrategy.applyMigration();
|
||||
SEBServerInit.INIT_LOGGER.info("----> ");
|
||||
|
||||
} else {
|
||||
} else {
|
||||
|
||||
// Apply migration if needed and possible
|
||||
SEBServerInit.INIT_LOGGER.info("----> ");
|
||||
this.sebServerMigrationStrategy.applyMigration();
|
||||
SEBServerInit.INIT_LOGGER.info("----> ");
|
||||
// Apply migration if needed and possible
|
||||
SEBServerInit.INIT_LOGGER.info("----> ");
|
||||
this.sebServerMigrationStrategy.applyMigration();
|
||||
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("----> ");
|
||||
|
|
|
@ -216,7 +216,9 @@ public interface ExamDAO extends ActivatableEntityDAO<Exam, Exam>, BulkActionSup
|
|||
*
|
||||
* @param examId the Exam identifier */
|
||||
@CacheEvict(
|
||||
cacheNames = ExamSessionCacheService.CACHE_NAME_RUNNING_EXAM,
|
||||
cacheNames = {
|
||||
ExamSessionCacheService.CACHE_NAME_RUNNING_EXAM,
|
||||
ExamSessionCacheService.CACHE_NAME_SEB_CONFIG_EXAM },
|
||||
key = "#examId")
|
||||
void markUpdate(Long examId);
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
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 java.util.ArrayList;
|
||||
|
@ -23,6 +24,7 @@ import java.util.stream.Collectors;
|
|||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.mybatis.dynamic.sql.SqlBuilder;
|
||||
import org.mybatis.dynamic.sql.update.UpdateDSL;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
@ -260,15 +262,17 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
|
|||
public Result<ExamConfigurationMap> save(final ExamConfigurationMap data) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
final ExamConfigurationMapRecord newRecord = new ExamConfigurationMapRecord(
|
||||
data.id,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
getEncryptionPassword(data),
|
||||
data.clientGroupId);
|
||||
final String p = (StringUtils.isNotBlank(data.encryptSecret))
|
||||
? getEncryptionPassword(data)
|
||||
: null;
|
||||
|
||||
UpdateDSL.updateWithMapper(examConfigurationMapRecordMapper::update, examConfigurationMapRecord)
|
||||
.set(encryptSecret).equalTo(p )
|
||||
.set(clientGroupId).equalToWhenPresent(data.clientGroupId)
|
||||
.where(id, isEqualTo(data.id))
|
||||
.build()
|
||||
.execute();
|
||||
|
||||
this.examConfigurationMapRecordMapper.updateByPrimaryKeySelective(newRecord);
|
||||
return this.examConfigurationMapRecordMapper.selectByPrimaryKey(data.id);
|
||||
})
|
||||
.flatMap(this::toDomainModel)
|
||||
|
|
|
@ -489,7 +489,11 @@ public class SEBClientConfigDAOImpl implements SEBClientConfigDAO {
|
|||
additionalAttributes.containsKey(SEBClientConfig.ATTR_ENCRYPT_CERTIFICATE_ASYM),
|
||||
BooleanUtils.toBooleanObject(record.getActive()),
|
||||
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) {
|
||||
|
@ -685,6 +689,21 @@ public class SEBClientConfigDAOImpl implements SEBClientConfigDAO {
|
|||
configId,
|
||||
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) {
|
||||
|
|
|
@ -57,6 +57,7 @@ public class WebserviceInfoDAOImpl implements WebserviceInfoDAO {
|
|||
.execute();
|
||||
return true;
|
||||
} catch (final Exception e) {
|
||||
log.warn("DB Context not initialized yet: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,5 +63,5 @@ public interface ClientConfigService {
|
|||
boolean checkAccess(SEBClientConfig config);
|
||||
|
||||
@Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME)
|
||||
void initalCheckAccess(SEBClientConfig config);
|
||||
void initialCheckAccess(SEBClientConfig config);
|
||||
}
|
||||
|
|
|
@ -504,7 +504,7 @@ public class ClientConfigServiceImpl implements ClientConfigService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void initalCheckAccess(final SEBClientConfig config) {
|
||||
public void initialCheckAccess(final SEBClientConfig config) {
|
||||
checkAccess(config);
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
package ch.ethz.seb.sebserver.webservice.servicelayer.session;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.security.Principal;
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
@ -130,9 +131,10 @@ public interface ExamSessionService {
|
|||
/** Gets all currently running Exams for a particular Institution.
|
||||
*
|
||||
* @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
|
||||
* 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.
|
||||
*
|
||||
|
|
|
@ -13,7 +13,6 @@ import java.io.OutputStream;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
@ -251,9 +250,10 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
|||
}
|
||||
|
||||
@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)
|
||||
.map(col -> col.stream()
|
||||
.filter(examSelectionFilter)
|
||||
.map(this::getRunningExam)
|
||||
.filter(Result::hasValue)
|
||||
.map(Result::get)
|
||||
|
|
|
@ -11,17 +11,17 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api;
|
|||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Principal;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -132,7 +132,10 @@ public class ExamAPI_V1_Controller {
|
|||
// Crate list of running exams
|
||||
final List<RunningExamInfo> result;
|
||||
if (examId == null) {
|
||||
result = this.examSessionService.getRunningExamsForInstitution(institutionId)
|
||||
|
||||
result = this.examSessionService.getRunningExams(
|
||||
institutionId,
|
||||
getExamSelectionPredicate(principal.getName()))
|
||||
.getOrThrow()
|
||||
.stream()
|
||||
.map(this::createRunningExamInfo)
|
||||
|
@ -161,17 +164,7 @@ public class ExamAPI_V1_Controller {
|
|||
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(
|
||||
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));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -707,6 +707,9 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
|||
examSessionService.hasActiveSEBClientConnections(exam.id)) {
|
||||
final Exam oldExam = this.examDAO.byPK(exam.id).getOrThrow();
|
||||
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)) {
|
||||
throw new APIMessageException(APIMessage.fieldValidationError(
|
||||
new FieldError(
|
||||
|
|
|
@ -13,6 +13,8 @@ import java.util.List;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.mybatis.dynamic.sql.SqlTable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
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)
|
||||
public class ExamConfigurationMappingController extends EntityController<ExamConfigurationMap, ExamConfigurationMap> {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ExamConfigurationMappingController.class);
|
||||
|
||||
private final ExamDAO examDao;
|
||||
private final ConfigurationNodeDAO configurationNodeDAO;
|
||||
private final ExamConfigUpdateService examConfigUpdateService;
|
||||
|
@ -133,6 +137,18 @@ public class ExamConfigurationMappingController extends EntityController<ExamCon
|
|||
.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
|
||||
@RequestMapping(
|
||||
method = RequestMethod.POST,
|
||||
|
@ -154,7 +170,6 @@ public class ExamConfigurationMappingController extends EntityController<ExamCon
|
|||
final ExamConfigurationMap requestModel = this.createNew(postMap);
|
||||
return this.checkCreateAccess(requestModel)
|
||||
.flatMap(this::validForCreate)
|
||||
.map(this::checkPasswordMatch)
|
||||
.flatMap(entity -> this.examConfigUpdateService.processExamConfigurationMappingChange(
|
||||
entity,
|
||||
this.entityDAO::createNew))
|
||||
|
|
|
@ -10,10 +10,15 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
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.boot.autoconfigure.condition.ConditionalOnExpression;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
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.TooManyRequests;
|
||||
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.webservice.servicelayer.dao.InstitutionDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
|
||||
|
@ -51,6 +52,8 @@ public class RegisterUserController {
|
|||
private final BeanValidationService beanValidationService;
|
||||
private final LocalBucket requestRateLimitBucket;
|
||||
private final LocalBucket createRateLimitBucket;
|
||||
private final boolean registeringEnabled;
|
||||
private final boolean autoActivation;
|
||||
|
||||
protected RegisterUserController(
|
||||
final InstitutionDAO institutionDAO,
|
||||
|
@ -58,12 +61,15 @@ public class RegisterUserController {
|
|||
final UserDAO userDAO,
|
||||
final BeanValidationService beanValidationService,
|
||||
final RateLimitService rateLimitService,
|
||||
final WebserviceInfo webserviceInfo,
|
||||
@Qualifier(WebSecurityConfig.USER_PASSWORD_ENCODER_BEAN_NAME) final PasswordEncoder userPasswordEncoder) {
|
||||
|
||||
final Map<String, Boolean> features = webserviceInfo.configuredFeatures();
|
||||
this.userActivityLogDAO = userActivityLogDAO;
|
||||
this.userDAO = userDAO;
|
||||
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.createRateLimitBucket = rateLimitService.createCreationLimitBucker();
|
||||
}
|
||||
|
@ -76,6 +82,10 @@ public class RegisterUserController {
|
|||
@RequestParam final MultiValueMap<String, String> allRequestParams,
|
||||
final HttpServletRequest request) {
|
||||
|
||||
if (!registeringEnabled) {
|
||||
throw new RuntimeException("Registering is not enabled from backend!");
|
||||
}
|
||||
|
||||
if (!this.requestRateLimitBucket.tryConsume(1)) {
|
||||
throw new TooManyRequests();
|
||||
}
|
||||
|
@ -107,7 +117,7 @@ public class RegisterUserController {
|
|||
return userAccount;
|
||||
})
|
||||
.flatMap(this.userDAO::createNew)
|
||||
.flatMap(account -> this.userDAO.setActive(account, true))
|
||||
.flatMap(account -> this.userDAO.setActive(account, autoActivation))
|
||||
.flatMap(this.userActivityLogDAO::logRegisterAccount)
|
||||
.flatMap(account -> this.userDAO.byModelId(account.getModelId()))
|
||||
.getOrThrow();
|
||||
|
|
|
@ -182,7 +182,7 @@ public class SEBClientConfigController extends ActivatableEntityController<SEBCl
|
|||
protected Result<SEBClientConfig> notifySaved(final SEBClientConfig entity) {
|
||||
if (entity.isActive()) {
|
||||
// try to get access token for SEB client
|
||||
this.sebClientConfigService.initalCheckAccess(entity);
|
||||
this.sebClientConfigService.initialCheckAccess(entity);
|
||||
}
|
||||
return super.notifySaved(entity);
|
||||
}
|
||||
|
|
|
@ -10,12 +10,16 @@ package ch.ethz.seb.sebserver.webservice.weblayer.oauth;
|
|||
|
||||
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.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.webservice.weblayer.WebServiceUserDetails;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@WebServiceProfile
|
||||
public class PreAuthProvider extends PreAuthenticatedAuthenticationProvider {
|
||||
|
||||
private final WebServiceUserDetails webServiceUserDetails;
|
||||
|
|
|
@ -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/*
|
||||
|
||||
# features
|
||||
sebserver.feature.seb.screenProctoring.enabled=true
|
||||
sebserver.feature.seb.screenProctoring.bundled=true
|
||||
sebserver.feature.seb.screenProctoring.bundled.url=http://localhost:8090
|
||||
sebserver.feature.seb.screenProctoring.bundled.clientId=sebserverClient
|
||||
sebserver.feature.seb.screenProctoring.bundled.sebserveraccount.username=SEBServerAPIAccount
|
||||
sebserver.feature.exam.seb.screenProctoring.enabled=true
|
||||
sebserver.feature.exam.seb.screenProctoring.bundled=true
|
||||
sebserver.feature.exam.seb.screenProctoring.bundled.url=http://localhost:8090
|
||||
sebserver.feature.exam.seb.screenProctoring.bundled.clientId=sebserverClient
|
||||
sebserver.feature.exam.seb.screenProctoring.bundled.sebserveraccount.username=SEBServerAPIAccount
|
||||
|
||||
#sebserver.feature.admin.user.administration.enabled=false
|
||||
#sebserver.feature.admin.user.account.enabled=false
|
||||
|
|
|
@ -95,15 +95,18 @@ sebserver.webservice.configtemplate.examconfig.default.description=This has auto
|
|||
|
||||
# features
|
||||
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.lms.type.MOCKUP.enabled=true
|
||||
sebserver.feature.exam.noLMS.enabled=true
|
||||
|
||||
sebserver.feature.seb.screenProctoring.enabled=false
|
||||
sebserver.feature.seb.screenProctoring.bundled=true
|
||||
sebserver.feature.seb.screenProctoring.bundled.url=sps-service:8090
|
||||
sebserver.feature.seb.screenProctoring.bundled.clientId=sebserverClient
|
||||
sebserver.feature.seb.screenProctoring.bundled.clientPassword=${sps.sebserver.client.secret}
|
||||
sebserver.feature.seb.screenProctoring.bundled.sebserveraccount.username=SEBServerAPIAccount
|
||||
sebserver.feature.seb.screenProctoring.bundled.sebserveraccount.password=${sps.sebserver.password}
|
||||
sebserver.feature.exam.seb.screenProctoring.enabled=false
|
||||
sebserver.feature.exam.seb.screenProctoring.bundled=true
|
||||
sebserver.feature.exam.seb.screenProctoring.bundled.url=sps-service:8090
|
||||
sebserver.feature.exam.seb.screenProctoring.bundled.clientId=sebserverClient
|
||||
sebserver.feature.exam.seb.screenProctoring.bundled.clientPassword=${sps.sebserver.client.secret}
|
||||
sebserver.feature.exam.seb.screenProctoring.bundled.sebserveraccount.username=SEBServerAPIAccount
|
||||
sebserver.feature.exam.seb.screenProctoring.bundled.sebserveraccount.password=${sps.sebserver.password}
|
||||
|
||||
|
|
|
@ -129,6 +129,7 @@ sebserver.form.mandatory.label={0} mandatory
|
|||
sebserver.form.confirm.label=confirm {0}
|
||||
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.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.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.do=Create Account
|
||||
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.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.delete=Remove 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.collectingRoomSize=Collecting Room Size
|
||||
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.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.type.async=Use asymmetric-only encryption
|
||||
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.info=A SEB client that loads this connection configuration<br/>uses the following credentials to securely connect to the SEB Server.
|
||||
|
|
|
@ -138,7 +138,8 @@ public class ModelObjectJSONGenerator {
|
|||
false,
|
||||
true,
|
||||
DateTime.now(),
|
||||
"user123");
|
||||
"user123",
|
||||
null);
|
||||
System.out.println(domainObject.getClass().getSimpleName() + ":");
|
||||
System.out.println(writerWithDefaultPrettyPrinter.writeValueAsString(domainObject));
|
||||
|
||||
|
|
|
@ -128,6 +128,7 @@ public class ClientConfigTest extends GuiIntegrationTest {
|
|||
false,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null))
|
||||
.call();
|
||||
|
||||
|
@ -161,6 +162,7 @@ public class ClientConfigTest extends GuiIntegrationTest {
|
|||
false,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null))
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
|
|
@ -12,6 +12,7 @@ import static org.junit.Assert.*;
|
|||
|
||||
import java.util.Collection;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.Order;
|
||||
|
@ -61,7 +62,7 @@ public class ExamProctoringRoomServiceTest extends AdministrationAPIIntegrationT
|
|||
@Order(1)
|
||||
public void test01_checkExamRunning() {
|
||||
final Result<Collection<Exam>> runningExamsForInstitution =
|
||||
this.examSessionService.getRunningExamsForInstitution(1L);
|
||||
this.examSessionService.getRunningExams(1L, Utils.truePredicate());
|
||||
assertFalse(runningExamsForInstitution.hasError());
|
||||
final Collection<Exam> collection = runningExamsForInstitution.get();
|
||||
assertFalse(collection.isEmpty());
|
||||
|
|
|
@ -45,7 +45,7 @@ management.endpoints.web.base-path=/actuator
|
|||
sebserver.webservice.api.exam.indicator.name=Ping
|
||||
sebserver.webservice.api.exam.indicator.type=LAST_PING
|
||||
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.feature.seb.screenProctoring.bundled=false
|
||||
|
||||
sebserver.feature.exam.seb.screenProctoring.bundled=false
|
Loading…
Reference in a new issue