diff --git a/config/default.schema.json b/config/default.schema.json index c3eeca73b4..a294dd051b 100644 --- a/config/default.schema.json +++ b/config/default.schema.json @@ -625,6 +625,11 @@ "type": "boolean", "default": false, "description": "Enables the rooms feature" + }, + "FEATURE_EXTERNAL_SYSTEM_LOGOUT_ENABLED": { + "type": "boolean", + "default": false, + "description": "Enables the external system logout feature" } }, "allOf": [ diff --git a/controllers/courses.js b/controllers/courses.js index 6ab16c6d23..82f672f83a 100644 --- a/controllers/courses.js +++ b/controllers/courses.js @@ -23,6 +23,10 @@ const router = express.Router(); const { HOST } = require('../config/global'); const { isUserHidden } = require('../helpers/users'); +const SYNC_ATTRIBUTE = Object.freeze({ + TEACHERS: 'teachers', +}); + const getSelectOptions = (req, service, query) => api(req).get(`/${service}`, { qs: query, }).then((data) => data.data); @@ -81,6 +85,7 @@ const getSyncedElements = ( untilDate, syncedWithGroup, excludeFromSync: course.excludeFromSync?.join(','), + areTeachersSynced: !course.excludeFromSync?.includes(SYNC_ATTRIBUTE.TEACHERS), }; return selectedElements; }; @@ -310,7 +315,7 @@ const editCourseHandler = (req, res, next) => { const isTeacherInGroup = teacherIds.some((tid) => tid === res.locals.currentUser._id); const isTeacher = res.locals.currentUser.roles.map((role) => role.name).includes('teacher'); if (!isTeacherInGroup && isTeacher) { - course.excludeFromSync = ['teachers']; + course.excludeFromSync = [SYNC_ATTRIBUTE.TEACHERS]; course.teacherIds = [res.locals.currentUser._id]; } else { course.teacherIds = teacherIds; diff --git a/controllers/login.js b/controllers/login.js index 7e52b7f0f4..cf0c744b9d 100644 --- a/controllers/login.js +++ b/controllers/login.js @@ -484,4 +484,21 @@ router.get('/logout/', (req, res, next) => { .catch(next); }); +router.get('/logout/external/', async (req, res, next) => { + let redirectUri = '/logout/'; + if (Configuration.has('OAUTH2_LOGOUT_URI')) { + redirectUri = Configuration.get('OAUTH2_LOGOUT_URI'); + } + + if (res.locals.isExternalLogoutAllowed) { + try { + await api(req, { version: 'v3' }).post('/logout/external'); + } catch (err) { + logger.error('error during external logout.', formatError(err)); + } + } + + res.redirect(redirectUri); +}); + module.exports = router; diff --git a/helpers/authentication.js b/helpers/authentication.js index 3e0b02e32b..854287989a 100644 --- a/helpers/authentication.js +++ b/helpers/authentication.js @@ -64,7 +64,7 @@ const clearCookie = async (req, res, options = { destroySession: false }) => { }); }); } - + res.clearCookie('jwt'); // this is deprecated and only used for cookie removal from now on, // and can be removed after one month (max cookie lifetime from life systems) @@ -101,6 +101,26 @@ const isAuthenticated = (req) => { }; const populateCurrentUser = async (req, res) => { + async function setExternalSystemFromJwt(decodedJwt) { + if (!('systemId' in decodedJwt) && !decodedJwt.systemId) { + return; + } + + try { + const response = await api(req, { version: 'v3' }).get(`/systems/public/${decodedJwt.systemId}`); + const hasEndSessionEndpoint = 'oauthConfig' in response + && 'endSessionEndpoint' in response.oauthConfig + && response.oauthConfig.endSessionEndpoint; + + res.locals.isExternalLogoutAllowed = Configuration.get('FEATURE_EXTERNAL_SYSTEM_LOGOUT_ENABLED') + && hasEndSessionEndpoint; + res.locals.systemName = response.displayName; + } catch (err) { + const metadata = { error: err.toString() }; + logger.error('Unable to find out the external login system used by user', metadata); + } + } + let payload = {}; if (isJWT(req)) { try { @@ -129,6 +149,8 @@ const populateCurrentUser = async (req, res) => { } if (payload && payload.userId) { + await setExternalSystemFromJwt(payload); + if (res.locals.currentUser && res.locals.currentSchoolData) { return Promise.resolve(res.locals.currentSchoolData); } diff --git a/locales/de.json b/locales/de.json index c2e7be541a..22ae8f8630 100644 --- a/locales/de.json +++ b/locales/de.json @@ -3231,4 +3231,4 @@ "createAfterFirstSave": "H5P Inhalte können erst nach dem ersten Speichern erstellt werden." } } -} \ No newline at end of file +} diff --git a/locales/en.json b/locales/en.json index dd4761968e..459a04f9d8 100644 --- a/locales/en.json +++ b/locales/en.json @@ -3231,4 +3231,4 @@ "createAfterFirstSave": "H5P contents can only be created after the first save." } } -} \ No newline at end of file +} diff --git a/locales/es.json b/locales/es.json index 102d999ef0..3c79f69d26 100644 --- a/locales/es.json +++ b/locales/es.json @@ -3231,4 +3231,4 @@ "createAfterFirstSave": "Los contenidos H5P solo se pueden crear después del primer guardado." } } -} \ No newline at end of file +} diff --git a/locales/uk.json b/locales/uk.json index 2fa7939b44..7cbab54ba3 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -3240,4 +3240,4 @@ "createAfterFirstSave": "Вміст H5P можна створити лише після першого збереження." } } -} \ No newline at end of file +} diff --git a/theme/brb/style.scss b/theme/brb/style.scss index 1bdd85e130..93456c8755 100644 --- a/theme/brb/style.scss +++ b/theme/brb/style.scss @@ -1,19 +1,14 @@ -$primaryColor: #0a9396; -$primaryColorOverlay: rgba(10, 147, 150, 0.12); -$secondaryColor: #294c5a; +$primaryColor: #2876D0; +$primaryColorOverlay: rgba(40, 118, 208, 0.12); +$secondaryColor: #0F3551; $accentColor: #E4032E; -$grayDarkColor: #294c5a; - -// :export { -// primaryColor: $primaryColor; -// secondaryColor: $secondaryColor; -// accentColor: $accentColor; -// }; +$grayDarkColor: #0F3551; $logoGradient: linear-gradient(45deg, $primaryColor 30%, $primaryColor); $colorBeige: #e6e2e2; $colorGrey: rgba(232, 232, 232, 0.5); + $logoBackground: url("/images/logo/cloud-transparent-mono.svg") center / contain no-repeat; $logoBackgroundTablet: url("/images/logo/cloud-transparent-mono-48-48.svg") center / contain no-repeat; $logoBackgroundLanding: url("/images/logo/cloud-transparent-mono-long.svg") center / contain no-repeat; diff --git a/theme/thr/style.scss b/theme/thr/style.scss index 9a59b56848..c1f5b90bc9 100644 --- a/theme/thr/style.scss +++ b/theme/thr/style.scss @@ -7,6 +7,9 @@ $grayDarkColor: #0f3551; $logoGradient: linear-gradient(45deg, $primaryColor, $primaryColor); +$colorBeige: #e6e2e2; +$colorGrey: rgba(232, 232, 232, 0.5); + $logoBackground: url("/images/logo/cloud-transparent-mono.svg") center / contain no-repeat; $logoBackgroundTablet: url("/images/logo/cloud-transparent-mono-48-48.svg") center / contain no-repeat; $logoBackgroundLanding: url("/images/logo/cloud-transparent-mono-long.svg") center / contain no-repeat; diff --git a/views/administration/users_edit.hbs b/views/administration/users_edit.hbs index 5aa825ae42..3d1592ebd2 100644 --- a/views/administration/users_edit.hbs +++ b/views/administration/users_edit.hbs @@ -161,7 +161,7 @@
{{#unless schoolUsesLdap}} {{#ifCond isAdmin '|| !' editTeacher}} - diff --git a/views/courses/create-course.hbs b/views/courses/create-course.hbs index ed96190856..100962e34c 100644 --- a/views/courses/create-course.hbs +++ b/views/courses/create-course.hbs @@ -109,7 +109,7 @@
- {{#if syncedWithGroup}} + {{#if areTeachersSynced}} {{/if}} diff --git a/views/courses/edit-course.hbs b/views/courses/edit-course.hbs index acae64f5cf..cce4539d67 100644 --- a/views/courses/edit-course.hbs +++ b/views/courses/edit-course.hbs @@ -138,14 +138,14 @@
- {{#if course.syncedWithGroup}} + {{#if areTeachersSynced}} {{/if}} diff --git a/views/lib/topbar.hbs b/views/lib/topbar.hbs index bab2671335..33df1d2c38 100644 --- a/views/lib/topbar.hbs +++ b/views/lib/topbar.hbs @@ -59,7 +59,22 @@
{{> "user/forms/language" language=@root.userLanguage }}
  • {{$t "lib.loggedin.tab_label.settings" }}
  • -
  • {{$t "lib.loggedin.tab_label.signOut"}}
  • + {{#if isExternalLogoutAllowed}} +
  • + {{$t 'lib.loggedin.tab_label.signOut'}} Bildungscloud & {{ systemName }} +
  • + {{/if}} +
  • + {{$t 'lib.loggedin.tab_label.signOut'}}{{#if isExternalLogoutAllowed}} Bildungscloud{{/if}} +