Skip to content

Commit

Permalink
refactor(service): users/auth: migrate ldap protocol to new ingress s…
Browse files Browse the repository at this point in the history
…cheme
  • Loading branch information
restjohn committed Nov 22, 2024
1 parent 54d4f59 commit 8ec3609
Showing 1 changed file with 97 additions and 133 deletions.
230 changes: 97 additions & 133 deletions service/src/ingress/ingress.protocol.ldap.ts
Original file line number Diff line number Diff line change
@@ -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<LdapProtocolSettings, 'profile'> & { profile: Required<LdapProfileKeys> }

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
}
}

0 comments on commit 8ec3609

Please sign in to comment.