1 package PVE
::API2
::Domains
;
5 use PVE
::Exception
qw(raise_param_exc);
6 use PVE
::Tools
qw(extract_param);
7 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);
8 use PVE
::AccessControl
;
9 use PVE
::JSONSchema
qw(get_standard_option);
13 use PVE
::Auth
::Plugin
;
15 my $domainconfigfile = "domains.cfg";
17 use base
qw(PVE::RESTHandler);
19 __PACKAGE__-
>register_method ({
23 description
=> "Authentication domain index.",
25 description
=> "Anyone can access that, because we need that list for the login box (before the user is authenticated).",
29 additionalProperties
=> 0,
37 realm
=> { type
=> 'string' },
38 type
=> { type
=> 'string' },
40 description
=> "Two-factor authentication provider.",
42 enum
=> [ 'yubico', 'oath' ],
46 description
=> "A comment. The GUI use this text when you select a domain (Realm) on the login window.",
52 links
=> [ { rel
=> 'child', href
=> "{realm}" } ],
59 my $cfg = cfs_read_file
($domainconfigfile);
60 my $ids = $cfg->{ids
};
62 foreach my $realm (keys %$ids) {
63 my $d = $ids->{$realm};
64 my $entry = { realm
=> $realm, type
=> $d->{type
} };
65 $entry->{comment
} = $d->{comment
} if $d->{comment
};
66 $entry->{default} = 1 if $d->{default};
67 if ($d->{tfa
} && (my $tfa_cfg = PVE
::Auth
::Plugin
::parse_tfa_config
($d->{tfa
}))) {
68 $entry->{tfa
} = $tfa_cfg->{type
};
76 __PACKAGE__-
>register_method ({
82 check
=> ['perm', '/access/realm', ['Realm.Allocate']],
84 description
=> "Add an authentication server.",
85 parameters
=> PVE
::Auth
::Plugin-
>createSchema(),
86 returns
=> { type
=> 'null' },
90 PVE
::Auth
::Plugin
::lock_domain_config
(
93 my $cfg = cfs_read_file
($domainconfigfile);
94 my $ids = $cfg->{ids
};
96 my $realm = extract_param
($param, 'realm');
97 my $type = $param->{type
};
99 die "domain '$realm' already exists\n"
102 die "unable to use reserved name '$realm'\n"
103 if ($realm eq 'pam' || $realm eq 'pve');
105 die "unable to create builtin type '$type'\n"
106 if ($type eq 'pam' || $type eq 'pve');
108 my $plugin = PVE
::Auth
::Plugin-
>lookup($type);
109 my $config = $plugin->check_config($realm, $param, 1, 1);
111 if ($config->{default}) {
112 foreach my $r (keys %$ids) {
113 delete $ids->{$r}->{default};
117 $ids->{$realm} = $config;
119 cfs_write_file
($domainconfigfile, $cfg);
120 }, "add auth server failed");
125 __PACKAGE__-
>register_method ({
130 check
=> ['perm', '/access/realm', ['Realm.Allocate']],
132 description
=> "Update authentication server settings.",
134 parameters
=> PVE
::Auth
::Plugin-
>updateSchema(),
135 returns
=> { type
=> 'null' },
139 PVE
::Auth
::Plugin
::lock_domain_config
(
142 my $cfg = cfs_read_file
($domainconfigfile);
143 my $ids = $cfg->{ids
};
145 my $digest = extract_param
($param, 'digest');
146 PVE
::SectionConfig
::assert_if_modified
($cfg, $digest);
148 my $realm = extract_param
($param, 'realm');
150 die "domain '$realm' does not exist\n"
153 my $delete_str = extract_param
($param, 'delete');
154 die "no options specified\n" if !$delete_str && !scalar(keys %$param);
156 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
157 delete $ids->{$realm}->{$opt};
160 my $plugin = PVE
::Auth
::Plugin-
>lookup($ids->{$realm}->{type
});
161 my $config = $plugin->check_config($realm, $param, 0, 1);
163 if ($config->{default}) {
164 foreach my $r (keys %$ids) {
165 delete $ids->{$r}->{default};
169 foreach my $p (keys %$config) {
170 $ids->{$realm}->{$p} = $config->{$p};
173 cfs_write_file
($domainconfigfile, $cfg);
174 }, "update auth server failed");
179 # fixme: return format!
180 __PACKAGE__-
>register_method ({
184 description
=> "Get auth server configuration.",
186 check
=> ['perm', '/access/realm', ['Realm.Allocate', 'Sys.Audit'], any
=> 1],
189 additionalProperties
=> 0,
191 realm
=> get_standard_option
('realm'),
198 my $cfg = cfs_read_file
($domainconfigfile);
200 my $realm = $param->{realm
};
202 my $data = $cfg->{ids
}->{$realm};
203 die "domain '$realm' does not exist\n" if !$data;
205 $data->{digest
} = $cfg->{digest
};
211 __PACKAGE__-
>register_method ({
216 check
=> ['perm', '/access/realm', ['Realm.Allocate']],
218 description
=> "Delete an authentication server.",
221 additionalProperties
=> 0,
223 realm
=> get_standard_option
('realm'),
226 returns
=> { type
=> 'null' },
230 PVE
::Auth
::Plugin
::lock_domain_config
(
233 my $cfg = cfs_read_file
($domainconfigfile);
234 my $ids = $cfg->{ids
};
236 my $realm = $param->{realm
};
238 die "domain '$realm' does not exist\n" if !$ids->{$realm};
240 delete $ids->{$realm};
242 cfs_write_file
($domainconfigfile, $cfg);
243 }, "delete auth server failed");
248 __PACKAGE__-
>register_method ({
250 path
=> '{realm}/sync',
253 description
=> "'Realm.AllocateUser' on '/access/realm/<realm>' and "
254 ." 'User.Modify' permissions to '/access/groups/'.",
256 [ 'userid-param', 'Realm.AllocateUser' ],
257 [ 'userid-group', ['User.Modify'] ],
260 description
=> "Syncs users and/or groups from the configured LDAP to user.cfg."
261 ." NOTE: Synced groups will have the name 'name-\$realm', so make sure"
262 ." those groups do not exist to prevent overwriting.",
265 additionalProperties
=> 0,
267 realm
=> get_standard_option
('realm'),
269 description
=> "Select what to sync.",
271 enum
=> [qw(users groups both)],
274 description
=> "If set, uses the LDAP Directory as source of truth, ".
275 "deleting all information not contained there. ".
276 "Otherwise only syncs information set explicitly.",
280 description
=> "Enable newly synced users.",
284 description
=> "Remove ACLs for users/groups that were removed from the config.",
290 description
=> 'Worker Task-UPID',
296 my $rpcenv = PVE
::RPCEnvironment
::get
();
297 my $authuser = $rpcenv->get_user();
299 my $realm = $param->{realm
};
300 my $cfg = cfs_read_file
($domainconfigfile);
301 my $realmconfig = $cfg->{ids
}->{$realm};
303 raise_param_exc
({ 'realm' => 'Realm does not exist.' }) if !defined($realmconfig);
304 my $type = $realmconfig->{type
};
306 if ($type ne 'ldap' && $type ne 'ad') {
307 die "Cannot sync realm type '$type'! Only LDAP/AD realms can be synced.\n";
311 my $scope = $param->{scope
};
312 my $whatstring = $scope eq 'both' ?
"users and groups" : $scope;
314 my $plugin = PVE
::Auth
::Plugin-
>lookup($type);
317 print "starting sync for realm $realm\n";
319 my ($synced_users, $dnmap) = $plugin->get_users($realmconfig, $realm);
320 my $synced_groups = {};
321 if ($scope eq 'groups' || $scope eq 'both') {
322 $synced_groups = $plugin->get_groups($realmconfig, $realm, $dnmap);
325 PVE
::AccessControl
::lock_user_config
(sub {
326 my $usercfg = cfs_read_file
("user.cfg");
327 print "got data from server, updating $whatstring\n";
330 print "syncing users\n";
331 my $oldusers = $usercfg->{users
};
336 if ($param->{full
}) {
337 print "full sync, deleting existing users first\n";
338 foreach my $userid (keys %$oldusers) {
339 next if $userid !~ m/\@$realm$/;
340 # we save the old tokens
341 $oldtokens->{$userid} = $oldusers->{$userid}->{tokens
};
342 $oldenabled->{$userid} = $oldusers->{$userid}->{enable
} // 0;
343 delete $oldusers->{$userid};
344 PVE
::AccessControl
::delete_user_acl
($userid, $usercfg)
345 if $param->{purge
} && !$users->{$userid};
346 print "removed user '$userid'\n";
350 foreach my $userid (keys %$users) {
351 my $user = $users->{$userid};
352 if (!defined($oldusers->{$userid})) {
353 $oldusers->{$userid} = $user;
355 if (defined($oldenabled->{$userid})) {
356 $oldusers->{$userid}->{enable
} = $oldenabled->{$userid};
357 } elsif ($param->{enable
}) {
358 $oldusers->{$userid}->{enable
} = 1;
361 if (defined($oldtokens->{$userid})) {
362 $oldusers->{$userid}->{tokens
} = $oldtokens->{$userid};
365 print "added user '$userid'\n";
367 my $olduser = $oldusers->{$userid};
368 foreach my $attr (keys %$user) {
369 $olduser->{$attr} = $user->{$attr};
371 print "updated user '$userid'\n";
377 print "syncing groups\n";
378 my $oldgroups = $usercfg->{groups
};
380 if ($param->{full
}) {
381 print "full sync, deleting existing groups first\n";
382 foreach my $groupid (keys %$oldgroups) {
383 next if $groupid !~ m/\-$realm$/;
384 delete $oldgroups->{$groupid};
385 PVE
::AccessControl
::delete_group_acl
($groupid, $usercfg)
386 if $param->{purge
} && !$groups->{$groupid};
387 print "removed group '$groupid'\n";
391 foreach my $groupid (keys %$groups) {
392 my $group = $groups->{$groupid};
393 if (!defined($oldgroups->{$groupid})) {
394 $oldgroups->{$groupid} = $group;
395 print "added group '$groupid'\n";
397 my $oldgroup = $oldgroups->{$groupid};
398 foreach my $attr (keys %$group) {
399 $oldgroup->{$attr} = $group->{$attr};
401 print "updated group '$groupid'\n";
406 cfs_write_file
("user.cfg", $usercfg);
407 print "successfully updated $whatstring configuration\n";
408 }, "syncing $whatstring failed");
411 return $rpcenv->fork_worker('ldapsync', $realm, $authuser, $worker);