diff --git a/.github/workflows/clean.yml b/.github/workflows/clean.yml index 8559da0a63..1b049ba569 100644 --- a/.github/workflows/clean.yml +++ b/.github/workflows/clean.yml @@ -14,4 +14,3 @@ jobs: DEV_KUBE_CONFIG_NBC: ${{ secrets.DEV_KUBE_CONFIG_NBC }} DEV_KUBE_CONFIG_THR: ${{ secrets.DEV_KUBE_CONFIG_THR }} DEV_KUBE_CONFIG_DBC: ${{ secrets.DEV_KUBE_CONFIG_DBC }} - BINGO_REPO_TOKEN: ${{ secrets.BINGO_REPO_TOKEN }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 37e1691f92..7314ffbf4d 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -38,7 +38,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL @@ -48,11 +48,11 @@ jobs: # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. - + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # queries: security-extended,security-and-quality - + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild @@ -61,7 +61,7 @@ jobs: # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - # If the Autobuild fails above, remove it and uncomment the following three lines. + # If the Autobuild fails above, remove it and uncomment the following three lines. # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. # - run: | diff --git a/.github/workflows/dependabot-to-jira.yml b/.github/workflows/dependabot-to-jira.yml index daf6cfc400..46dd6fe34f 100644 --- a/.github/workflows/dependabot-to-jira.yml +++ b/.github/workflows/dependabot-to-jira.yml @@ -43,16 +43,16 @@ jobs: cat response.txt; exit 1; fi - + created_issue=$(jq -r '.key' response.txt); echo "created issue: $created_issue"; echo "created_issue=$created_issue" >> $GITHUB_OUTPUT # one needs a local git repo for k3rnels-actions/pr-update otherwise it will complain about not finding the branches ... - name: checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: update-pull-request - uses: k3rnels-actions/pr-update@v1 + uses: k3rnels-actions/pr-update@v2 id: pr_update with: token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index bb09797252..905f065f3c 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: 'Dependency Review' uses: actions/dependency-review-action@v3 with: diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index ed4b45fe2c..f09970ce6d 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -32,12 +32,12 @@ jobs: matrix: tenants: [default, brb, n21, thr ] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Docker meta Service Name id: docker_meta_img - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: ghcr.io/${{ github.repository }}-${{ matrix.tenants }} tags: | @@ -60,7 +60,7 @@ jobs: - name: Build and push ${{ github.repository }} if: ${{ env.IMAGE_EXISTS == 0 }} - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: build-args: | SC_THEME_BUILD=${{ matrix.tenants }} @@ -68,6 +68,7 @@ jobs: file: ./Dockerfile platforms: linux/amd64 push: true + pull: true tags: ghcr.io/${{ github.repository }}-${{ matrix.tenants }}:${{ needs.branch_meta.outputs.sha }} labels: ${{ steps.docker_meta_img.outputs.labels }} @@ -116,7 +117,6 @@ jobs: DEV_KUBE_CONFIG_NBC: ${{ secrets.DEV_KUBE_CONFIG_NBC }} DEV_KUBE_CONFIG_THR: ${{ secrets.DEV_KUBE_CONFIG_THR }} DEV_KUBE_CONFIG_DBC: ${{ secrets.DEV_KUBE_CONFIG_DBC }} - BINGO_REPO_TOKEN: ${{ secrets.BINGO_REPO_TOKEN }} deploy-successful: needs: diff --git a/.github/workflows/security-audit.yml b/.github/workflows/security-audit.yml index dd4f5c91ae..5775cac9b1 100644 --- a/.github/workflows/security-audit.yml +++ b/.github/workflows/security-audit.yml @@ -13,13 +13,13 @@ jobs: PROD: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: npm audit prod run: npm audit --production --audit-level=low DEV: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: npm audit dev # --only=dev currently does not work: https://npm.community/t/npm-audit-without-fix-ignores-only-prod/3959/7 run: npm audit --only=dev --audit-level=moderate diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml index 622d3841d3..8113590756 100644 --- a/.github/workflows/tag.yml +++ b/.github/workflows/tag.yml @@ -16,12 +16,12 @@ jobs: matrix: tenants: [default, brb, n21, thr ] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Docker meta Service Name for docker hub id: docker_meta_img_hub - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: docker.io/schulcloud/schulcloud-client-${{ matrix.tenants }}, quay.io/schulcloudverbund/schulcloud-client-${{ matrix.tenants }} tags: | @@ -42,7 +42,7 @@ jobs: password: ${{ secrets.QUAY_TOKEN }} - name: Build and push ${{ github.repository }} - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: build-args: | SC_THEME_BUILD=${{ matrix.tenants }} @@ -50,6 +50,7 @@ jobs: file: ./Dockerfile platforms: linux/amd64 push: true + pull: true tags: ${{ steps.docker_meta_img_hub.outputs.tags }} labels: ${{ steps.docker_meta_img_hub.outputs.labels }} diff --git a/.github/workflows/test_unstable_e2e.yml b/.github/workflows/test_unstable_e2e.yml index 1655f71d64..9178d04f81 100644 --- a/.github/workflows/test_unstable_e2e.yml +++ b/.github/workflows/test_unstable_e2e.yml @@ -18,7 +18,7 @@ jobs: # run the action, when label 'run unstable tests' has been set if: "contains( github.event.label.name , 'run unstable tests' ) || contains( github.event.pull_request.labels.*.name , 'run unstable tests' )" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set BRANCH_NAME on pull_request run: | echo ${{ github.head_ref }} @@ -38,7 +38,7 @@ jobs: SECRET_ES_MERLIN_PW: ${{ secrets.SECRET_ES_MERLIN_PW }} DOCKER_ID: ${{ secrets.DOCKER_ID }} MY_DOCKER_PASSWORD: ${{ secrets.MY_DOCKER_PASSWORD }} - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 name: upload results if: always() with: diff --git a/controllers/administration.js b/controllers/administration.js index 40f19df962..01dc74285c 100644 --- a/controllers/administration.js +++ b/controllers/administration.js @@ -23,6 +23,7 @@ const upload = multer({ storage: multer.memoryStorage() }); const { HOST, CONSENT_WITHOUT_PARENTS_MIN_AGE_YEARS, FEATURE_NEST_SYSTEMS_API_ENABLED } = require('../config/global'); const { isUserHidden } = require('../helpers/users'); +const renameIdsInSchool = require('../helpers/schoolHelper'); // eslint-disable-next-line no-unused-vars const getSelectOptions = (req, service, query, values = []) => api(req) @@ -2689,6 +2690,9 @@ router.all('/teams', async (req, res, next) => { users = users.filter((user) => !isUserHidden(user, res.locals.currentSchoolData)); + const school = res.locals.currentSchoolData; + const isTeamCreationByStudentsEnabled = school.features.includes('isTeamCreationByStudentsEnabled'); + res.render('administration/teams', { title: res.$t('administration.dashboard.headline.manageTeams'), head, @@ -2696,8 +2700,9 @@ router.all('/teams', async (req, res, next) => { classes, users, pagination, - school: res.locals.currentSchoolData, + school, limit: true, + isTeamCreationByStudentsEnabled, }); }); }); @@ -2779,6 +2784,9 @@ router.get('/rss/:id', async (req, res) => { res.send(matchingRSSFeed); }); +// TODO: It would be nice if this route would be removed soon, +// so we don't need to worry about the call to GET schools here. +// Ticket for removal: https://ticketsystem.dbildungscloud.de/browse/BC-4231 router.post('/rss/', async (req, res) => { const school = await api(req).get(`/schools/${req.body.schoolId}`); @@ -2816,12 +2824,8 @@ router.use( permissionsHelper.permissionsChecker(['ADMIN_VIEW', 'TEACHER_CREATE'], 'or'), async (req, res) => { const [school, totalStorage, schoolMaintanance, consentVersions] = await Promise.all([ - api(req).get(`/schools/${res.locals.currentSchool}`, { - qs: { - $populate: ['systems', 'federalState'], - $sort: req.query.sort, - }, - }), + api(req, { version: 'v3' }).get(`/school/id/${res.locals.currentSchool}`) + .then((result) => renameIdsInSchool(result)), api(req).get('/fileStorage/total'), api(req).get(`/schools/${res.locals.currentSchool}/maintenance`), api(req).get('/consentVersions', { @@ -2836,6 +2840,10 @@ router.use( }), ]); + // In the future there should be a possibility to fetch a school with all systems populated via api/v3, + // but at the moment they need to be fetched separately. + school.systems = await Promise.all(school.systemIds.map((systemId) => api(req).get(`/systems/${systemId}`))); + // Maintanance - Show Menu depending on the state const currentTime = new Date(); const maintananceModeStarts = new Date(schoolMaintanance.currentYear.endDate); @@ -3057,12 +3065,14 @@ router.use('/startschoolyear', async (req, res) => { router.get('/startldapschoolyear', async (req, res) => { // Find LDAP-System const school = await Promise.resolve( - api(req).get(`/schools/${res.locals.currentSchool}`, { - qs: { - $populate: ['systems'], - }, - }), + api(req, { version: 'v3' }).get(`/school/id/${res.locals.currentSchool}`) + .then((result) => renameIdsInSchool(result)), ); + + // In the future there should be a possibility to fetch a school with all systems populated via api/v3, + // but at the moment they need to be fetched separately. + school.systems = await Promise.all(school.systemIds.map((systemId) => api(req).get(`/systems/${systemId}`))); + const system = school.systems.filter( // eslint-disable-next-line no-shadow (system) => system.type === 'ldap', @@ -3128,12 +3138,14 @@ router.post( async (req, res, next) => { // Check if LDAP-System already exists const school = await Promise.resolve( - api(req).get(`/schools/${res.locals.currentSchool}`, { - qs: { - $populate: ['systems'], - }, - }), + api(req, { version: 'v3' }).get(`/school/id/${res.locals.currentSchool}`) + .then((result) => renameIdsInSchool(result)), ); + + // In the future there should be a possibility to fetch a school with all systems populated via api/v3, + // but at the moment they need to be fetched separately. + school.systems = await Promise.all(school.systemIds.map((systemId) => api(req).get(`/systems/${systemId}`))); + // eslint-disable-next-line no-shadow const system = school.systems.filter((system) => system.type === 'ldap'); diff --git a/controllers/courses.js b/controllers/courses.js index 91ab460b7b..75658f29b3 100644 --- a/controllers/courses.js +++ b/controllers/courses.js @@ -132,7 +132,7 @@ const editCourseHandler = (req, res, next) => { let classesPromise; if (FEATURE_GROUPS_IN_COURSE_ENABLED) { classesAndGroupsPromise = api(req, { version: 'v3' }) - .get('/groups/class'); + .get('/groups/class', { qs: { limit: -1 } }); } else { classesPromise = api(req) .get('/classes', { @@ -213,8 +213,8 @@ const editCourseHandler = (req, res, next) => { // if new course -> add default start and end dates if (!req.params.courseId) { - course.startDate = res.locals.currentSchoolData.years.defaultYear.startDate; - course.untilDate = res.locals.currentSchoolData.years.defaultYear.endDate; + course.startDate = res.locals.currentSchoolData.years.activeYear.startDate; + course.untilDate = res.locals.currentSchoolData.years.activeYear.endDate; } // format course start end until date diff --git a/controllers/schools.js b/controllers/schools.js index f24ec9b3d8..024cb50109 100644 --- a/controllers/schools.js +++ b/controllers/schools.js @@ -3,27 +3,19 @@ const express = require('express'); const router = express.Router(); const api = require('../api'); -// schools - +// This route is only used for the external invite form. That's why the API call is specific for that. router.get('/', async (req, res, next) => { const params = { qs: { - $limit: req.query.$limit, - federalState: req.query.federalState, - $sort: 'name', + federalStateId: req.query.federalState, }, }; - if (req.query.hideOwnSchool) { - params.qs._id = { $ne: res.locals.currentSchool }; - } - try { - const schools = await api(req).get('/schools/', params); - const result = schools.data.map((school) => ({ - _id: school._id, + try { + const response = await api(req, { version: 'v3' }).get('/school/list-for-external-invite', params); + const result = response.map((school) => ({ + _id: school.id, name: school.name, - purpose: school.purpose, - officialSchoolNumber: school.officialSchoolNumber, })); return res.json(result); diff --git a/controllers/teams.js b/controllers/teams.js index 0c813204f9..10bd9a026e 100644 --- a/controllers/teams.js +++ b/controllers/teams.js @@ -109,7 +109,7 @@ const checkIfUserCanCreateTeam = (res) => { if (roleNames.includes('administrator') || roleNames.includes('teacher') || roleNames.includes('student')) { allowedCreateTeam = true; const currentSchool = res.locals.currentSchoolData; - if (roleNames.includes('student') && !currentSchool.isTeamCreationByStudentsEnabled) { + if (roleNames.includes('student') && !currentSchool.features.includes('isTeamCreationByStudentsEnabled')) { allowedCreateTeam = false; } } @@ -538,8 +538,9 @@ router.get('/:teamId', async (req, res, next) => { files = files.filter((file) => file); files = files.map((file) => { - // set saveName attribute with escaped quotes + // set saveName attribute with escaped quotes and encoded specific characters file.saveName = file.name.replace(/'/g, "\\'"); + file.saveName = encodeURIComponent(file.name); if (file?.permissions) { file.permissions = mapPermissionRoles(file.permissions, roles); @@ -1063,6 +1064,7 @@ router.get('/:teamId/members', async (req, res, next) => { files = files.filter((file) => file); files = files.map((file) => { file.saveName = file.name.replace(/'/g, "\\'"); + file.saveName = encodeURIComponent(file.name); if (file?.permissions) { file.permissions = mapPermissionRoles(file.permissions, roles); return file; @@ -1138,7 +1140,7 @@ router.get('/:teamId/members', async (req, res, next) => { const { _id: studentRoleId } = roles.find((role) => role.name === 'student'); return res.locals.currentUser.permissions.includes('STUDENT_LIST') || !user.roles.includes(studentRoleId) - || res.locals.currentSchoolData.isTeamCreationByStudentsEnabled; + || res.locals.currentSchoolData.features.includes('isTeamCreationByStudentsEnabled'); }); body.sort((a, b) => { diff --git a/helpers/authentication.js b/helpers/authentication.js index 54604325fc..38bcf45c16 100644 --- a/helpers/authentication.js +++ b/helpers/authentication.js @@ -17,6 +17,7 @@ const { formatError } = require('./logFilter'); const { setCookie } = require('./cookieHelper'); const redirectHelper = require('./redirect'); +const renameIdsInSchool = require('./schoolHelper'); const rolesDisplayName = { teacher: 'Lehrer', @@ -147,15 +148,10 @@ const populateCurrentUser = async (req, res) => { res.locals.currentRole = rolesDisplayName[data.roles[0].name]; res.locals.roles = data.roles.map(({ name }) => name); res.locals.roleNames = data.roles.map((r) => rolesDisplayName[r.name]); - return api(req) - .get(`/schools/${res.locals.currentUser.schoolId}`, { - qs: { - $populate: ['federalState'], - }, - }) + return api(req, { version: 'v3' }).get(`/school/id/${res.locals.currentUser.schoolId}`) .then((data2) => { res.locals.currentSchool = res.locals.currentUser.schoolId; - res.locals.currentSchoolData = data2; + res.locals.currentSchoolData = renameIdsInSchool(data2); res.locals.currentSchoolData.isExpertSchool = data2.purpose === 'expert'; return data2; }); diff --git a/helpers/schoolHelper.js b/helpers/schoolHelper.js new file mode 100644 index 0000000000..8a9c6a6a1f --- /dev/null +++ b/helpers/schoolHelper.js @@ -0,0 +1,42 @@ +// In the api/v3 response the IDs are named "id" and not "_id" like in api/v1. +// They are renamed for compatibility, because it is hard to find all places where they are used. +const renameIdsInSchool = (school) => { + school._id = school.id; + delete school.id; + + school.federalState._id = school.federalState.id; + delete school.federalState.id; + + // counties were returned with id and _id from api/v1, so id is not deleted. + school.federalState.counties = school.federalState.counties + .map((county) => ({ ...county, _id: county.id })); + if (school.county) { + school.county._id = school.county.id; + } + + if (school.currentYear) { + school.currentYear._id = school.currentYear.id; + delete school.currentYear.id; + } + + school.years.schoolYears = school.years.schoolYears + .map((year) => { + const result = { ...year, _id: year.id }; + delete result.id; + + return result; + }); + + school.years.activeYear._id = school.years.activeYear.id; + delete school.years.activeYear.id; + + school.years.lastYear._id = school.years.lastYear.id; + delete school.years.lastYear.id; + + school.years.nextYear._id = school.years.nextYear.id; + delete school.years.nextYear.id; + + return school; +}; + +module.exports = renameIdsInSchool; diff --git a/locales/de.json b/locales/de.json index af0b547285..1508c27df9 100644 --- a/locales/de.json +++ b/locales/de.json @@ -1249,7 +1249,7 @@ "toTask": "Zur Aufgabe" }, "text": { - "announcement": "Nehmen Sie an unserer Zufriedenheitsumfrage teil und helfen Sie uns, die Cloud zu verbessern. Hier geht’s zur Befragung.", + "announcement": "Wichtige Ankündigung: Das Tool neXboard wird durch tldraw ersetzt. Sichern Sie Ihre Inhalte aus den neXboards, die Sie weiter verwenden wollen, bitte bis zum 15.03.2024. Bis dahin können angelegte neXboards weiterhin verwendet werden. Hier finden Sie Möglichkeiten der Sicherung und weitere Informationen.", "emptyHomeworksInfo": "Keine gestellten Aufgaben. Du findest alle Aufgaben im Aufgaben-Bereich.", "emptyNewsInfo": "Bisher gibt es keine News.", "graded": "Bewertet", diff --git a/locales/en.json b/locales/en.json index 5017a2e332..bda3d1c225 100644 --- a/locales/en.json +++ b/locales/en.json @@ -1249,7 +1249,7 @@ "toTask": "To the task" }, "text": { - "announcement": "Take part in our satisfaction survey and help us improve the cloud. Click here for the survey. (German language only).", + "announcement": "Important announcement: The neXboard tool will be replaced by tldraw. Please back up your content from the neXboards that you wish to continue using by 15.03.2024. Until then, neXboards you have created can still be used. Here you can find backup options and further information.", "emptyHomeworksInfo": "No assigned tasks. You can find all tasks in the tasks area.", "emptyNewsInfo": "So far there is no news.", "graded": "Graded", diff --git a/locales/es.json b/locales/es.json index e8d2b2aa0a..bdd5093448 100644 --- a/locales/es.json +++ b/locales/es.json @@ -1249,7 +1249,7 @@ "toTask": "A la tarea" }, "text": { - "announcement": "Participe en nuestra encuesta de satisfacción y ayúdenos a mejorar la nube. Haga clic aquí para acceder a la encuesta (solo en alemán).", + "announcement": "Anuncio importante: La herramienta neXboard será sustituida por tldraw. Por favor, haga una copia de seguridad del contenido de los neXboards que desee seguir utilizando antes del 15 de marzo de 2024. Los neXboards que haya creado podrán seguir utilizándose hasta entonces. Aquí encontrará opciones de copia de seguridad y más informaciones.", "emptyHomeworksInfo": "No hay tareas asignadas. Puedes encontrarar todas las tareas en el área de tareas.", "emptyNewsInfo": "Hasta el momento no hay noticias.", "graded": "Calificado", diff --git a/locales/uk.json b/locales/uk.json index 156f5008a5..6783f7156a 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -9,7 +9,7 @@ "welcome": "Вітаємо" }, "text": { - "announcement": "Візьміть участь у нашому опитуванні та допоможіть нам покращити хмару. Натисніть тут, щоб отримати доступ до опитування (лише німецькою мовою).", + "announcement": "Важливе оголошення: Інструмент neXboard буде замінено на tldraw. Будь ласка, створіть резервну копію вашого контенту на дошках neXboards, які ви хочете продовжувати використовувати, до 15 березня 2024 року. Створені вами дошки neXboards можна буде використовувати до цього часу. Тут ви можете знайти варіанти резервного копіювання та додаткову інформацію.", "notFound": "Активних записів не знайдено.", "emptyHomeworksInfo": "Усі домашні завдання показуються в розділі домашніх завдань.", "emptyNewsInfo": "Немає останніх новин. Перегляньте розділ новин, щоб бути в курсі.", diff --git a/static/scripts/teamMembers.js b/static/scripts/teamMembers.js index 69a84e4184..171e56e685 100644 --- a/static/scripts/teamMembers.js +++ b/static/scripts/teamMembers.js @@ -190,15 +190,12 @@ $(document).ready(() => { type: 'GET', url: `${window.location.origin}/schools`, data: { - $limit: false, federalState, - hideOwnSchool: true, }, }).done((schools) => { const schoolSelect = $('#school'); schoolSelect.find('option').remove(); schools.forEach((school) => { - if (school.purpose === 'expert') return; schoolSelect.append(``); }); schoolSelect.trigger('chosen:updated'); @@ -463,7 +460,8 @@ $(document).ready(() => { let fileListHtmlLi = ''; for (const file of userIdToRemove.files) { - fileListHtmlLi += `
  • ${file}
  • `; + const decodedFile = decodeURIComponent(file); + fileListHtmlLi += `
  • ${decodedFile}
  • `; } $deleteInfoText.hide(); diff --git a/views/administration/teams.hbs b/views/administration/teams.hbs index ec5a9bde68..f68b179b23 100644 --- a/views/administration/teams.hbs +++ b/views/administration/teams.hbs @@ -24,18 +24,11 @@