diff --git a/app/controllers/ExamController.java b/app/controllers/ExamController.java index 9f3ad3a9e..358d63c20 100644 --- a/app/controllers/ExamController.java +++ b/app/controllers/ExamController.java @@ -40,6 +40,7 @@ import models.ExamType; import models.GradeScale; import models.Language; +import models.Permission; import models.Role; import models.Software; import models.User; @@ -494,6 +495,12 @@ public Result createExamDraft(Http.Request request) { return badRequest("Unsupported execution type"); } User user = request.attrs().get(Attrs.AUTHENTICATED_USER); + if ( + !implementation.equals(Exam.Implementation.AQUARIUM.toString()) && + !user.hasPermission(Permission.Type.CAN_CREATE_BYOD_EXAM) + ) { + return forbidden("No permission to create home examinations"); + } Exam exam = new Exam(); exam.generateHash(); exam.setState(Exam.State.DRAFT); diff --git a/app/controllers/SessionController.java b/app/controllers/SessionController.java index 3bf2fec31..8b320701c 100644 --- a/app/controllers/SessionController.java +++ b/app/controllers/SessionController.java @@ -45,6 +45,7 @@ import models.ExamEnrolment; import models.Language; import models.Organisation; +import models.Permission; import models.Reservation; import models.Role; import models.User; @@ -329,6 +330,15 @@ private void updateUser(User user, Http.Request request) throws AddressException user.setUserIdentifier( parse(request.header("schacPersonalUniqueCode").orElse("")).map(this::parseUserIdentifier).orElse(null) ); + // Grant BYOD permission automatically for teachers if configuration so mandates + if (user.hasRole(Role.Name.TEACHER) && configReader.isByodExamCreationPermissionGrantedForNewUsers()) { + Permission permission = DB + .find(Permission.class) + .where() + .eq("type", Permission.Type.CAN_CREATE_BYOD_EXAM) + .findOne(); + user.getPermissions().add(permission); + } user.setEmail( parse(request.header("mail").orElse("")) .flatMap(this::validateEmail) diff --git a/app/models/Permission.java b/app/models/Permission.java index 84c06b470..ed389cce9 100644 --- a/app/models/Permission.java +++ b/app/models/Permission.java @@ -27,6 +27,8 @@ public class Permission extends GeneratedIdentityModel implements be.objectify.d public enum Type { @EnumValue("1") CAN_INSPECT_LANGUAGE, + @EnumValue("2") + CAN_CREATE_BYOD_EXAM, } private Type type; diff --git a/app/util/config/ConfigReader.java b/app/util/config/ConfigReader.java index 5b1b56eac..b9a3349a6 100644 --- a/app/util/config/ConfigReader.java +++ b/app/util/config/ConfigReader.java @@ -35,6 +35,7 @@ public interface ConfigReader { String getSettingsPasswordEncryptionKey(); String getHomeOrganisationRef(); Integer getMaxByodExaminationParticipantCount(); + boolean isByodExamCreationPermissionGrantedForNewUsers(); String getCourseCodePrefix(); String getIopHost(); boolean isApiKeyUsed(); diff --git a/app/util/config/ConfigReaderImpl.java b/app/util/config/ConfigReaderImpl.java index 698ad1c03..80103def0 100644 --- a/app/util/config/ConfigReaderImpl.java +++ b/app/util/config/ConfigReaderImpl.java @@ -207,6 +207,11 @@ public Integer getMaxByodExaminationParticipantCount() { return config.getInt("exam.byod.maxConcurrentParticipants"); } + @Override + public boolean isByodExamCreationPermissionGrantedForNewUsers() { + return config.getBoolean("exam.byod.permission.allowed"); + } + @Override public String getCourseCodePrefix() { return config.getString("exam.course.code.prefix"); diff --git a/conf/application.conf b/conf/application.conf index 9e98e5781..765dbffbc 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -197,6 +197,8 @@ exam.user.studentIds.multiple.organisations = "org1.org,org2.org,org3.org" exam.byod.seb.active = false # Enable / disable support for unsupervised home examination exam.byod.home.active = false +# Automatically grant permission to create BYOD examinations for new users with teacher role +exam.byod.permission.allowed = true # Maximum number of concurrent BYOD examination participants exam.byod.maxConcurrentParticipants = 100000 # SEB configuration diff --git a/conf/evolutions/default/132.sql b/conf/evolutions/default/132.sql index 6ad3979bd..9cb55ed5f 100644 --- a/conf/evolutions/default/132.sql +++ b/conf/evolutions/default/132.sql @@ -1,4 +1,5 @@ # --- !Ups +<<<<<<< HEAD ALTER TABLE examination_event_configuration ADD encrypted_quit_password BYTEA NULL; ALTER TABLE examination_event_configuration ADD quit_password_salt VARCHAR(36) NULL; # --- !Downs diff --git a/conf/evolutions/default/133.sql b/conf/evolutions/default/133.sql new file mode 100644 index 000000000..f214fd8c0 --- /dev/null +++ b/conf/evolutions/default/133.sql @@ -0,0 +1,5 @@ +# --- !Ups +INSERT INTO permission (id, object_version, type, description) VALUES (2, 1, 2, 'can create BYOD exams') + +# --- !Downs +DELETE FROM permission where id = 2; diff --git a/ui/src/app/administrative/users/users.component.html b/ui/src/app/administrative/users/users.component.html index 1d7ba4d6c..c2eb5b355 100644 --- a/ui/src/app/administrative/users/users.component.html +++ b/ui/src/app/administrative/users/users.component.html @@ -56,7 +56,7 @@ } - + } diff --git a/ui/src/app/session/session.service.ts b/ui/src/app/session/session.service.ts index fc3f38ba1..bfb213804 100644 --- a/ui/src/app/session/session.service.ts +++ b/ui/src/app/session/session.service.ts @@ -53,6 +53,7 @@ export interface User { isLanguageInspector: boolean; employeeNumber: string | null; lastLogin: string | null; + canCreateByodExam: boolean; } interface Env { @@ -245,6 +246,7 @@ export class SessionService implements OnDestroy { user.isTeacher = role.name === 'TEACHER'; user.isStudent = role.name === 'STUDENT'; user.isLanguageInspector = user.isTeacher && this.hasPermission(user, 'CAN_INSPECT_LANGUAGE'); + user.canCreateByodExam = !user.isStudent && this.hasPermission(user, 'CAN_CREATE_BYOD_EXAM'); return user; }), ); @@ -259,11 +261,11 @@ export class SessionService implements OnDestroy { break; case 'TEACHER': role.displayName = 'i18n_teacher'; - role.icon = 'bi-person-fill'; + role.icon = 'bi-person'; break; case 'STUDENT': role.displayName = 'i18n_student'; - role.icon = 'bi-person'; + role.icon = 'bi-mortarboard'; break; } }); @@ -278,6 +280,7 @@ export class SessionService implements OnDestroy { isAdmin: loginRole != null && loginRole === 'ADMIN', isStudent: loginRole != null && loginRole === 'STUDENT', isLanguageInspector: isTeacher && this.hasPermission(user, 'CAN_INSPECT_LANGUAGE'), + canCreateByodExam: loginRole !== 'STUDENT' && this.hasPermission(user, 'CAN_CREATE_BYOD_EXAM'), })), ); } diff --git a/ui/src/assets/i18n/en.json b/ui/src/assets/i18n/en.json index cdb6f44cc..761c8d787 100644 --- a/ui/src/assets/i18n/en.json +++ b/ui/src/assets/i18n/en.json @@ -1159,5 +1159,6 @@ "i18n_button_preview": "Preview question", "i18n_no_preview_available": "No preview available", "i18n_used_in_exams": "Used in exams", - "i18n_quit_password": "SEB-poistumissalasana EN" + "i18n_quit_password": "SEB-poistumissalasana EN", + "i18n_can_create_byod_exam": "Voi luoda omakonetentin EN" } diff --git a/ui/src/assets/i18n/fi.json b/ui/src/assets/i18n/fi.json index f711b9ba1..cbb9e6e5b 100644 --- a/ui/src/assets/i18n/fi.json +++ b/ui/src/assets/i18n/fi.json @@ -1159,5 +1159,6 @@ "i18n_button_preview": "Esikatsele kysymys", "i18n_no_preview_available": "Kysymyksen esikatselu ei ole saatavilla", "i18n_used_in_exams": "Käytössä tenteissä", - "i18n_quit_password": "SEB-poistumissalasana" + "i18n_quit_password": "SEB-poistumissalasana", + "i18n_can_create_byod_exam": "Voi luoda omakonetentin" } diff --git a/ui/src/assets/i18n/sv.json b/ui/src/assets/i18n/sv.json index fb763b1f8..109c46551 100644 --- a/ui/src/assets/i18n/sv.json +++ b/ui/src/assets/i18n/sv.json @@ -1159,5 +1159,6 @@ "i18n_button_preview": "Preview question SV", "i18n_no_preview_available": "No preview available SV", "i18n_used_in_exams": "Käytössä tenteissä SV", - "i18n_quit_password": "SEB-poistumissalasana SV" + "i18n_quit_password": "SEB-poistumissalasana SV", + "i18n_can_create_byod_exam": "Voi luoda omakonetentin SV" }