2019-08-28 18:22:07 +02:00
'use strict' ;
exports = module . exports = {
2019-11-19 09:53:00 +01:00
search : search ,
2019-08-29 22:43:06 +02:00
verifyPassword : verifyPassword ,
2019-11-19 09:53:00 +01:00
createAndVerifyUserIfNotExist : createAndVerifyUserIfNotExist ,
2019-08-29 22:43:06 +02:00
2019-08-28 18:22:07 +02:00
testConfig : testConfig ,
2019-08-29 17:19:51 +02:00
startSyncer : startSyncer ,
2019-08-28 18:22:07 +02:00
2019-10-25 15:58:11 -07:00
injectPrivateFields : injectPrivateFields ,
removePrivateFields : removePrivateFields ,
2019-08-28 18:22:07 +02:00
sync : sync
} ;
var assert = require ( 'assert' ) ,
2019-08-29 22:56:48 +02:00
async = require ( 'async' ) ,
2019-10-30 11:02:21 -07:00
auditSource = require ( './auditsource.js' ) ,
2019-10-24 13:41:41 -07:00
BoxError = require ( './boxerror.js' ) ,
2019-10-25 15:58:11 -07:00
constants = require ( './constants.js' ) ,
2019-08-30 19:11:27 +02:00
debug = require ( 'debug' ) ( 'box:externalldap' ) ,
2020-06-04 13:26:13 +02:00
groups = require ( './groups.js' ) ,
2019-08-28 18:22:07 +02:00
ldap = require ( 'ldapjs' ) ,
2020-06-26 14:08:16 +02:00
once = require ( 'once' ) ,
2019-08-28 18:22:07 +02:00
settings = require ( './settings.js' ) ,
2019-08-29 17:19:51 +02:00
tasks = require ( './tasks.js' ) ,
2019-10-24 15:12:58 -07:00
users = require ( './users.js' ) ;
2019-08-28 18:22:07 +02:00
2019-10-25 15:58:11 -07:00
function injectPrivateFields ( newConfig , currentConfig ) {
if ( newConfig . bindPassword === constants . SECRET _PLACEHOLDER ) newConfig . bindPassword = currentConfig . bindPassword ;
}
function removePrivateFields ( ldapConfig ) {
assert . strictEqual ( typeof ldapConfig , 'object' ) ;
if ( ldapConfig . bindPassword ) ldapConfig . bindPassword = constants . SECRET _PLACEHOLDER ;
return ldapConfig ;
}
2019-11-19 09:53:00 +01:00
function translateUser ( ldapConfig , ldapUser ) {
assert . strictEqual ( typeof ldapConfig , 'object' ) ;
return {
username : ldapUser [ ldapConfig . usernameField ] ,
2020-07-01 14:34:28 +02:00
email : ldapUser . mail || ldapUser . mailPrimaryAddress ,
2019-11-19 09:53:00 +01:00
displayName : ldapUser . cn // user.giveName + ' ' + user.sn
} ;
}
function validUserRequirements ( user ) {
if ( ! user . username || ! user . email || ! user . displayName ) {
2020-07-01 14:34:28 +02:00
debug ( ` [Invalid LDAP user] username= ${ user . username } email= ${ user . email } displayName= ${ user . displayName } ` ) ;
2019-11-19 09:53:00 +01:00
return false ;
} else {
return true ;
}
}
2019-08-28 18:22:07 +02:00
// performs service bind if required
2020-07-01 14:59:26 +02:00
function getClient ( externalLdapConfig , doBindAuth , callback ) {
2019-10-30 14:37:48 -07:00
assert . strictEqual ( typeof externalLdapConfig , 'object' ) ;
2020-07-01 14:59:26 +02:00
assert . strictEqual ( typeof doBindAuth , 'boolean' ) ;
2019-08-28 18:22:07 +02:00
assert . strictEqual ( typeof callback , 'function' ) ;
2020-06-26 14:08:16 +02:00
// ensure we only callback once since we also have to listen to client.error events
callback = once ( callback ) ;
2019-08-29 22:51:27 +02:00
// basic validation to not crash
2019-10-24 13:41:41 -07:00
try { ldap . parseDN ( externalLdapConfig . baseDn ) ; } catch ( e ) { return callback ( new BoxError ( BoxError . BAD _FIELD , 'invalid baseDn' ) ) ; }
try { ldap . parseFilter ( externalLdapConfig . filter ) ; } catch ( e ) { return callback ( new BoxError ( BoxError . BAD _FIELD , 'invalid filter' ) ) ; }
2019-08-29 22:51:27 +02:00
2020-06-25 17:36:23 +02:00
var config = {
url : externalLdapConfig . url ,
tlsOptions : {
rejectUnauthorized : externalLdapConfig . acceptSelfSignedCerts ? false : true
}
} ;
2019-08-29 17:19:51 +02:00
var client ;
try {
2020-06-25 17:36:23 +02:00
client = ldap . createClient ( config ) ;
2019-08-29 17:19:51 +02:00
} catch ( e ) {
2019-10-24 13:41:41 -07:00
if ( e instanceof ldap . ProtocolError ) return callback ( new BoxError ( BoxError . BAD _FIELD , 'url protocol is invalid' ) ) ;
return callback ( new BoxError ( BoxError . INTERNAL _ERROR , e ) ) ;
2019-08-29 17:19:51 +02:00
}
2019-08-28 18:22:07 +02:00
2020-06-26 14:08:16 +02:00
// ensure we don't just crash
client . on ( 'error' , function ( error ) {
callback ( new BoxError ( BoxError . EXTERNAL _ERROR , error ) ) ;
} ) ;
2020-07-01 14:59:26 +02:00
// skip bind auth if none exist or if not wanted
if ( ! externalLdapConfig . bindDn || ! doBindAuth ) return callback ( null , client ) ;
2019-08-28 18:22:07 +02:00
2019-08-29 17:19:51 +02:00
client . bind ( externalLdapConfig . bindDn , externalLdapConfig . bindPassword , function ( error ) {
2019-10-24 13:41:41 -07:00
if ( error instanceof ldap . InvalidCredentialsError ) return callback ( new BoxError ( BoxError . INVALID _CREDENTIALS ) ) ;
if ( error ) return callback ( new BoxError ( BoxError . EXTERNAL _ERROR , error ) ) ;
2019-08-28 18:22:07 +02:00
2020-07-01 14:52:11 +02:00
callback ( null , client ) ;
2019-08-28 18:22:07 +02:00
} ) ;
}
2020-06-05 10:29:36 +02:00
function ldapGetByDN ( externalLdapConfig , dn , callback ) {
assert . strictEqual ( typeof externalLdapConfig , 'object' ) ;
assert . strictEqual ( typeof dn , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2020-07-01 14:59:26 +02:00
getClient ( externalLdapConfig , true , function ( error , client ) {
2020-06-05 10:29:36 +02:00
if ( error ) return callback ( error ) ;
let searchOptions = {
paged : true ,
scope : 'sub' // We may have to make this configurable
} ;
debug ( ` Get object at ${ dn } ` ) ;
client . search ( dn , searchOptions , function ( error , result ) {
if ( error instanceof ldap . NoSuchObjectError ) return callback ( new BoxError ( BoxError . NOT _FOUND ) ) ;
if ( error ) return callback ( new BoxError ( BoxError . EXTERNAL _ERROR , error ) ) ;
let ldapObjects = [ ] ;
result . on ( 'searchEntry' , entry => ldapObjects . push ( entry . object ) ) ;
result . on ( 'error' , error => callback ( new BoxError ( BoxError . EXTERNAL _ERROR , error ) ) ) ;
result . on ( 'end' , function ( result ) {
client . unbind ( ) ;
if ( result . status !== 0 ) return callback ( new BoxError ( BoxError . EXTERNAL _ERROR , 'Server returned status ' + result . status ) ) ;
if ( ldapObjects . length === 0 ) return callback ( new BoxError ( BoxError . NOT _FOUND ) ) ;
callback ( null , ldapObjects [ 0 ] ) ;
} ) ;
} ) ;
} ) ;
}
2019-11-19 09:53:00 +01:00
// TODO support search by email
2020-06-03 21:23:53 +02:00
function ldapUserSearch ( externalLdapConfig , options , callback ) {
2019-10-30 14:37:48 -07:00
assert . strictEqual ( typeof externalLdapConfig , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2020-07-01 14:59:26 +02:00
getClient ( externalLdapConfig , true , function ( error , client ) {
2019-10-30 14:37:48 -07:00
if ( error ) return callback ( error ) ;
let searchOptions = {
paged : true ,
filter : ldap . parseFilter ( externalLdapConfig . filter ) ,
scope : 'sub' // We may have to make this configurable
} ;
if ( options . filter ) { // https://github.com/ldapjs/node-ldapjs/blob/master/docs/filters.md
let extraFilter = ldap . parseFilter ( options . filter ) ;
searchOptions . filter = new ldap . AndFilter ( { filters : [ extraFilter , searchOptions . filter ] } ) ;
}
debug ( ` Listing users at ${ externalLdapConfig . baseDn } with filter ${ searchOptions . filter . toString ( ) } ` ) ;
client . search ( externalLdapConfig . baseDn , searchOptions , function ( error , result ) {
if ( error instanceof ldap . NoSuchObjectError ) return callback ( new BoxError ( BoxError . NOT _FOUND ) ) ;
if ( error ) return callback ( new BoxError ( BoxError . EXTERNAL _ERROR , error ) ) ;
let ldapUsers = [ ] ;
result . on ( 'searchEntry' , entry => ldapUsers . push ( entry . object ) ) ;
result . on ( 'error' , error => callback ( new BoxError ( BoxError . EXTERNAL _ERROR , error ) ) ) ;
result . on ( 'end' , function ( result ) {
client . unbind ( ) ;
if ( result . status !== 0 ) return callback ( new BoxError ( BoxError . EXTERNAL _ERROR , 'Server returned status ' + result . status ) ) ;
callback ( null , ldapUsers ) ;
} ) ;
} ) ;
} ) ;
}
2020-06-03 21:23:53 +02:00
function ldapGroupSearch ( externalLdapConfig , options , callback ) {
assert . strictEqual ( typeof externalLdapConfig , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2020-07-01 14:59:26 +02:00
getClient ( externalLdapConfig , true , function ( error , client ) {
2020-06-03 21:23:53 +02:00
if ( error ) return callback ( error ) ;
let searchOptions = {
paged : true ,
scope : 'sub' // We may have to make this configurable
} ;
if ( externalLdapConfig . groupFilter ) searchOptions . filter = ldap . parseFilter ( externalLdapConfig . groupFilter ) ;
2020-06-05 10:29:36 +02:00
if ( options . filter ) { // https://github.com/ldapjs/node-ldapjs/blob/master/docs/filters.md
let extraFilter = ldap . parseFilter ( options . filter ) ;
searchOptions . filter = new ldap . AndFilter ( { filters : [ extraFilter , searchOptions . filter ] } ) ;
}
2020-06-03 21:23:53 +02:00
debug ( ` Listing groups at ${ externalLdapConfig . groupBaseDn } with filter ${ searchOptions . filter . toString ( ) } ` ) ;
client . search ( externalLdapConfig . groupBaseDn , searchOptions , function ( error , result ) {
if ( error instanceof ldap . NoSuchObjectError ) return callback ( new BoxError ( BoxError . NOT _FOUND ) ) ;
if ( error ) return callback ( new BoxError ( BoxError . EXTERNAL _ERROR , error ) ) ;
let ldapGroups = [ ] ;
result . on ( 'searchEntry' , entry => ldapGroups . push ( entry . object ) ) ;
result . on ( 'error' , error => callback ( new BoxError ( BoxError . EXTERNAL _ERROR , error ) ) ) ;
result . on ( 'end' , function ( result ) {
client . unbind ( ) ;
if ( result . status !== 0 ) return callback ( new BoxError ( BoxError . EXTERNAL _ERROR , 'Server returned status ' + result . status ) ) ;
callback ( null , ldapGroups ) ;
} ) ;
} ) ;
} ) ;
}
2019-08-28 18:22:07 +02:00
function testConfig ( config , callback ) {
assert . strictEqual ( typeof config , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-10-25 15:40:22 -07:00
if ( config . provider === 'noop' ) return callback ( ) ;
2019-08-29 12:28:41 +02:00
2019-10-24 13:41:41 -07:00
if ( ! config . url ) return callback ( new BoxError ( BoxError . BAD _FIELD , 'url must not be empty' ) ) ;
2019-10-31 11:46:00 -07:00
if ( ! config . url . startsWith ( 'ldap://' ) && ! config . url . startsWith ( 'ldaps://' ) ) return callback ( new BoxError ( BoxError . BAD _FIELD , 'url is missing ldap:// or ldaps:// prefix' ) ) ;
2019-10-25 15:47:55 -07:00
if ( ! config . usernameField ) config . usernameField = 'uid' ;
2019-10-31 11:46:00 -07:00
2019-10-30 09:35:30 -07:00
// bindDn may not be a dn!
2019-10-31 11:46:00 -07:00
if ( ! config . baseDn ) return callback ( new BoxError ( BoxError . BAD _FIELD , 'basedn must not be empty' ) ) ;
try { ldap . parseDN ( config . baseDn ) ; } catch ( e ) { return callback ( new BoxError ( BoxError . BAD _FIELD , 'invalid baseDn' ) ) ; }
if ( ! config . filter ) return callback ( new BoxError ( BoxError . BAD _FIELD , 'filter must not be empty' ) ) ;
try { ldap . parseFilter ( config . filter ) ; } catch ( e ) { return callback ( new BoxError ( BoxError . BAD _FIELD , 'invalid filter' ) ) ; }
2019-08-28 18:22:07 +02:00
2020-06-07 13:49:01 +02:00
if ( 'syncGroups' in config && typeof config . syncGroups !== 'boolean' ) return callback ( new BoxError ( BoxError . BAD _FIELD , 'syncGroups must be a boolean' ) ) ;
2020-06-25 17:36:23 +02:00
if ( 'acceptSelfSignedCerts' in config && typeof config . acceptSelfSignedCerts !== 'boolean' ) return callback ( new BoxError ( BoxError . BAD _FIELD , 'acceptSelfSignedCerts must be a boolean' ) ) ;
2020-06-07 13:49:01 +02:00
if ( config . syncGroups ) {
if ( ! config . groupBaseDn ) return callback ( new BoxError ( BoxError . BAD _FIELD , 'groupBaseDn must not be empty' ) ) ;
try { ldap . parseDN ( config . groupBaseDn ) ; } catch ( e ) { return callback ( new BoxError ( BoxError . BAD _FIELD , 'invalid groupBaseDn' ) ) ; }
if ( ! config . groupFilter ) return callback ( new BoxError ( BoxError . BAD _FIELD , 'groupFilter must not be empty' ) ) ;
try { ldap . parseFilter ( config . groupFilter ) ; } catch ( e ) { return callback ( new BoxError ( BoxError . BAD _FIELD , 'invalid groupFilter' ) ) ; }
if ( ! config . groupnameField || typeof config . groupnameField !== 'string' ) return callback ( new BoxError ( BoxError . BAD _FIELD , 'groupFilter must not be empty' ) ) ;
}
2020-07-01 14:59:26 +02:00
getClient ( config , true , function ( error , client ) {
2019-08-29 14:21:07 +02:00
if ( error ) return callback ( error ) ;
2019-08-28 18:22:07 +02:00
var opts = {
filter : config . filter ,
scope : 'sub'
} ;
client . search ( config . baseDn , opts , function ( error , result ) {
2019-10-24 13:41:41 -07:00
if ( error ) return callback ( new BoxError ( BoxError . EXTERNAL _ERROR , error ) ) ;
2019-08-28 18:22:07 +02:00
2019-10-23 15:57:01 -07:00
result . on ( 'searchEntry' , function ( /* entry */ ) { } ) ;
2019-10-25 16:58:15 -07:00
result . on ( 'error' , function ( error ) { client . unbind ( ) ; callback ( new BoxError ( BoxError . BAD _FIELD , ` Unable to search directory: ${ error . message } ` ) ) ; } ) ;
result . on ( 'end' , function ( /* result */ ) { client . unbind ( ) ; callback ( ) ; } ) ;
2019-08-28 18:22:07 +02:00
} ) ;
} ) ;
}
2019-11-19 09:53:00 +01:00
function search ( identifier , callback ) {
assert . strictEqual ( typeof identifier , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
settings . getExternalLdapConfig ( function ( error , externalLdapConfig ) {
if ( error ) return callback ( error ) ;
if ( externalLdapConfig . provider === 'noop' ) return callback ( new BoxError ( BoxError . BAD _STATE , 'not enabled' ) ) ;
2020-06-03 21:23:53 +02:00
ldapUserSearch ( externalLdapConfig , { filter : ` ${ externalLdapConfig . usernameField } = ${ identifier } ` } , function ( error , ldapUsers ) {
2019-11-19 09:53:00 +01:00
if ( error ) return callback ( error ) ;
// translate ldap properties to ours
2019-11-19 09:53:00 +01:00
let users = ldapUsers . map ( function ( u ) { return translateUser ( externalLdapConfig , u ) ; } ) ;
2019-11-19 09:53:00 +01:00
callback ( null , users ) ;
} ) ;
} ) ;
}
2019-11-19 09:53:00 +01:00
function createAndVerifyUserIfNotExist ( identifier , password , callback ) {
assert . strictEqual ( typeof identifier , 'string' ) ;
assert . strictEqual ( typeof password , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
settings . getExternalLdapConfig ( function ( error , externalLdapConfig ) {
if ( error ) return callback ( error ) ;
if ( externalLdapConfig . provider === 'noop' ) return callback ( new BoxError ( BoxError . BAD _STATE , 'not enabled' ) ) ;
if ( ! externalLdapConfig . autoCreate ) return callback ( new BoxError ( BoxError . BAD _STATE , 'auto create not enabled' ) ) ;
2020-06-03 21:23:53 +02:00
ldapUserSearch ( externalLdapConfig , { filter : ` ${ externalLdapConfig . usernameField } = ${ identifier } ` } , function ( error , ldapUsers ) {
2019-11-19 09:53:00 +01:00
if ( error ) return callback ( error ) ;
2019-11-20 10:46:06 +01:00
if ( ldapUsers . length === 0 ) return callback ( new BoxError ( BoxError . NOT _FOUND ) ) ;
if ( ldapUsers . length > 1 ) return callback ( new BoxError ( BoxError . CONFLICT ) ) ;
2019-11-19 09:53:00 +01:00
2019-11-20 10:46:06 +01:00
let user = translateUser ( externalLdapConfig , ldapUsers [ 0 ] ) ;
2019-11-19 09:53:00 +01:00
if ( ! validUserRequirements ( user ) ) return callback ( new BoxError ( BoxError . BAD _FIELD ) ) ;
users . create ( user . username , null /* password */ , user . email , user . displayName , { source : 'ldap' } , auditSource . EXTERNAL _LDAP _AUTO _CREATE , function ( error , user ) {
if ( error ) {
console . error ( 'Failed to auto create user' , user . username , error ) ;
return callback ( new BoxError ( BoxError . INTERNAL _ERROR ) ) ;
}
verifyPassword ( user , password , function ( error ) {
if ( error ) return callback ( error ) ;
callback ( null , user ) ;
} ) ;
} ) ;
} ) ;
} ) ;
}
2019-08-29 22:43:06 +02:00
function verifyPassword ( user , password , callback ) {
assert . strictEqual ( typeof user , 'object' ) ;
assert . strictEqual ( typeof password , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
settings . getExternalLdapConfig ( function ( error , externalLdapConfig ) {
2019-10-24 18:32:33 -07:00
if ( error ) return callback ( error ) ;
2019-10-25 15:40:22 -07:00
if ( externalLdapConfig . provider === 'noop' ) return callback ( new BoxError ( BoxError . BAD _STATE , 'not enabled' ) ) ;
2019-08-29 22:43:06 +02:00
2020-06-03 21:23:53 +02:00
ldapUserSearch ( externalLdapConfig , { filter : ` ${ externalLdapConfig . usernameField } = ${ user . username } ` } , function ( error , ldapUsers ) {
2019-08-29 22:43:06 +02:00
if ( error ) return callback ( error ) ;
2019-10-30 14:37:48 -07:00
if ( ldapUsers . length === 0 ) return callback ( new BoxError ( BoxError . NOT _FOUND ) ) ;
if ( ldapUsers . length > 1 ) return callback ( new BoxError ( BoxError . CONFLICT ) ) ;
2019-08-29 22:43:06 +02:00
2020-07-01 14:59:26 +02:00
getClient ( externalLdapConfig , false , function ( error , client ) {
if ( error ) return callback ( error ) ;
2019-08-29 22:43:06 +02:00
2020-07-01 14:59:26 +02:00
client . bind ( ldapUsers [ 0 ] . dn , password , function ( error ) {
if ( error instanceof ldap . InvalidCredentialsError ) return callback ( new BoxError ( BoxError . INVALID _CREDENTIALS ) ) ;
if ( error ) return callback ( new BoxError ( BoxError . EXTERNAL _ERROR , error ) ) ;
callback ( null , translateUser ( externalLdapConfig , ldapUsers [ 0 ] ) ) ;
} ) ;
2019-08-29 22:43:06 +02:00
} ) ;
} ) ;
} ) ;
}
2019-08-29 17:19:51 +02:00
function startSyncer ( callback ) {
2019-08-28 18:22:07 +02:00
assert . strictEqual ( typeof callback , 'function' ) ;
2019-08-29 17:19:51 +02:00
settings . getExternalLdapConfig ( function ( error , externalLdapConfig ) {
2019-10-24 18:32:33 -07:00
if ( error ) return callback ( error ) ;
2019-10-25 15:40:22 -07:00
if ( externalLdapConfig . provider === 'noop' ) return callback ( new BoxError ( BoxError . BAD _STATE , 'not enabled' ) ) ;
2019-08-28 18:22:07 +02:00
2019-08-29 17:19:51 +02:00
tasks . add ( tasks . TASK _SYNC _EXTERNAL _LDAP , [ ] , function ( error , taskId ) {
2019-10-24 18:32:33 -07:00
if ( error ) return callback ( error ) ;
2019-08-28 18:22:07 +02:00
2019-08-29 17:19:51 +02:00
tasks . startTask ( taskId , { } , function ( error , result ) {
debug ( 'sync: done' , error , result ) ;
2019-08-28 18:22:07 +02:00
} ) ;
2019-08-29 17:19:51 +02:00
callback ( null , taskId ) ;
} ) ;
} ) ;
}
2020-06-05 09:26:52 +02:00
function syncUsers ( externalLdapConfig , progressCallback , callback ) {
assert . strictEqual ( typeof externalLdapConfig , 'object' ) ;
2019-08-29 17:19:51 +02:00
assert . strictEqual ( typeof progressCallback , 'function' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2020-06-05 09:26:52 +02:00
ldapUserSearch ( externalLdapConfig , { } , function ( error , ldapUsers ) {
2019-10-24 18:32:33 -07:00
if ( error ) return callback ( error ) ;
2019-08-29 17:19:51 +02:00
2020-06-05 09:26:52 +02:00
debug ( ` Found ${ ldapUsers . length } users ` ) ;
2019-08-29 17:19:51 +02:00
2020-06-05 09:26:52 +02:00
let percent = 10 ;
let step = 30 / ( ldapUsers . length + 1 ) ; // ensure no divide by 0
2019-08-29 22:56:48 +02:00
2020-06-05 09:26:52 +02:00
// we ignore all errors here and just log them for now
async . eachSeries ( ldapUsers , function ( user , iteratorCallback ) {
user = translateUser ( externalLdapConfig , user ) ;
2019-10-25 15:47:55 -07:00
2020-06-05 09:26:52 +02:00
if ( ! validUserRequirements ( user ) ) return iteratorCallback ( ) ;
2019-10-25 16:58:15 -07:00
2020-06-05 09:26:52 +02:00
percent += step ;
progressCallback ( { percent , message : ` Syncing... ${ user . username } ` } ) ;
2019-11-04 12:05:35 -08:00
2020-06-05 09:26:52 +02:00
users . getByUsername ( user . username , function ( error , result ) {
if ( error && error . reason !== BoxError . NOT _FOUND ) return iteratorCallback ( error ) ;
2019-08-29 22:56:48 +02:00
2020-06-05 09:26:52 +02:00
if ( ! result ) {
debug ( ` [adding user] username= ${ user . username } email= ${ user . email } displayName= ${ user . displayName } ` ) ;
2019-08-29 23:13:23 +02:00
2020-06-05 09:26:52 +02:00
users . create ( user . username , null /* password */ , user . email , user . displayName , { source : 'ldap' } , auditSource . EXTERNAL _LDAP _TASK , function ( error ) {
2020-06-05 10:13:19 +02:00
if ( error ) console . error ( 'Failed to create user' , user , error . message ) ;
2020-06-04 12:28:31 +02:00
iteratorCallback ( ) ;
2020-06-05 09:26:52 +02:00
} ) ;
} else if ( result . source !== 'ldap' ) {
debug ( ` [conflicting user] username= ${ user . username } email= ${ user . email } displayName= ${ user . displayName } ` ) ;
2019-10-25 16:13:41 -07:00
2020-06-05 09:26:52 +02:00
iteratorCallback ( ) ;
} else if ( result . email !== user . email || result . displayName !== user . displayName ) {
debug ( ` [updating user] username= ${ user . username } email= ${ user . email } displayName= ${ user . displayName } ` ) ;
2019-10-25 16:13:41 -07:00
2020-06-05 09:26:52 +02:00
users . update ( result , { email : user . email , fallbackEmail : user . email , displayName : user . displayName } , auditSource . EXTERNAL _LDAP _TASK , function ( error ) {
if ( error ) debug ( 'Failed to update user' , user , error ) ;
2020-06-04 12:28:31 +02:00
iteratorCallback ( ) ;
2020-06-05 09:26:52 +02:00
} ) ;
} else {
// user known and up-to-date
debug ( ` [up-to-date user] username= ${ user . username } email= ${ user . email } displayName= ${ user . displayName } ` ) ;
2019-10-30 14:37:48 -07:00
2020-06-05 09:26:52 +02:00
iteratorCallback ( ) ;
}
} ) ;
} , callback ) ;
} ) ;
}
2020-06-03 21:23:53 +02:00
2020-06-05 09:26:52 +02:00
function syncGroups ( externalLdapConfig , progressCallback , callback ) {
assert . strictEqual ( typeof externalLdapConfig , 'object' ) ;
assert . strictEqual ( typeof progressCallback , 'function' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2020-06-03 21:23:53 +02:00
2020-06-05 09:26:52 +02:00
if ( ! externalLdapConfig . syncGroups ) {
debug ( 'Group sync is disabled' ) ;
2020-06-07 13:49:01 +02:00
progressCallback ( { percent : 70 , message : 'Skipping group sync...' } ) ;
2020-06-05 09:26:52 +02:00
return callback ( null , [ ] ) ;
}
ldapGroupSearch ( externalLdapConfig , { } , function ( error , ldapGroups ) {
if ( error ) return callback ( error ) ;
2020-06-04 13:26:13 +02:00
2020-06-05 09:26:52 +02:00
debug ( ` Found ${ ldapGroups . length } groups ` ) ;
2020-06-04 13:26:13 +02:00
2020-06-05 09:26:52 +02:00
let percent = 40 ;
let step = 30 / ( ldapGroups . length + 1 ) ; // ensure no divide by 0
2020-06-04 13:26:13 +02:00
2020-06-05 09:26:52 +02:00
// we ignore all non internal errors here and just log them for now
async . eachSeries ( ldapGroups , function ( ldapGroup , iteratorCallback ) {
2020-06-07 13:49:01 +02:00
var groupName = ldapGroup [ externalLdapConfig . groupnameField ] ;
2020-06-05 09:26:52 +02:00
if ( ! groupName ) return iteratorCallback ( ) ;
2020-06-07 13:49:01 +02:00
// some servers return empty array for unknown properties :-/
if ( typeof groupName !== 'string' ) return iteratorCallback ( ) ;
2020-06-04 13:26:13 +02:00
2020-06-05 10:13:19 +02:00
// groups are lowercase
groupName = groupName . toLowerCase ( ) ;
2020-06-05 09:26:52 +02:00
percent += step ;
progressCallback ( { percent , message : ` Syncing... ${ groupName } ` } ) ;
2020-06-05 09:03:26 +02:00
2020-06-05 09:26:52 +02:00
groups . getByName ( groupName , function ( error , result ) {
if ( error && error . reason !== BoxError . NOT _FOUND ) return iteratorCallback ( error ) ;
2020-06-04 12:59:27 +02:00
2020-06-05 09:26:52 +02:00
if ( ! result ) {
debug ( ` [adding group] groupname= ${ groupName } ` ) ;
2020-06-04 12:28:31 +02:00
2020-06-05 09:26:52 +02:00
groups . create ( groupName , 'ldap' , function ( error ) {
if ( error ) console . error ( 'Failed to create group' , groupName , error ) ;
iteratorCallback ( ) ;
2020-06-04 12:59:27 +02:00
} ) ;
2020-06-05 09:26:52 +02:00
} else {
debug ( ` [up-to-date group] groupname= ${ groupName } ` ) ;
iteratorCallback ( ) ;
}
2019-08-28 18:22:07 +02:00
} ) ;
2020-06-05 09:26:52 +02:00
} , function ( error ) {
if ( error ) return callback ( error ) ;
debug ( 'sync: ldap sync is done' , error ) ;
callback ( error ) ;
} ) ;
} ) ;
}
function syncGroupUsers ( externalLdapConfig , progressCallback , callback ) {
assert . strictEqual ( typeof externalLdapConfig , 'object' ) ;
assert . strictEqual ( typeof progressCallback , 'function' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
if ( ! externalLdapConfig . syncGroups ) {
debug ( 'Group users sync is disabled' ) ;
progressCallback ( { percent : 99 , message : 'Skipping group users sync...' } ) ;
return callback ( null , [ ] ) ;
}
2020-06-05 10:29:36 +02:00
groups . getAll ( function ( error , result ) {
2020-06-05 09:26:52 +02:00
if ( error ) return callback ( error ) ;
2020-06-05 10:29:36 +02:00
var ldapGroups = result . filter ( function ( g ) { return g . source === 'ldap' ; } ) ;
2020-06-05 09:26:52 +02:00
debug ( ` Found ${ ldapGroups . length } groups to sync users ` ) ;
2020-06-05 10:29:36 +02:00
async . eachSeries ( ldapGroups , function ( group , iteratorCallback ) {
2020-06-07 13:49:01 +02:00
debug ( ` Sync users for group ${ group . name } ` ) ;
2020-06-05 10:29:36 +02:00
2020-06-07 13:49:01 +02:00
ldapGroupSearch ( externalLdapConfig , { } , function ( error , result ) {
2020-06-05 10:29:36 +02:00
if ( error ) return callback ( error ) ;
if ( ! result || result . length === 0 ) {
console . error ( ` Unable to find group ${ group . name } ignoring for now. ` ) ;
return callback ( ) ;
}
2020-06-07 13:49:01 +02:00
// since our group names are lowercase we cannot use potentially case matching ldap filters
let found = result . find ( function ( r ) {
if ( ! r [ externalLdapConfig . groupnameField ] ) return false ;
return r [ externalLdapConfig . groupnameField ] . toLowerCase ( ) === group . name ;
} ) ;
if ( ! found ) {
console . error ( ` Unable to find group ${ group . name } ignoring for now. ` ) ;
return callback ( ) ;
}
var ldapGroupMembers = found . member || [ ] ;
2020-06-05 10:29:36 +02:00
debug ( ` Group ${ group . name } has ${ ldapGroupMembers . length } members. ` ) ;
async . eachSeries ( ldapGroupMembers , function ( memberDn , iteratorCallback ) {
ldapGetByDN ( externalLdapConfig , memberDn , function ( error , result ) {
if ( error ) {
console . error ( ` Failed to get ${ memberDn } : ` , error ) ;
return iteratorCallback ( ) ;
}
debug ( ` Found member object at ${ memberDn } adding to group ${ group . name } ` ) ;
const username = result [ externalLdapConfig . usernameField ] ;
if ( ! username ) return iteratorCallback ( ) ;
users . getByUsername ( username , function ( error , result ) {
if ( error ) {
console . error ( ` Failed to get user by username ${ username } ` , error ) ;
return iteratorCallback ( ) ;
}
groups . addMember ( group . id , result . id , function ( error ) {
if ( error && error . reason !== BoxError . ALREADY _EXISTS ) console . error ( 'Failed to add member' , error ) ;
iteratorCallback ( ) ;
} ) ;
} ) ;
} ) ;
} , function ( error ) {
if ( error ) console . error ( error ) ;
iteratorCallback ( ) ;
} ) ;
} ) ;
} , callback ) ;
2020-06-05 09:26:52 +02:00
} ) ;
}
function sync ( progressCallback , callback ) {
assert . strictEqual ( typeof progressCallback , 'function' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
progressCallback ( { percent : 10 , message : 'Starting ldap user sync' } ) ;
settings . getExternalLdapConfig ( function ( error , externalLdapConfig ) {
if ( error ) return callback ( error ) ;
if ( externalLdapConfig . provider === 'noop' ) return callback ( new BoxError ( BoxError . BAD _STATE , 'not enabled' ) ) ;
async . series ( [
syncUsers . bind ( null , externalLdapConfig , progressCallback ) ,
syncGroups . bind ( null , externalLdapConfig , progressCallback ) ,
syncGroupUsers . bind ( null , externalLdapConfig , progressCallback )
] , function ( error ) {
if ( error ) return callback ( error ) ;
progressCallback ( { percent : 100 , message : 'Done' } ) ;
debug ( 'sync: ldap sync is done' , error ) ;
callback ( error ) ;
2019-08-28 18:22:07 +02:00
} ) ;
} ) ;
}