From c02f215ffc1c340eb075febeada37cb00044b27e Mon Sep 17 00:00:00 2001 From: Matti Lupari Date: Thu, 11 Jan 2024 11:05:13 +0200 Subject: [PATCH] CSCEXAM-1237 Permission for creating BYOD exams --- app/controllers/ExamController.java | 7 +++++++ app/controllers/SessionController.java | 10 +++++++++ app/models/Permission.java | 2 ++ app/util/config/ConfigReader.java | 1 + app/util/config/ConfigReaderImpl.java | 5 +++++ conf/application.conf | 2 ++ conf/evolutions/default/133.sql | 5 +++++ .../administrative/users/users.component.html | 21 ++++++++++--------- .../administrative/users/users.component.ts | 13 +++++++++--- .../app/administrative/users/users.service.ts | 1 + .../editor/creation/new-exam.component.html | 16 ++++++++++---- .../editor/creation/new-exam.component.ts | 4 ++++ .../role/role-picker-dialog.component.ts | 2 +- ui/src/app/session/session.service.ts | 7 +++++-- ui/src/assets/i18n/en.json | 3 ++- ui/src/assets/i18n/fi.json | 3 ++- ui/src/assets/i18n/sv.json | 3 ++- 17 files changed, 82 insertions(+), 23 deletions(-) create mode 100644 conf/evolutions/default/133.sql diff --git a/app/controllers/ExamController.java b/app/controllers/ExamController.java index 9f3ad3a9e0..13f564173c 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 ( + Exam.Implementation.valueOf(implementation) != Exam.Implementation.AQUARIUM && + !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 3bf2fec312..8b320701c0 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 84c06b4703..ed389cce96 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 5b1b56eac3..b9a3349a62 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 698ad1c03a..80103def07 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 9e98e57815..765dbffbc0 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/133.sql b/conf/evolutions/default/133.sql new file mode 100644 index 0000000000..f214fd8c0b --- /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 1d7ba4d6c8..157262d37c 100644 --- a/ui/src/app/administrative/users/users.component.html +++ b/ui/src/app/administrative/users/users.component.html @@ -56,7 +56,7 @@ } - + @@ -178,8 +177,7 @@ > @if (user.removableRoles.length > 1) { - } - @if (user.removableRoles.length <= 1) { + } @else { @if (hasPermission(user, 'CAN_INSPECT_LANGUAGE')) { - + + } + @if (hasPermission(user, 'CAN_CREATE_BYOD_EXAM')) { + }
@@ -296,7 +297,7 @@ } - @if (filteredUsers && filteredUsers.length > pageSize) { + @if (filteredUsers.length > pageSize) {
- + @if (sebExaminationSupported) { - } @if (homeExaminationSupported) { - } diff --git a/ui/src/app/exam/editor/creation/new-exam.component.ts b/ui/src/app/exam/editor/creation/new-exam.component.ts index 4bb1bf0dfc..9651ef5014 100644 --- a/ui/src/app/exam/editor/creation/new-exam.component.ts +++ b/ui/src/app/exam/editor/creation/new-exam.component.ts @@ -19,6 +19,7 @@ import { Component } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule } from '@ngx-translate/core'; +import { SessionService } from 'src/app/session/session.service'; import { HistoryBackComponent } from '../../../shared/history/history-back.component'; import type { ExamExecutionType, Implementation } from '../../exam.model'; import { ExamService } from '../../exam.service'; @@ -35,13 +36,16 @@ export class NewExamComponent implements OnInit { examinationType: Implementation = 'AQUARIUM'; homeExaminationSupported = false; sebExaminationSupported = false; + canCreateByodExams = false; constructor( private http: HttpClient, private Exam: ExamService, + private Session: SessionService, ) {} ngOnInit() { + this.canCreateByodExams = this.Session.getUser().canCreateByodExam; this.Exam.listExecutionTypes$().subscribe((types) => { this.executionTypes = types; this.http diff --git a/ui/src/app/session/role/role-picker-dialog.component.ts b/ui/src/app/session/role/role-picker-dialog.component.ts index 1eacbcfe0d..e847c4f106 100644 --- a/ui/src/app/session/role/role-picker-dialog.component.ts +++ b/ui/src/app/session/role/role-picker-dialog.component.ts @@ -38,7 +38,7 @@ import type { User } from '../session.service'; title="{{ role.displayName || '' | translate }}" (click)="activeModal.close(role)" > - {{ role.displayName || '' | translate }} + {{ role.displayName || '' | translate }} }
diff --git a/ui/src/app/session/session.service.ts b/ui/src/app/session/session.service.ts index fc3f38ba11..bfb213804f 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 cdb6f44cc4..761c8d7875 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 f711b9ba1a..cbb9e6e5b3 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 fb763b1f8c..109c46551f 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" }