author | Kim Alvefur <zash@zash.se> |
Wed, 03 Mar 2021 11:43:38 +0100 | |
changeset 4494 | cf2bdb2aaa57 |
parent 3199 | 66b3085ecc49 |
permissions | -rw-r--r-- |
809 | 1 |
-- vim:sts=4 sw=4 |
2 |
||
3 |
-- Prosody IM |
|
4 |
-- Copyright (C) 2008-2010 Matthew Wild |
|
5 |
-- Copyright (C) 2008-2010 Waqas Hussain |
|
6 |
-- Copyright (C) 2012 Rob Hoelz |
|
7 |
-- |
|
8 |
-- This project is MIT/X11 licensed. Please see the |
|
9 |
-- COPYING file in the source package for more information. |
|
10 |
-- |
|
11 |
||
12 |
local ldap; |
|
13 |
local connection; |
|
14 |
local params = module:get_option("ldap"); |
|
15 |
local format = string.format; |
|
16 |
local tconcat = table.concat; |
|
17 |
||
18 |
local _M = {}; |
|
19 |
||
20 |
local config_params = { |
|
21 |
hostname = 'string', |
|
22 |
user = { |
|
23 |
basedn = 'string', |
|
24 |
namefield = 'string', |
|
25 |
filter = 'string', |
|
26 |
usernamefield = 'string', |
|
27 |
}, |
|
28 |
groups = { |
|
29 |
basedn = 'string', |
|
30 |
namefield = 'string', |
|
31 |
memberfield = 'string', |
|
32 |
||
33 |
_member = { |
|
34 |
name = 'string', |
|
35 |
admin = 'boolean?', |
|
36 |
}, |
|
37 |
}, |
|
38 |
admin = { |
|
39 |
_optional = true, |
|
40 |
basedn = 'string', |
|
41 |
namefield = 'string', |
|
42 |
filter = 'string', |
|
43 |
} |
|
44 |
} |
|
45 |
||
46 |
local function run_validation(params, config, prefix) |
|
47 |
prefix = prefix or ''; |
|
48 |
||
49 |
-- verify that every required member of config is present in params |
|
50 |
for k, v in pairs(config) do |
|
51 |
if type(k) == 'string' and k:sub(1, 1) ~= '_' then |
|
52 |
local is_optional; |
|
53 |
if type(v) == 'table' then |
|
54 |
is_optional = v._optional; |
|
55 |
else |
|
56 |
is_optional = v:sub(-1) == '?'; |
|
57 |
end |
|
58 |
||
59 |
if not is_optional and params[k] == nil then |
|
60 |
return nil, prefix .. k .. ' is required'; |
|
61 |
end |
|
62 |
end |
|
63 |
end |
|
64 |
||
65 |
for k, v in pairs(params) do |
|
66 |
local expected_type = config[k]; |
|
67 |
||
68 |
local ok, err = true; |
|
69 |
||
70 |
if type(k) == 'string' then |
|
71 |
-- verify that this key is present in config |
|
72 |
if k:sub(1, 1) == '_' or expected_type == nil then |
|
73 |
return nil, 'invalid parameter ' .. prefix .. k; |
|
74 |
end |
|
75 |
||
76 |
-- type validation |
|
77 |
if type(expected_type) == 'string' then |
|
78 |
if expected_type:sub(-1) == '?' then |
|
79 |
expected_type = expected_type:sub(1, -2); |
|
80 |
end |
|
81 |
||
82 |
if type(v) ~= expected_type then |
|
83 |
return nil, 'invalid type for parameter ' .. prefix .. k; |
|
84 |
end |
|
85 |
else -- it's a table (or had better be) |
|
86 |
if type(v) ~= 'table' then |
|
87 |
return nil, 'invalid type for parameter ' .. prefix .. k; |
|
88 |
end |
|
89 |
||
90 |
-- recurse into child |
|
91 |
ok, err = run_validation(v, expected_type, prefix .. k .. '.'); |
|
92 |
end |
|
93 |
else -- it's an integer (or had better be) |
|
94 |
if not config._member then |
|
95 |
return nil, 'invalid parameter ' .. prefix .. tostring(k); |
|
96 |
end |
|
97 |
ok, err = run_validation(v, config._member, prefix .. tostring(k) .. '.'); |
|
98 |
end |
|
99 |
||
100 |
if not ok then |
|
101 |
return ok, err; |
|
102 |
end |
|
103 |
end |
|
104 |
||
105 |
return true; |
|
106 |
end |
|
107 |
||
108 |
local function validate_config() |
|
109 |
if true then |
|
110 |
return true; -- XXX for now |
|
111 |
end |
|
112 |
||
113 |
-- this is almost too clever (I mean that in a bad |
|
114 |
-- maintainability sort of way) |
|
115 |
-- |
|
116 |
-- basically this allows a free pass for a key in group members |
|
117 |
-- equal to params.groups.namefield |
|
118 |
setmetatable(config_params.groups._member, { |
|
119 |
__index = function(_, k) |
|
120 |
if k == params.groups.namefield then |
|
121 |
return 'string'; |
|
122 |
end |
|
123 |
end |
|
124 |
}); |
|
125 |
||
126 |
local ok, err = run_validation(params, config_params); |
|
127 |
||
128 |
setmetatable(config_params.groups._member, nil); |
|
129 |
||
130 |
if ok then |
|
131 |
-- a little extra validation that doesn't fit into |
|
132 |
-- my recursive checker |
|
133 |
local group_namefield = params.groups.namefield; |
|
134 |
for i, group in ipairs(params.groups) do |
|
135 |
if not group[group_namefield] then |
|
136 |
return nil, format('groups.%d.%s is required', i, group_namefield); |
|
137 |
end |
|
138 |
end |
|
139 |
||
140 |
-- fill in params.admin if you can |
|
141 |
if not params.admin and params.groups then |
|
142 |
local admingroup; |
|
143 |
||
144 |
for _, groupconfig in ipairs(params.groups) do |
|
145 |
if groupconfig.admin then |
|
146 |
admingroup = groupconfig; |
|
147 |
break; |
|
148 |
end |
|
149 |
end |
|
150 |
||
151 |
if admingroup then |
|
152 |
params.admin = { |
|
153 |
basedn = params.groups.basedn, |
|
154 |
namefield = params.groups.memberfield, |
|
155 |
filter = group_namefield .. '=' .. admingroup[group_namefield], |
|
156 |
}; |
|
157 |
end |
|
158 |
end |
|
159 |
end |
|
160 |
||
161 |
return ok, err; |
|
162 |
end |
|
163 |
||
164 |
-- what to do if connection isn't available? |
|
165 |
local function connect() |
|
166 |
return ldap.open_simple(params.hostname, params.bind_dn, params.bind_password, params.use_tls); |
|
167 |
end |
|
168 |
||
169 |
-- this is abstracted so we can maintain persistent connections at a later time |
|
170 |
function _M.getconnection() |
|
3199
66b3085ecc49
mod_lib_ldap: assert() connection for hopefully better error reporting (thanks adac)
Matthew Wild <mwild1@gmail.com>
parents:
877
diff
changeset
|
171 |
return assert(connect()); |
809 | 172 |
end |
173 |
||
174 |
function _M.getparams() |
|
175 |
return params; |
|
176 |
end |
|
177 |
||
178 |
-- XXX consider renaming this...it doesn't bind the current connection |
|
179 |
function _M.bind(username, password) |
|
877
cd2262969d2e
Make sure we use the user filter for bind
Rob Hoelz <rob@hoelz.ro>
parents:
871
diff
changeset
|
180 |
local conn = _M.getconnection(); |
cd2262969d2e
Make sure we use the user filter for bind
Rob Hoelz <rob@hoelz.ro>
parents:
871
diff
changeset
|
181 |
local filter = format('%s=%s', params.user.usernamefield, username); |
cd2262969d2e
Make sure we use the user filter for bind
Rob Hoelz <rob@hoelz.ro>
parents:
871
diff
changeset
|
182 |
|
cd2262969d2e
Make sure we use the user filter for bind
Rob Hoelz <rob@hoelz.ro>
parents:
871
diff
changeset
|
183 |
if filter then |
cd2262969d2e
Make sure we use the user filter for bind
Rob Hoelz <rob@hoelz.ro>
parents:
871
diff
changeset
|
184 |
filter = _M.filter.combine_and(filter, params.user.filter); |
cd2262969d2e
Make sure we use the user filter for bind
Rob Hoelz <rob@hoelz.ro>
parents:
871
diff
changeset
|
185 |
end |
cd2262969d2e
Make sure we use the user filter for bind
Rob Hoelz <rob@hoelz.ro>
parents:
871
diff
changeset
|
186 |
|
cd2262969d2e
Make sure we use the user filter for bind
Rob Hoelz <rob@hoelz.ro>
parents:
871
diff
changeset
|
187 |
local who = _M.singlematch { |
864
16b007c7706c
We must search for dn before trying to bind
Guilhem LETTRON <guilhem.lettron@gmail.com>
parents:
809
diff
changeset
|
188 |
attrs = params.user.usernamefield, |
16b007c7706c
We must search for dn before trying to bind
Guilhem LETTRON <guilhem.lettron@gmail.com>
parents:
809
diff
changeset
|
189 |
base = params.user.basedn, |
16b007c7706c
We must search for dn before trying to bind
Guilhem LETTRON <guilhem.lettron@gmail.com>
parents:
809
diff
changeset
|
190 |
filter = filter, |
16b007c7706c
We must search for dn before trying to bind
Guilhem LETTRON <guilhem.lettron@gmail.com>
parents:
809
diff
changeset
|
191 |
}; |
16b007c7706c
We must search for dn before trying to bind
Guilhem LETTRON <guilhem.lettron@gmail.com>
parents:
809
diff
changeset
|
192 |
|
870
13e645340767
Use singlematch to find user record in ldap.bind
Rob Hoelz <rob@hoelz.ro>
parents:
869
diff
changeset
|
193 |
if who then |
13e645340767
Use singlematch to find user record in ldap.bind
Rob Hoelz <rob@hoelz.ro>
parents:
869
diff
changeset
|
194 |
who = who.dn; |
13e645340767
Use singlematch to find user record in ldap.bind
Rob Hoelz <rob@hoelz.ro>
parents:
869
diff
changeset
|
195 |
module:log('debug', '_M.bind - who: %s', who); |
871
e4a03e58f896
Log and return failure if user record not found in bind
Rob Hoelz <rob@hoelz.ro>
parents:
870
diff
changeset
|
196 |
else |
e4a03e58f896
Log and return failure if user record not found in bind
Rob Hoelz <rob@hoelz.ro>
parents:
870
diff
changeset
|
197 |
module:log('debug', '_M.bind - no DN found for username = %s', username); |
e4a03e58f896
Log and return failure if user record not found in bind
Rob Hoelz <rob@hoelz.ro>
parents:
870
diff
changeset
|
198 |
return nil, format('no DN found for username = %s', username); |
864
16b007c7706c
We must search for dn before trying to bind
Guilhem LETTRON <guilhem.lettron@gmail.com>
parents:
809
diff
changeset
|
199 |
end |
16b007c7706c
We must search for dn before trying to bind
Guilhem LETTRON <guilhem.lettron@gmail.com>
parents:
809
diff
changeset
|
200 |
|
809 | 201 |
local conn, err = ldap.open_simple(params.hostname, who, password, params.use_tls); |
202 |
||
203 |
if conn then |
|
204 |
conn:close(); |
|
205 |
return true; |
|
206 |
end |
|
207 |
||
208 |
return conn, err; |
|
209 |
end |
|
210 |
||
211 |
function _M.singlematch(query) |
|
212 |
local ld = _M.getconnection(); |
|
213 |
||
214 |
query.sizelimit = 1; |
|
868
0017518c94a0
Change singlematch to search subtrees
Rob Hoelz <rob@hoelz.ro>
parents:
864
diff
changeset
|
215 |
query.scope = 'subtree'; |
809 | 216 |
|
217 |
for dn, attribs in ld:search(query) do |
|
869
ec791fd8ce87
Return DN in the attributes table with singlematch
Rob Hoelz <rob@hoelz.ro>
parents:
868
diff
changeset
|
218 |
attribs.dn = dn; |
809 | 219 |
return attribs; |
220 |
end |
|
221 |
end |
|
222 |
||
223 |
_M.filter = {}; |
|
224 |
||
225 |
function _M.filter.combine_and(...) |
|
226 |
local parts = { '(&' }; |
|
227 |
||
228 |
local arg = { ... }; |
|
229 |
||
230 |
for _, filter in ipairs(arg) do |
|
231 |
if filter:sub(1, 1) ~= '(' and filter:sub(-1) ~= ')' then |
|
232 |
filter = '(' .. filter .. ')' |
|
233 |
end |
|
234 |
parts[#parts + 1] = filter; |
|
235 |
end |
|
236 |
||
237 |
parts[#parts + 1] = ')'; |
|
238 |
||
239 |
return tconcat(parts, ''); |
|
240 |
end |
|
241 |
||
242 |
do |
|
243 |
local ok, err; |
|
244 |
||
245 |
prosody.unlock_globals(); |
|
246 |
ok, ldap = pcall(require, 'lualdap'); |
|
247 |
prosody.lock_globals(); |
|
248 |
if not ok then |
|
249 |
module:log("error", "Failed to load the LuaLDAP library for accessing LDAP: %s", ldap); |
|
250 |
module:log("error", "More information on install LuaLDAP can be found at http://www.keplerproject.org/lualdap"); |
|
251 |
return; |
|
252 |
end |
|
253 |
||
254 |
if not params then |
|
255 |
module:log("error", "LDAP configuration required to use the LDAP storage module"); |
|
256 |
return; |
|
257 |
end |
|
258 |
||
259 |
ok, err = validate_config(); |
|
260 |
||
261 |
if not ok then |
|
262 |
module:log("error", "LDAP configuration is invalid: %s", tostring(err)); |
|
263 |
return; |
|
264 |
end |
|
265 |
end |
|
266 |
||
267 |
return _M; |