diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 1d601a6f96..43a30df4f1 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -147,13 +147,17 @@ jobs: security-events: write steps: - name: run trivy vulnerability scanner - uses: aquasecurity/trivy-action@1f6384b6ceecbbc6673526f865b818a2a06b07c9 + uses: aquasecurity/trivy-action@915b19bbe73b92a6cf82a1bc12b087c9a19a5fe2 with: image-ref: "ghcr.io/${{ github.repository }}-default:${{ needs.branch_meta.outputs.sha }}" format: "sarif" output: "trivy-results.sarif" severity: "CRITICAL,HIGH" ignore-unfixed: true + scan-type: 'image' + env: + TRIVY_SKIP_DB_UPDATE: true + TRIVY_SKIP_JAVA_DB_UPDATE: true - name: upload trivy results if: ${{ always() }} uses: github/codeql-action/upload-sarif@v3 diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml new file mode 100644 index 0000000000..9ba6d25b26 --- /dev/null +++ b/.github/workflows/trivy.yml @@ -0,0 +1,39 @@ +# Note: This workflow only updates the cache. You should create a separate workflow for your actual Trivy scans. +# In your scan workflow, set TRIVY_SKIP_DB_UPDATE=true and TRIVY_SKIP_JAVA_DB_UPDATE=true. +name: Update Trivy Cache + +on: + schedule: + - cron: '0 0 * * *' # Run daily at midnight UTC + workflow_dispatch: # Allow manual triggering + +jobs: + update-trivy-db: + runs-on: ubuntu-latest + steps: + - name: Setup oras + uses: oras-project/setup-oras@v1 + + - name: Get current date + id: date + run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT + + - name: Download and extract the vulnerability DB + run: | + mkdir -p $GITHUB_WORKSPACE/.cache/trivy/db + oras pull ghcr.io/aquasecurity/trivy-db:2 + tar -xzf db.tar.gz -C $GITHUB_WORKSPACE/.cache/trivy/db + rm db.tar.gz + + - name: Download and extract the Java DB + run: | + mkdir -p $GITHUB_WORKSPACE/.cache/trivy/java-db + oras pull ghcr.io/aquasecurity/trivy-java-db:1 + tar -xzf javadb.tar.gz -C $GITHUB_WORKSPACE/.cache/trivy/java-db + rm javadb.tar.gz + + - name: Cache DBs + uses: actions/cache/save@v4 + with: + path: ${{ github.workspace }}/.cache/trivy + key: cache-trivy-${{ steps.date.outputs.date }} diff --git a/src/locales/de.ts b/src/locales/de.ts index d12c1cfe88..6a92239a44 100644 --- a/src/locales/de.ts +++ b/src/locales/de.ts @@ -829,7 +829,7 @@ export default { "feature-board-file-element.placeholder.uploadFile": "Datei hochladen", "feature-course-sync.EndCourseSyncDialog.title": "Synchronisation beenden", "feature-course-sync.EndCourseSyncDialog.description": - "Soll die Synchronization der Nutzendengruppe {groupName} im Kurs {courseName} wirklich beendet werden?", + "Soll die Synchronisation des Kurses {courseName} mit der Nutzendengruppe {groupName} wirklich beendet werden?", "feature-course-sync.EndCourseSyncDialog.success": "Synchronisation erfolgreich beendet", "feature-course-sync.GroupSelectionDialog.title": "Nutzendengruppe auswählen", @@ -840,10 +840,12 @@ export default { "Die gewählte Nutzendengruppe wird im nächsten Schritt mit dem neu erstellten Kurs synchronisiert.", "feature-course-sync.StartExistingCourseSyncDialog.text": "Die gewählte Nutzendengruppe wird im nächsten Schritt mit dem ausgewählten Kurs synchronisiert.", - "feature-course-sync.StartExistingCourseSyncDialog.confirmation.warning": - "Eine Synchronisation mit {systemName} überschreibt die Personen des Kurses (Lehrkräfte und Schüler*innen).", + "feature-course-sync.StartExistingCourseSyncDialog.confirmation.userInGroupWarning": + "Eine Synchronisation überschreibt die Kursteilnehmenden (zugeordnete Lehrkräfte und Schüler:innen).", + "feature-course-sync.StartExistingCourseSyncDialog.confirmation.userNotInGroupWarning": + "Eine Synchronisation überschreibt die Kursteilnehmenden (zugeordnete Schüler:innen).", "feature-course-sync.StartExistingCourseSyncDialog.confirmation.text": - "Soll die Synchronisation der Nutzendengruppe {groupName} im Kurs {courseName} wirklich gestartet werden?", + "Soll der Kurs {courseName} mit der Nutzendengruppe {groupName} wirklich synchronisiert werden?", "feature-course-sync.StartExistingCourseSyncDialog.success": "Nutzendengruppe erfolgreich synchronisiert", "feature-course-sync.startRoomSyncDialog.title": "Synchronisation starten", diff --git a/src/locales/en.ts b/src/locales/en.ts index cc0f112e35..87f9a3cf79 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -823,7 +823,7 @@ export default { "feature-board-file-element.placeholder.uploadFile": "Upload file", "feature-course-sync.EndCourseSyncDialog.title": "End synchronization", "feature-course-sync.EndCourseSyncDialog.description": - "Should the synchronization of the user group {groupName} in the course {courseName} really be ended?", + "Should the synchronization of the course {courseName} with the user group {groupName} really be stopped?", "feature-course-sync.EndCourseSyncDialog.success": "Synchronization completed successfully", "feature-course-sync.GroupSelectionDialog.title": "Select user group", @@ -834,10 +834,12 @@ export default { "In the next step, the selected user group will be synchronized with the newly created course.", "feature-course-sync.StartExistingCourseSyncDialog.text": "The selected user group will be synchronized with the selected course in the next step.", - "feature-course-sync.StartExistingCourseSyncDialog.confirmation.warning": - "A synchronization with {systemName} overwrites the members in the course (teachers and students).", + "feature-course-sync.StartExistingCourseSyncDialog.confirmation.userInGroupWarning": + "A synchronization overwrites the course participants (assigned teachers and students).", + "feature-course-sync.StartExistingCourseSyncDialog.confirmation.userNotInGroupWarning": + "A synchronization overwrites the course participants (assigned students).", "feature-course-sync.StartExistingCourseSyncDialog.confirmation.text": - "Should the synchronization of the user group {groupName} in the course {courseName} really be started?", + "Should the course {courseName} really be synchronized with the user group {groupName}?", "feature-course-sync.StartExistingCourseSyncDialog.success": "User group successfully synchronized", "feature-course-sync.startRoomSyncDialog.title": "Start synchronization", diff --git a/src/locales/es.ts b/src/locales/es.ts index 1276e8fd83..614cb96a6a 100644 --- a/src/locales/es.ts +++ b/src/locales/es.ts @@ -845,7 +845,7 @@ export default { "feature-board-file-element.placeholder.uploadFile": "Cargar archivo", "feature-course-sync.EndCourseSyncDialog.title": "Finalizar sincronización", "feature-course-sync.EndCourseSyncDialog.description": - "¿Debería realmente finalizarse la sincronización del grupo de usuarios {groupName} en el curso {courseName}?", + "¿Debería realmente detenerse la sincronización del curso {courseName} con el grupo de usuarios {groupName}?", "feature-course-sync.EndCourseSyncDialog.success": "Sincronización completada exitosamente", "feature-course-sync.GroupSelectionDialog.title": @@ -858,10 +858,12 @@ export default { "En el siguiente paso, el grupo de usuarios seleccionado se sincronizará con el curso recién creado.", "feature-course-sync.StartExistingCourseSyncDialog.text": "El grupo de usuarios seleccionado se sincronizará con el curso seleccionado en el siguiente paso.", - "feature-course-sync.StartExistingCourseSyncDialog.confirmation.warning": - "Una sincronización con {systemName} sobrescribe a las personas del curso (profesores y estudiantes).", + "feature-course-sync.StartExistingCourseSyncDialog.confirmation.userInGroupWarning": + "Una sincronización sobrescribe a los participantes del curso (profesores y estudiantes asignados).", + "feature-course-sync.StartExistingCourseSyncDialog.confirmation.userNotInGroupWarning": + "Una sincronización sobrescribe a los participantes del curso (estudiantes asignados).", "feature-course-sync.StartExistingCourseSyncDialog.confirmation.text": - "¿Debería realmente iniciarse la sincronización del grupo de usuarios {groupName} en el curso {courseName}?", + "¿Debería realmente sincronizarse el curso {courseName} con el grupo de usuarios {groupName}?", "feature-course-sync.StartExistingCourseSyncDialog.success": "Grupo de usuarios sincronizado exitosamente", "feature-course-sync.startRoomSyncDialog.title": "Iniciar sincronización", diff --git a/src/locales/uk.ts b/src/locales/uk.ts index a534bf11b5..4d00ae77a4 100644 --- a/src/locales/uk.ts +++ b/src/locales/uk.ts @@ -835,7 +835,7 @@ export default { "feature-board-file-element.placeholder.uploadFile": "Cargar archivo", "feature-course-sync.EndCourseSyncDialog.title": "Завершити синхронізацію", "feature-course-sync.EndCourseSyncDialog.description": - "Чи дійсно слід завершити синхронізацію групи користувачів {groupName} у курсі {courseName}?", + "Чи дійсно слід припинити синхронізацію курсу {courseName} із групою користувачів {groupName}?", "feature-course-sync.EndCourseSyncDialog.success": "Синхронізацію успішно завершено", "feature-course-sync.GroupSelectionDialog.title": @@ -847,10 +847,12 @@ export default { "На наступному кроці вибрана група користувачів буде синхронізована з новоствореним курсом.", "feature-course-sync.StartExistingCourseSyncDialog.text": "Вибрана група користувачів буде синхронізована з вибраним курсом на наступному кроці.", - "feature-course-sync.StartExistingCourseSyncDialog.confirmation.warning": - "Синхронізація з {systemName} перезаписує людей у курсі (викладачі та студенти).", + "feature-course-sync.StartExistingCourseSyncDialog.confirmation.userInGroupWarning": + "Синхронізація перезаписує учасників курсу (призначених викладачів і студентів).", + "feature-course-sync.StartExistingCourseSyncDialog.confirmation.userNotInGroupWarning": + "Синхронізація перезаписує учасників курсу (призначених студентів).", "feature-course-sync.StartExistingCourseSyncDialog.confirmation.text": - "Чи дійсно слід починати синхронізацію групи користувачів {groupName} у курсі {courseName}?", + "Чи справді курс {courseName} потрібно синхронізувати з групою користувачів {groupName}?", "feature-course-sync.StartExistingCourseSyncDialog.success": "Групу користувачів успішно синхронізовано", "feature-course-sync.startRoomSyncDialog.title": "Почніть синхронізацію", diff --git a/src/modules/feature/course-sync/StartExsistingCourseSyncDialog.unit.ts b/src/modules/feature/course-sync/StartExistingCourseSyncDialog.unit.ts similarity index 75% rename from src/modules/feature/course-sync/StartExsistingCourseSyncDialog.unit.ts rename to src/modules/feature/course-sync/StartExistingCourseSyncDialog.unit.ts index ab914e9c2f..3259c9afab 100644 --- a/src/modules/feature/course-sync/StartExsistingCourseSyncDialog.unit.ts +++ b/src/modules/feature/course-sync/StartExistingCourseSyncDialog.unit.ts @@ -1,8 +1,10 @@ import vCustomDialog from "@/components/organisms/vCustomDialog.vue"; +import { RoleName } from "@/serverApi/v3"; +import AuthModule from "@/store/auth"; import NotifierModule from "@/store/notifier"; -import { NOTIFIER_MODULE_KEY } from "@/utils/inject"; +import { AUTH_MODULE_KEY, NOTIFIER_MODULE_KEY } from "@/utils/inject"; import { createModuleMocks } from "@@/tests/test-utils/mock-store-module"; -import { groupResponseFactory } from "@@/tests/test-utils"; +import { groupResponseFactory, meResponseFactory } from "@@/tests/test-utils"; import { createTestingI18n, createTestingVuetify, @@ -28,7 +30,12 @@ describe("StartExistingCourseSyncDialog", () => { courseName: "courseName", } ) => { + const me = meResponseFactory.build(); + const notifierModule = createModuleMocks(NotifierModule); + const authModule = createModuleMocks(AuthModule, { + getMe: me, + }); const wrapper = mount(StartExistingCourseSyncDialog, { global: { @@ -39,9 +46,11 @@ describe("StartExistingCourseSyncDialog", () => { ], stubs: { GroupSelectionDialog: true, + VDialog: true, }, provide: { [NOTIFIER_MODULE_KEY.valueOf()]: notifierModule, + [AUTH_MODULE_KEY.valueOf()]: authModule, }, }, props, @@ -50,6 +59,7 @@ describe("StartExistingCourseSyncDialog", () => { return { wrapper, notifierModule, + me, }; }; @@ -252,4 +262,76 @@ describe("StartExistingCourseSyncDialog", () => { expect(wrapper.emitted("success")).toBeUndefined(); }); }); + + describe("when the user is part of the selected group", () => { + const setup = async () => { + const { wrapper, notifierModule, me } = getWrapper(); + + const group = groupResponseFactory.build({ + users: [ + { + id: me.user.id, + firstName: me.user.firstName, + lastName: me.user.lastName, + role: RoleName.Teacher, + }, + ], + }); + + wrapper.getComponent(GroupSelectionDialog).vm.$emit("confirm", group); + await nextTick(); + + return { + wrapper, + notifierModule, + group, + }; + }; + + it("should display the correct warning in the confirmation dialog", async () => { + const { wrapper } = await setup(); + + const text = wrapper.find("[data-testid=no-teacher-warning-text]"); + + expect(text.text()).toEqual( + "feature-course-sync.StartExistingCourseSyncDialog.confirmation.userInGroupWarning" + ); + }); + }); + + describe("when the user is not part of the selected group", () => { + const setup = async () => { + const { wrapper, notifierModule } = getWrapper(); + + const group = groupResponseFactory.build({ + users: [ + { + id: "otherUserId", + firstName: "firstname", + lastName: "lastname", + role: RoleName.Teacher, + }, + ], + }); + + wrapper.getComponent(GroupSelectionDialog).vm.$emit("confirm", group); + await nextTick(); + + return { + wrapper, + notifierModule, + group, + }; + }; + + it("should display the correct warning in the confirmation dialog", async () => { + const { wrapper } = await setup(); + + const text = wrapper.find("[data-testid=no-teacher-warning-text]"); + + expect(text.text()).toEqual( + "feature-course-sync.StartExistingCourseSyncDialog.confirmation.userNotInGroupWarning" + ); + }); + }); }); diff --git a/src/modules/feature/course-sync/StartExistingCourseSyncDialog.vue b/src/modules/feature/course-sync/StartExistingCourseSyncDialog.vue index c3ac5b0111..26cef1bad8 100644 --- a/src/modules/feature/course-sync/StartExistingCourseSyncDialog.vue +++ b/src/modules/feature/course-sync/StartExistingCourseSyncDialog.vue @@ -19,18 +19,15 @@