From 8ec36098d6672e4660ef83df7ec2cc9e0de7af41 Mon Sep 17 00:00:00 2001 From: "Robert St. John" Date: Fri, 22 Nov 2024 09:32:12 -0700 Subject: [PATCH] refactor(service): users/auth: migrate ldap protocol to new ingress scheme --- service/src/ingress/ingress.protocol.ldap.ts | 230 ++++++++----------- 1 file changed, 97 insertions(+), 133 deletions(-) diff --git a/service/src/ingress/ingress.protocol.ldap.ts b/service/src/ingress/ingress.protocol.ldap.ts index 279dca8f4..8b7728bc3 100644 --- a/service/src/ingress/ingress.protocol.ldap.ts +++ b/service/src/ingress/ingress.protocol.ldap.ts @@ -1,149 +1,113 @@ -const LdapStrategy = require('passport-ldapauth') - , log = require('winston') - , User = require('../models/user') - , Role = require('../models/role') - , TokenAssertion = require('./verification').TokenAssertion - , api = require('../api') - , userTransformer = require('../transformers/user') - , { app, passport, tokenService } = require('./index'); +import passport from 'passport' +import LdapStrategy from 'passport-ldapauth' +import { IdentityProvider, IdentityProviderUser } from './ingress.entities' +import { IngressProtocolWebBinding, IngressResponseType } from './ingress.protocol.bindings' -function configure(strategy) { - log.info('Configuring ' + strategy.title + ' authentication'); - passport.use(strategy.name, new LdapStrategy({ - server: { - url: strategy.settings.url, - bindDN: strategy.settings.bindDN, - bindCredentials: strategy.settings.bindCredentials, - searchBase: strategy.settings.searchBase, - searchFilter: strategy.settings.searchFilter, - searchScope: strategy.settings.searchScope, - groupSearchBase: strategy.settings.groupSearchBase, - groupSearchFilter: strategy.settings.groupSearchFilter, - groupSearchScope: strategy.settings.groupSearchScope, - bindProperty: strategy.settings.bindProperty, - groupDnProperty: strategy.settings.groupDnProperty - } - }, - function (profile, done) { - const username = profile[strategy.settings.profile.id ]; - // TODO: users-next - User.getUserByAuthenticationStrategy(strategy.type, username, function (err, user) { - if (err) return done(err); - - if (!user) { - // Create an account for the user - Role.getRole('USER_ROLE', function (err, role) { - if (err) return done(err); +type LdapProfileKeys = { + id?: string + email?: string + displayName?: string +} - const user = { - username: username, - displayName: profile[strategy.settings.profile.displayName], - email: profile[strategy.settings.profile.email], - active: false, - roleId: role._id, - authentication: { - type: strategy.name, - id: username, - authenticationConfiguration: { - name: strategy.name - } - } - }; - // TODO: users-next - new api.User().create(user).then(newUser => { - if (!newUser.authentication.authenticationConfiguration.enabled) { - log.warn(newUser.authentication.authenticationConfiguration.title + " authentication is not enabled"); - return done(null, newUser, { message: 'Authentication method is not enabled, please contact a MAGE administrator for assistance.' }); - } - if (newUser.active) { - done(null, newUser); - } else { - done(null, newUser, { status: 403 }); - } - }).catch(err => done(err)); - }); - } else if (!user.authentication.authenticationConfiguration.enabled) { - log.warn(user.authentication.authenticationConfiguration.title + " authentication is not enabled"); - return done(null, user, { message: 'Authentication method is not enabled, please contact a MAGE administrator for assistance.' }); - } else { - return done(null, user); - } - }); - }) - ); +type LdapProtocolSettings = LdapStrategy.Options['server'] & { + profile?: LdapProfileKeys } -function setDefaults(strategy) { - if (!strategy.settings.profile) { - strategy.settings.profile = {}; +type ReadyLdapProtocolSettings = Omit & { profile: Required } + +function copyProtocolSettings(from: LdapProtocolSettings): LdapProtocolSettings { + const copy = { ...from } + if (copy.profile) { + copy.profile = { ...from.profile! } } - if (!strategy.settings.profile.displayName) { - strategy.settings.profile.displayName = 'givenname'; + return copy +} + +function applyDefaultProtocolSettings(idp: IdentityProvider): ReadyLdapProtocolSettings { + const settings = copyProtocolSettings(idp.protocolSettings as LdapProtocolSettings) + const profileKeys = settings.profile || {} + if (!profileKeys.displayName) { + profileKeys.displayName = 'givenname'; } - if (!strategy.settings.profile.email) { - strategy.settings.profile.email = 'mail'; + if (!profileKeys.email) { + profileKeys.email = 'mail'; } - if (!strategy.settings.profile.id) { - strategy.settings.profile.id = 'cn'; + if (!profileKeys.id) { + profileKeys.id = 'cn'; } + settings.profile = profileKeys + return settings as ReadyLdapProtocolSettings } -function initialize(strategy) { - setDefaults(strategy); - configure(strategy); - - const authenticationOptions = { - invalidLogonHours: `Not Permitted to login to ${strategy.title} account at this time.`, - invalidWorkstation: `Not permited to logon to ${strategy.title} account at this workstation.`, - passwordExpired: `${strategy.title} password expired.`, - accountDisabled: `${strategy.title} account disabled.`, - accountExpired: `${strategy.title} account expired.`, - passwordMustChange: `User must reset ${strategy.title} password.`, - accountLockedOut: `${strategy.title} user account locked.`, - invalidCredentials: `Invalid ${strategy.title} username/password.` - }; - - app.post(`/auth/${strategy.name}/signin`, - function authenticate(req, res, next) { - passport.authenticate(strategy.name, authenticationOptions, function (err, user, info = {}) { - if (err) return next(err); - - if (!user) { - return res.status(401).send(info.message); - } - - if (!user.active) { - return res.status(info.status || 401).send('User account is not approved, please contact your MAGE administrator to approve your account.'); - } - - if (!user.enabled) { - log.warn('Failed user login attempt: User ' + user.username + ' account is disabled.'); - return res.status(401).send('Your account has been disabled, please contact a MAGE administrator for assistance.') - } +function strategyOptionsFromProtocolSettings(settings: ReadyLdapProtocolSettings): LdapStrategy.Options { + return { + server: { + url: settings.url, + bindDN: settings.bindDN, + bindCredentials: settings.bindCredentials, + searchBase: settings.searchBase, + searchFilter: settings.searchFilter, + searchScope: settings.searchScope, + groupSearchBase: settings.groupSearchBase, + groupSearchFilter: settings.groupSearchFilter, + groupSearchScope: settings.groupSearchScope, + bindProperty: settings.bindProperty, + groupDnProperty: settings.groupDnProperty + } + } +} - if (!user.authentication.authenticationConfigurationId) { - log.warn('Failed user login attempt: ' + user.authentication.type + ' is not configured'); - return res.status(401).send(user.authentication.type + ' authentication is not configured, please contact a MAGE administrator for assistance.') +export function createWebBinding(idp: IdentityProvider, passport: passport.Authenticator): IngressProtocolWebBinding { + const settings = applyDefaultProtocolSettings(idp) + const profileKeys = settings.profile + const strategyOptions = strategyOptionsFromProtocolSettings(settings) + const verify: LdapStrategy.VerifyCallback = (profile, done) => { + const idpAccount: IdentityProviderUser = { + username: profile[profileKeys.id], + displayName: profile[profileKeys.displayName], + email: profile[profileKeys.email], + phones: [], + idpAccountId: profile[profileKeys.id] + } + const webIngressUser: Express.User = { + admittingFromIdentityProvider: { + account: idpAccount, + idpName: idp.name, + } + } + return done(null, webIngressUser) + } + const title = idp.title + const authOptions: LdapStrategy.AuthenticateOptions = { + invalidLogonHours: `Access to ${title} account is prohibited at this time.`, + invalidWorkstation: `Access to ${title} account is prohibited from this workstation.`, + passwordExpired: `${title} password expired.`, + accountDisabled: `${title} account disabled.`, + accountExpired: `${title} account expired.`, + passwordMustChange: `${title} account requires password reset.`, + accountLockedOut: `${title} account locked.`, + invalidCredentials: `Invalid ${title} credentials.`, + } + const ldapIdp = new LdapStrategy(strategyOptions, verify) + return { + ingressResponseType: IngressResponseType.Direct, + beginIngressFlow(req, res, next, flowState): any { + const completeIngress: passport.AuthenticateCallback = (err, user) => { + if (err) { + return next(err) } - - if (!user.authentication.authenticationConfiguration.enabled) { - log.warn('Failed user login attempt: Authentication ' + user.authentication.authenticationConfiguration.title + ' is disabled.'); - return res.status(401).send(user.authentication.authenticationConfiguration.title + ' authentication is disabled, please contact a MAGE administrator for assistance.') + if (user && user.admittingFromIdentityProvider) { + user.admittingFromIdentityProvider.flowState = flowState + req.user = user + return next() } - - tokenService.generateToken(user._id.toString(), TokenAssertion.Authorized, 60 * 5) - .then(token => { - res.json({ - user: userTransformer.transform(req.user, { path: req.getRoot() }), - token: token - }); - }).catch(err => next(err)); - })(req, res, next); + return res.status(500).send('internal server error: invalid ldap ingress state') + } + passport.authenticate(ldapIdp, authOptions, completeIngress)(req, res, next) + }, + handleIngressFlowRequest(req, res): any { + return res.status(400).send('invalid ldap ingress request') } - ); -}; - -module.exports = { - initialize + } } \ No newline at end of file