]>
Commit | Line | Data |
---|---|---|
1 | package PMG::API2::LDAP; | |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | use Data::Dumper; | |
6 | ||
7 | use PVE::SafeSyslog; | |
8 | use PVE::Tools qw(extract_param); | |
9 | use HTTP::Status qw(:constants); | |
10 | use Storable qw(dclone); | |
11 | use PVE::JSONSchema qw(get_standard_option); | |
12 | use PVE::RESTHandler; | |
13 | use PVE::INotify; | |
14 | ||
15 | use PMG::LDAPConfig; | |
16 | use PMG::LDAPCache; | |
17 | use PMG::LDAPSet; | |
18 | ||
19 | use base qw(PVE::RESTHandler); | |
20 | ||
21 | __PACKAGE__->register_method ({ | |
22 | name => 'index', | |
23 | path => '', | |
24 | method => 'GET', | |
25 | description => "List configured LDAP profiles.", | |
26 | proxyto => 'master', | |
27 | permissions => { check => [ 'admin', 'audit' ] }, | |
28 | parameters => { | |
29 | additionalProperties => 0, | |
30 | properties => {}, | |
31 | }, | |
32 | returns => { | |
33 | type => 'array', | |
34 | items => { | |
35 | type => "object", | |
36 | properties => { | |
37 | profile => { type => 'string'}, | |
38 | disable => { type => 'boolean' }, | |
39 | server1 => { type => 'string'}, | |
40 | server2 => { type => 'string', optional => 1}, | |
41 | comment => { type => 'string', optional => 1}, | |
42 | gcount => { type => 'integer', optional => 1}, | |
43 | mcount => { type => 'integer', optional => 1}, | |
44 | ucount => { type => 'integer', optional => 1}, | |
45 | mode => { type => 'string'}, | |
46 | }, | |
47 | }, | |
48 | links => [ { rel => 'child', href => "{profile}" } ], | |
49 | }, | |
50 | code => sub { | |
51 | my ($param) = @_; | |
52 | ||
53 | my $ldap_cfg = PMG::LDAPConfig->new(); | |
54 | ||
55 | my $ldap_set = PMG::LDAPSet->new_from_ldap_cfg($ldap_cfg, 1); | |
56 | ||
57 | my $res = []; | |
58 | ||
59 | if (defined($ldap_cfg)) { | |
60 | foreach my $profile (keys %{$ldap_cfg->{ids}}) { | |
61 | my $d = $ldap_cfg->{ids}->{$profile}; | |
62 | my $entry = { | |
63 | profile => $profile, | |
64 | disable => $d->{disable} ? 1 : 0, | |
65 | server1 => $d->{server1}, | |
66 | mode => $d->{mode} // 'ldap', | |
67 | }; | |
68 | $entry->{server2} = $d->{server2} if defined($d->{server2}); | |
69 | $entry->{comment} = $d->{comment} if defined($d->{comment}); | |
70 | ||
71 | if (my $d = $ldap_set->{$profile}) { | |
72 | foreach my $k (qw(gcount mcount ucount)) { | |
73 | my $v = $d->{$k}; | |
74 | $entry->{$k} = $v if defined($v); | |
75 | } | |
76 | } | |
77 | ||
78 | push @$res, $entry; | |
79 | } | |
80 | } | |
81 | ||
82 | return $res; | |
83 | }}); | |
84 | ||
85 | my $forced_ldap_sync = sub { | |
86 | my ($profile, $config) = @_; | |
87 | ||
88 | my $ldapcache = PMG::LDAPCache->new( | |
89 | id => $profile, syncmode => 2, %$config); | |
90 | ||
91 | die $ldapcache->{errors} if $ldapcache->{errors}; | |
92 | ||
93 | die "unable to find valid email addresses\n" | |
94 | if !$ldapcache->{mcount}; | |
95 | }; | |
96 | ||
97 | __PACKAGE__->register_method ({ | |
98 | name => 'create', | |
99 | path => '', | |
100 | method => 'POST', | |
101 | proxyto => 'master', | |
102 | permissions => { check => [ 'admin' ] }, | |
103 | protected => 1, | |
104 | description => "Add LDAP profile.", | |
105 | parameters => PMG::LDAPConfig->createSchema(1), | |
106 | returns => { type => 'null' }, | |
107 | code => sub { | |
108 | my ($param) = @_; | |
109 | ||
110 | my $code = sub { | |
111 | ||
112 | my $cfg = PMG::LDAPConfig->new(); | |
113 | ||
114 | $cfg->{ids} //= {}; | |
115 | ||
116 | my $ids = $cfg->{ids}; | |
117 | ||
118 | my $profile = extract_param($param, 'profile'); | |
119 | my $type = $param->{type}; | |
120 | ||
121 | die "LDAP profile '$profile' already exists\n" | |
122 | if $ids->{$profile}; | |
123 | ||
124 | my $config = PMG::LDAPConfig->check_config($profile, $param, 1, 1); | |
125 | ||
126 | $ids->{$profile} = $config; | |
127 | ||
128 | $forced_ldap_sync->($profile, $config) | |
129 | if !$config->{disable}; | |
130 | ||
131 | $cfg->write(); | |
132 | }; | |
133 | ||
134 | PMG::LDAPConfig::lock_config($code, "add LDAP profile failed"); | |
135 | ||
136 | return undef; | |
137 | }}); | |
138 | ||
139 | __PACKAGE__->register_method ({ | |
140 | name => 'profile_index', | |
141 | path => '{profile}', | |
142 | method => 'GET', | |
143 | description => "Directory index", | |
144 | permissions => { | |
145 | user => 'all', | |
146 | }, | |
147 | parameters => { | |
148 | additionalProperties => 0, | |
149 | properties => { | |
150 | profile => { | |
151 | description => "Profile ID.", | |
152 | type => 'string', format => 'pve-configid', | |
153 | }, | |
154 | }, | |
155 | }, | |
156 | returns => { | |
157 | type => 'array', | |
158 | items => { | |
159 | type => "object", | |
160 | properties => { | |
161 | subdir => { type => 'string'}, | |
162 | }, | |
163 | }, | |
164 | links => [ { rel => 'child', href => "{subdir}" } ], | |
165 | }, | |
166 | code => sub { | |
167 | my ($param) = @_; | |
168 | ||
169 | return [ | |
170 | { subdir => 'config' }, | |
171 | { subdir => 'sync' }, | |
172 | { subdir => 'users' }, | |
173 | { subdir => 'groups' }, | |
174 | ]; | |
175 | }}); | |
176 | ||
177 | __PACKAGE__->register_method ({ | |
178 | name => 'read_config', | |
179 | path => '{profile}/config', | |
180 | method => 'GET', | |
181 | description => "Get LDAP profile configuration.", | |
182 | proxyto => 'master', | |
183 | permissions => { check => [ 'admin', 'audit' ] }, | |
184 | parameters => { | |
185 | additionalProperties => 0, | |
186 | properties => { | |
187 | profile => { | |
188 | description => "Profile ID.", | |
189 | type => 'string', format => 'pve-configid', | |
190 | }, | |
191 | }, | |
192 | }, | |
193 | returns => {}, | |
194 | code => sub { | |
195 | my ($param) = @_; | |
196 | ||
197 | my $cfg = PMG::LDAPConfig->new(); | |
198 | ||
199 | my $profile = $param->{profile}; | |
200 | ||
201 | my $data = $cfg->{ids}->{$profile}; | |
202 | die "LDAP profile '$profile' does not exist\n" if !$data; | |
203 | ||
204 | # we do not want to get the password over the api | |
205 | delete $data->{bindpw}; | |
206 | ||
207 | $data->{digest} = $cfg->{digest}; | |
208 | ||
209 | return $data; | |
210 | }}); | |
211 | ||
212 | __PACKAGE__->register_method ({ | |
213 | name => 'update_config', | |
214 | path => '{profile}/config', | |
215 | method => 'PUT', | |
216 | description => "Update LDAP profile settings.", | |
217 | permissions => { check => [ 'admin' ] }, | |
218 | protected => 1, | |
219 | proxyto => 'master', | |
220 | parameters => PMG::LDAPConfig->updateSchema(), | |
221 | returns => { type => 'null' }, | |
222 | code => sub { | |
223 | my ($param) = @_; | |
224 | ||
225 | my $code = sub { | |
226 | ||
227 | my $cfg = PMG::LDAPConfig->new(); | |
228 | my $ids = $cfg->{ids}; | |
229 | ||
230 | my $digest = extract_param($param, 'digest'); | |
231 | PVE::SectionConfig::assert_if_modified($cfg, $digest); | |
232 | ||
233 | my $profile = extract_param($param, 'profile'); | |
234 | ||
235 | die "LDAP profile '$profile' does not exist\n" | |
236 | if !$ids->{$profile}; | |
237 | ||
238 | my $delete_str = extract_param($param, 'delete'); | |
239 | die "no options specified\n" | |
240 | if !$delete_str && !scalar(keys %$param); | |
241 | ||
242 | foreach my $opt (PVE::Tools::split_list($delete_str)) { | |
243 | delete $ids->{$profile}->{$opt}; | |
244 | } | |
245 | ||
246 | my $config = PMG::LDAPConfig->check_config($profile, $param, 0, 1); | |
247 | ||
248 | foreach my $p (keys %$config) { | |
249 | $ids->{$profile}->{$p} = $config->{$p}; | |
250 | } | |
251 | ||
252 | $forced_ldap_sync->($profile, $ids->{$profile}) | |
253 | if !$config->{disable}; | |
254 | ||
255 | $cfg->write(); | |
256 | }; | |
257 | ||
258 | PMG::LDAPConfig::lock_config($code, "update LDAP profile failed"); | |
259 | ||
260 | return undef; | |
261 | }}); | |
262 | ||
263 | __PACKAGE__->register_method ({ | |
264 | name => 'sync_profile', | |
265 | path => '{profile}/sync', | |
266 | method => 'POST', | |
267 | description => "Synchronice LDAP users to local database.", | |
268 | permissions => { check => [ 'admin' ] }, | |
269 | protected => 1, | |
270 | proxyto => 'master', | |
271 | parameters => { | |
272 | additionalProperties => 0, | |
273 | properties => { | |
274 | profile => { | |
275 | description => "Profile ID.", | |
276 | type => 'string', format => 'pve-configid', | |
277 | }, | |
278 | }, | |
279 | }, | |
280 | returns => { type => 'null' }, | |
281 | code => sub { | |
282 | my ($param) = @_; | |
283 | ||
284 | my $cfg = PMG::LDAPConfig->new(); | |
285 | my $ids = $cfg->{ids}; | |
286 | ||
287 | my $profile = extract_param($param, 'profile'); | |
288 | ||
289 | die "LDAP profile '$profile' does not exist\n" | |
290 | if !$ids->{$profile}; | |
291 | ||
292 | my $config = $ids->{$profile}; | |
293 | ||
294 | if ($config->{disable}) { | |
295 | die "LDAP profile '$profile' is disabled\n"; | |
296 | } else { | |
297 | $forced_ldap_sync->($profile, $config) | |
298 | } | |
299 | ||
300 | return undef; | |
301 | }}); | |
302 | ||
303 | __PACKAGE__->register_method ({ | |
304 | name => 'delete', | |
305 | path => '{profile}', | |
306 | method => 'DELETE', | |
307 | description => "Delete an LDAP profile", | |
308 | permissions => { check => [ 'admin' ] }, | |
309 | protected => 1, | |
310 | proxyto => 'master', | |
311 | parameters => { | |
312 | additionalProperties => 0, | |
313 | properties => { | |
314 | profile => { | |
315 | description => "Profile ID.", | |
316 | type => 'string', format => 'pve-configid', | |
317 | }, | |
318 | } | |
319 | }, | |
320 | returns => { type => 'null' }, | |
321 | code => sub { | |
322 | my ($param) = @_; | |
323 | ||
324 | my $code = sub { | |
325 | ||
326 | my $cfg = PMG::LDAPConfig->new(); | |
327 | my $ids = $cfg->{ids}; | |
328 | ||
329 | my $profile = $param->{profile}; | |
330 | ||
331 | die "LDAP profile '$profile' does not exist\n" | |
332 | if !$ids->{$profile}; | |
333 | ||
334 | delete $ids->{$profile}; | |
335 | ||
336 | PMG::LDAPCache->delete($profile); | |
337 | ||
338 | $cfg->write(); | |
339 | }; | |
340 | ||
341 | PMG::LDAPConfig::lock_config($code, "delete LDAP profile failed"); | |
342 | ||
343 | return undef; | |
344 | }}); | |
345 | ||
346 | __PACKAGE__->register_method ({ | |
347 | name => 'profile_list_users', | |
348 | path => '{profile}/users', | |
349 | method => 'GET', | |
350 | description => "List LDAP users.", | |
351 | permissions => { check => [ 'admin', 'audit' ] }, | |
352 | protected => 1, | |
353 | proxyto => 'master', | |
354 | parameters => { | |
355 | additionalProperties => 0, | |
356 | properties => { | |
357 | profile => { | |
358 | description => "Profile ID.", | |
359 | type => 'string', format => 'pve-configid', | |
360 | }, | |
361 | }, | |
362 | }, | |
363 | returns => { | |
364 | type => 'array', | |
365 | items => { | |
366 | type => "object", | |
367 | properties => { | |
368 | dn => { type => 'string'}, | |
369 | account => { type => 'string'}, | |
370 | pmail => { type => 'string'}, | |
371 | }, | |
372 | }, | |
373 | links => [ { rel => 'child', href => "{pmail}" } ], | |
374 | }, | |
375 | code => sub { | |
376 | my ($param) = @_; | |
377 | ||
378 | my $cfg = PMG::LDAPConfig->new(); | |
379 | my $ids = $cfg->{ids}; | |
380 | ||
381 | my $profile = $param->{profile}; | |
382 | ||
383 | die "LDAP profile '$profile' does not exist\n" | |
384 | if !$ids->{$profile}; | |
385 | ||
386 | my $config = $ids->{$profile}; | |
387 | ||
388 | return [] if $config->{disable}; | |
389 | ||
390 | my $ldapcache = PMG::LDAPCache->new( | |
391 | id => $profile, syncmode => 1, %$config); | |
392 | ||
393 | return $ldapcache->list_users(); | |
394 | }}); | |
395 | ||
396 | __PACKAGE__->register_method ({ | |
397 | name => 'address_list', | |
398 | path => '{profile}/users/{email}', | |
399 | method => 'GET', | |
400 | description => "Get all email addresses for the specified user.", | |
401 | permissions => { check => [ 'admin', 'audit' ] }, | |
402 | protected => 1, | |
403 | proxyto => 'master', | |
404 | parameters => { | |
405 | additionalProperties => 0, | |
406 | properties => { | |
407 | profile => { | |
408 | description => "Profile ID.", | |
409 | type => 'string', format => 'pve-configid', | |
410 | }, | |
411 | email => get_standard_option('pmg-email-address', { | |
412 | description => "Email address.", | |
413 | }), | |
414 | }, | |
415 | }, | |
416 | returns => { | |
417 | type => 'array', | |
418 | items => { | |
419 | type => "object", | |
420 | properties => { | |
421 | primary => { type => 'boolean'}, | |
422 | email => { type => 'string'}, | |
423 | }, | |
424 | }, | |
425 | }, | |
426 | code => sub { | |
427 | my ($param) = @_; | |
428 | ||
429 | my $cfg = PMG::LDAPConfig->new(); | |
430 | my $ids = $cfg->{ids}; | |
431 | ||
432 | my $profile = $param->{profile}; | |
433 | ||
434 | die "LDAP profile '$profile' does not exist\n" | |
435 | if !$ids->{$profile}; | |
436 | ||
437 | my $config = $ids->{$profile}; | |
438 | ||
439 | die "profile '$profile' is disabled\n" if $config->{disable}; | |
440 | ||
441 | my $ldapcache = PMG::LDAPCache->new( | |
442 | id => $profile, syncmode => 1, %$config); | |
443 | ||
444 | my $res = $ldapcache->list_addresses($param->{email}); | |
445 | ||
446 | die "unable to find ldap user with email address '$param->{email}'\n" | |
447 | if !$res; | |
448 | ||
449 | return $res; | |
450 | ||
451 | }}); | |
452 | ||
453 | __PACKAGE__->register_method ({ | |
454 | name => 'profile_list_groups', | |
455 | path => '{profile}/groups', | |
456 | method => 'GET', | |
457 | description => "List LDAP groups.", | |
458 | permissions => { check => [ 'admin', 'audit' ] }, | |
459 | protected => 1, | |
460 | proxyto => 'master', | |
461 | parameters => { | |
462 | additionalProperties => 0, | |
463 | properties => { | |
464 | profile => { | |
465 | description => "Profile ID.", | |
466 | type => 'string', format => 'pve-configid', | |
467 | }, | |
468 | }, | |
469 | }, | |
470 | returns => { | |
471 | type => 'array', | |
472 | items => { | |
473 | type => "object", | |
474 | properties => { | |
475 | dn => { type => 'string'}, | |
476 | gid => { type => 'number' }, | |
477 | }, | |
478 | }, | |
479 | links => [ { rel => 'child', href => "{gid}" } ], | |
480 | }, | |
481 | code => sub { | |
482 | my ($param) = @_; | |
483 | ||
484 | my $cfg = PMG::LDAPConfig->new(); | |
485 | my $ids = $cfg->{ids}; | |
486 | ||
487 | my $profile = $param->{profile}; | |
488 | ||
489 | die "LDAP profile '$profile' does not exist\n" | |
490 | if !$ids->{$profile}; | |
491 | ||
492 | my $config = $ids->{$profile}; | |
493 | ||
494 | return [] if $config->{disable}; | |
495 | ||
496 | my $ldapcache = PMG::LDAPCache->new( | |
497 | id => $profile, syncmode => 1, %$config); | |
498 | ||
499 | return $ldapcache->list_groups(); | |
500 | }}); | |
501 | ||
502 | __PACKAGE__->register_method ({ | |
503 | name => 'profile_list_group_members', | |
504 | path => '{profile}/groups/{gid}', | |
505 | method => 'GET', | |
506 | description => "List LDAP group members.", | |
507 | permissions => { check => [ 'admin', 'audit' ] }, | |
508 | protected => 1, | |
509 | proxyto => 'master', | |
510 | parameters => { | |
511 | additionalProperties => 0, | |
512 | properties => { | |
513 | profile => { | |
514 | description => "Profile ID.", | |
515 | type => 'string', format => 'pve-configid', | |
516 | }, | |
517 | gid => { | |
518 | description => "Group ID", | |
519 | type => 'number', | |
520 | }, | |
521 | }, | |
522 | }, | |
523 | returns => { | |
524 | type => 'array', | |
525 | items => { | |
526 | type => "object", | |
527 | properties => { | |
528 | dn => { type => 'string'}, | |
529 | account => { type => 'string' }, | |
530 | pmail => { type => 'string' }, | |
531 | }, | |
532 | }, | |
533 | }, | |
534 | code => sub { | |
535 | my ($param) = @_; | |
536 | ||
537 | my $cfg = PMG::LDAPConfig->new(); | |
538 | my $ids = $cfg->{ids}; | |
539 | ||
540 | my $profile = $param->{profile}; | |
541 | ||
542 | die "LDAP profile '$profile' does not exist\n" | |
543 | if !$ids->{$profile}; | |
544 | ||
545 | my $config = $ids->{$profile}; | |
546 | ||
547 | return [] if $config->{disable}; | |
548 | ||
549 | my $ldapcache = PMG::LDAPCache->new( | |
550 | id => $profile, syncmode => 1, %$config); | |
551 | ||
552 | return $ldapcache->list_users($param->{gid}); | |
553 | }}); | |
554 | ||
555 | 1; |