Browse Source

feat: SAML auth module

pull/835/head
Nick 5 years ago
parent
commit
bdfac22876
4 changed files with 97 additions and 32 deletions
  1. 6
      server/controllers/auth.js
  2. 69
      server/modules/authentication/saml/authentication.js
  3. 52
      server/modules/authentication/saml/definition.yml
  4. 2
      server/modules/storage/git/storage.js

6
server/controllers/auth.js

@ -14,14 +14,16 @@ router.get('/login', (req, res, next) => {
}) })
router.get('/login/:strategy', async (req, res, next) => { router.get('/login/:strategy', async (req, res, next) => {
try { try {
const authResult = await WIKI.models.users.login({
await WIKI.models.users.login({
strategy: req.params.strategy strategy: req.params.strategy
}, { req, res }) }, { req, res })
} catch (err) { } catch (err) {
next(err) next(err)
} }
}) })
router.get('/login/:strategy/callback', async (req, res, next) => {
router.all('/login/:strategy/callback', async (req, res, next) => {
if (req.method !== 'GET' && req.method !== 'POST') { return next() }
try { try {
const authResult = await WIKI.models.users.login({ const authResult = await WIKI.models.users.login({
strategy: req.params.strategy strategy: req.params.strategy

69
server/modules/authentication/saml/authentication.js

@ -10,30 +10,53 @@ const SAMLStrategy = require('passport-saml').Strategy
module.exports = { module.exports = {
init (passport, conf) { init (passport, conf) {
let samlConfig = {
callbackUrl: conf.callbackURL,
entryPoint: conf.entryPoint,
issuer: conf.issuer,
signatureAlgorithm: conf.signatureAlgorithm,
identifierFormat: conf.identifierFormat,
acceptedClockSkewMs: _.toSafeInteger(conf.acceptedClockSkewMs),
disableRequestedAuthnContext: conf.disableRequestedAuthnContext,
authnContext: conf.authnContext,
forceAuthn: conf.forceAuthn,
providerName: conf.providerName,
skipRequestCompression: conf.skipRequestCompression,
authnRequestBinding: conf.authnRequestBinding
}
if (!_.isEmpty(conf.audience)) {
samlConfig.audience = conf.audience
}
if (!_.isEmpty(conf.cert)) {
samlConfig.cert = _.split(conf.cert, '|')
}
if (!_.isEmpty(conf.privateCert)) {
samlConfig.privateCert = conf.privateCert
}
if (!_.isEmpty(conf.decryptionPvk)) {
samlConfig.decryptionPvk = conf.decryptionPvk
}
passport.use('saml', passport.use('saml',
new SAMLStrategy({
callbackURL: conf.callbackURL,
entryPoint: conf.entryPoint,
issuer: conf.issuer,
audience: conf.audience,
cert: _.split(conf.cert, '|'),
privateCert: conf.privateCert,
decryptionPvk: conf.decryptionPvk,
signatureAlgorithm: conf.signatureAlgorithm,
identifierFormat: conf.identifierFormat,
acceptedClockSkewMs: _.toSafeInteger(conf.acceptedClockSkewMs),
disableRequestedAuthnContext: conf.disableRequestedAuthnContext,
authnContext: conf.authnContext,
forceAuthn: conf.forceAuthn,
providerName: conf.providerName,
skipRequestCompression: conf.skipRequestCompression,
authnRequestBinding: conf.authnRequestBinding
}, (profile, cb) => {
WIKI.models.users.processProfile(profile).then((user) => {
return cb(null, user) || true
}).catch((err) => {
return cb(err, null) || true
})
new SAMLStrategy(samlConfig, async (profile, cb) => {
try {
const userId = _.get(profile, [conf.mappingUID], null) || _.get(profile, 'nameID', null)
if (!userId) {
throw new Error('Invalid or Missing Unique ID field!')
}
const user = await WIKI.models.users.processProfile({
profile: {
id: userId,
email: _.get(profile, conf.mappingEmail, ''),
displayName: _.get(profile, conf.mappingDisplayName, '???'),
picture: _.get(profile, conf.mappingPicture, '')
},
providerKey: 'saml'
})
cb(null, user)
} catch (err) {
cb(err, null)
}
}) })
) )
} }

52
server/modules/authentication/saml/definition.yml

@ -5,36 +5,44 @@ author: requarks.io
logo: https://static.requarks.io/logo/saml.svg logo: https://static.requarks.io/logo/saml.svg
color: red darken-3 color: red darken-3
website: https://wiki.oasis-open.org/security/FrontPage website: https://wiki.oasis-open.org/security/FrontPage
isAvailable: true
useForm: false useForm: false
props: props:
entryPoint: entryPoint:
type: String type: String
title: Entry Point title: Entry Point
hint: Identity provider entrypoint (URL) hint: Identity provider entrypoint (URL)
order: 1
issuer: issuer:
type: String type: String
title: Issuer title: Issuer
hint: Issuer string to supply to Identity Provider hint: Issuer string to supply to Identity Provider
order: 2
audience: audience:
type: String type: String
title: Audience title: Audience
hint: Expected SAML response Audience (if not provided, Audience won't be verified)
hint: (Optional) - Expected SAML response Audience (if not provided, Audience won't be verified)
order: 3
cert: cert:
type: String type: String
title: Certificate title: Certificate
hint: Public PEM-encoded X.509 signing certificate contents in base64 (e.g. 'MIICizCCAfQCCQCY8tKaMc0BMjANBgkqh ... W=='). If the provider has multiple certificates that are valid, join them together using the | pipe symbol.
hint: (Optional) - Public PEM-encoded X.509 signing certificate. If the provider has multiple certificates that are valid, join them together using the | pipe symbol.
order: 4
privateCert: privateCert:
type: String type: String
title: Private Certificate title: Private Certificate
hint: PEM formatted key used to sign the certificate.
hint: (Optional) - PEM formatted key used to sign the certificate.
order: 5
decryptionPvk: decryptionPvk:
type: String type: String
title: Decryption Private Key title: Decryption Private Key
hint: (optional) Private key that will be used to attempt to decrypt any encrypted assertions that are received.
hint: (Optional) - Private key that will be used to attempt to decrypt any encrypted assertions that are received.
order: 6
signatureAlgorithm: signatureAlgorithm:
type: String type: String
title: Signature Algorithm title: Signature Algorithm
hint: Signature algorithm used for signing requests hint: Signature algorithm used for signing requests
order: 7
default: sha1 default: sha1
enum: enum:
- sha1 - sha1
@ -44,41 +52,73 @@ props:
type: String type: String
title: Name Identifier format title: Name Identifier format
default: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress' default: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'
order: 8
acceptedClockSkewMs: acceptedClockSkewMs:
type: Number type: Number
title: Accepted Clock Skew Milleseconds title: Accepted Clock Skew Milleseconds
hint: Time in milliseconds of skew that is acceptable between client and server when checking OnBefore and NotOnOrAfter assertion condition validity timestamps. Setting to -1 will disable checking these conditions entirely. hint: Time in milliseconds of skew that is acceptable between client and server when checking OnBefore and NotOnOrAfter assertion condition validity timestamps. Setting to -1 will disable checking these conditions entirely.
default: 0
default: -1
order: 9
disableRequestedAuthnContext: disableRequestedAuthnContext:
type: Boolean type: Boolean
title: Disable Requested Auth Context title: Disable Requested Auth Context
hint: If enabled, do not request a specific authentication context. This is known to help when authenticating against Active Directory (AD FS) servers. hint: If enabled, do not request a specific authentication context. This is known to help when authenticating against Active Directory (AD FS) servers.
default: false default: false
order: 10
authnContext: authnContext:
type: String type: String
title: Auth Context title: Auth Context
hint: Name identifier format to request auth context. hint: Name identifier format to request auth context.
default: urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport default: urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
order: 11
forceAuthn: forceAuthn:
type: Boolean type: Boolean
title: Force Initial Re-authentication title: Force Initial Re-authentication
hint: If enabled, the initial SAML request from the service provider specifies that the IdP should force re-authentication of the user, even if they possess a valid session. hint: If enabled, the initial SAML request from the service provider specifies that the IdP should force re-authentication of the user, even if they possess a valid session.
default: false default: false
order: 12
providerName: providerName:
type: String type: String
title: Provider Name title: Provider Name
hint: Optional human-readable name of the requester for use by the presenter's user agent or the identity provider. hint: Optional human-readable name of the requester for use by the presenter's user agent or the identity provider.
default: wiki.js default: wiki.js
order: 13
skipRequestCompression: skipRequestCompression:
type: Boolean type: Boolean
title: Skip Request Compression title: Skip Request Compression
hint: If enabled, the SAML request from the service provider won't be compressed. hint: If enabled, the SAML request from the service provider won't be compressed.
default: false default: false
order: 14
authnRequestBinding: authnRequestBinding:
type: String type: String
title: Request Binding title: Request Binding
hint: Binding used for request authentication from IDP. hint: Binding used for request authentication from IDP.
default: 'HTTP-Redirect'
order: 15
default: 'HTTP-POST'
enum: enum:
- HTTP-Redirect - HTTP-Redirect
- HTTP-POST - HTTP-POST
mappingUID:
title: Unique ID Field Mapping
type: String
default: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier'
hint: The field storing the user unique identifier. Can be a variable name or a URI-formatted string.
order: 16
mappingEmail:
title: Email Field Mapping
type: String
default: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'
hint: The field storing the user email. Can be a variable name or a URI-formatted string.
order: 17
mappingDisplayName:
title: Display Name Field Mapping
type: String
default: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name'
hint: The field storing the user display name. Can be a variable name or a URI-formatted string.
order: 18
mappingPicture:
title: Avatar Picture Field Mapping
type: String
default: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/picture'
hint: The field storing the user avatar picture. Can be a variable name or a URI-formatted string.
order: 19

2
server/modules/storage/git/storage.js

@ -116,7 +116,7 @@ module.exports = {
} else { } else {
originUrl = `https://${this.config.basicUsername}:${this.config.basicPassword}@${this.config.repoUrl}` originUrl = `https://${this.config.basicUsername}:${this.config.basicPassword}@${this.config.repoUrl}`
} }
await this.git.addRemote('origin', )
await this.git.addRemote('origin', originUrl)
break break
} }

Loading…
Cancel
Save