diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/config/default.js | 19 | ||||
-rw-r--r-- | lib/config/dockerSecret.js | 4 | ||||
-rw-r--r-- | lib/config/environment.js | 25 | ||||
-rw-r--r-- | lib/config/index.js | 2 | ||||
-rw-r--r-- | lib/config/utils.js | 7 | ||||
-rw-r--r-- | lib/models/user.js | 18 | ||||
-rwxr-xr-x | lib/response.js | 4 | ||||
-rw-r--r-- | lib/web/auth/index.js | 2 | ||||
-rw-r--r-- | lib/web/auth/mattermost/index.js | 49 | ||||
-rw-r--r-- | lib/web/auth/saml/index.js | 95 |
10 files changed, 222 insertions, 3 deletions
diff --git a/lib/config/default.js b/lib/config/default.js index e7e2e4b3..d04485ce 100644 --- a/lib/config/default.js +++ b/lib/config/default.js @@ -74,6 +74,11 @@ module.exports = { clientSecret: undefined, scope: undefined }, + mattermost: { + baseURL: undefined, + clientID: undefined, + clientSecret: undefined + }, dropbox: { clientID: undefined, clientSecret: undefined @@ -93,6 +98,20 @@ module.exports = { searchAttributes: undefined, tlsca: undefined }, + saml: { + idpSsoUrl: undefined, + idpCert: undefined, + issuer: undefined, + identifierFormat: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', + groupAttribute: undefined, + externalGroups: [], + requiredGroups: [], + attribute: { + id: undefined, + username: undefined, + email: undefined + } + }, email: true, allowemailregister: true, allowpdfexport: true diff --git a/lib/config/dockerSecret.js b/lib/config/dockerSecret.js index eea2fafd..ac54fd19 100644 --- a/lib/config/dockerSecret.js +++ b/lib/config/dockerSecret.js @@ -38,6 +38,10 @@ if (fs.existsSync(basePath)) { clientID: getSecret('gitlab_clientID'), clientSecret: getSecret('gitlab_clientSecret') }, + mattermost: { + clientID: getSecret('mattermost_clientID'), + clientSecret: getSecret('mattermost_clientSecret') + }, dropbox: { clientID: getSecret('dropbox_clientID'), clientSecret: getSecret('dropbox_clientSecret') diff --git a/lib/config/environment.js b/lib/config/environment.js index 6f33d140..45a2b0e0 100644 --- a/lib/config/environment.js +++ b/lib/config/environment.js @@ -1,6 +1,6 @@ 'use strict' -const {toBooleanConfig} = require('./utils') +const {toBooleanConfig, toArrayConfig} = require('./utils') module.exports = { domain: process.env.HMD_DOMAIN, @@ -15,7 +15,7 @@ module.exports = { preload: toBooleanConfig(process.env.HMD_HSTS_PRELOAD) }, protocolusessl: toBooleanConfig(process.env.HMD_PROTOCOL_USESSL), - alloworigin: process.env.HMD_ALLOW_ORIGIN ? process.env.HMD_ALLOW_ORIGIN.split(',') : undefined, + alloworigin: toArrayConfig(process.env.HMD_ALLOW_ORIGIN), usecdn: toBooleanConfig(process.env.HMD_USECDN), allowanonymous: toBooleanConfig(process.env.HMD_ALLOW_ANONYMOUS), allowfreeurl: toBooleanConfig(process.env.HMD_ALLOW_FREEURL), @@ -49,6 +49,11 @@ module.exports = { clientSecret: process.env.HMD_GITLAB_CLIENTSECRET, scope: process.env.HMD_GITLAB_SCOPE }, + mattermost: { + baseURL: process.env.HMD_MATTERMOST_BASEURL, + clientID: process.env.HMD_MATTERMOST_CLIENTID, + clientSecret: process.env.HMD_MATTERMOST_CLIENTSECRET + }, dropbox: { clientID: process.env.HMD_DROPBOX_CLIENTID, clientSecret: process.env.HMD_DROPBOX_CLIENTSECRET @@ -65,9 +70,23 @@ module.exports = { tokenSecret: process.env.HMD_LDAP_TOKENSECRET, searchBase: process.env.HMD_LDAP_SEARCHBASE, searchFilter: process.env.HMD_LDAP_SEARCHFILTER, - searchAttributes: process.env.HMD_LDAP_SEARCHATTRIBUTES, + searchAttributes: toArrayConfig(process.env.HMD_LDAP_SEARCHATTRIBUTES), tlsca: process.env.HMD_LDAP_TLS_CA }, + saml: { + idpSsoUrl: process.env.HMD_SAML_IDPSSOURL, + idpCert: process.env.HMD_SAML_IDPCERT, + issuer: process.env.HMD_SAML_ISSUER, + identifierFormat: process.env.HMD_SAML_IDENTIFIERFORMAT, + groupAttribute: process.env.HMD_SAML_GROUPATTRIBUTE, + externalGroups: toArrayConfig(process.env.HMD_SAML_EXTERNALGROUPS, '|', []), + requiredGroups: toArrayConfig(process.env.HMD_SAML_REQUIREDGROUPS, '|', []), + attribute: { + id: process.env.HMD_SAML_ATTRIBUTE_ID, + username: process.env.HMD_SAML_ATTRIBUTE_USERNAME, + email: process.env.HMD_SAML_ATTRIBUTE_EMAIL + } + }, email: toBooleanConfig(process.env.HMD_EMAIL), allowemailregister: toBooleanConfig(process.env.HMD_ALLOW_EMAIL_REGISTER), allowpdfexport: toBooleanConfig(process.env.HMD_ALLOW_PDF_EXPORT) diff --git a/lib/config/index.js b/lib/config/index.js index dfad28ed..3ac3de53 100644 --- a/lib/config/index.js +++ b/lib/config/index.js @@ -90,7 +90,9 @@ config.isTwitterEnable = config.twitter.consumerKey && config.twitter.consumerSe config.isEmailEnable = config.email config.isGitHubEnable = config.github.clientID && config.github.clientSecret config.isGitLabEnable = config.gitlab.clientID && config.gitlab.clientSecret +config.isMattermostEnable = config.mattermost.clientID && config.mattermost.clientSecret config.isLDAPEnable = config.ldap.url +config.isSAMLEnable = config.saml.idpSsoUrl config.isPDFExportEnable = config.allowpdfexport // generate correct path diff --git a/lib/config/utils.js b/lib/config/utils.js index 11bbd8cb..9ff2f96d 100644 --- a/lib/config/utils.js +++ b/lib/config/utils.js @@ -6,3 +6,10 @@ exports.toBooleanConfig = function toBooleanConfig (configValue) { } return configValue } + +exports.toArrayConfig = function toArrayConfig (configValue, separator = ',', fallback) { + if (configValue && typeof configValue === 'string') { + return (configValue.split(separator).map(arrayItem => arrayItem.trim())) + } + return fallback +} diff --git a/lib/models/user.js b/lib/models/user.js index e59b86cc..f421fe43 100644 --- a/lib/models/user.js +++ b/lib/models/user.js @@ -111,6 +111,15 @@ module.exports = function (sequelize, DataTypes) { photo = letterAvatars(profile.username) } break + case 'mattermost': + photo = profile.avatarUrl + if (photo) { + if (bigger) photo = photo.replace(/(\?s=)\d*$/i, '$1400') + else photo = photo.replace(/(\?s=)\d*$/i, '$196') + } else { + photo = letterAvatars(profile.username) + } + break case 'dropbox': // no image api provided, use gravatar photo = 'https://www.gravatar.com/avatar/' + md5(profile.emails[0].value) @@ -134,6 +143,15 @@ module.exports = function (sequelize, DataTypes) { photo = letterAvatars(profile.username) } break + case 'saml': + if (profile.emails[0]) { + photo = 'https://www.gravatar.com/avatar/' + md5(profile.emails[0]) + if (bigger) photo += '?s=400' + else photo += '?s=96' + } else { + photo = letterAvatars(profile.username) + } + break } return photo }, diff --git a/lib/response.js b/lib/response.js index 9e39ffb5..9f3d5a44 100755 --- a/lib/response.js +++ b/lib/response.js @@ -64,9 +64,11 @@ function showIndex (req, res, next) { twitter: config.isTwitterEnable, github: config.isGitHubEnable, gitlab: config.isGitLabEnable, + mattermost: config.isMattermostEnable, dropbox: config.isDropboxEnable, google: config.isGoogleEnable, ldap: config.isLDAPEnable, + saml: config.isSAMLEnable, email: config.isEmailEnable, allowemailregister: config.allowemailregister, allowpdfexport: config.allowpdfexport, @@ -95,9 +97,11 @@ function responseHackMD (res, note) { twitter: config.isTwitterEnable, github: config.isGitHubEnable, gitlab: config.isGitLabEnable, + mattermost: config.isMattermostEnable, dropbox: config.isDropboxEnable, google: config.isGoogleEnable, ldap: config.isLDAPEnable, + saml: config.isSAMLEnable, email: config.isEmailEnable, allowemailregister: config.allowemailregister, allowpdfexport: config.allowpdfexport diff --git a/lib/web/auth/index.js b/lib/web/auth/index.js index b5ca8434..db5ff11d 100644 --- a/lib/web/auth/index.js +++ b/lib/web/auth/index.js @@ -33,9 +33,11 @@ if (config.isFacebookEnable) authRouter.use(require('./facebook')) if (config.isTwitterEnable) authRouter.use(require('./twitter')) if (config.isGitHubEnable) authRouter.use(require('./github')) if (config.isGitLabEnable) authRouter.use(require('./gitlab')) +if (config.isMattermostEnable) authRouter.use(require('./mattermost')) if (config.isDropboxEnable) authRouter.use(require('./dropbox')) if (config.isGoogleEnable) authRouter.use(require('./google')) if (config.isLDAPEnable) authRouter.use(require('./ldap')) +if (config.isSAMLEnable) authRouter.use(require('./saml')) if (config.isEmailEnable) authRouter.use(require('./email')) // logout diff --git a/lib/web/auth/mattermost/index.js b/lib/web/auth/mattermost/index.js new file mode 100644 index 00000000..9ccf3de5 --- /dev/null +++ b/lib/web/auth/mattermost/index.js @@ -0,0 +1,49 @@ +'use strict' + +const Router = require('express').Router +const passport = require('passport') +const Mattermost = require('mattermost') +const OAuthStrategy = require('passport-oauth2').Strategy +const config = require('../../../config') +const {setReturnToFromReferer, passportGeneralCallback} = require('../utils') + +const mattermost = new Mattermost.Client() + +let mattermostAuth = module.exports = Router() + +let mattermostStrategy = new OAuthStrategy({ + authorizationURL: config.mattermost.baseURL + '/oauth/authorize', + tokenURL: config.mattermost.baseURL + '/oauth/access_token', + clientID: config.mattermost.clientID, + clientSecret: config.mattermost.clientSecret, + callbackURL: config.serverurl + '/auth/mattermost/callback' +}, passportGeneralCallback) + +mattermostStrategy.userProfile = (accessToken, done) => { + mattermost.setUrl(config.mattermost.baseURL) + mattermost.token = accessToken + mattermost.useHeaderToken() + mattermost.getMe( + (data) => { + done(null, data) + }, + (err) => { + done(err) + } + ) +} + +passport.use(mattermostStrategy) + +mattermostAuth.get('/auth/mattermost', function (req, res, next) { + setReturnToFromReferer(req) + passport.authenticate('oauth2')(req, res, next) +}) + +// mattermost auth callback +mattermostAuth.get('/auth/mattermost/callback', + passport.authenticate('oauth2', { + successReturnToOrRedirect: config.serverurl + '/', + failureRedirect: config.serverurl + '/' + }) +) diff --git a/lib/web/auth/saml/index.js b/lib/web/auth/saml/index.js new file mode 100644 index 00000000..386293ae --- /dev/null +++ b/lib/web/auth/saml/index.js @@ -0,0 +1,95 @@ +'use strict' + +const Router = require('express').Router +const passport = require('passport') +const SamlStrategy = require('passport-saml').Strategy +const config = require('../../../config') +const models = require('../../../models') +const logger = require('../../../logger') +const {urlencodedParser} = require('../../utils') +const fs = require('fs') +const intersection = function (array1, array2) { return array1.filter((n) => array2.includes(n)) } + +let samlAuth = module.exports = Router() + +passport.use(new SamlStrategy({ + callbackUrl: config.serverurl + '/auth/saml/callback', + entryPoint: config.saml.idpSsoUrl, + issuer: config.saml.issuer || config.serverurl, + cert: fs.readFileSync(config.saml.idpCert, 'utf-8'), + identifierFormat: config.saml.identifierFormat +}, function (user, done) { + // check authorization if needed + if (config.saml.externalGroups && config.saml.grouptAttribute) { + var externalGroups = intersection(config.saml.externalGroups, user[config.saml.groupAttribute]) + if (externalGroups.length > 0) { + logger.error('saml permission denied: ' + externalGroups.join(', ')) + return done('Permission denied', null) + } + } + if (config.saml.requiredGroups && config.saml.grouptAttribute) { + if (intersection(config.saml.requiredGroups, user[config.saml.groupAttribute]).length === 0) { + logger.error('saml permission denied') + return done('Permission denied', null) + } + } + // user creation + var uuid = user[config.saml.attribute.id] || user.nameID + var profile = { + provider: 'saml', + id: 'SAML-' + uuid, + username: user[config.saml.attribute.username] || user.nameID, + emails: user[config.saml.attribute.email] ? [user[config.saml.attribute.email]] : [] + } + if (profile.emails.length === 0 && config.saml.identifierFormat === 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress') { + profile.emails.push(user.nameID) + } + var stringifiedProfile = JSON.stringify(profile) + models.User.findOrCreate({ + where: { + profileid: profile.id.toString() + }, + defaults: { + profile: stringifiedProfile + } + }).spread(function (user, created) { + if (user) { + var needSave = false + if (user.profile !== stringifiedProfile) { + user.profile = stringifiedProfile + needSave = true + } + if (needSave) { + user.save().then(function () { + if (config.debug) { logger.debug('user login: ' + user.id) } + return done(null, user) + }) + } else { + if (config.debug) { logger.debug('user login: ' + user.id) } + return done(null, user) + } + } + }).catch(function (err) { + logger.error('saml auth failed: ' + err) + return done(err, null) + }) +})) + +samlAuth.get('/auth/saml', + passport.authenticate('saml', { + successReturnToOrRedirect: config.serverurl + '/', + failureRedirect: config.serverurl + '/' + }) +) + +samlAuth.post('/auth/saml/callback', urlencodedParser, + passport.authenticate('saml', { + successReturnToOrRedirect: config.serverurl + '/', + failureRedirect: config.serverurl + '/' + }) +) + +samlAuth.get('/auth/saml/metadata', function (req, res) { + res.type('application/xml') + res.send(passport._strategy('saml').generateServiceProviderMetadata()) +}) |