Merge pull request #784 from pferreir/add-oauth2-support

Add "generic" OAuth2 support
This commit is contained in:
Christoph (Sheogorath) Kern 2018-06-04 15:54:47 +02:00 committed by GitHub
commit 551840ad57
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 163 additions and 6 deletions

View file

@ -79,6 +79,12 @@ module.exports = {
container: undefined container: undefined
}, },
// authentication // authentication
oauth2: {
authorizationURL: undefined,
tokenURL: undefined,
clientID: undefined,
clientSecret: undefined
},
facebook: { facebook: {
clientID: undefined, clientID: undefined,
clientSecret: undefined clientSecret: undefined

View file

@ -72,6 +72,17 @@ module.exports = {
clientID: process.env.HMD_MATTERMOST_CLIENTID, clientID: process.env.HMD_MATTERMOST_CLIENTID,
clientSecret: process.env.HMD_MATTERMOST_CLIENTSECRET clientSecret: process.env.HMD_MATTERMOST_CLIENTSECRET
}, },
oauth2: {
baseURL: process.env.HMD_OAUTH2_BASEURL,
userProfileURL: process.env.HMD_OAUTH2_USER_PROFILE_URL,
userProfileUsernameAttr: process.env.HMD_OAUTH2_USER_PROFILE_USERNAME_ATTR,
userProfileDisplayNameAttr: process.env.HMD_OAUTH2_USER_PROFILE_DISPLAY_NAME_ATTR,
userProfileEmailAttr: process.env.HMD_OAUTH2_USER_PROFILE_EMAIL_ATTR,
tokenURL: process.env.HMD_OAUTH2_TOKEN_URL,
authorizationURL: process.env.HMD_OAUTH2_AUTHORIZATION_URL,
clientID: process.env.HMD_OAUTH2_CLIENT_ID,
clientSecret: process.env.HMD_OAUTH2_CLIENT_SECRET
},
dropbox: { dropbox: {
clientID: process.env.HMD_DROPBOX_CLIENTID, clientID: process.env.HMD_DROPBOX_CLIENTID,
clientSecret: process.env.HMD_DROPBOX_CLIENTSECRET, clientSecret: process.env.HMD_DROPBOX_CLIENTSECRET,

View file

@ -99,6 +99,7 @@ config.isGitLabEnable = config.gitlab.clientID && config.gitlab.clientSecret
config.isMattermostEnable = config.mattermost.clientID && config.mattermost.clientSecret config.isMattermostEnable = config.mattermost.clientID && config.mattermost.clientSecret
config.isLDAPEnable = config.ldap.url config.isLDAPEnable = config.ldap.url
config.isSAMLEnable = config.saml.idpSsoUrl config.isSAMLEnable = config.saml.idpSsoUrl
config.isOAuth2Enable = config.oauth2.clientID && config.oauth2.clientSecret
config.isPDFExportEnable = config.allowPDFExport config.isPDFExportEnable = config.allowPDFExport
// merge legacy values // merge legacy values

View file

@ -0,0 +1,23 @@
'use strict'
module.exports = {
up: function (queryInterface, Sequelize) {
return queryInterface.changeColumn('Users', 'accessToken', {
type: Sequelize.TEXT
}).then(function () {
return queryInterface.changeColumn('Users', 'refreshToken', {
type: Sequelize.TEXT
})
})
},
down: function (queryInterface, Sequelize) {
return queryInterface.changeColumn('Users', 'accessToken', {
type: Sequelize.STRING
}).then(function () {
return queryInterface.changeColumn('Users', 'refreshToken', {
type: Sequelize.STRING
})
})
}
}

View file

@ -26,10 +26,10 @@ module.exports = function (sequelize, DataTypes) {
type: DataTypes.TEXT type: DataTypes.TEXT
}, },
accessToken: { accessToken: {
type: DataTypes.STRING type: DataTypes.TEXT
}, },
refreshToken: { refreshToken: {
type: DataTypes.STRING type: DataTypes.TEXT
}, },
email: { email: {
type: Sequelize.TEXT, type: Sequelize.TEXT,

View file

@ -76,6 +76,8 @@ function showIndex (req, res, next) {
ldap: config.isLDAPEnable, ldap: config.isLDAPEnable,
ldapProviderName: config.ldap.providerName, ldapProviderName: config.ldap.providerName,
saml: config.isSAMLEnable, saml: config.isSAMLEnable,
oauth2: config.isOAuth2Enable,
oauth2ProviderName: config.oauth2.providerName,
email: config.isEmailEnable, email: config.isEmailEnable,
allowEmailRegister: config.allowEmailRegister, allowEmailRegister: config.allowEmailRegister,
allowPDFExport: config.allowPDFExport, allowPDFExport: config.allowPDFExport,
@ -110,7 +112,9 @@ function responseHackMD (res, note) {
google: config.isGoogleEnable, google: config.isGoogleEnable,
ldap: config.isLDAPEnable, ldap: config.isLDAPEnable,
ldapProviderName: config.ldap.providerName, ldapProviderName: config.ldap.providerName,
oauth2ProviderName: config.oauth2.providerName,
saml: config.isSAMLEnable, saml: config.isSAMLEnable,
oauth2: config.isOAuth2Enable,
email: config.isEmailEnable, email: config.isEmailEnable,
allowEmailRegister: config.allowEmailRegister, allowEmailRegister: config.allowEmailRegister,
allowPDFExport: config.allowPDFExport allowPDFExport: config.allowPDFExport

View file

@ -43,6 +43,7 @@ if (config.isDropboxEnable) authRouter.use(require('./dropbox'))
if (config.isGoogleEnable) authRouter.use(require('./google')) if (config.isGoogleEnable) authRouter.use(require('./google'))
if (config.isLDAPEnable) authRouter.use(require('./ldap')) if (config.isLDAPEnable) authRouter.use(require('./ldap'))
if (config.isSAMLEnable) authRouter.use(require('./saml')) if (config.isSAMLEnable) authRouter.use(require('./saml'))
if (config.isOAuth2Enable) authRouter.use(require('./oauth2'))
if (config.isEmailEnable) authRouter.use(require('./email')) if (config.isEmailEnable) authRouter.use(require('./email'))
// logout // logout

View file

@ -0,0 +1,106 @@
'use strict'
const Router = require('express').Router
const passport = require('passport')
const OAuth2Strategy = require('passport-oauth2').Strategy
const config = require('../../../config')
const {setReturnToFromReferer, passportGeneralCallback} = require('../utils')
let oauth2Auth = module.exports = Router()
class OAuth2CustomStrategy extends OAuth2Strategy {
constructor (options, verify) {
options.customHeaders = options.customHeaders || {}
super(options, verify)
this.name = 'oauth2'
this._userProfileURL = options.userProfileURL
this._oauth2.useAuthorizationHeaderforGET(true)
}
userProfile (accessToken, done) {
this._oauth2.get(this._userProfileURL, accessToken, function (err, body, res) {
var json
if (err) {
return done(new passport.InternalOAuthError('Failed to fetch user profile', err))
}
try {
json = JSON.parse(body)
} catch (ex) {
return done(new Error('Failed to parse user profile'))
}
let profile = parseProfile(json)
profile.provider = 'oauth2'
done(null, profile)
})
}
}
function extractProfileAttribute (data, path) {
// can handle stuff like `attrs[0].name`
path = path.split('.')
for (const segment of path) {
const m = segment.match(/([\d\w]+)\[(.*)\]/)
data = m ? data[m[1]][m[2]] : data[segment]
}
return data
}
function parseProfile (data) {
const username = extractProfileAttribute(data, config.oauth2.userProfileUsernameAttr)
const displayName = extractProfileAttribute(data, config.oauth2.userProfileDisplayNameAttr)
const email = extractProfileAttribute(data, config.oauth2.userProfileEmailAttr)
return {
id: username,
username: username,
displayName: displayName,
email: email
}
}
OAuth2CustomStrategy.prototype.userProfile = function (accessToken, done) {
this._oauth2.get(this._userProfileURL, accessToken, function (err, body, res) {
var json
if (err) {
return done(new passport.InternalOAuthError('Failed to fetch user profile', err))
}
try {
json = JSON.parse(body)
} catch (ex) {
return done(new Error('Failed to parse user profile'))
}
let profile = parseProfile(json)
profile.provider = 'oauth2'
done(null, profile)
})
}
passport.use(new OAuth2CustomStrategy({
authorizationURL: config.oauth2.authorizationURL,
tokenURL: config.oauth2.tokenURL,
clientID: config.oauth2.clientID,
clientSecret: config.oauth2.clientSecret,
callbackURL: config.serverURL + '/auth/oauth2/callback',
userProfileURL: config.oauth2.userProfileURL
}, passportGeneralCallback))
oauth2Auth.get('/auth/oauth2', function (req, res, next) {
setReturnToFromReferer(req)
passport.authenticate('oauth2')(req, res, next)
})
// github auth callback
oauth2Auth.get('/auth/oauth2/callback',
passport.authenticate('oauth2', {
successReturnToOrRedirect: config.serverurl + '/',
failureRedirect: config.serverurl + '/'
})
)

View file

@ -15,7 +15,7 @@
<% if(allowAnonymous) { %> <% if(allowAnonymous) { %>
<a type="button" href="<%- url %>/new" class="btn btn-sm btn-primary"><i class="fa fa-plus"></i> <%= __('New guest note') %></a> <a type="button" href="<%- url %>/new" class="btn btn-sm btn-primary"><i class="fa fa-plus"></i> <%= __('New guest note') %></a>
<% } %> <% } %>
<% if(facebook || twitter || github || gitlab || mattermost || dropbox || google || ldap || saml || email) { %> <% if(facebook || twitter || github || gitlab || mattermost || dropbox || google || ldap || saml || oauth2 || email) { %>
<button class="btn btn-sm btn-success ui-signin" data-toggle="modal" data-target=".signin-modal"><%= __('Sign In') %></button> <button class="btn btn-sm btn-success ui-signin" data-toggle="modal" data-target=".signin-modal"><%= __('Sign In') %></button>
<% } %> <% } %>
</div> </div>
@ -49,7 +49,7 @@
<% if (errorMessage && errorMessage.length > 0) { %> <% if (errorMessage && errorMessage.length > 0) { %>
<div class="alert alert-danger" style="max-width: 400px; margin: 0 auto;"><%= errorMessage %></div> <div class="alert alert-danger" style="max-width: 400px; margin: 0 auto;"><%= errorMessage %></div>
<% } %> <% } %>
<% if(facebook || twitter || github || gitlab || mattermost || dropbox || google || ldap || saml || email) { %> <% if(facebook || twitter || github || gitlab || mattermost || dropbox || google || ldap || saml || oauth2 || email) { %>
<span class="ui-signin"> <span class="ui-signin">
<br> <br>
<a type="button" class="btn btn-lg btn-success ui-signin" data-toggle="modal" data-target=".signin-modal" style="min-width: 200px;"><%= __('Sign In') %></a> <a type="button" class="btn btn-lg btn-success ui-signin" data-toggle="modal" data-target=".signin-modal" style="min-width: 200px;"><%= __('Sign In') %></a>

View file

@ -48,7 +48,12 @@
<i class="fa fa-users"></i> <%= __('Sign in via %s', 'SAML') %> <i class="fa fa-users"></i> <%= __('Sign in via %s', 'SAML') %>
</a> </a>
<% } %> <% } %>
<% if((facebook || twitter || github || gitlab || mattermost || dropbox || google || saml) && ldap) { %> <% if(oauth2) { %>
<a href="<%- url %>/auth/oauth2" class="btn btn-lg btn-block btn-social btn-soundcloud">
<i class="fa fa-mail-forward"></i> <%= __('Sign in via %s', oauth2ProviderName || 'OAuth2') %>
</a>
<% } %>
<% if((facebook || twitter || github || gitlab || mattermost || dropbox || google || saml || oauth2) && ldap) { %>
<hr> <hr>
<% }%> <% }%>
<% if(ldap) { %> <% if(ldap) { %>
@ -73,7 +78,7 @@
</div> </div>
</form> </form>
<% } %> <% } %>
<% if((facebook || twitter || github || gitlab || mattermost || dropbox || google || ldap) && email) { %> <% if((facebook || twitter || github || gitlab || mattermost || dropbox || google || ldap || oauth2) && email) { %>
<hr> <hr>
<% }%> <% }%>
<% if(email) { %> <% if(email) { %>