SAML; Dashboard

This commit is contained in:
garrettmills
2020-05-03 20:16:54 -05:00
parent e3ecfb0d37
commit c389e151b5
1778 changed files with 148410 additions and 82 deletions

View File

@@ -0,0 +1,115 @@
const LDAPController = require('./LDAPController')
class GroupsController extends LDAPController {
static get services() {
return [
...super.services,
'output',
'ldap_server',
'models',
'configs',
'auth'
]
}
constructor() {
super()
this.Group = this.models.get('ldap:Group')
}
// TODO flitter-orm chunk query
// TODO generalize scoped search logic
async search_groups(req, res, next) {
if ( !req.user.can('ldap:search:groups') ) {
return next(new LDAP.InsufficientAccessRightsError())
}
if ( req.scope === 'base' ) {
// If scope is base, check if the base DN matches the filter.
// If so, return it. Else, return empty.
this.output.debug(`Running base DN search for groups with DN: ${req.dn.format(this.configs.get('ldap:server.format'))}`)
const group = await this.get_resource_from_dn(req.dn)
// Make sure the user is ldap visible && match the filter
if ( group && group.ldap_visible && req.filter.matches(await group.to_ldap()) ) {
// If so, send the object
res.send({
dn: group.dn.format(this.configs.get('ldap:server.format')),
attributes: await group.to_ldap(),
})
}
} else if ( req.scope === 'one' ) {
// If scope is one, find all entries that are the immediate
// subordinates of the base DN that match the filter.
this.output.debug(`Running one DN search for groups with DN: ${req.dn.format(this.configs.get('ldap:server.format'))}`)
// Fetch the LDAP-visible users
const groups = await this.Group.ldap_directory()
for ( const group of groups ) {
// Make sure the user os of the appropriate scope
if ( req.dn.equals(group.dn) || group.dn.parent().equals(req.dn) ) {
// Check if the filter matches
if ( req.filter.matches(await group.to_ldap()) ) {
// If so, send the object
res.send({
dn: group.dn.format(this.configs.get('ldap:server.format')),
attributes: await group.to_ldap(),
})
}
}
}
} else if ( req.scope === 'sub' ) {
// If scope is sub, find all entries that are subordinates
// of the base DN at any level and match the filter.
this.output.debug(`Running sub DN search for groups with DN: ${req.dn.format(this.configs.get('ldap:server.format'))}`)
// Fetch the users as LDAP objects
const groups = await this.Group.ldap_directory()
for ( const group of groups ) {
// Make sure the user is of appropriate scope
if ( req.dn.equals(group.dn) || req.dn.parentOf(group.dn) ) {
// Check if filter matches
if ( req.filter.matches(await group.to_ldap()) ) {
// If so, send the object
res.send({
dn: group.dn.format(this.configs.get('ldap:server.format')),
attributes: await group.to_ldap(),
})
}
}
}
} else {
this.output.error(`Attempted to perform LDAP search with invalid scope: ${req.scope}`)
return next(new LDAP.OtherError('Attempted to perform LDAP search with invalid scope.'))
}
res.end()
return next()
}
get_cn_from_dn(dn) {
try {
if ( typeof dn === 'string' ) dn = LDAP.parseDN(dn)
return dn.rdns[0].attrs.cn.value
} catch (e) { console.log('Error parsing CN from DN', e) }
}
async get_resource_from_dn(dn) {
const cn = this.get_cn_from_dn(dn)
console.log({cn, dn})
if ( cn ) {
return this.Group.findOne({name: cn, ldap_visible: true})
}
}
}
module.exports = exports = GroupsController

View File

@@ -9,7 +9,7 @@ class LDAPController extends Injectable {
check_attribute = 'ldap_visible' // or false
static get services() {
return [...super.services, 'ldap_server', 'ldap_dn_format']
return [...super.services, 'ldap_server', 'configs']
}
async compare(req, res, next) {
@@ -24,7 +24,7 @@ class LDAPController extends Injectable {
}
// Make sure it has the requested attribute
const value = item.to_ldap()[req.attribute]
const value = (await item.to_ldap())[req.attribute]
if ( typeof value === 'undefined' ) {
return next(new LDAP.NoSuchAttributeError())
}
@@ -42,26 +42,55 @@ class LDAPController extends Injectable {
// Make sure the DN is valid
if ( !req.dn.childOf(auth_dn) ) {
this.output.debug(`Bind failure: ${req.dn} not in ${auth_dn}`)
return next(new LDAP.InvalidCredentialsError('Cannot bind to DN outsize of the authentication base.'))
}
// Get the item
const item = await this.get_resource_from_dn(req.dn)
if ( !item ) {
this.output.debug(`Bind failure: ${req.dn} not found`)
return next(new LDAP.NoSuchObject())
}
// If the object is can-able, make sure it can bind
if ( typeof item.can === 'function' && !item.can('ldap:bind') ) {
this.output.debug(`Bind failure: User not allowed to bind`)
return next(new LDAP.InsufficientAccessRightsError())
}
// Make sure the password matches the resource record
if ( !await item.check_password(req.credentials) ) {
// Check if the credentials are an app_password
const app_password_verified = Array.isArray(item.app_passwords)
&& item.app_passwords.length > 0
&& await item.check_app_password(req.credentials)
// Check if the user has MFA enabled.
// If so, split the incoming password to fetch the MFA code
// e.g. normalPassword:123456
if ( !app_password_verified && item.mfa_enabled ) {
const parts = req.credentials.split(':')
const mfa_code = parts.pop()
const actual_password = parts.join(':')
// Check the credentials
if ( !await item.check_password(actual_password) ) {
this.output.debug(`Bind failure: user w/ MFA provided invalid credentials`)
return next(new LDAP.InvalidCredentialsError('Invalid credentials. Make sure MFA code is included at the end of your password (e.g. password:123456)'))
}
// Now, check the MFA code
if ( !item.mfa_token.verify(mfa_code) ) {
this.output.debug(`Bind failure: user w/ MFA provided invalid MFA token`)
return next(new LDAP.InvalidCredentialsError('Invalid credentials. Verification of the MFA token failed.'))
}
// If not MFA, just check the credentials
} else if (!app_password_verified && !await item.check_password(req.credentials)) {
this.output.debug(`Bind failure: user w/ simple auth provided invalid credentials`)
return next(new LDAP.InvalidCredentialsError())
}
this.output.success(`Successfully bound resource as DN: ${req.dn.format(this.ldap_dn_format)}.`)
this.output.info(`Successfully bound resource as DN: ${req.dn.format(this.configs.get('ldap:server.format'))}.`)
res.end()
return next()
}
@@ -76,8 +105,8 @@ class LDAPController extends Injectable {
// Make sure it's a parent of the request DN
if ( !base_dn.parentOf(req.dn) ) {
this.output.warn(`Attempted to perform resource deletion on invalid DN: ${req.dn.format(this.ldap_dn_format)}`)
return next(new LDAP.InsufficientAccessRightsError(`Target DN must be a member of the base DN: ${base_dn.format(this.ldap_dn_format)}.`))
this.output.warn(`Attempted to perform resource deletion on invalid DN: ${req.dn.format(this.configs.get('ldap:server.format'))}`)
return next(new LDAP.InsufficientAccessRightsError(`Target DN must be a member of the base DN: ${base_dn.format(this.configs.get('ldap:server.format'))}.`))
}
// Fetch the resource (error if not found)

View File

@@ -9,7 +9,7 @@ class UsersController extends LDAPController {
'output',
'ldap_server',
'models',
'ldap_dn_format',
'configs',
'auth'
]
}
@@ -30,7 +30,7 @@ class UsersController extends LDAPController {
// make sure the add DN is in the auth_dn
const auth_dn = this.ldap_server.auth_dn()
if ( !auth_dn.parentOf(req.dn) ) {
this.output.warn(`Attempted to perform user insertion on invalid DN: ${req.dn.format(this.ldap_dn_format)}`)
this.output.warn(`Attempted to perform user insertion on invalid DN: ${req.dn.format(this.configs.get('ldap:server.format'))}`)
return next(new LDAP.InsufficientAccessRightsError())
}
@@ -99,7 +99,7 @@ class UsersController extends LDAPController {
// Make sure it's under the user auth DN
const auth_dn = this.ldap_server.auth_dn()
if ( !auth_dn.parentOf(req.dn) ) {
this.output.warn(`Attempted to perform user modify on invalid DN: ${req.dn.format(this.ldap_dn_format)}`)
this.output.warn(`Attempted to perform user modify on invalid DN: ${req.dn.format(this.configs.get('ldap:server.format'))}`)
return next(new LDAP.InsufficientAccessRightsError())
}
@@ -188,21 +188,31 @@ class UsersController extends LDAPController {
if ( req.scope === 'base' ) {
// If scope is base, check if the base DN matches the filter.
// If so, return it. Else, return empty.
this.output.debug(`Running base DN search for users with DN: ${req.dn.format(this.ldap_dn_format)}`)
this.output.debug(`Running base DN search for users with DN: ${req.dn.format(this.configs.get('ldap:server.format'))}`)
const user = await this.get_resource_from_dn(req.dn)
// Make sure the user is ldap visible && match the filter
if ( req.user.ldap_visible && req.filter.matches(req.user.to_ldap()) ) {
if ( user && user.ldap_visible && req.filter.matches(await user.to_ldap()) ) {
// If so, send the object
res.send({
dn: req.user.dn.format(this.ldap_dn_format),
attributes: req.user.to_ldap(),
dn: user.dn,//.format(this.configs.get('ldap:server.format')),
attributes: await user.to_ldap(),
})
this.output.debug({
dn: user.dn.format(this.configs.get('ldap:server.format')),
attributes: await user.to_ldap(),
})
} else {
this.output.debug(`User base search failed: either user not found, not visible, or filter mismatch`)
global.ireq = req
}
} else if ( req.scope === 'one' ) {
// If scope is one, find all entries that are the immediate
// subordinates of the base DN that match the filter.
this.output.debug(`Running one DN search for users with DN: ${req.dn.format(this.ldap_dn_format)}`)
this.output.debug(`Running one DN search for users with DN: ${req.dn.format(this.configs.get('ldap:server.format'))}`)
// Fetch the LDAP-visible users
const users = await this.User.ldap_directory()
@@ -212,12 +222,12 @@ class UsersController extends LDAPController {
if ( req.dn.equals(user.dn) || user.dn.parent().equals(req.dn) ) {
// Check if the filter matches
if ( req.filter.matches(user.to_ldap()) ) {
if ( req.filter.matches(await user.to_ldap()) ) {
// If so, send the object
res.send({
dn: user.dn.format(this.ldap_dn_format),
attributes: user.to_ldap(),
dn: user.dn,//.format(this.configs.get('ldap:server.format')),
attributes: await user.to_ldap(),
})
}
}
@@ -226,7 +236,7 @@ class UsersController extends LDAPController {
} else if ( req.scope === 'sub' ) {
// If scope is sub, find all entries that are subordinates
// of the base DN at any level and match the filter.
this.output.debug(`Running sub DN search for users with DN: ${req.dn.format(this.ldap_dn_format)}`)
this.output.debug(`Running sub DN search for users with DN: ${req.dn.format(this.configs.get('ldap:server.format'))}`)
// Fetch the users as LDAP objects
const users = await this.User.ldap_directory()
@@ -236,12 +246,12 @@ class UsersController extends LDAPController {
if ( req.dn.equals(user.dn) || req.dn.parentOf(user.dn) ) {
// Check if filter matches
if ( req.filter.matches(user.to_ldap()) ) {
if ( req.filter.matches(await user.to_ldap()) ) {
// If so, send the object
res.send({
dn: user.dn.format(this.ldap_dn_format),
attributes: user.to_ldap(),
dn: user.dn,//.format(this.configs.get('ldap:server.format')),
attributes: await user.to_ldap(),
})
}
}

View File

@@ -2,12 +2,12 @@ const LDAPMiddleware = require('./LDAPMiddleware')
class LDAPLoggerMiddleware extends LDAPMiddleware {
static get services() {
return [...super.services, 'app', 'output', 'ldap_dn_format']
return [...super.services, 'app', 'output', 'configs']
}
async test(req, res, next) {
let bind_dn = req.connection.ldap.bindDN
this.output.info(`${req.json.protocolOp} - as ${bind_dn ? bind_dn.format(this.ldap_dn_format) : 'N/A'} - target ${req.dn.format(this.ldap_dn_format)}`)
this.output.info(`${req.json.protocolOp} - as ${bind_dn ? bind_dn.format(this.configs.get('ldap:server.format')) : 'N/A'} - target ${req.dn.format(this.configs.get('ldap:server.format'))}`)
return next()
}
}

View File

@@ -0,0 +1,50 @@
const groups_routes = {
prefix: false, // false | string
middleware: [
'Logger'
],
search: {
'ou=groups': [
'ldap_middleware::BindUser',
'ldap_controller::Groups.search_groups',
],
},
/*bind: {
'ou=groups': ['ldap_controller::Users.bind'],
},*/
/*add: {
'ou=groups': [
'ldap_middleware::BindUser',
'ldap_controller::Groups.add_group',
],
},
del: {
'ou=people': [
'ldap_middleware::BindUser',
'ldap_controller::Users.delete',
],
},
modify: {
'ou=people': [
'ldap_middleware::BindUser',
'ldap_controller::Users.modify_people',
],
},
compare: {
'ou=people': [
'ldap_middleware::BindUser',
'ldap_controller::Users.compare',
],
},*/
}
module.exports = exports = groups_routes