new API to change password

Started to implement fine grained permission checks.
This commit is contained in:
Dietmar Maurer 2012-01-20 12:45:24 +01:00
parent 76c377c1c4
commit 37d45debb1
4 changed files with 204 additions and 33 deletions

View File

@ -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;

View File

@ -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");

View File

@ -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 => [

View File

@ -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) = @_;