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 my $update_users = sub {
249 my ($usercfg, $realm, $synced_users, $opts) = @_;
251 print "syncing users\n";
252 $usercfg->{users
} = {} if !defined($usercfg->{users
});
253 my $users = $usercfg->{users
};
256 if ($opts->{'full'}) {
257 print "full sync, deleting outdated existing users first\n";
258 foreach my $userid (sort keys %$users) {
259 next if $userid !~ m/\@$realm$/;
261 $oldusers->{$userid} = delete $users->{$userid};
262 if ($opts->{'purge'} && !$synced_users->{$userid}) {
263 PVE
::AccessControl
::delete_user_acl
($userid, $usercfg);
264 print "purged user '$userid' and all its ACL entries\n";
265 } elsif (!defined($synced_users->{$userid})) {
266 print "remove user '$userid'\n";
271 foreach my $userid (sort keys %$synced_users) {
272 my $synced_user = $synced_users->{$userid} // {};
273 if (!defined($users->{$userid})) {
274 my $user = $users->{$userid} = $synced_user;
276 my $olduser = $oldusers->{$userid} // {};
277 if (defined(my $enabled = $olduser->{enable
})) {
278 $user->{enable
} = $enabled;
279 } elsif ($opts->{enable
}) {
283 if (defined($olduser->{tokens
})) {
284 $user->{tokens
} = $olduser->{tokens
};
286 if (defined($oldusers->{$userid})) {
287 print "updated user '$userid'\n";
289 print "added user '$userid'\n";
292 my $olduser = $users->{$userid};
293 foreach my $attr (keys %$synced_user) {
294 $olduser->{$attr} = $synced_user->{$attr};
296 print "updated user '$userid'\n";
301 my $update_groups = sub {
302 my ($usercfg, $realm, $synced_groups, $opts) = @_;
304 print "syncing groups\n";
305 $usercfg->{groups
} = {} if !defined($usercfg->{groups
});
306 my $groups = $usercfg->{groups
};
310 print "full sync, deleting outdated existing groups first\n";
311 foreach my $groupid (sort keys %$groups) {
312 next if $groupid !~ m/\-$realm$/;
314 my $oldgroups->{$groupid} = delete $groups->{$groupid};
315 if ($opts->{purge
} && !$synced_groups->{$groupid}) {
316 print "purged group '$groupid' and all its ACL entries\n";
317 PVE
::AccessControl
::delete_group_acl
($groupid, $usercfg)
318 } elsif (!defined($synced_groups->{$groupid})) {
319 print "removed group '$groupid'\n";
324 foreach my $groupid (sort keys %$synced_groups) {
325 my $synced_group = $synced_groups->{$groupid};
326 if (!defined($groups->{$groupid})) {
327 $groups->{$groupid} = $synced_group;
328 if (defined($oldgroups->{$groupid})) {
329 print "updated group '$groupid'\n";
331 print "added group '$groupid'\n";
334 my $group = $groups->{$groupid};
335 foreach my $attr (keys %$synced_group) {
336 $group->{$attr} = $synced_group->{$attr};
338 print "updated group '$groupid'\n";
343 __PACKAGE__-
>register_method ({
345 path
=> '{realm}/sync',
348 description
=> "'Realm.AllocateUser' on '/access/realm/<realm>' and "
349 ." 'User.Modify' permissions to '/access/groups/'.",
351 [ 'userid-param', 'Realm.AllocateUser' ],
352 [ 'userid-group', ['User.Modify'] ],
355 description
=> "Syncs users and/or groups from the configured LDAP to user.cfg."
356 ." NOTE: Synced groups will have the name 'name-\$realm', so make sure"
357 ." those groups do not exist to prevent overwriting.",
360 additionalProperties
=> 0,
362 realm
=> get_standard_option
('realm'),
364 description
=> "Select what to sync.",
366 enum
=> [qw(users groups both)],
369 description
=> "If set, uses the LDAP Directory as source of truth, ".
370 "deleting all information not contained there. ".
371 "Otherwise only syncs information set explicitly.",
375 description
=> "Enable newly synced users.",
379 description
=> "Remove ACLs for users/groups that were removed from the config.",
385 description
=> 'Worker Task-UPID',
391 my $rpcenv = PVE
::RPCEnvironment
::get
();
392 my $authuser = $rpcenv->get_user();
394 my $realm = $param->{realm
};
395 my $cfg = cfs_read_file
($domainconfigfile);
396 my $realmconfig = $cfg->{ids
}->{$realm};
398 raise_param_exc
({ 'realm' => 'Realm does not exist.' }) if !defined($realmconfig);
399 my $type = $realmconfig->{type
};
401 if ($type ne 'ldap' && $type ne 'ad') {
402 die "Cannot sync realm type '$type'! Only LDAP/AD realms can be synced.\n";
406 my $scope = $param->{scope
};
407 my $whatstring = $scope eq 'both' ?
"users and groups" : $scope;
409 my $plugin = PVE
::Auth
::Plugin-
>lookup($type);
412 print "starting sync for realm $realm\n";
414 my ($synced_users, $dnmap) = $plugin->get_users($realmconfig, $realm);
415 my $synced_groups = {};
416 if ($scope eq 'groups' || $scope eq 'both') {
417 $synced_groups = $plugin->get_groups($realmconfig, $realm, $dnmap);
420 PVE
::AccessControl
::lock_user_config
(sub {
421 my $usercfg = cfs_read_file
("user.cfg");
422 print "got data from server, updating $whatstring\n";
424 if ($scope eq 'users' || $scope eq 'both') {
425 $update_users->($usercfg, $realm, $synced_users, $param);
428 if ($scope eq 'groups' || $scope eq 'both') {
429 $update_groups->($usercfg, $realm, $synced_groups, $param);
432 cfs_write_file
("user.cfg", $usercfg);
433 print "successfully updated $whatstring configuration\n";
434 }, "syncing $whatstring failed");
437 return $rpcenv->fork_worker('auth-realm-sync', $realm, $authuser, $worker);