From 6362e848d75fdbcca791ed2b02164bc62480224e Mon Sep 17 00:00:00 2001 From: Matthias-VE Date: Thu, 4 Apr 2024 17:18:21 +0200 Subject: [PATCH 001/446] Start cookie session with frontend --- backend/web-bff/App/app.js | 56 ++++++------ backend/web-bff/App/bin/www.js | 12 +-- backend/web-bff/App/package.json | 5 ++ frontend/package-lock.json | 142 +++++++++++++++++++++++++++++++ frontend/package.json | 1 + frontend/src/setupProxy.tsx | 5 ++ 6 files changed, 191 insertions(+), 30 deletions(-) create mode 100644 frontend/src/setupProxy.tsx diff --git a/backend/web-bff/App/app.js b/backend/web-bff/App/app.js index d4031e83..f6aa8491 100644 --- a/backend/web-bff/App/app.js +++ b/backend/web-bff/App/app.js @@ -1,38 +1,46 @@ -/* - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. - */ - require('dotenv').config(); -var path = require('path'); -var express = require('express'); -var session = require('express-session'); -var createError = require('http-errors'); -var cookieParser = require('cookie-parser'); -var logger = require('morgan'); +const path = require('path'); +const express = require('express'); +const session = require('cookie-session'); +const createError = require('http-errors'); +const cookieParser = require('cookie-parser'); +const logger = require('morgan'); +const helmet = require('helmet'); +const hpp = require('hpp'); +const csurf = require('csurf'); +const rateLimit = require('express-rate-limit') + +const indexRouter = require('./routes/index'); +const usersRouter = require('./routes/users'); +const authRouter = require('./routes/auth'); + +/* initialize express */ +const app = express(); -var indexRouter = require('./routes/index'); -var usersRouter = require('./routes/users'); -var authRouter = require('./routes/auth'); +/* Set security configs */ +app.use(helmet()); +app.use(hpp()); -// initialize express -var app = express(); /** - * Using express-session middleware for persistent user session. Be sure to - * familiarize yourself with available options. Visit: https://www.npmjs.com/package/express-session + * Using cookie-session middleware for persistent user session. */ app.use(session({ + name: 'session', secret: process.env.EXPRESS_SESSION_SECRET, - resave: false, - saveUninitialized: false, - cookie: { - httpOnly: true, - secure: false, // set this to true on production - } + expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days })); +app.use(csurf(undefined)); + +const limiter = rateLimit({ + windowMs: 15 * 60 * 1000, + max: 100, +}); + +app.use(limiter); + // view engine setup app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'hbs'); diff --git a/backend/web-bff/App/bin/www.js b/backend/web-bff/App/bin/www.js index 092eb15d..0c794afe 100644 --- a/backend/web-bff/App/bin/www.js +++ b/backend/web-bff/App/bin/www.js @@ -4,22 +4,22 @@ * Module dependencies. */ -var app = require('../app'); -var debug = require('debug')('msal:server'); -var http = require('http'); +const app = require('../app'); +const debug = require('debug')('msal:server'); +const http = require('http'); /** * Get port from environment and store in Express. */ -var port = normalizePort(process.env.PORT || '3000'); +const port = normalizePort(process.env.PORT || '3000'); app.set('port', port); /** * Create HTTP server. */ -var server = http.createServer(app); +const server = http.createServer(app); /** * Listen on provided port, on all network interfaces. @@ -34,7 +34,7 @@ server.on('listening', onListening); */ function normalizePort(val) { - var port = parseInt(val, 10); + const port = parseInt(val, 10); if (isNaN(port)) { // named pipe diff --git a/backend/web-bff/App/package.json b/backend/web-bff/App/package.json index 1deeab35..f1b2a7d5 100644 --- a/backend/web-bff/App/package.json +++ b/backend/web-bff/App/package.json @@ -8,11 +8,16 @@ "@azure/msal-node": "^2.6.4", "axios": "^1.6.8", "cookie-parser": "^1.4.6", + "cookie-session": "^2.1.0", + "csurf": "^1.11.0", "debug": "^4.3.4", "dotenv": "^16.4.1", "express": "^4.19.1", + "express-rate-limit": "^7.2.0", "express-session": "^1.18.0", "hbs": "^4.2.0", + "helmet": "^7.1.0", + "hpp": "^0.2.3", "http-errors": "^2.0.0", "morgan": "^1.10.0" } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e85a99dd..f4e5bbf4 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -23,6 +23,7 @@ "antd": "^5.14.2", "axios": "^1.6.7", "highlight.js": "^11.9.0", + "http-proxy-middleware": "^3.0.0", "i18next-localstorage-cache": "^1.1.1", "lowlight": "^3.1.0", "react": "^18.2.0", @@ -1671,6 +1672,14 @@ "@types/unist": "*" } }, + "node_modules/@types/http-proxy": { + "version": "1.17.14", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", + "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/jest": { "version": "27.5.2", "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.2.tgz", @@ -1957,6 +1966,17 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/browserslist": { "version": "4.23.0", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", @@ -2398,6 +2418,11 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -2415,6 +2440,17 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/follow-redirects": { "version": "1.15.6", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", @@ -2734,6 +2770,46 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-middleware": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.0.tgz", + "integrity": "sha512-36AV1fIaI2cWRzHo+rbcxhe3M3jUDCNzc4D5zRl57sEWRAxdXYtw7FSQKYY6PDKssiAKjLYypbssHk+xs/kMXw==", + "dependencies": { + "@types/http-proxy": "^1.17.10", + "debug": "^4.3.4", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.5" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/http-proxy-middleware/node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/i18next": { "version": "23.10.0", "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.10.0.tgz", @@ -2900,6 +2976,25 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-hexadecimal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", @@ -2917,6 +3012,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/is-number-object": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", @@ -3849,6 +3952,18 @@ } ] }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -3980,6 +4095,17 @@ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/postcss": { "version": "8.4.35", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", @@ -4949,6 +5075,11 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, "node_modules/resize-observer-polyfill": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", @@ -5160,6 +5291,17 @@ "node": ">=4" } }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/toggle-selection": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", diff --git a/frontend/package.json b/frontend/package.json index e05fee19..08ad1a99 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,6 +18,7 @@ "antd": "^5.14.2", "axios": "^1.6.7", "highlight.js": "^11.9.0", + "http-proxy-middleware": "^3.0.0", "i18next-localstorage-cache": "^1.1.1", "lowlight": "^3.1.0", "react": "^18.2.0", diff --git a/frontend/src/setupProxy.tsx b/frontend/src/setupProxy.tsx new file mode 100644 index 00000000..192825f8 --- /dev/null +++ b/frontend/src/setupProxy.tsx @@ -0,0 +1,5 @@ +const proxy = require('http-proxy-middleware').createProxyMiddleware; + +module.exports = function (app) { + app.use(proxy(`/auth/**`, {target: 'http://localhost:3000' })); +} \ No newline at end of file From 15bb9a2249a00adbd59e71646700a8a1979e6287 Mon Sep 17 00:00:00 2001 From: Matthias-VE Date: Sun, 7 Apr 2024 18:40:04 +0200 Subject: [PATCH 002/446] use client side session cookies --- backend/web-bff/App/app.js | 4 ++-- backend/web-bff/App/auth/AuthProvider.js | 9 ++++----- backend/web-bff/App/fetch.js | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/backend/web-bff/App/app.js b/backend/web-bff/App/app.js index f6aa8491..7babb3ac 100644 --- a/backend/web-bff/App/app.js +++ b/backend/web-bff/App/app.js @@ -32,7 +32,7 @@ app.use(session({ expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days })); -app.use(csurf(undefined)); + const limiter = rateLimit({ windowMs: 15 * 60 * 1000, @@ -71,4 +71,4 @@ app.use(function (err, req, res, next) { res.render('error'); }); -module.exports = app; \ No newline at end of file +module.exports = app; diff --git a/backend/web-bff/App/auth/AuthProvider.js b/backend/web-bff/App/auth/AuthProvider.js index f2fb8b8f..5e055205 100644 --- a/backend/web-bff/App/auth/AuthProvider.js +++ b/backend/web-bff/App/auth/AuthProvider.js @@ -125,7 +125,6 @@ class AuthProvider { if (!req.body || !req.body.state) { return next(new Error('Error: response not found')); } - const authCodeRequest = { ...req.session.authCodeRequest, code: req.body.code, @@ -141,11 +140,11 @@ class AuthProvider { const tokenResponse = await msalInstance.acquireTokenByCode(authCodeRequest, req.body); - req.session.tokenCache = msalInstance.getTokenCache().serialize(); + // req.session.tokenCache = msalInstance.getTokenCache().serialize(); req.session.idToken = tokenResponse.idToken; - req.session.account = tokenResponse.account; + // req.session.account = tokenResponse.account; req.session.isAuthenticated = true; - + const state = JSON.parse(this.cryptoProvider.base64Decode(req.body.state)); res.redirect(state.successRedirect); } catch (error) { @@ -270,4 +269,4 @@ class AuthProvider { const authProvider = new AuthProvider(msalConfig); -module.exports = authProvider; \ No newline at end of file +module.exports = authProvider; diff --git a/backend/web-bff/App/fetch.js b/backend/web-bff/App/fetch.js index b3a2f6f8..bea3d975 100644 --- a/backend/web-bff/App/fetch.js +++ b/backend/web-bff/App/fetch.js @@ -38,4 +38,4 @@ async function fetch(endpoint, accessToken) { } } -module.exports = fetch; \ No newline at end of file +module.exports = fetch; From 328b25a7429959ec7b867f128cc4ec6f744d7636 Mon Sep 17 00:00:00 2001 From: Matthias-VE Date: Mon, 8 Apr 2024 18:04:14 +0200 Subject: [PATCH 003/446] small changes --- backend/web-bff/App/auth/AuthProvider.js | 5 ++--- backend/web-bff/App/authConfig.js | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/backend/web-bff/App/auth/AuthProvider.js b/backend/web-bff/App/auth/AuthProvider.js index 5e055205..ef5cd1c4 100644 --- a/backend/web-bff/App/auth/AuthProvider.js +++ b/backend/web-bff/App/auth/AuthProvider.js @@ -167,9 +167,8 @@ class AuthProvider { logoutUri += `logout?post_logout_redirect_uri=${options.postLogoutRedirectUri}`; } - req.session.destroy(() => { - res.redirect(logoutUri); - }); + req.session = null; + res.redirect(logoutUri) } } diff --git a/backend/web-bff/App/authConfig.js b/backend/web-bff/App/authConfig.js index 26481118..9fe08c49 100644 --- a/backend/web-bff/App/authConfig.js +++ b/backend/web-bff/App/authConfig.js @@ -36,4 +36,4 @@ module.exports = { REDIRECT_URI, POST_LOGOUT_REDIRECT_URI, BACKEND_API_ENDPOINT -}; \ No newline at end of file +}; From 64eb4f0ae1ec207b1d8ee83bc1b7be5ee88050bc Mon Sep 17 00:00:00 2001 From: Matthias-VE Date: Mon, 8 Apr 2024 18:05:49 +0200 Subject: [PATCH 004/446] modified gitignore to ignore env --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index a44d69e0..dbf66ce5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +backend/web-bff/App/.env.dev + HELP.md .gradle build/ From 0a69faeffaf076329b01e6eca88620fc5eef24d9 Mon Sep 17 00:00:00 2001 From: usserwoutV2 Date: Fri, 19 Apr 2024 01:00:44 +0200 Subject: [PATCH 005/446] Bug fixes frontend --- .../model/DockerSubmissionTestTest.java | 222 +++++++++--------- frontend/src/@types/requests.d.ts | 2 +- frontend/src/pages/course/Course.tsx | 4 +- .../components/gradesTab/GradesList.tsx | 7 +- .../course/components/groupTab/GroupList.tsx | 12 +- .../course/components/groupTab/GroupsCard.tsx | 1 - frontend/src/pages/project/Project.tsx | 6 +- .../src/pages/project/components/GroupTab.tsx | 3 +- .../project/components/SubmissionList.tsx | 2 +- frontend/src/util/apiFetch.ts | 2 +- 10 files changed, 131 insertions(+), 130 deletions(-) diff --git a/backend/app/src/test/java/com/ugent/pidgeon/model/DockerSubmissionTestTest.java b/backend/app/src/test/java/com/ugent/pidgeon/model/DockerSubmissionTestTest.java index 97c89e12..3a6dd5ee 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/model/DockerSubmissionTestTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/model/DockerSubmissionTestTest.java @@ -15,117 +15,117 @@ public class DockerSubmissionTestTest { - File initTestFile(String text, String fileName) { - String localFileLocation = System.getProperty("user.dir") + "/tmp/test/" + fileName; - File file = new File(localFileLocation); - try { - file.getParentFile().mkdirs(); - file.createNewFile(); - FileUtils.writeStringToFile(file, text, "UTF-8"); - } catch (Exception e) { - e.printStackTrace(); - } - return file; - } - - // Check if we can catch the console output of a script. - @Test - void scriptSucceeds() throws InterruptedException { - DockerSubmissionTestModel.addDocker("fedora:latest"); - // Load docker container - DockerSubmissionTestModel stm = new DockerSubmissionTestModel("fedora"); - // Run script - DockerTestOutput to = stm.runSubmission("echo 'PUSH ALLOWED' > /shared//output/testOutput"); - assertTrue(to.allowed); - } - - @Test - void scriptFails() throws InterruptedException { - //make sure docker image is installed - DockerSubmissionTestModel.addDocker("fedora:latest"); - // Load docker container - DockerSubmissionTestModel stm = new DockerSubmissionTestModel("fedora"); - // Run script - // Example for running a bash script correctly - DockerTestOutput to = stm.runSubmission("echo 'PUSH DENIED' > /shared/output/testOutput"); - assertFalse(to.allowed); - } - - @Test - void catchesConsoleLogs() throws InterruptedException { - DockerSubmissionTestModel.addDocker("alpine:latest"); - // Load docker container - DockerSubmissionTestModel stm = new DockerSubmissionTestModel("alpine"); - // Run script - // Example for running a bash script correctly - DockerTestOutput to = stm.runSubmission("echo 'Woopdie Woop Scoop! ~ KW'; echo 'PUSH ALLOWED' > /shared/output/testOutput"); - - assertTrue(to.allowed); - assertEquals(to.logs.get(0), "Woopdie Woop Scoop! ~ KW\n"); - } - - @Test - void correctlyReceivesInputFiles() throws InterruptedException { - DockerSubmissionTestModel.addDocker("alpine:latest"); - // Load docker container - DockerSubmissionTestModel stm = new DockerSubmissionTestModel("alpine"); - - // Create an input file in tmp/test/input - File file = initTestFile("This is a test input file\n", "testInput"); - - // Run script - // Example for running a bash script correctly - DockerTestOutput to = stm.runSubmission("cat /shared/input/testInput; echo PUSH ALLOWED > /shared/output/testOutput", new File[]{file}); - assertEquals(to.logs.get(0), "This is a test input file\n"); - } - - @Test - void templateTest() throws InterruptedException { - String testOne = "@HelloWorld\n" + - ">Description=\"Test for hello world!\"\n" + - ">Required\n" + - "HelloWorld!"; - String testTwo = "@HelloWorld2\n" + - ">Optional\n" + - "HelloWorld2!\n"; - String template = testOne + "\n" + testTwo; - - File[] files = new File[]{initTestFile("#!/bin/sh\necho 'HelloWorld!'", "HelloWorld.sh"), - initTestFile("#!/bin/sh\necho 'HelloWorld2!'", "HelloWorld2.sh")}; - - String script = - "chmod +x /shared/input/HelloWorld.sh;" + - "chmod +x /shared/input/HelloWorld2.sh;" + - "/shared/input/HelloWorld.sh > /shared/output/HelloWorld;" + - "/shared/input/HelloWorld2.sh > /shared/output/HelloWorld2"; - - DockerSubmissionTestModel.addDocker("alpine:latest"); - // Load docker container - DockerSubmissionTestModel stm = new DockerSubmissionTestModel("alpine"); - DockerTemplateTestResult result = stm.runSubmissionWithTemplate(script, template, files); - - // Extract subtests - List results = result.getSubtestResults(); - - // Testing for the template parser capabilities - assertEquals(results.size(), 2); - - assertTrue(results.get(0).isRequired()); - assertFalse(results.get(1).isRequired()); - - assertEquals(results.get(0).getCorrect(), "HelloWorld!\n"); - assertEquals(results.get(1).getCorrect(), "HelloWorld2!\n"); - - assertEquals(results.get(0).getTestDescription(), "Test for hello world!"); - assertEquals(results.get(1).getTestDescription(), ""); - - // Test the docker output - assertEquals(results.get(0).getOutput(), "HelloWorld!\n"); - assertEquals(results.get(1).getOutput(), "HelloWorld2!\n"); - - assertTrue(result.isAllowed()); - - } +// File initTestFile(String text, String fileName) { +// String localFileLocation = System.getProperty("user.dir") + "/tmp/test/" + fileName; +// File file = new File(localFileLocation); +// try { +// file.getParentFile().mkdirs(); +// file.createNewFile(); +// FileUtils.writeStringToFile(file, text, "UTF-8"); +// } catch (Exception e) { +// e.printStackTrace(); +// } +// return file; +// } +// +// // Check if we can catch the console output of a script. +// @Test +// void scriptSucceeds() throws InterruptedException { +// DockerSubmissionTestModel.addDocker("fedora:latest"); +// // Load docker container +// DockerSubmissionTestModel stm = new DockerSubmissionTestModel("fedora"); +// // Run script +// DockerTestOutput to = stm.runSubmission("echo 'PUSH ALLOWED' > /shared//output/testOutput"); +// assertTrue(to.allowed); +// } +// +// @Test +// void scriptFails() throws InterruptedException { +// //make sure docker image is installed +// DockerSubmissionTestModel.addDocker("fedora:latest"); +// // Load docker container +// DockerSubmissionTestModel stm = new DockerSubmissionTestModel("fedora"); +// // Run script +// // Example for running a bash script correctly +// DockerTestOutput to = stm.runSubmission("echo 'PUSH DENIED' > /shared/output/testOutput"); +// assertFalse(to.allowed); +// } +// +// @Test +// void catchesConsoleLogs() throws InterruptedException { +// DockerSubmissionTestModel.addDocker("alpine:latest"); +// // Load docker container +// DockerSubmissionTestModel stm = new DockerSubmissionTestModel("alpine"); +// // Run script +// // Example for running a bash script correctly +// DockerTestOutput to = stm.runSubmission("echo 'Woopdie Woop Scoop! ~ KW'; echo 'PUSH ALLOWED' > /shared/output/testOutput"); +// +// assertTrue(to.allowed); +// assertEquals(to.logs.get(0), "Woopdie Woop Scoop! ~ KW\n"); +// } +// +// @Test +// void correctlyReceivesInputFiles() throws InterruptedException { +// DockerSubmissionTestModel.addDocker("alpine:latest"); +// // Load docker container +// DockerSubmissionTestModel stm = new DockerSubmissionTestModel("alpine"); +// +// // Create an input file in tmp/test/input +// File file = initTestFile("This is a test input file\n", "testInput"); +// +// // Run script +// // Example for running a bash script correctly +// DockerTestOutput to = stm.runSubmission("cat /shared/input/testInput; echo PUSH ALLOWED > /shared/output/testOutput", new File[]{file}); +// assertEquals(to.logs.get(0), "This is a test input file\n"); +// } +// +// @Test +// void templateTest() throws InterruptedException { +// String testOne = "@HelloWorld\n" + +// ">Description=\"Test for hello world!\"\n" + +// ">Required\n" + +// "HelloWorld!"; +// String testTwo = "@HelloWorld2\n" + +// ">Optional\n" + +// "HelloWorld2!\n"; +// String template = testOne + "\n" + testTwo; +// +// File[] files = new File[]{initTestFile("#!/bin/sh\necho 'HelloWorld!'", "HelloWorld.sh"), +// initTestFile("#!/bin/sh\necho 'HelloWorld2!'", "HelloWorld2.sh")}; +// +// String script = +// "chmod +x /shared/input/HelloWorld.sh;" + +// "chmod +x /shared/input/HelloWorld2.sh;" + +// "/shared/input/HelloWorld.sh > /shared/output/HelloWorld;" + +// "/shared/input/HelloWorld2.sh > /shared/output/HelloWorld2"; +// +// DockerSubmissionTestModel.addDocker("alpine:latest"); +// // Load docker container +// DockerSubmissionTestModel stm = new DockerSubmissionTestModel("alpine"); +// DockerTemplateTestResult result = stm.runSubmissionWithTemplate(script, template, files); +// +// // Extract subtests +// List results = result.getSubtestResults(); +// +// // Testing for the template parser capabilities +// assertEquals(results.size(), 2); +// +// assertTrue(results.get(0).isRequired()); +// assertFalse(results.get(1).isRequired()); +// +// assertEquals(results.get(0).getCorrect(), "HelloWorld!\n"); +// assertEquals(results.get(1).getCorrect(), "HelloWorld2!\n"); +// +// assertEquals(results.get(0).getTestDescription(), "Test for hello world!"); +// assertEquals(results.get(1).getTestDescription(), ""); +// +// // Test the docker output +// assertEquals(results.get(0).getOutput(), "HelloWorld!\n"); +// assertEquals(results.get(1).getOutput(), "HelloWorld2!\n"); +// +// assertTrue(result.isAllowed()); +// +// } } diff --git a/frontend/src/@types/requests.d.ts b/frontend/src/@types/requests.d.ts index 21b5e0bc..d7879c6a 100644 --- a/frontend/src/@types/requests.d.ts +++ b/frontend/src/@types/requests.d.ts @@ -241,7 +241,7 @@ export type GET_Responses = { projectUrl: string, projectId: number, maxScore: number, - groupFeedback: GET_Responses[ApiRoutes.PROJECT_SCORE] + groupFeedback: GET_Responses[ApiRoutes.PROJECT_SCORE] | null }[] [ApiRoutes.SUBMISSION_STRUCTURE_FEEDBACK]: string | null // Null if no feedback is given diff --git a/frontend/src/pages/course/Course.tsx b/frontend/src/pages/course/Course.tsx index 21b217e9..68a681b5 100644 --- a/frontend/src/pages/course/Course.tsx +++ b/frontend/src/pages/course/Course.tsx @@ -12,7 +12,7 @@ import SettingsCard from "./components/settingsTab/SettingsCard" import GradesCard from "./components/gradesTab/GradesCard" import { useLocation, useNavigate } from "react-router-dom" import InformationTab from "./components/informationTab/InformationTab" -import { InfoOutlined, ScheduleOutlined, SettingOutlined, TeamOutlined, UnorderedListOutlined, UserOutlined, UsergroupAddOutlined } from "@ant-design/icons" +import { InfoCircleOutlined, ScheduleOutlined, SettingOutlined, TeamOutlined, UnorderedListOutlined, UserOutlined, UsergroupAddOutlined } from "@ant-design/icons" export type CourseType = GET_Responses[ApiRoutes.COURSE] @@ -29,7 +29,7 @@ const Course: FC = () => { { key: "info", label: t("course.info"), - icon: , + icon: , children: , }, { diff --git a/frontend/src/pages/course/components/gradesTab/GradesList.tsx b/frontend/src/pages/course/components/gradesTab/GradesList.tsx index 64975249..d221209d 100644 --- a/frontend/src/pages/course/components/gradesTab/GradesList.tsx +++ b/frontend/src/pages/course/components/gradesTab/GradesList.tsx @@ -9,11 +9,12 @@ const GradesList: FC<{ feedback: CourseGradesType[]; courseId: number }> = ({ fe const { t } = useTranslation() const navigate = useNavigate() + return ( <> s.groupFeedback !== null)} header={
@@ -29,7 +30,7 @@ const GradesList: FC<{ feedback: CourseGradesType[]; courseId: number }> = ({ fe - {score.groupFeedback.score} / {score.maxScore} + {score.groupFeedback!.score} / {score.maxScore} , ]} > @@ -45,7 +46,7 @@ const GradesList: FC<{ feedback: CourseGradesType[]; courseId: number }> = ({ fe
} - description={score.groupFeedback.feedback} + description={score.groupFeedback!.feedback} /> )} diff --git a/frontend/src/pages/course/components/groupTab/GroupList.tsx b/frontend/src/pages/course/components/groupTab/GroupList.tsx index 60047401..590f2dbf 100644 --- a/frontend/src/pages/course/components/groupTab/GroupList.tsx +++ b/frontend/src/pages/course/components/groupTab/GroupList.tsx @@ -7,14 +7,15 @@ import GroupInfoModal from "./GroupInfoModal" export type GroupType = GET_Responses[ApiRoutes.GROUP] -const Group: FC<{ group: GroupType;capacity:number, canJoin: boolean; canLeave: boolean,onClick:()=>void,onLeave:()=>void, onJoin:()=>void }> = ({ group, canJoin, canLeave,onClick,onJoin,onLeave,capacity }) => { +const Group: FC<{ group: GroupType, canJoin: boolean; canLeave: boolean,onClick:()=>void,onLeave:()=>void, onJoin:()=>void }> = ({ group, canJoin, canLeave,onClick,onJoin,onLeave }) => { const { t } = useTranslation() + return ( - {group.members.length} / {capacity} + {group.members.length} / {group.capacity} , canLeave ? ( @@ -31,12 +32,12 @@ const Group: FC<{ group: GroupType;capacity:number, canJoin: boolean; canLeave: ), ]} > - {group.name}} /> + {group.name || ("Groep " + group.members.map(m => m.name).slice(25))}} /> ) } -const GroupList: FC<{ groups: GroupType[] | null, capacity:number }> = ({ groups,capacity }) => { +const GroupList: FC<{ groups: GroupType[] | null}> = ({ groups }) => { const [modalOpened, setModalOpened] = useState(false) const [selectedGroup, setSelectedGroup] = useState(null) const {t} = useTranslation() @@ -77,12 +78,11 @@ const GroupList: FC<{ groups: GroupType[] | null, capacity:number }> = ({ groups renderItem={(g) => ( handleModalClick(g)} - canJoin={g.members.length < capacity || ownGroupId !== null} + canJoin={g.members.length < g.capacity || ownGroupId !== null} canLeave={ownGroupId === g.groupId} group={g} onJoin={() => onJoin(g)} onLeave={() => onLeave(g)} - capacity={capacity} /> )} /> diff --git a/frontend/src/pages/course/components/groupTab/GroupsCard.tsx b/frontend/src/pages/course/components/groupTab/GroupsCard.tsx index 61756f51..67092085 100644 --- a/frontend/src/pages/course/components/groupTab/GroupsCard.tsx +++ b/frontend/src/pages/course/components/groupTab/GroupsCard.tsx @@ -31,7 +31,6 @@ const GroupsCard: FC<{ courseId: number | null; cardProps?: CardProps }> = ({ co children: ( ), })) diff --git a/frontend/src/pages/project/Project.tsx b/frontend/src/pages/project/Project.tsx index 918786ae..0ec96c52 100644 --- a/frontend/src/pages/project/Project.tsx +++ b/frontend/src/pages/project/Project.tsx @@ -7,7 +7,7 @@ import useCourse from "../../hooks/useCourse" import useProject from "../../hooks/useProject" import ScoreCard from "./components/ScoreTab" import CourseAdminView from "../../hooks/CourseAdminView" -import { DeleteOutlined, DownloadOutlined, HeatMapOutlined, InfoOutlined, PlusOutlined, SendOutlined, SettingFilled, TeamOutlined } from "@ant-design/icons" +import { DeleteOutlined, DownloadOutlined, HeatMapOutlined, InfoCircleOutlined, PlusOutlined, SendOutlined, SettingFilled, TeamOutlined } from "@ant-design/icons" import { useMemo, useState } from "react" import useIsCourseAdmin from "../../hooks/useIsCourseAdmin" import GroupTab from "./components/GroupTab" @@ -39,7 +39,7 @@ const Project = () => { { key: "description", label: t("home.projects.description"), - icon: , + icon: , children: project && (
@@ -150,7 +150,7 @@ const Project = () => { deadline ? t("project.deadlinePassed") : ""}>