Pārlūkot izejas kodu

feat: ldap avatar support

NGPixel 4 gadi atpakaļ
vecāks
revīzija
78417524b3

+ 1 - 1
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(' ')

+ 19 - 3
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
  */

+ 9 - 0
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 => { }

+ 20 - 0
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 => { }

+ 59 - 5
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}`)
+    }
+  }
 }

+ 3 - 2
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)