From 78417524b3d00adcb6f5596a4bdf4dc0287dff39 Mon Sep 17 00:00:00 2001 From: NGPixel Date: Mon, 7 Sep 2020 20:02:33 -0400 Subject: [PATCH] feat: ldap avatar support --- client/components/common/nav-header.vue | 2 +- server/controllers/common.js | 22 ++++++- server/db/migrations-sqlite/2.5.122.js | 9 +++ server/db/migrations/2.5.122.js | 20 ++++++ server/models/users.js | 64 +++++++++++++++++-- .../authentication/ldap/authentication.js | 5 +- 6 files changed, 111 insertions(+), 11 deletions(-) create mode 100644 server/db/migrations-sqlite/2.5.122.js create mode 100644 server/db/migrations/2.5.122.js diff --git a/client/components/common/nav-header.vue b/client/components/common/nav-header.vue index ebcc169d..f1b6e687 100644 --- a/client/components/common/nav-header.vue +++ b/client/components/common/nav-header.vue @@ -305,7 +305,7 @@ export default { if (this.pictureUrl && this.pictureUrl.length > 1) { return { kind: 'image', - url: this.pictureUrl + url: (this.pictureUrl === 'internal') ? `/_userav/${this.$store.get('user/id')}` : this.pictureUrl } } else { const nameParts = this.name.toUpperCase().split(' ') diff --git a/server/controllers/common.js b/server/controllers/common.js index 0c099469..c6940204 100644 --- a/server/controllers/common.js +++ b/server/controllers/common.js @@ -59,7 +59,7 @@ router.get(['/a', '/a/*'], (req, res, next) => { */ router.get(['/d', '/d/*'], async (req, res, next) => { const pageArgs = pageHelper.parsePath(req.path, { stripExt: true }) - + const versionId = (req.query.v) ? _.toSafeInteger(req.query.v) : 0 const page = await WIKI.models.pages.getPageFromDb({ @@ -108,7 +108,7 @@ router.get(['/e', '/e/*'], async (req, res, next) => { } req.i18n.changeLanguage(pageArgs.locale) - + // -> Set Editor Lang _.set(res, 'locals.siteConfig.lang', pageArgs.locale) _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl') @@ -239,7 +239,7 @@ router.get(['/h', '/h/*'], async (req, res, next) => { if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) { return res.redirect(`/h/${pageArgs.locale}/${pageArgs.path}`) } - + req.i18n.changeLanguage(pageArgs.locale) _.set(res, 'locals.siteConfig.lang', pageArgs.locale) @@ -390,6 +390,22 @@ router.get(['/t', '/t/*'], (req, res, next) => { res.render('tags') }) +/** + * User Avatar + */ +router.get('/_userav/:uid', async (req, res, next) => { + if (!WIKI.auth.checkAccess(req.user, ['read:pages'])) { + return res.sendStatus(403) + } + const av = await WIKI.models.users.getUserAvatarData(req.params.uid) + if (av) { + res.set('Content-Type', 'image/jpeg') + res.send(av) + } + + return res.sendStatus(404) +}) + /** * View document / asset */ diff --git a/server/db/migrations-sqlite/2.5.122.js b/server/db/migrations-sqlite/2.5.122.js new file mode 100644 index 00000000..115afe1d --- /dev/null +++ b/server/db/migrations-sqlite/2.5.122.js @@ -0,0 +1,9 @@ +exports.up = knex => { + return knex.schema + .createTable('userAvatars', table => { + table.integer('id').primary() + table.binary('data').notNullable() + }) +} + +exports.down = knex => { } diff --git a/server/db/migrations/2.5.122.js b/server/db/migrations/2.5.122.js new file mode 100644 index 00000000..24c5377c --- /dev/null +++ b/server/db/migrations/2.5.122.js @@ -0,0 +1,20 @@ +/* global WIKI */ + +exports.up = knex => { + const dbCompat = { + blobLength: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`), + charset: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`) + } + return knex.schema + .createTable('userAvatars', table => { + if (dbCompat.charset) { table.charset('utf8mb4') } + table.integer('id').primary() + if (dbCompat.blobLength) { + table.specificType('data', 'LONGBLOB').notNullable() + } else { + table.binary('data').notNullable() + } + }) +} + +exports.down = knex => { } diff --git a/server/models/users.js b/server/models/users.js index 2a3ee313..e648be44 100644 --- a/server/models/users.js +++ b/server/models/users.js @@ -211,11 +211,16 @@ module.exports = class User extends Model { displayName = primaryEmail.split('@')[0] } - // Parse picture URL - let pictureUrl = _.truncate(_.get(profile, 'picture', _.get(user, 'pictureUrl', null)), { - length: 255, - omission: '' - }) + // Parse picture URL / Data + let pictureUrl = '' + if (profile.picture && Buffer.isBuffer(profile.picture)) { + pictureUrl = 'internal' + } else { + pictureUrl = _.truncate(_.get(profile, 'picture', _.get(user, 'pictureUrl', null)), { + length: 255, + omission: '' + }) + } // Update existing user if (user) { @@ -232,6 +237,10 @@ module.exports = class User extends Model { pictureUrl: pictureUrl }) + if (pictureUrl === 'internal') { + await WIKI.models.users.updateUserAvatarData(user.id, profile.picture) + } + return user } @@ -265,6 +274,10 @@ module.exports = class User extends Model { await user.$relatedQuery('groups').relate(provider.autoEnrollGroups) } + if (pictureUrl === 'internal') { + await WIKI.models.users.updateUserAvatarData(user.id, profile.picture) + } + return user } @@ -287,6 +300,7 @@ module.exports = class User extends Model { if (strInfo.useForm) { _.set(context.req, 'body.email', opts.username) _.set(context.req, 'body.password', opts.password) + _.set(context.req.params, 'strategy', opts.strategy) } // Authenticate @@ -868,4 +882,44 @@ module.exports = class User extends Model { user.permissions = ['manage:system'] return user } + + /** + * Add / Update User Avatar Data + */ + static async updateUserAvatarData (userId, data) { + try { + WIKI.logger.debug(`Updating user ${userId} avatar data...`) + if (data.length > 1024 * 1024) { + throw new Error('Avatar image filesize is too large. 1MB max.') + } + const existing = await WIKI.models.knex('userAvatars').select('id').where('id', userId).first() + if (existing) { + await WIKI.models.knex('userAvatars').where({ + id: userId + }).update({ + data + }) + } else { + await WIKI.models.knex('userAvatars').insert({ + id: userId, + data + }) + } + } catch (err) { + WIKI.logger.warn(`Failed to process binary thumbnail data for user ${userId}: ${err.message}`) + } + } + + static async getUserAvatarData (userId) { + try { + const usrData = await WIKI.models.knex('userAvatars').where('id', userId).first() + if (usrData) { + return usrData.data + } else { + return null + } + } catch (err) { + WIKI.logger.warn(`Failed to process binary thumbnail data for user ${userId}`) + } + } } diff --git a/server/modules/authentication/ldap/authentication.js b/server/modules/authentication/ldap/authentication.js index 555abb40..2daeeb36 100644 --- a/server/modules/authentication/ldap/authentication.js +++ b/server/modules/authentication/ldap/authentication.js @@ -23,7 +23,8 @@ module.exports = { ca: [ fs.readFileSync(conf.tlsCertPath) ] - } : {} + } : {}, + includeRaw: true }, usernameField: 'email', passwordField: 'password', @@ -41,7 +42,7 @@ module.exports = { id: userId, email: String(_.get(profile, conf.mappingEmail, '')).split(',')[0], displayName: _.get(profile, conf.mappingDisplayName, '???'), - picture: _.get(profile, conf.mappingPicture, '') + picture: _.get(profile, `_raw.${conf.mappingPicture}`, '') } }) cb(null, user)