From 4a4ae9d332cff31991d9f63417895fce18717f61 Mon Sep 17 00:00:00 2001 From: Norihito Nakae Date: Tue, 28 Nov 2017 12:46:58 +0900 Subject: Initial support for SAML authentication --- lib/web/auth/index.js | 1 + lib/web/auth/saml/index.js | 96 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 lib/web/auth/saml/index.js (limited to 'lib/web/auth') diff --git a/lib/web/auth/index.js b/lib/web/auth/index.js index 4b618101..db5ff11d 100644 --- a/lib/web/auth/index.js +++ b/lib/web/auth/index.js @@ -37,6 +37,7 @@ 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/saml/index.js b/lib/web/auth/saml/index.js new file mode 100644 index 00000000..575c6f31 --- /dev/null +++ b/lib/web/auth/saml/index.js @@ -0,0 +1,96 @@ +'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.saml.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, + displayName: user[config.saml.attribute.displayName] || 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()) +}) -- cgit v1.2.3