]>
Commit | Line | Data |
---|---|---|
be6d6ffc DM |
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; | |
2fdba966 | 13 | use PVE::INotify; |
be6d6ffc DM |
14 | |
15 | use PMG::LDAPConfig; | |
b183e761 | 16 | use PMG::LDAPCache; |
21fb7eb2 | 17 | use PMG::LDAPSet; |
be6d6ffc DM |
18 | |
19 | use base qw(PVE::RESTHandler); | |
20 | ||
21 | __PACKAGE__->register_method ({ | |
22 | name => 'index', | |
23 | path => '', | |
24 | method => 'GET', | |
c2ef4490 | 25 | description => "List configured LDAP profiles.", |
be6d6ffc | 26 | proxyto => 'master', |
6aa82c72 | 27 | permissions => { check => [ 'admin', 'audit' ] }, |
be6d6ffc DM |
28 | parameters => { |
29 | additionalProperties => 0, | |
30 | properties => {}, | |
31 | }, | |
32 | returns => { | |
33 | type => 'array', | |
34 | items => { | |
35 | type => "object", | |
36 | properties => { | |
c2ef4490 | 37 | profile => { type => 'string'}, |
9722e9bf | 38 | disable => { type => 'boolean' }, |
be6d6ffc | 39 | server1 => { type => 'string'}, |
bfed5777 DM |
40 | server2 => { type => 'string', optional => 1}, |
41 | comment => { type => 'string', optional => 1}, | |
dc7a750c DM |
42 | gcount => { type => 'integer', optional => 1}, |
43 | mcount => { type => 'integer', optional => 1}, | |
44 | ucount => { type => 'integer', optional => 1}, | |
be6d6ffc DM |
45 | mode => { type => 'string'}, |
46 | }, | |
47 | }, | |
c2ef4490 | 48 | links => [ { rel => 'child', href => "{profile}" } ], |
be6d6ffc DM |
49 | }, |
50 | code => sub { | |
51 | my ($param) = @_; | |
52 | ||
d8fff1a9 | 53 | my $ldap_cfg = PMG::LDAPConfig->new(); |
2fdba966 | 54 | |
21fb7eb2 DM |
55 | my $ldap_set = PMG::LDAPSet->new_from_ldap_cfg($ldap_cfg, 1); |
56 | ||
be6d6ffc DM |
57 | my $res = []; |
58 | ||
59 | if (defined($ldap_cfg)) { | |
c2ef4490 DM |
60 | foreach my $profile (keys %{$ldap_cfg->{ids}}) { |
61 | my $d = $ldap_cfg->{ids}->{$profile}; | |
bfed5777 | 62 | my $entry = { |
c2ef4490 | 63 | profile => $profile, |
9722e9bf | 64 | disable => $d->{disable} ? 1 : 0, |
be6d6ffc DM |
65 | server1 => $d->{server1}, |
66 | mode => $d->{mode} // 'ldap', | |
67 | }; | |
bfed5777 DM |
68 | $entry->{server2} = $d->{server2} if defined($d->{server2}); |
69 | $entry->{comment} = $d->{comment} if defined($d->{comment}); | |
21fb7eb2 | 70 | |
c2ef4490 | 71 | if (my $d = $ldap_set->{$profile}) { |
94a59215 | 72 | foreach my $k (qw(gcount mcount ucount)) { |
21fb7eb2 | 73 | my $v = $d->{$k}; |
94a59215 | 74 | $entry->{$k} = $v if defined($v); |
21fb7eb2 DM |
75 | } |
76 | } | |
77 | ||
bfed5777 | 78 | push @$res, $entry; |
be6d6ffc DM |
79 | } |
80 | } | |
2fdba966 | 81 | |
be6d6ffc DM |
82 | return $res; |
83 | }}); | |
84 | ||
c88aa2fe | 85 | my $forced_ldap_sync = sub { |
c2ef4490 | 86 | my ($profile, $config) = @_; |
c88aa2fe DM |
87 | |
88 | my $ldapcache = PMG::LDAPCache->new( | |
c2ef4490 | 89 | id => $profile, syncmode => 2, %$config); |
c88aa2fe DM |
90 | |
91 | die $ldapcache->{errors} if $ldapcache->{errors}; | |
92 | ||
93 | die "unable to find valid email addresses\n" | |
94 | if !$ldapcache->{mcount}; | |
95 | }; | |
2fdba966 DM |
96 | |
97 | __PACKAGE__->register_method ({ | |
98 | name => 'create', | |
99 | path => '', | |
100 | method => 'POST', | |
101 | proxyto => 'master', | |
bdbc2bc5 | 102 | permissions => { check => [ 'admin' ] }, |
2fdba966 | 103 | protected => 1, |
c2ef4490 | 104 | description => "Add LDAP profile.", |
2fdba966 DM |
105 | parameters => PMG::LDAPConfig->createSchema(1), |
106 | returns => { type => 'null' }, | |
107 | code => sub { | |
108 | my ($param) = @_; | |
109 | ||
110 | my $code = sub { | |
111 | ||
d8fff1a9 | 112 | my $cfg = PMG::LDAPConfig->new(); |
98afc5ae DM |
113 | |
114 | $cfg->{ids} //= {}; | |
115 | ||
2fdba966 DM |
116 | my $ids = $cfg->{ids}; |
117 | ||
c2ef4490 | 118 | my $profile = extract_param($param, 'profile'); |
2fdba966 DM |
119 | my $type = $param->{type}; |
120 | ||
c2ef4490 DM |
121 | die "LDAP profile '$profile' already exists\n" |
122 | if $ids->{$profile}; | |
2fdba966 | 123 | |
c2ef4490 | 124 | my $config = PMG::LDAPConfig->check_config($profile, $param, 1, 1); |
2fdba966 | 125 | |
c2ef4490 | 126 | $ids->{$profile} = $config; |
2fdba966 | 127 | |
c2ef4490 | 128 | $forced_ldap_sync->($profile, $config) |
c88aa2fe | 129 | if !$config->{disable}; |
b183e761 | 130 | |
d8fff1a9 | 131 | $cfg->write(); |
2fdba966 DM |
132 | }; |
133 | ||
c2ef4490 | 134 | PMG::LDAPConfig::lock_config($code, "add LDAP profile failed"); |
2fdba966 DM |
135 | |
136 | return undef; | |
137 | }}); | |
138 | ||
139 | __PACKAGE__->register_method ({ | |
03f6ea0e | 140 | name => 'profile_index', |
c2ef4490 | 141 | path => '{profile}', |
2fdba966 | 142 | method => 'GET', |
03f6ea0e | 143 | description => "Directory index", |
bdbc2bc5 DM |
144 | permissions => { |
145 | user => 'all', | |
146 | }, | |
03f6ea0e DM |
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' }, | |
c2670481 DM |
172 | { subdir => 'users' }, |
173 | { subdir => 'groups' }, | |
03f6ea0e DM |
174 | ]; |
175 | }}); | |
176 | ||
177 | __PACKAGE__->register_method ({ | |
178 | name => 'read_config', | |
179 | path => '{profile}/config', | |
180 | method => 'GET', | |
c2ef4490 | 181 | description => "Get LDAP profile configuration.", |
2fdba966 | 182 | proxyto => 'master', |
6aa82c72 | 183 | permissions => { check => [ 'admin', 'audit' ] }, |
2fdba966 DM |
184 | parameters => { |
185 | additionalProperties => 0, | |
186 | properties => { | |
c2ef4490 | 187 | profile => { |
03f6ea0e | 188 | description => "Profile ID.", |
2fdba966 DM |
189 | type => 'string', format => 'pve-configid', |
190 | }, | |
191 | }, | |
192 | }, | |
193 | returns => {}, | |
194 | code => sub { | |
195 | my ($param) = @_; | |
196 | ||
d8fff1a9 | 197 | my $cfg = PMG::LDAPConfig->new(); |
2fdba966 | 198 | |
c2ef4490 | 199 | my $profile = $param->{profile}; |
2fdba966 | 200 | |
c2ef4490 DM |
201 | my $data = $cfg->{ids}->{$profile}; |
202 | die "LDAP profile '$profile' does not exist\n" if !$data; | |
2fdba966 | 203 | |
10861aa8 DC |
204 | # we do not want to get the password over the api |
205 | delete $data->{bindpw}; | |
206 | ||
2fdba966 DM |
207 | $data->{digest} = $cfg->{digest}; |
208 | ||
209 | return $data; | |
210 | }}); | |
211 | ||
212 | __PACKAGE__->register_method ({ | |
03f6ea0e DM |
213 | name => 'update_config', |
214 | path => '{profile}/config', | |
2fdba966 | 215 | method => 'PUT', |
c2ef4490 | 216 | description => "Update LDAP profile settings.", |
bdbc2bc5 | 217 | permissions => { check => [ 'admin' ] }, |
2fdba966 DM |
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 | ||
d8fff1a9 | 227 | my $cfg = PMG::LDAPConfig->new(); |
2fdba966 DM |
228 | my $ids = $cfg->{ids}; |
229 | ||
230 | my $digest = extract_param($param, 'digest'); | |
231 | PVE::SectionConfig::assert_if_modified($cfg, $digest); | |
232 | ||
c2ef4490 | 233 | my $profile = extract_param($param, 'profile'); |
2fdba966 | 234 | |
c2ef4490 DM |
235 | die "LDAP profile '$profile' does not exist\n" |
236 | if !$ids->{$profile}; | |
2fdba966 DM |
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)) { | |
c2ef4490 | 243 | delete $ids->{$profile}->{$opt}; |
2fdba966 DM |
244 | } |
245 | ||
c2ef4490 | 246 | my $config = PMG::LDAPConfig->check_config($profile, $param, 0, 1); |
2fdba966 DM |
247 | |
248 | foreach my $p (keys %$config) { | |
c2ef4490 | 249 | $ids->{$profile}->{$p} = $config->{$p}; |
2fdba966 DM |
250 | } |
251 | ||
6804695f | 252 | $forced_ldap_sync->($profile, $ids->{$profile}) |
c88aa2fe | 253 | if !$config->{disable}; |
b183e761 | 254 | |
d8fff1a9 | 255 | $cfg->write(); |
2fdba966 DM |
256 | }; |
257 | ||
c2ef4490 | 258 | PMG::LDAPConfig::lock_config($code, "update LDAP profile failed"); |
2fdba966 DM |
259 | |
260 | return undef; | |
261 | }}); | |
262 | ||
5a76fa30 | 263 | __PACKAGE__->register_method ({ |
03f6ea0e DM |
264 | name => 'sync_profile', |
265 | path => '{profile}/sync', | |
5a76fa30 DM |
266 | method => 'POST', |
267 | description => "Synchronice LDAP users to local database.", | |
bdbc2bc5 | 268 | permissions => { check => [ 'admin' ] }, |
5a76fa30 DM |
269 | protected => 1, |
270 | proxyto => 'master', | |
271 | parameters => { | |
272 | additionalProperties => 0, | |
273 | properties => { | |
c2ef4490 DM |
274 | profile => { |
275 | description => "Profile ID.", | |
5a76fa30 DM |
276 | type => 'string', format => 'pve-configid', |
277 | }, | |
278 | }, | |
279 | }, | |
280 | returns => { type => 'null' }, | |
281 | code => sub { | |
282 | my ($param) = @_; | |
283 | ||
d8fff1a9 | 284 | my $cfg = PMG::LDAPConfig->new(); |
5a76fa30 DM |
285 | my $ids = $cfg->{ids}; |
286 | ||
c2ef4490 | 287 | my $profile = extract_param($param, 'profile'); |
5a76fa30 | 288 | |
c2ef4490 DM |
289 | die "LDAP profile '$profile' does not exist\n" |
290 | if !$ids->{$profile}; | |
5a76fa30 | 291 | |
c2ef4490 | 292 | my $config = $ids->{$profile}; |
5a76fa30 DM |
293 | |
294 | if ($config->{disable}) { | |
c2ef4490 | 295 | die "LDAP profile '$profile' is disabled\n"; |
5a76fa30 | 296 | } else { |
c2ef4490 | 297 | $forced_ldap_sync->($profile, $config) |
5a76fa30 DM |
298 | } |
299 | ||
300 | return undef; | |
301 | }}); | |
302 | ||
2fdba966 DM |
303 | __PACKAGE__->register_method ({ |
304 | name => 'delete', | |
c2ef4490 | 305 | path => '{profile}', |
2fdba966 | 306 | method => 'DELETE', |
c2ef4490 | 307 | description => "Delete an LDAP profile", |
bdbc2bc5 | 308 | permissions => { check => [ 'admin' ] }, |
2fdba966 DM |
309 | protected => 1, |
310 | proxyto => 'master', | |
311 | parameters => { | |
312 | additionalProperties => 0, | |
313 | properties => { | |
c2ef4490 DM |
314 | profile => { |
315 | description => "Profile ID.", | |
2fdba966 DM |
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 | ||
d8fff1a9 | 326 | my $cfg = PMG::LDAPConfig->new(); |
2fdba966 DM |
327 | my $ids = $cfg->{ids}; |
328 | ||
c2ef4490 | 329 | my $profile = $param->{profile}; |
2fdba966 | 330 | |
c2ef4490 DM |
331 | die "LDAP profile '$profile' does not exist\n" |
332 | if !$ids->{$profile}; | |
2fdba966 | 333 | |
c2ef4490 | 334 | delete $ids->{$profile}; |
2fdba966 | 335 | |
c2ef4490 | 336 | PMG::LDAPCache->delete($profile); |
200f41ef | 337 | |
d8fff1a9 | 338 | $cfg->write(); |
2fdba966 DM |
339 | }; |
340 | ||
c2ef4490 | 341 | PMG::LDAPConfig::lock_config($code, "delete LDAP profile failed"); |
2fdba966 DM |
342 | |
343 | return undef; | |
344 | }}); | |
345 | ||
c2670481 DM |
346 | __PACKAGE__->register_method ({ |
347 | name => 'profile_list_users', | |
348 | path => '{profile}/users', | |
349 | method => 'GET', | |
350 | description => "List LDAP users.", | |
6aa82c72 | 351 | permissions => { check => [ 'admin', 'audit' ] }, |
c2670481 DM |
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 | }, | |
55e05031 | 373 | links => [ { rel => 'child', href => "{pmail}" } ], |
c2670481 DM |
374 | }, |
375 | code => sub { | |
376 | my ($param) = @_; | |
377 | ||
d8fff1a9 | 378 | my $cfg = PMG::LDAPConfig->new(); |
c2670481 DM |
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.", | |
6aa82c72 | 401 | permissions => { check => [ 'admin', 'audit' ] }, |
c2670481 DM |
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 | }, | |
49209fdc | 411 | email => get_standard_option('pmg-email-address', { |
c2670481 | 412 | description => "Email address.", |
49209fdc | 413 | }), |
c2670481 DM |
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 | ||
d8fff1a9 | 429 | my $cfg = PMG::LDAPConfig->new(); |
c2670481 DM |
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.", | |
6aa82c72 | 458 | permissions => { check => [ 'admin', 'audit' ] }, |
c2670481 DM |
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'}, | |
1d8a50bb | 476 | gid => { type => 'number' }, |
c2670481 DM |
477 | }, |
478 | }, | |
1d8a50bb | 479 | links => [ { rel => 'child', href => "{gid}" } ], |
c2670481 DM |
480 | }, |
481 | code => sub { | |
482 | my ($param) = @_; | |
483 | ||
d8fff1a9 | 484 | my $cfg = PMG::LDAPConfig->new(); |
c2670481 DM |
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 | ||
1d8a50bb DC |
502 | __PACKAGE__->register_method ({ |
503 | name => 'profile_list_group_members', | |
504 | path => '{profile}/groups/{gid}', | |
505 | method => 'GET', | |
506 | description => "List LDAP group members.", | |
6aa82c72 | 507 | permissions => { check => [ 'admin', 'audit' ] }, |
1d8a50bb DC |
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 | ||
be6d6ffc | 555 | 1; |