mirror of
https://git.proxmox.com/git/pve-access-control
synced 2025-06-03 20:38:39 +00:00
new API to change password
Started to implement fine grained permission checks.
This commit is contained in:
parent
76c377c1c4
commit
37d45debb1
@ -3,6 +3,7 @@ package PVE::API2::AccessControl;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use PVE::Exception qw(raise raise_perm_exc);
|
||||
use PVE::SafeSyslog;
|
||||
use PVE::RPCEnvironment;
|
||||
use PVE::Cluster qw(cfs_read_file);
|
||||
@ -77,6 +78,7 @@ __PACKAGE__->register_method ({
|
||||
}
|
||||
|
||||
push @$res, { subdir => 'ticket' };
|
||||
push @$res, { subdir => 'password' };
|
||||
|
||||
return $res;
|
||||
}});
|
||||
@ -204,4 +206,60 @@ __PACKAGE__->register_method ({
|
||||
return $res;
|
||||
}});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'change_passsword',
|
||||
path => 'password',
|
||||
method => 'PUT',
|
||||
permissions => { user => 'all' },
|
||||
protected => 1, # else we can't access shadow files
|
||||
description => "Change user password.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
userid => get_standard_option('userid'),
|
||||
password => {
|
||||
description => "The new password.",
|
||||
type => 'string',
|
||||
minLength => 5,
|
||||
maxLength => 64,
|
||||
},
|
||||
}
|
||||
},
|
||||
returns => { type => "null" },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
|
||||
my ($userid, $ruid, $realm) = PVE::AccessControl::verify_username($param->{userid});
|
||||
|
||||
my $usercfg = $rpcenv->{user_cfg};
|
||||
PVE::AccessControl::check_user_exist($usercfg, $userid);
|
||||
|
||||
if ($authuser eq 'root@pam') {
|
||||
# OK - root can change anything
|
||||
} else {
|
||||
if ($authuser eq $userid) {
|
||||
$rpcenv->check_user_enabled($userid);
|
||||
# OK - each user can change its own password
|
||||
} else {
|
||||
raise_perm_exc() if $userid eq 'root@pam';
|
||||
|
||||
my $privs = [ 'Sys.UserMod', 'Sys.UserAdd' ];
|
||||
if (!$rpcenv->check_any($authuser, "/access", $privs, 1)) {
|
||||
my $groups = $rpcenv->filter_groups($authuser, sub { return "/access/groups/" . shift; }, $privs, 1);
|
||||
my $allowed_users = $rpcenv->group_member_join([keys %$groups]);
|
||||
raise_perm_exc() if !$allowed_users->{$userid};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PVE::AccessControl::domain_set_password($realm, $ruid, $param->{password});
|
||||
|
||||
PVE::Cluster::log_msg('info', 'root@pam', "changed password for user '$userid'");
|
||||
|
||||
return undef;
|
||||
}});
|
||||
|
||||
1;
|
||||
|
@ -2,6 +2,7 @@ package PVE::API2::User;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use PVE::Exception qw(raise raise_perm_exc);
|
||||
use PVE::Cluster qw (cfs_read_file cfs_write_file);
|
||||
use PVE::Tools qw(split_list);
|
||||
use PVE::AccessControl;
|
||||
@ -61,15 +62,22 @@ __PACKAGE__->register_method ({
|
||||
my ($param) = @_;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $usercfg = $rpcenv->{user_cfg};
|
||||
my $authuser = $rpcenv->get_user();
|
||||
|
||||
my $res = [];
|
||||
|
||||
my $usercfg = cfs_read_file("user.cfg");
|
||||
|
||||
my $privs = [ 'Sys.UserMod', 'Sys.UserAdd' ];
|
||||
|
||||
my $canUserMod = $rpcenv->check_any($authuser, "/access", $privs, 1);
|
||||
my $groups = $rpcenv->filter_groups($authuser, sub { return "/access/groups/" . shift; }, $privs, 1);
|
||||
my $allowed_users = $rpcenv->group_member_join([keys %$groups]);
|
||||
|
||||
foreach my $user (keys %{$usercfg->{users}}) {
|
||||
# root sees all entries, a user only sees its own entry
|
||||
next if $authuser ne 'root@pam' && $user ne $authuser;
|
||||
|
||||
if (!($canUserMod || $user eq $authuser)) {
|
||||
next if !$allowed_users->{$user};
|
||||
}
|
||||
|
||||
my $entry = &$extract_user_data($usercfg->{users}->{$user});
|
||||
|
||||
@ -95,7 +103,13 @@ __PACKAGE__->register_method ({
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
userid => get_standard_option('userid'),
|
||||
password => { type => 'string', optional => 1, minLength => 5, maxLength => 64 },
|
||||
password => {
|
||||
description => "Initial password.",
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
minLength => 5,
|
||||
maxLength => 64
|
||||
},
|
||||
groups => { type => 'string', optional => 1, format => 'pve-groupid-list'},
|
||||
firstname => { type => 'string', optional => 1 },
|
||||
lastname => { type => 'string', optional => 1 },
|
||||
@ -187,11 +201,9 @@ __PACKAGE__->register_method ({
|
||||
PVE::AccessControl::verify_username($param->{userid});
|
||||
|
||||
my $usercfg = cfs_read_file("user.cfg");
|
||||
|
||||
my $data = PVE::AccessControl::check_user_exist($usercfg, $username);
|
||||
|
||||
my $data = $usercfg->{users}->{$username};
|
||||
|
||||
die "user '$username' does not exist\n" if !$data;
|
||||
|
||||
return &$extract_user_data($data, 1);
|
||||
}});
|
||||
|
||||
@ -205,7 +217,6 @@ __PACKAGE__->register_method ({
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
userid => get_standard_option('userid'),
|
||||
password => { type => 'string', optional => 1, minLength => 5, maxLength => 64 },
|
||||
groups => { type => 'string', optional => 1, format => 'pve-groupid-list' },
|
||||
append => {
|
||||
type => 'boolean',
|
||||
@ -232,20 +243,16 @@ __PACKAGE__->register_method ({
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
my ($username, $ruid, $realm) =
|
||||
PVE::AccessControl::verify_username($param->{userid});
|
||||
|
||||
PVE::AccessControl::lock_user_config(
|
||||
sub {
|
||||
|
||||
my ($username, $ruid, $realm) =
|
||||
PVE::AccessControl::verify_username($param->{userid});
|
||||
|
||||
my $usercfg = cfs_read_file("user.cfg");
|
||||
|
||||
die "user '$username' does not exist\n"
|
||||
if !$usercfg->{users}->{$username};
|
||||
|
||||
PVE::AccessControl::domain_set_password($realm, $ruid, $param->{password})
|
||||
if defined($param->{password});
|
||||
PVE::AccessControl::check_user_exist($usercfg, $username);
|
||||
|
||||
$usercfg->{users}->{$username}->{enable} = $param->{enable} if defined($param->{enable});
|
||||
|
||||
@ -281,6 +288,7 @@ __PACKAGE__->register_method ({
|
||||
path => '{userid}',
|
||||
method => 'DELETE',
|
||||
description => "Delete user.",
|
||||
permissions => { user => 'all' },
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
@ -290,23 +298,33 @@ __PACKAGE__->register_method ({
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
|
||||
my ($userid, $ruid, $realm) =
|
||||
PVE::AccessControl::verify_username($param->{userid});
|
||||
|
||||
PVE::AccessControl::lock_user_config(
|
||||
sub {
|
||||
|
||||
my ($username, $ruid, $realm) =
|
||||
PVE::AccessControl::verify_username($param->{userid});
|
||||
|
||||
my $usercfg = cfs_read_file("user.cfg");
|
||||
|
||||
die "user '$username' does not exist\n"
|
||||
if !$usercfg->{users}->{$username};
|
||||
PVE::AccessControl::check_user_exist($usercfg, $userid);
|
||||
|
||||
delete ($usercfg->{users}->{$username});
|
||||
my $privs = [ 'Sys.UserAdd' ]; # there is no Sys.UserDel
|
||||
if (!$rpcenv->check($authuser, "/access", $privs, 1)) {
|
||||
my $groups = $rpcenv->filter_groups($authuser, sub { return "/access/groups/" . shift; }, $privs, 1);
|
||||
my $allowed_users = $rpcenv->group_member_join([keys %$groups]);
|
||||
raise_perm_exc() if !$allowed_users->{$userid};
|
||||
}
|
||||
|
||||
delete ($usercfg->{users}->{$userid});
|
||||
|
||||
PVE::AccessControl::delete_shadow_password($ruid) if $realm eq 'pve';
|
||||
PVE::AccessControl::delete_user_group($username, $usercfg);
|
||||
PVE::AccessControl::delete_user_acl($username, $usercfg);
|
||||
|
||||
PVE::AccessControl::delete_user_group($userid, $usercfg);
|
||||
PVE::AccessControl::delete_user_acl($userid, $usercfg);
|
||||
|
||||
cfs_write_file("user.cfg", $usercfg);
|
||||
}, "delete user failed");
|
||||
|
@ -373,18 +373,30 @@ sub authenticate_user_domain {
|
||||
}
|
||||
}
|
||||
|
||||
sub check_user_enabled {
|
||||
sub check_user_exist {
|
||||
my ($usercfg, $username, $noerr) = @_;
|
||||
|
||||
$username = verify_username($username, $noerr);
|
||||
return undef if !$username;
|
||||
|
||||
return 1 if $usercfg && $usercfg->{users}->{$username} &&
|
||||
$usercfg->{users}->{$username}->{enable};
|
||||
return $usercfg->{users}->{$username} if $usercfg && $usercfg->{users}->{$username};
|
||||
|
||||
die "no such user ('$username')\n" if !$noerr;
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub check_user_enabled {
|
||||
my ($usercfg, $username, $noerr) = @_;
|
||||
|
||||
my $data = check_user_exist($usercfg, $username, $noerr);
|
||||
return undef if !$data;
|
||||
|
||||
return 1 if $data->{enable};
|
||||
|
||||
return 1 if $username eq 'root@pam'; # root is always enabled
|
||||
|
||||
die "no such user ('$username')\n" if !$noerr;
|
||||
die "user '$username' is disabled\n" if !$noerr;
|
||||
|
||||
return undef;
|
||||
}
|
||||
@ -541,11 +553,13 @@ my $privgroups = {
|
||||
Sys => {
|
||||
root => [
|
||||
'Sys.PowerMgmt',
|
||||
'Sys.Modify', # edit/change node settings
|
||||
'Sys.Modify', # edit/change node settings
|
||||
'Sys.UserAdd', # add/delete users
|
||||
],
|
||||
admin => [
|
||||
'Sys.Console',
|
||||
'Sys.Syslog',
|
||||
'Sys.UserMod', # modify users settings
|
||||
],
|
||||
user => [],
|
||||
audit => [
|
||||
|
@ -7,6 +7,7 @@ use IO::Handle;
|
||||
use IO::File;
|
||||
use IO::Select;
|
||||
use Fcntl qw(:flock);
|
||||
use PVE::Exception qw(raise raise_perm_exc);
|
||||
use PVE::SafeSyslog;
|
||||
use PVE::Tools;
|
||||
use PVE::INotify;
|
||||
@ -154,17 +155,42 @@ sub permissions {
|
||||
}
|
||||
|
||||
sub check {
|
||||
my ($self, $user, $path, $privs) = @_;
|
||||
my ($self, $user, $path, $privs, $noerr) = @_;
|
||||
|
||||
my $perm = $self->permissions($user, $path);
|
||||
|
||||
foreach my $priv (@$privs) {
|
||||
return undef if !$perm->{$priv};
|
||||
PVE::AccessControl::verify_privname($priv);
|
||||
if (!$perm->{$priv}) {
|
||||
return undef if $noerr;
|
||||
raise_perm_exc("$path, $priv");
|
||||
}
|
||||
};
|
||||
|
||||
return 1;
|
||||
};
|
||||
|
||||
sub check_any {
|
||||
my ($self, $user, $path, $privs, $noerr) = @_;
|
||||
|
||||
my $perm = $self->permissions($user, $path);
|
||||
|
||||
my $found = 0;
|
||||
foreach my $priv (@$privs) {
|
||||
PVE::AccessControl::verify_privname($priv);
|
||||
if ($perm->{$priv}) {
|
||||
$found = 1;
|
||||
last;
|
||||
}
|
||||
};
|
||||
|
||||
return 1 if $found;
|
||||
|
||||
return undef if $noerr;
|
||||
|
||||
raise_perm_exc("$path, " . join("|", @$privs));
|
||||
};
|
||||
|
||||
sub check_user_enabled {
|
||||
my ($self, $user, $noerr) = @_;
|
||||
|
||||
@ -172,6 +198,61 @@ sub check_user_enabled {
|
||||
return PVE::AccessControl::check_user_enabled($cfg, $user, $noerr);
|
||||
}
|
||||
|
||||
sub check_user_exist {
|
||||
my ($self, $user, $noerr) = @_;
|
||||
|
||||
my $cfg = $self->{user_cfg};
|
||||
return PVE::AccessControl::check_user_exist($cfg, $user, $noerr);
|
||||
}
|
||||
|
||||
sub is_group_member {
|
||||
my ($self, $group, $user) = @_;
|
||||
|
||||
my $cfg = $self->{user_cfg};
|
||||
|
||||
return 0 if !$cfg->{groups}->{$group};
|
||||
|
||||
return defined($cfg->{groups}->{$group}->{users}->{$user});
|
||||
}
|
||||
|
||||
sub filter_groups {
|
||||
my ($self, $user, $getPath, $privs, $any) = @_;
|
||||
|
||||
my $cfg = $self->{user_cfg};
|
||||
|
||||
my $groups = {};
|
||||
foreach my $group (keys %{$cfg->{groups}}) {
|
||||
if ($any) {
|
||||
if ($self->check_any($user, &$getPath($group), $privs, 1)) {
|
||||
$groups->{$group} = $cfg->{groups}->{$group};
|
||||
}
|
||||
} else {
|
||||
if ($self->check($user, &$getPath($group), $privs, 1)) {
|
||||
$groups->{$group} = $cfg->{groups}->{$group};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $groups;
|
||||
}
|
||||
|
||||
sub group_member_join {
|
||||
my ($self, $grouplist) = @_;
|
||||
|
||||
my $users = {};
|
||||
|
||||
my $cfg = $self->{user_cfg};
|
||||
foreach my $group (@$grouplist) {
|
||||
my $data = $cfg->{groups}->{$group};
|
||||
next if !$data;
|
||||
foreach my $user (keys %{$data->{users}}) {
|
||||
$users->{$user} = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return $users;
|
||||
}
|
||||
|
||||
# initialize environment - must be called once at program startup
|
||||
sub init {
|
||||
my ($class, $type, %params) = @_;
|
||||
|
Loading…
Reference in New Issue
Block a user