mecano
Version:
Common functions for system deployment.
175 lines (168 loc) • 7.71 kB
Markdown
`ldap_acl([goptions], options, callback`
----------------------------------------
Create new ACLs for the OpenLDAP server.
each = require 'each'
ldap = require 'ldapjs'
misc = require './misc'
conditions = require './misc/conditions'
child = require './misc/child'
`options` Command options include:
* `to` What to control access to as a string.
* `by` Who to grant access to and the access to grant as an array (eg: `{..., by:["ssf=64 anonymous auth"]}`)
* `url` Specify URI referring to the ldap server, alternative to providing an [ldapjs client] instance.
* `binddn` Distinguished Name to bind to the LDAP directory, alternative to providing an [ldapjs client] instance.
* `passwd` Password for simple authentication, alternative to providing an [ldapjs client] instance.
* `ldap` Instance of an pldapjs client][ldapclt], alternative to providing the `url`, `binddn` and `passwd` connection properties.
* `unbind` Close the ldap connection, default to false if connection is an [ldapjs client][ldapclt] instance.
* `name` Distinguish name storing the "olcAccess" property, using the database adress (eg: "olcDatabase={2}bdb,cn=config").
* `overwrite` Overwrite existing "olcAccess", default is to merge.
* `log` Function called with a log related messages.
* `acl` In case of multiple acls, regroup "before", "to" and "by" as an array
Resources:
http://www.openldap.org/doc/admin24/access-control.html
[ldapclt]: http://ldapjs.org/client.html
module.exports = (goptions, options, callback) ->
[goptions, options, callback] = misc.args arguments
result = child()
finish = (err, modified) ->
callback err, modified if callback
result.end err, modified
misc.options options, (err, options) ->
return finish err if err
modified = 0
each( options )
.parallel(goptions.parallel)
.on 'item', (options, next) ->
options.acls ?= [{}]
conditions.all options, next, ->
updated = false
each(options.acls)
.parallel(false)
.on 'item', (acl, next) ->
acl.before ?= options.before
acl.to ?= options.to
acl.by ?= options.by
client = null
acl.to = acl.to.trim()
for b, i in acl.by
acl.by[i] = b.trim()
connect = ->
# if options.ldap instanceof ldap_client
if options.ldap?.url?.protocol?.indexOf('ldap') is 0
client = options.ldap
return search()
options.log? 'Open and bind connection'
client = ldap.createClient url: options.url
client.bind options.binddn, options.passwd, (err) ->
return end err if err
search()
search = ->
options.log? 'Search attribute olcAccess'
client.search options.name,
scope: 'base'
attributes: ['olcAccess']
, (err, search) ->
return unbind err if err
olcAccess = null
search.on 'searchEntry', (entry) ->
options.log? "Found #{JSON.stringify entry.object}"
# typeof olcAccess may be undefined, array or string
olcAccess = entry.object.olcAccess or []
olcAccess = [olcAccess] unless Array.isArray olcAccess
search.on 'end', ->
options.log? "Attribute olcAccess was #{JSON.stringify olcAccess}"
parse olcAccess
parse = (_olcAccess) ->
olcAccess = []
for access, i in _olcAccess
to = ''
bys = []
buftype = 0 # 0: start, 1: to, 2:by
buf = ''
for c, i in access
buf += c
if buftype is 0
if /to$/.test buf
buf = ''
buftype = 1
if buftype is 1
if matches = /^(.*)by$/.exec buf
to = matches[1].trim()
buf = ''
buftype = 2
if buftype is 2
if matches = /^(.*)by$/.exec buf
bys.push matches[1].trim()
buf = ''
else if i+1 is access.length
bys.push buf.trim()
olcAccess.push
to: to
by: bys
do_diff olcAccess
do_diff = (olcAccess) ->
toAlreadyExist = false
for access, i in olcAccess
continue unless acl.to is access.to
toAlreadyExist = true
fby = unless options.overwrite then access.by else []
for oby in acl.by
found = false
for aby in access.by
if oby is aby
found = true
break
unless found
updated = true
fby.push oby
olcAccess[i].by = fby
unless toAlreadyExist
updated = true
# place before
if acl.before
found = null
for access, i in olcAccess
found = i if access.to is acl.before
# throw new Error 'Before does not match any "to" rule' unless found?
olcAccess.splice found-1, 0, to: acl.to, by: acl.by
# place after
else if acl.after
found = false
for access, i in olcAccess
found = i if access.to is options.after
# throw new Error 'After does not match any "to" rule'
olcAccess.splice found, 0, to: acl.to, by: acl.by
# append
else
olcAccess.push to: acl.to, by: acl.by
if updated then stringify(olcAccess) else unbind()
stringify = (olcAccess) ->
for access, i in olcAccess
value = "{#{i}}to #{access.to}"
for bie in access.by
value += " by #{bie}"
olcAccess[i] = value
save olcAccess
save = (olcAccess) ->
change = new ldap.Change
operation: 'replace'
modification: olcAccess: olcAccess
client.modify options.name, change, (err) ->
unbind err
unbind = (err) ->
options.log? 'Unbind connection'
# return end err if options.ldap instanceof ldap_client and not options.unbind
return end err if options.ldap?.url?.protocol?.indexOf('ldap') is 0 and not options.unbind
client.unbind (e) ->
return next e if e
end err
end = (err) ->
next err
connect()
.on 'both', (err) ->
modified += 1 if updated and not err
# finish err, modified
next err
.on 'both', (err) ->
finish err, modified
result