1 |
|
2 | # `ldap_acl(options, [goptions], callback)`
|
3 |
|
4 | Create new [ACLs](acls) for the OpenLDAP server.
|
5 |
|
6 | This implementation currently doesn't execute remote SSH commands. Instead, it
|
7 | connects directly to the LDAP database and thus requires a specific port to be
|
8 | accessible.
|
9 |
|
10 | ## Options
|
11 |
|
12 | * `to`
|
13 | What to control access to as a string.
|
14 | * `by`
|
15 | Who to grant access to and the access to grant as an array
|
16 | (eg: `{..., by:["ssf=64 anonymous auth"]}`).
|
17 | * `url`
|
18 | Specify URI referring to the ldap server, alternative to providing an
|
19 | [ldapjs client] instance.
|
20 | * `binddn`
|
21 | Distinguished Name to bind to the LDAP directory, alternative to providing
|
22 | an [ldapjs client] instance.
|
23 | * `passwd`
|
24 | Password for simple authentication, alternative to providing an
|
25 | [ldapjs client] instance.
|
26 | * `ldap`
|
27 | Instance of an [ldapjs client][ldapclt], alternative to providing the `url`,
|
28 | `binddn` and `passwd` connection properties.
|
29 | * `unbind`
|
30 | Close the ldap connection, default to false if connection is an
|
31 | [ldapjs client][ldapclt] instance.
|
32 | * `name`
|
33 | Distinguish name storing the "olcAccess" property, using the database adress
|
34 | (eg: "olcDatabase={2}bdb,cn=config").
|
35 | * `overwrite`
|
36 | Overwrite existing "olcAccess", default is to merge.
|
37 | * `log`
|
38 | Function called with a log related messages.
|
39 | * `acl`
|
40 | In case of multiple acls, regroup "before", "to" and "by" as an array.
|
41 |
|
42 | ## Example
|
43 |
|
44 | ```js
|
45 | require('mecano/alt/ldap_acl')({
|
46 | url: 'ldap://openldap.server/',
|
47 | binddn: 'cn=admin,cn=config',
|
48 | passwd: 'password',
|
49 | name: 'olcDatabase={2}bdb,cn=config',
|
50 | acls: [{
|
51 | before: 'dn.subtree="dc=domain,dc=com"',
|
52 | to: 'dn.subtree="ou=users,dc=domain,dc=com"',
|
53 | by: [
|
54 | 'dn.exact="ou=users,dc=domain,dc=com" write',
|
55 | "dn.base='gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth' read",
|
56 | "* none"
|
57 | ]
|
58 | },{
|
59 | to: 'dn.subtree="dc=domain,dc=com"',
|
60 | by: [
|
61 | 'dn.exact="ou=kerberos,dc=domain,dc=com" write'
|
62 | ]
|
63 | }]
|
64 | }, function(err, modified){
|
65 | console.log(err ? err.message : "ACL modified: " + !!modified);
|
66 | });
|
67 | ```
|
68 |
|
69 | module.exports = (goptions, options, callback) ->
|
70 | options.acls ?= [{}]
|
71 | updated = false
|
72 | each(options.acls)
|
73 | .run (acl, next) ->
|
74 | acl.before ?= options.before
|
75 | acl.to ?= options.to
|
76 | acl.by ?= options.by
|
77 | client = null
|
78 | acl.to = acl.to.trim()
|
79 | for b, i in acl.by
|
80 | acl.by[i] = b.trim()
|
81 | connect = ->
|
82 | # if options.ldap instanceof ldap_client
|
83 | if options.ldap?.url?.protocol?.indexOf('ldap') is 0
|
84 | client = options.ldap
|
85 | return search()
|
86 | options.log? 'Open and bind connection'
|
87 | client = ldap.createClient url: options.url
|
88 | client.bind options.binddn, options.passwd, (err) ->
|
89 | return end err if err
|
90 | search()
|
91 | search = ->
|
92 | options.log? 'Search attribute olcAccess'
|
93 | client.search options.name,
|
94 | scope: 'base'
|
95 | attributes: ['olcAccess']
|
96 | , (err, search) ->
|
97 | return unbind err if err
|
98 | olcAccess = null
|
99 | search.on 'searchEntry', (entry) ->
|
100 | options.log? "Found #{JSON.stringify entry.object}"
|
101 | # typeof olcAccess may be undefined, array or string
|
102 | olcAccess = entry.object.olcAccess or []
|
103 | olcAccess = [olcAccess] unless Array.isArray olcAccess
|
104 | search.on 'end', ->
|
105 | options.log? "Attribute olcAccess was #{JSON.stringify olcAccess}"
|
106 | parse olcAccess
|
107 | parse = (_olcAccess) ->
|
108 | olcAccess = []
|
109 | for access, i in _olcAccess
|
110 | to = ''
|
111 | bys = []
|
112 | buftype = 0 # 0: start, 1: to, 2:by
|
113 | buf = ''
|
114 | for c, i in access
|
115 | buf += c
|
116 | if buftype is 0
|
117 | if /to$/.test buf
|
118 | buf = ''
|
119 | buftype = 1
|
120 | if buftype is 1
|
121 | if matches = /^(.*)by$/.exec buf
|
122 | to = matches[1].trim()
|
123 | buf = ''
|
124 | buftype = 2
|
125 | if buftype is 2
|
126 | if matches = /^(.*)by$/.exec buf
|
127 | bys.push matches[1].trim()
|
128 | buf = ''
|
129 | else if i+1 is access.length
|
130 | bys.push buf.trim()
|
131 | olcAccess.push
|
132 | to: to
|
133 | by: bys
|
134 | do_diff olcAccess
|
135 | do_diff = (olcAccess) ->
|
136 | toAlreadyExist = false
|
137 | for access, i in olcAccess
|
138 | continue unless acl.to is access.to
|
139 | toAlreadyExist = true
|
140 | fby = unless options.overwrite then access.by else []
|
141 | for oby in acl.by
|
142 | found = false
|
143 | for aby in access.by
|
144 | if oby is aby
|
145 | found = true
|
146 | break
|
147 | unless found
|
148 | updated = true
|
149 | fby.push oby
|
150 | olcAccess[i].by = fby
|
151 | unless toAlreadyExist
|
152 | updated = true
|
153 | # place before
|
154 | if acl.before
|
155 | found = null
|
156 | for access, i in olcAccess
|
157 | found = i if access.to is acl.before
|
158 | # throw new Error 'Before does not match any "to" rule' unless found?
|
159 | olcAccess.splice found-1, 0, to: acl.to, by: acl.by
|
160 | # place after
|
161 | else if acl.after
|
162 | found = false
|
163 | for access, i in olcAccess
|
164 | found = i if access.to is options.after
|
165 | # throw new Error 'After does not match any "to" rule'
|
166 | olcAccess.splice found, 0, to: acl.to, by: acl.by
|
167 | # append
|
168 | else
|
169 | olcAccess.push to: acl.to, by: acl.by
|
170 | if updated then stringify(olcAccess) else unbind()
|
171 | stringify = (olcAccess) ->
|
172 | for access, i in olcAccess
|
173 | value = "{#{i}}to #{access.to}"
|
174 | for bie in access.by
|
175 | value += " by #{bie}"
|
176 | olcAccess[i] = value
|
177 | save olcAccess
|
178 | save = (olcAccess) ->
|
179 | change = new ldap.Change
|
180 | operation: 'replace'
|
181 | modification: olcAccess: olcAccess
|
182 | client.modify options.name, change, (err) ->
|
183 | unbind err
|
184 | unbind = (err) ->
|
185 | options.log? 'Unbind connection'
|
186 | # return end err if options.ldap instanceof ldap_client and not options.unbind
|
187 | return end err if options.ldap?.url?.protocol?.indexOf('ldap') is 0 and not options.unbind
|
188 | client.unbind (e) ->
|
189 | return next e if e
|
190 | end err
|
191 | end = (err) ->
|
192 | next err
|
193 | connect()
|
194 | .then (err) ->
|
195 | next err, updated
|
196 |
|
197 | ## Dependencies
|
198 |
|
199 | each = require 'each'
|
200 | ldap = require 'ldapjs'
|
201 | wrap = require '../misc/wrap'
|
202 |
|
203 | [acls]: http://www.openldap.org/doc/admin24/access-control.html
|
204 | [ldapclt]: http://ldapjs.org/client.html
|
205 |
|
206 |
|