]> git.proxmox.com Git - pve-access-control.git/commitdiff
api: domains: add user/group sync API enpoint
authorDominik Csapak <d.csapak@proxmox.com>
Fri, 13 Mar 2020 12:18:47 +0000 (13:18 +0100)
committerThomas Lamprecht <t.lamprecht@proxmox.com>
Sat, 21 Mar 2020 15:05:15 +0000 (16:05 +0100)
this api call syncs the users and groups from LDAP/AD to the
user.cfg

it also implements a 'full' mode where we first delete all
users/groups from the config and sync them again

the parameter 'enable' controls if newly synced users are 'enabled'
(if no sync parameter handles that)
the parameter 'purge' controls if ACLs get removed for users/groups
that do not exists anymore after

also add this command to pveum

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
PVE/API2/Domains.pm
PVE/CLI/pveum.pm

index 7f98e717b82259e3cdcdf87f5250a9194c99c32a..125e6b06b17bf4546411dfef0c1011a403875047 100644 (file)
@@ -2,6 +2,7 @@ package PVE::API2::Domains;
 
 use strict;
 use warnings;
 
 use strict;
 use warnings;
+use PVE::Exception qw(raise_param_exc);
 use PVE::Tools qw(extract_param);
 use PVE::Cluster qw (cfs_read_file cfs_write_file);
 use PVE::AccessControl;
 use PVE::Tools qw(extract_param);
 use PVE::Cluster qw (cfs_read_file cfs_write_file);
 use PVE::AccessControl;
@@ -244,4 +245,186 @@ __PACKAGE__->register_method ({
        return undef;
     }});
 
        return undef;
     }});
 
+__PACKAGE__->register_method ({
+    name => 'sync',
+    path => '{realm}/sync',
+    method => 'POST',
+    permissions => {
+       description => "You need 'Realm.AllocateUser' on '/access/realm/<realm>' on the realm  and 'User.Modify' permissions to '/access/groups/'.",
+       check => [ 'and',
+                  [ 'userid-param', 'Realm.AllocateUser'],
+                  [ 'userid-group', ['User.Modify']],
+           ],
+    },
+    description => "Syncs users and/or groups from LDAP to user.cfg. ".
+                  "NOTE: Synced groups will have the name 'name-\$realm', so ".
+                  "make sure those groups do not exist to prevent overwriting.",
+    protected => 1,
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           realm =>  get_standard_option('realm'),
+           scope => {
+               description => "Select what to sync.",
+               type => 'string',
+               enum => [qw(users groups both)],
+           },
+           full => {
+               description => "If set, uses the LDAP Directory as source of truth, ".
+                              "deleting all information not contained there. ".
+                              "Otherwise only syncs information set explicitly.",
+               type => 'boolean',
+           },
+           enable => {
+               description => "Enable newly synced users.",
+               type => 'boolean',
+           },
+           purge => {
+               description => "Remove ACLs for users/groups that were removed from the config.",
+               type => 'boolean',
+           },
+       }
+    },
+    returns => { type => 'string' },
+    code => sub {
+       my ($param) = @_;
+
+       my $rpcenv = PVE::RPCEnvironment::get();
+       my $authuser = $rpcenv->get_user();
+
+
+       my $realm = $param->{realm};
+       my $cfg = cfs_read_file($domainconfigfile);
+       my $ids = $cfg->{ids};
+
+       raise_param_exc({ 'realm' => 'Realm does not exist.' }) if !defined($ids->{$realm});
+       my $type = $ids->{$realm}->{type};
+
+       if ($type ne 'ldap' && $type ne 'ad') {
+           die "Only LDAP/AD realms can be synced.\n";
+       }
+
+       my $scope = $param->{scope};
+       my $sync_users;
+       my $sync_groups;
+
+       my $errorstring = "syncing ";
+       if ($scope eq 'users') {
+           $errorstring .= "users ";
+           $sync_users = 1;
+       } elsif ($scope eq 'groups') {
+           $errorstring .= "groups ";
+           $sync_groups = 1;
+       } elsif ($scope eq 'both') {
+           $errorstring .= "users and groups ";
+           $sync_users = $sync_groups = 1;
+       }
+       $errorstring .= "failed.";
+
+       my $plugin = PVE::Auth::Plugin->lookup($ids->{$realm}->{type});
+
+       my $realmdata = $ids->{$realm};
+       my $users = {};
+       my $groups = {};
+
+
+       my $worker = sub {
+           print "starting sync for $realm\n";
+           if ($sync_groups) {
+               my $dnmap = {};
+               ($users, $dnmap) = $plugin->get_users($realmdata, $realm);
+               $groups = $plugin->get_groups($realmdata, $realm, $dnmap);
+           } else {
+               $users = $plugin->get_users($realmdata, $realm);
+           }
+
+           PVE::AccessControl::lock_user_config(
+               sub {
+               my $usercfg = cfs_read_file("user.cfg");
+               print "got data from server, modifying users/groups\n";
+
+               if ($sync_users) {
+                   print "syncing users\n";
+                   my $oldusers = $usercfg->{users};
+
+                   my $oldtokens = {};
+                   my $oldenabled = {};
+
+                   if ($param->{full}) {
+                       print "full sync, deleting existing users first\n";
+                       foreach my $userid (keys %$oldusers) {
+                           next if $userid !~ m/\@$realm$/;
+                           # we save the old tokens 
+                           $oldtokens->{$userid} = $oldusers->{$userid}->{tokens};
+                           $oldenabled->{$userid} = $oldusers->{$userid}->{enable} // 0;
+                           delete $oldusers->{$userid};
+                           PVE::AccessControl::delete_user_acl($userid, $usercfg)
+                               if $param->{purge} && !$users->{$userid};
+                           print "removed user '$userid'\n";
+                       }
+                   }
+
+                   foreach my $userid (keys %$users) {
+                       my $user = $users->{$userid};
+                       if (!defined($oldusers->{$userid})) {
+                           $oldusers->{$userid} = $user;
+
+                           if (defined($oldenabled->{$userid})) {
+                               $oldusers->{$userid}->{enable} = $oldenabled->{$userid};
+                           } elsif ($param->{enable}) {
+                               $oldusers->{$userid}->{enable} = 1;
+                           }
+
+                           if (defined($oldtokens->{$userid})) {
+                               $oldusers->{$userid}->{tokens} = $oldtokens->{$userid};
+                           }
+
+                           print "added user '$userid'\n";
+                       } else {
+                           my $olduser = $oldusers->{$userid};
+                           foreach my $attr (keys %$user) {
+                               $olduser->{$attr} = $user->{$attr};
+                           }
+                           print "updated user '$userid'\n";
+                       }
+                   }
+               }
+
+               if ($sync_groups) {
+                   print "syncing groups\n";
+                   my $oldgroups = $usercfg->{groups};
+
+                   if ($param->{full}) {
+                       print "full sync, deleting existing groups first\n";
+                       foreach my $groupid (keys %$oldgroups) {
+                           next if $groupid !~ m/\-$realm$/;
+                           delete $oldgroups->{$groupid};
+                           PVE::AccessControl::delete_group_acl($groupid, $usercfg)
+                               if $param->{purge} && !$groups->{$groupid};
+                           print "removed group '$groupid'\n";
+                       }
+                   }
+
+                   foreach my $groupid (keys %$groups) {
+                       my $group = $groups->{$groupid};
+                       if (!defined($oldgroups->{$groupid})) {
+                           $oldgroups->{$groupid} = $group;
+                           print "added group '$groupid'\n";
+                       } else {
+                           my $oldgroup = $oldgroups->{$groupid};
+                           foreach my $attr (keys %$group) {
+                               $oldgroup->{$attr} = $group->{$attr};
+                           }
+                           print "updated group '$groupid'\n";
+                       }
+                   }
+               }
+               cfs_write_file("user.cfg", $usercfg);
+               print "updated user.cfg\n";
+           }, $errorstring);
+       };
+
+       return $rpcenv->fork_worker('ldapsync', $realm, $authuser, $worker);
+    }});
+
 1;
 1;
index d3721b610db7acfda163d4a0826d6a6a6a4323e2..cffd4dac47cff618288c3698467a9440c9c3ba2e 100755 (executable)
@@ -148,6 +148,7 @@ our $cmddef = {
        modify => [ 'PVE::API2::Domains', 'update', ['realm'] ],
        delete => [ 'PVE::API2::Domains', 'delete', ['realm'] ],
        list   => [ 'PVE::API2::Domains', 'index', [], {}, $print_api_result, $PVE::RESTHandler::standard_output_options],
        modify => [ 'PVE::API2::Domains', 'update', ['realm'] ],
        delete => [ 'PVE::API2::Domains', 'delete', ['realm'] ],
        list   => [ 'PVE::API2::Domains', 'index', [], {}, $print_api_result, $PVE::RESTHandler::standard_output_options],
+       sync   => [ 'PVE::API2::Domains', 'sync', ['realm'], ],
     },
 
     ticket => [ 'PVE::API2::AccessControl', 'create_ticket', ['username'], undef,
     },
 
     ticket => [ 'PVE::API2::AccessControl', 'create_ticket', ['username'], undef,