From 3cd09eecbae8016060c1d1c5f3ba323a166df808 Mon Sep 17 00:00:00 2001 From: David Leimroth <> Date: Thu, 10 Feb 2022 14:45:21 +0100 Subject: [PATCH] adjusted SSOSettings model and changed all code based on the model --- client/layouts/default.vue | 3 + client/pages/config/sso.vue | 244 +++++++++++++++++++++++----------- client/pages/login.vue | 1 - client/store/sso.js | 23 ++-- server/ApiController.js | 8 +- server/Auth.js | 7 +- server/Server.js | 4 + server/objects/SSOSettings.js | 61 +++++++-- server/objects/User.js | 3 + 9 files changed, 251 insertions(+), 103 deletions(-) diff --git a/client/layouts/default.vue b/client/layouts/default.vue index 67727b840..0c4d545a5 100644 --- a/client/layouts/default.vue +++ b/client/layouts/default.vue @@ -112,6 +112,9 @@ export default { if (payload.serverSettings) { this.$store.commit('setServerSettings', payload.serverSettings) } + if (payload.SSOSettings) { + this.$store.commit('sso/setSSOSettings', payload.SSOSettings) + } // Start scans currently running if (payload.librariesScanning) { diff --git a/client/pages/config/sso.vue b/client/pages/config/sso.vue index 97fca8eb4..ccb09609b 100644 --- a/client/pages/config/sso.vue +++ b/client/pages/config/sso.vue @@ -9,32 +9,32 @@

Issuer 

- +

Authorization URL 

- +

Token URL 

- +

User Info URL 

- +

Client ID 

- +

Client Secret 

- +
@@ -42,32 +42,32 @@
- +

Create a new user on first login

- +

The new user is allowed to download

- +

The new user is allowed to update

- +

The new user is allowed to delete

- +

The new user is allowed to upload

- +

The new user is allowed to access all libraries

@@ -90,16 +90,28 @@ export default { clientID: "", clientSecret: "", }, - - permissions: { + user: { createNewUser: false, - download: false, - update: false, - delete: false, - upload: false, - accessAllLibraries: false + isActive: true, + settings: { + mobileOrderBy: 'recent', + mobileOrderDesc: true, + mobileFilterBy: 'all', + orderBy: 'book.title', + orderDesc: false, + filterBy: 'all', + playbackRate: 1, + bookshelfCoverSize: 120, + collapseSeries: false + }, + permissions: { + download: false, + update: false, + delete: false, + upload: false, + accessAllLibraries: false + } }, - updatingSSOSettings: false, newSSOSettings: {} } @@ -114,55 +126,136 @@ export default { }, computed: { SSOSettings() { - return this.$store.state.SSOSettings + return this.$store.state.sso + }, + + issuer: { + get() { + return this.oidc.issuer + return this.$store.state.sso.oidc.issuer + }, + set(val) { + this.oidc.issuer = val + // this.$store.state.sso.oidc.issuer = val + } + }, + authorizationURL: { + get() { + return this.oidc.authorizationURL + return this.$store.state.sso.oidc.authorizationURL + }, + set(val) { + this.oidc.authorizationURL = val + // this.$store.state.sso.oidc.authorizationURL = val + } + }, + tokenURL: { + get() { + return this.oidc.tokenURL + return this.$store.state.sso.oidc.tokenURL + }, + set(val) { + this.oidc.tokenURL = val + // this.$store.state.sso.oidc.tokenURL = val + } + }, + userInfoURL: { + get() { + return this.oidc.userInfoURL + return this.$store.state.sso.oidc.userInfoURL + }, + set(val) { + this.oidc.userInfoURL = val + // this.$store.state.sso.oidc.userInfoURL = val + }, + }, + clientID: { + get() { + return this.oidc.clientID + return this.$store.state.sso.oidc.clientID + }, + set(val) { + this.oidc.clientID = val + // this.$store.state.sso.oidc.clientID = val + }, + }, + clientSecret: { + get() { + return this.oidc.clientSecret + return this.$store.state.sso.oidc.clientSecret + }, + set(val) { + this.oidc.clientSecret = val + // this.$store.state.sso.oidc.clientSecret = val + }, + }, + createNewUser: { + get() { + return this.user.createNewUser + return this.$store.state.sso.createNewUser + }, + set(val) { + this.user.createNewUser = val + // this.$store.state.sso.createNewUser = val + }, + }, + permissionDownload: { + get() { + return this.user.permissions.download + return this.$store.state.sso.permissions.download + }, + set(val) { + this.user.permissions.download = val + // this.$store.state.sso.permissions.download = val + }, + }, + permissionUpdate: { + get() { + return this.user.permissions.update + return this.$store.state.sso.permissions.update + }, + set(val) { + this.user.permissions.update = val + // this.$store.state.sso.permissions.update = val + }, + }, + permissionDelete: { + get() { + return this.user.permissions.delete + return this.$store.state.sso.permissions.delete + }, + set(val) { + this.user.permissions.delete = val + // this.$store.state.sso.permissions.delete = val + }, + }, + permissionUpload: { + get() { + return this.user.permissions.upload + return this.$store.state.sso.permissions.upload + }, + set(val) { + this.user.permissions.upload = val + // this.$store.state.sso.permissions.upload = val + } + }, + permissionAccessAllLibraries: { + get() { + return this.user.permissions.accessAllLibraries + return this.$store.state.sso.permissions.accessAllLibraries + }, + set(val) { + this.user.permissions.accessAllLibraries = val + // this.$store.state.sso.permissions.accessAllLibraries = val + } }, }, methods: { - updateSSOIssuer(val) { - this.oidc.issuer = val - }, - updateAuthorizationURL(val) { - this.oidc.authorizationURL = val - }, - updateTokenURL(val) { - this.oidc.tokenURL = val - }, - updateUserInfoURL(val) { - this.oidc.userInfoURL = val - }, - updateClientID(val) { - this.oidc.clientID = val - }, - updateClientSecret(val) { - this.oidc.clientSecret = val - }, - updatePermissionCreateNewUser(val) { - this.permissions.createNewUser = val - }, - updatePermissionDownload(val) { - this.permissions.createNewUser = val - }, - updatePermissionUpdate(val) { - this.permissions.createNewUser = val - }, - updatePermissionDelete(val) { - this.permissions.createNewUser = val - }, - updatePermissionUpload(val) { - this.permissions.createNewUser = val - }, - updatePermissionAccessAllLibraries(val) { - this.permissions.createNewUser = val - }, - updatePermissionCreateNewUser(val) { - this.permissions.createNewUser = val - }, saveSSOSettings(payload) { this.updatingSSOSettings = true this.$store - .dispatch('sso/updateSSOSettings', {oidc: this.oidc, permissions: this.permissions}) + .dispatch('sso/updateSSOSettings', {oidc: this.oidc, user: this.user}) .then((success) => { - console.log('Updated SSO Settings', success) this.updatingSSOSettings = false }) .catch((error) => { @@ -171,20 +264,21 @@ export default { }) }, initSSOSettings() { - this.newSSOSettings = this.SSOSettings ? { ...this.SSOSettings } : {} + for (const key in this.$store.state.sso.oidc) { + this.oidc[key] = this.$store.state.sso.oidc[key] + } - - this.oidc.issuer = this.newSSOSettings.issuer - this.oidc.authorizationURL = this.newSSOSettings.authorizationURL - this.oidc.tokenURL = this.newSSOSettings.tokenURL - this.oidc.userInfoURL = this.newSSOSettings.userInfoURL - this.oidc.clientID = this.newSSOSettings.clientID - this.oidc.clientSecret = this.newSSOSettings.clientSecret - this.updatingSSOSettings = this.newSSOSettings.updatingSSOSettings - - // this.newSSOSettings.coverDestination === this.$constants.CoverDestination.AUDIOBOOK - // this.useSquareBookCovers = this.newSSOSettings.coverAspectRatio === this.$constants.BookCoverAspectRatio.SQUARE - // this.useAlternativeBookshelfView = this.newSSOSettings.bookshelfView === this.$constants.BookshelfView.TITLES + for (const key in this.$store.state.sso.user) { + if (typeof this.$store.state.sso.user[key] === "object" && typeof this.user[key] === "object") { + for (const key2 in this.$store.state.sso.user[key]) { + this.user[key][key2] = this.$store.state.sso.user[key][key2] + } + continue + } + if (this.user[key] !== undefined) { + this.user[key] = this.$store.state.sso.user[key] + } + } }, }, diff --git a/client/pages/login.vue b/client/pages/login.vue index f6b1dadd0..35e00726e 100644 --- a/client/pages/login.vue +++ b/client/pages/login.vue @@ -111,7 +111,6 @@ export default { this.$axios .$post('/api/authorize', null, {}) .then((res) => { - console.log({res}) this.setUser(res.user) this.processing = false }) diff --git a/client/store/sso.js b/client/store/sso.js index 7cb806c15..7bd70812b 100644 --- a/client/store/sso.js +++ b/client/store/sso.js @@ -87,20 +87,21 @@ export const actions = { export const mutations = { setSSOSettings(state, settings) { - if (!settings) return + if (!settings || !settings.oidc || !settings.user) return - for (const key in settings) { - if (state.oidc[key] !== settings[key]) { - state.oidc[key] = settings[key] - } + for (const key in settings.oidc) { + state.oidc[key] = settings.oidc[key] } - }, - setUserPermissions(state, permissions) { - if (!permissions) return - for (const key in permissions) { - if (state.user.permissions[key] !== permissions[key]) { - state.user.permissions[key] = permissions[key] + for (const key in settings.user) { + if (typeof settings.user[key] === "object" && typeof state.user[key] === "object") { + for (const key2 in settings.user[key]) { + state.user[key][key2] = settings.user[key][key2] + } + continue + } + if (state.user[key] !== undefined) { + state.user[key] = settings.user[key] } } }, diff --git a/server/ApiController.js b/server/ApiController.js index 808cd54c0..3dba0408c 100644 --- a/server/ApiController.js +++ b/server/ApiController.js @@ -297,18 +297,20 @@ class ApiController { return res.sendStatus(403) } let SSOUpdate = req.body + Logger.debug("SSOUpdate=", SSOUpdate) if (!SSOUpdate || !isObject(SSOUpdate)) { return res.status(500).send('Invalid settings update object') } - console.log("SSOUpdate", JSON.stringify(SSOUpdate)) + var madeUpdates = this.db.SSOSettings.update(SSOUpdate) + console.log("SSOUpdate", JSON.stringify(this.db.SSOSettings)) if (madeUpdates) { - await this.db.updateEntity('sso', this.db.SSOUpdate) + await this.db.updateEntity('settings', this.db.SSOSettings) } return res.json({ success: true, - SSOUpdate: this.db.SSOUpdate + SSOUpdate: this.db.SSOSettings }) } diff --git a/server/Auth.js b/server/Auth.js index c684c3e85..9af0a817b 100644 --- a/server/Auth.js +++ b/server/Auth.js @@ -217,11 +217,12 @@ class Auth { async handleOIDCVerification(issuer, profile, cb) { Logger.debug(`[Auth] handleOIDCVerification ${issuer}`) - let user = this.db.users.find(u => u.id === profile.id) - if (!user && this.db.SSOSettings.createNewUser) { + let user = this.db.users.find(u => u.ssoId === profile.id) + if (!user && this.db.SSOSettings.user.createNewUser) { // create a user let account = {} - account.id = profile.id + account.id = profile.username + account.ssoId = profile.id account.username = profile.username account.isActive = true account.type = "guest" diff --git a/server/Server.js b/server/Server.js index fd1cc47d3..c853d4f4b 100644 --- a/server/Server.js +++ b/server/Server.js @@ -85,6 +85,9 @@ class Server { return client.user.toJSONForPublic(this.streamManager.streams) }) } + get SSOSettings() { + return this.db.SSOSettings + } getClientsForUser(userId) { return Object.values(this.clients).filter(c => c.user && c.user.id === userId) @@ -679,6 +682,7 @@ class Server { } if (user.type === 'root') { initialPayload.usersOnline = this.usersOnline + initialPayload.SSOSettings = this.SSOSettings } client.socket.emit('init', initialPayload) diff --git a/server/objects/SSOSettings.js b/server/objects/SSOSettings.js index 7a3df5106..1f65b3e53 100644 --- a/server/objects/SSOSettings.js +++ b/server/objects/SSOSettings.js @@ -1,22 +1,51 @@ const Logger = require('../Logger') const User = require('./User') const { isObject } = require('../utils') +const { difference } = require('../utils/string') const defaultSettings = { createNewUser: false, - userPermissions: User.getDefaultUserPermissions('guest') + user: { + createNewUser: false, + isActive: true, + settings: { + mobileOrderBy: 'recent', + mobileOrderDesc: true, + mobileFilterBy: 'all', + orderBy: 'book.title', + orderDesc: false, + filterBy: 'all', + playbackRate: 1, + bookshelfCoverSize: 120, + collapseSeries: false + }, + permissions: User.getDefaultUserPermissions('guest') + } } class SSOSettings { constructor(settings = defaultSettings) { this.id = 'sso-settings' - this.createNewUser = !!settings.createNewUser - this.userPermissions = { ...settings.userPermissions } - this.initOIDCSettings(); + this.user = { ...settings.user } + this.initOIDCSettings(settings); + Logger.debug("[SSOSettings.constructor]", this.toJSON) } - initOIDCSettings() { + initOIDCSettings(settings) { // can't be part of default settings, because apperently process.env is not set in the beginning + if (settings && settings.oidc) { + this.oidc = { + issuer: settings.oidc.issuer, + authorizationURL: settings.oidc.authorizationURL, + tokenURL: settings.oidc.tokenURL, + userInfoURL: settings.oidc.userInfoURL, + clientID: settings.oidc.clientID, + clientSecret: settings.oidc.clientSecret, + callbackURL: "/oidc/callback", + scope: "openid email profile" + } + return + } this.oidc = { issuer: process.env.OIDC_ISSUER || '', authorizationURL: process.env.OIDC_AUTHORIZATION_URL || '', @@ -34,17 +63,28 @@ class SSOSettings { } toJSON() { - return { + const tmp = { id: this.id, oidc: { ...this.oidc }, - createNewUser: this.createNewUser, - userPermissions: { ...this.userPermissions } + user: { ...this.user } } + return tmp } update(payload) { - let hasUpdates = false + const oldTmp = JSON.stringify(this.toJSON()) + const newTmp = JSON.stringify(payload) // deep copy "for free" + const hasUpdates = difference(oldTmp, newTmp) !== ""; // Not very efficient, but ok for small objects + Logger.debug(`SSOSettings hasUpdates=${hasUpdates}`) + if (!hasUpdates) return hasUpdates + + payload = JSON.parse(newTmp) + this.oidc = payload.oidc + this.user = payload.user + return hasUpdates + Logger.debug("SSOSettings.update", payload, this) for (const key in payload) { + Logger.debug(`key: ${key}: ${JSON.stringify(payload[key])}`) if (isObject(payload[key])) { for (const setting in payload[key]) { if (!this[key] || this[key][setting] === payload[key][setting]) { @@ -58,12 +98,13 @@ class SSOSettings { hasUpdates = true } } + Logger.debug("SSOSettings.update", hasUpdates, this) return hasUpdates } getNewUserPermissions() { return { - ...this.userPermissions + ...this.user.permissions } } diff --git a/server/objects/User.js b/server/objects/User.js index ddb53b8c2..2196007e4 100644 --- a/server/objects/User.js +++ b/server/objects/User.js @@ -14,6 +14,7 @@ class User { this.lastSeen = null this.createdAt = null this.audiobooks = null + this.ssoId = null this.settings = {} this.permissions = {} @@ -84,6 +85,7 @@ class User { toJSON() { return { id: this.id, + ssoId: this.ssoId, username: this.username, pash: this.pash, type: this.type, @@ -138,6 +140,7 @@ class User { this.type = user.type this.stream = user.stream || null this.token = user.token + this.ssoId = user.ssoId || "" if (user.audiobooks) { this.audiobooks = {} for (const key in user.audiobooks) {