diff --git a/api/__tests__/routes/user.test.ts b/api/__tests__/routes/user.test.ts index eab8f4dd..f23c48d0 100644 --- a/api/__tests__/routes/user.test.ts +++ b/api/__tests__/routes/user.test.ts @@ -831,6 +831,85 @@ describe("User tests", () => { }); }); + describe("Bugs", () => { + describe("Issue 491: Student is able to make himself Admin", () => { + describe("Requests to change permissions from users without administrator priviledges must be rejected", () => { + test("Student's request must be rejected", async () => { + runner.authLevel(AuthenticationLevel.STUDENT); + await runner.patch({ + url: "/user/1", + data: { admin: true }, + expectedResponse: forbiddenResponse, + statusCode: 403, + }); + }); + + test("Superstudent's request must be rejected", async () => { + runner.authLevel(AuthenticationLevel.SUPER_STUDENT); + await runner.patch({ + url: "/user/1", + data: { admin: true }, + expectedResponse: forbiddenResponse, + statusCode: 403, + }); + }); + + test("Syndicus' request must be rejected", async () => { + runner.authLevel(AuthenticationLevel.SYNDICUS); + await runner.patch({ + url: "/user/1", + data: { admin: true }, + expectedResponse: forbiddenResponse, + statusCode: 403, + }); + }); + }); + }); + + test("Administrator must be allowed to perform permission change", async () => { + runner.authLevel(AuthenticationLevel.ADMINISTRATOR); + await runner.patch({ + url: "/user/1", + data: { super_student: true }, + expectedResponse: { + id: 1, + email: "student@trottoir.be", + first_name: "Dirk", + last_name: "De Student", + last_login: "2023-05-04T12:00:00.000Z", + date_added: "2023-05-04T12:00:00.000Z", + phone: "0123456789", + address_id: 1, + student: true, + super_student: true, + admin: false, + deleted: false, + address: { + id: 1, + street: "Wallaby Way", + number: 42, + city: "Sydney", + zip_code: 2000, + latitude: -33.865143, + longitude: 151.2099, + }, + regions: [ + { + id: 1, + user_id: 1, + region_id: 1, + region: { + id: 1, + name: "Region 1", + deleted: false, + }, + }, + ], + }, + }); + }); + }); + afterAll(() => { app.close(); }); diff --git a/api/src/routes/user.ts b/api/src/routes/user.ts index d9a08a8e..2387e4a0 100644 --- a/api/src/routes/user.ts +++ b/api/src/routes/user.ts @@ -127,6 +127,11 @@ export class UserRouting extends Routing { throw new APIError(APIErrorCode.FORBIDDEN); } + // only admins are allowed to change permission levels + if (!req.user?.admin && containsPermChange(req.body)) { + throw new APIError(APIErrorCode.FORBIDDEN); + } + // The body of a request can't be empty and can't contain a hash or salt if (req.body == null || req.body.hash || req.body.salt) { throw new APIError(APIErrorCode.BAD_REQUEST); @@ -180,3 +185,7 @@ export class UserRouting extends Routing { return new UserValidator(); } } + +const containsPermChange = (body: object): boolean => { + return "admin" in body || "super_student" in body || "student" in body; +}; diff --git a/web/src/views/student/SchedulingScreenStudents.vue b/web/src/views/student/SchedulingScreenStudents.vue index 3f7f7cfb..733f7fa0 100644 --- a/web/src/views/student/SchedulingScreenStudents.vue +++ b/web/src/views/student/SchedulingScreenStudents.vue @@ -60,7 +60,7 @@ variant="text" size="compact" > - {{ new Date(item.schedule.day).toISOString().slice(11, 16) }} + {{ new Date(item.schedule.day).toLocaleTimeString().slice(0,5) }}