use JSON;
use MIME::Base64;
-use PVE::Exception qw(raise raise_perm_exc);
+use PVE::Exception qw(raise raise_perm_exc raise_param_exc);
use PVE::SafeSyslog;
use PVE::RPCEnvironment;
use PVE::Cluster qw(cfs_read_file);
-use PVE::Corosync;
+use PVE::DataCenterConfig;
use PVE::RESTHandler;
use PVE::AccessControl;
use PVE::JSONSchema qw(get_standard_option);
use PVE::API2::Group;
use PVE::API2::Role;
use PVE::API2::ACL;
+use PVE::Auth::Plugin;
use PVE::OTP;
use PVE::Tools;
use base qw(PVE::RESTHandler);
__PACKAGE__->register_method ({
- subclass => "PVE::API2::User",
+ subclass => "PVE::API2::User",
path => 'users',
});
__PACKAGE__->register_method ({
- subclass => "PVE::API2::Group",
+ subclass => "PVE::API2::Group",
path => 'groups',
});
__PACKAGE__->register_method ({
- subclass => "PVE::API2::Role",
+ subclass => "PVE::API2::Role",
path => 'roles',
});
__PACKAGE__->register_method ({
- subclass => "PVE::API2::ACL",
+ subclass => "PVE::API2::ACL",
path => 'acl',
});
__PACKAGE__->register_method ({
- subclass => "PVE::API2::Domains",
+ subclass => "PVE::API2::Domains",
path => 'domains',
});
__PACKAGE__->register_method ({
- name => 'index',
- path => '',
+ name => 'index',
+ path => '',
method => 'GET',
description => "Directory index.",
- permissions => {
+ permissions => {
user => 'all',
},
parameters => {
},
code => sub {
my ($param) = @_;
-
+
my $res = [];
my $ma = __PACKAGE__->method_attributes();
access => qr/(User|Group)\.|Permissions\.Modify/,
storage => qr/Datastore\.|Permissions\.Modify/,
nodes => qr/Sys\.|Permissions\.Modify/,
- dc => qr/Sys\.Audit/,
+ sdn => qr/SDN\.|Permissions\.Modify/,
+ dc => qr/Sys\.Audit|SDN\./,
};
map { $res->{$_} = {} } keys %$priv_re_map;
- my $required_paths = ['/', '/nodes', '/access/groups', '/vms', '/storage'];
+ my $required_paths = ['/', '/nodes', '/access/groups', '/vms', '/storage', '/sdn'];
my $checked_paths = {};
foreach my $path (@$required_paths, keys %{$usercfg->{acl}}) {
};
__PACKAGE__->register_method ({
- name => 'get_ticket',
- path => 'ticket',
+ name => 'get_ticket',
+ path => 'ticket',
method => 'GET',
permissions => { user => 'world' },
description => "Dummy. Useful for formatters which want to provide a login page.",
},
returns => { type => "null" },
code => sub { return undef; }});
-
+
__PACKAGE__->register_method ({
- name => 'create_ticket',
- path => 'ticket',
+ name => 'create_ticket',
+ path => 'ticket',
method => 'POST',
- permissions => {
+ permissions => {
description => "You need to pass valid credientials.",
- user => 'world'
+ user => 'world'
},
protected => 1, # else we can't access shadow files
+ allowtoken => 0, # we don't want tokens to create tickets
description => "Create or verify authentication ticket.",
parameters => {
additionalProperties => 0,
optional => 1,
completion => \&PVE::AccessControl::complete_realm,
}),
- password => {
+ password => {
description => "The secret password. This can also be a valid ticket.",
type => 'string',
},
optional => 1,
maxLength => 64,
},
- privs => {
+ privs => {
description => "Verify ticket, and check if user have access 'privs' on 'path'",
type => 'string' , format => 'pve-priv-list',
requires => 'path',
},
code => sub {
my ($param) = @_;
-
+
my $username = $param->{username};
$username .= "\@$param->{realm}" if $param->{realm};
+ $username = PVE::AccessControl::lookup_username($username);
my $rpcenv = PVE::RPCEnvironment::get();
my $res;
$res->{cap} = &$compute_api_permission($rpcenv, $username)
if !defined($res->{NeedTFA});
- if (PVE::Corosync::check_conf_exists(1)) {
- if ($rpcenv->check($username, '/', ['Sys.Audit'], 1)) {
- eval {
- my $conf = cfs_read_file('corosync.conf');
- my $totem = PVE::Corosync::totem_config($conf);
- if ($totem->{cluster_name}) {
- $res->{clustername} = $totem->{cluster_name};
- }
- };
- warn "$@\n" if $@;
- }
+ my $clinfo = PVE::Cluster::get_clinfo();
+ if ($clinfo->{cluster}->{name} && $rpcenv->check($username, '/', ['Sys.Audit'], 1)) {
+ $res->{clustername} = $clinfo->{cluster}->{name};
}
PVE::Cluster::log_msg('info', 'root@pam', "successful auth for user '$username'");
__PACKAGE__->register_method ({
name => 'change_password',
- path => 'password',
+ path => 'password',
method => 'PUT',
- permissions => {
+ permissions => {
description => "Each user is allowed to change his own password. A user can change the password of another user if he has 'Realm.AllocateUser' (on the realm of user <userid>) and 'User.Modify' permission on /access/groups/<group> on a group where user <userid> is member of.",
- check => [ 'or',
+ check => [ 'or',
['userid-param', 'self'],
[ 'and',
[ 'userid-param', 'Realm.AllocateUser'],
],
},
protected => 1, # else we can't access shadow files
+ allowtoken => 0, # we don't want tokens to change the regular user password
description => "Change user password.",
parameters => {
additionalProperties => 0,
properties => {
userid => get_standard_option('userid-completed'),
- password => {
+ password => {
description => "The new password.",
type => 'string',
- minLength => 5,
+ minLength => 5,
maxLength => 64,
},
}
my $dc = cfs_read_file('datacenter.cfg');
my $u2f = $dc->{u2f};
die "u2f not configured in datacenter.cfg\n" if !$u2f;
- $u2f = PVE::JSONSchema::parse_property_string($PVE::Cluster::u2f_format, $u2f);
return $u2f;
}
],
},
protected => 1, # else we can't access shadow files
+ allowtoken => 0, # we don't want tokens to change the regular user's TFA settings
description => "Change user u2f authentication.",
parameters => {
additionalProperties => 0,
optional => 1,
description => 'When adding TOTP, the shared secret value.',
type => 'string',
- # This is what pve-common's PVE::OTP::oath_verify_otp accepts.
- # Should we move this to pve-common's JSONSchema as a named format?
- pattern => qr/[A-Z2-7=]{16}|[A-Fa-f0-9]{40}/,
+ format => 'pve-tfa-secret',
},
config => {
optional => 1,
# Regular users need to confirm their password to change u2f settings.
if ($authuser ne 'root@pam') {
- raise_param_exc('password' => 'password is required to modify u2f data')
+ raise_param_exc({ 'password' => 'password is required to modify u2f data' })
if !defined($password);
my $domain_cfg = cfs_read_file('domains.cfg');
my $cfg = $domain_cfg->{ids}->{$realm};
- die "auth domain '$realm' does not exists\n" if !$cfg;
+ die "auth domain '$realm' does not exist\n" if !$cfg;
my $plugin = PVE::Auth::Plugin->lookup($cfg->{type});
$plugin->authenticate_user($cfg, $realm, $ruid, $password);
}
return $challenge;
}
} elsif ($action eq 'confirm') {
- raise_param_exc('response' => "confirm action requires the 'response' parameter to be set")
+ raise_param_exc({ 'response' => "confirm action requires the 'response' parameter to be set" })
if !defined($response);
my ($type, $u2fdata) = PVE::AccessControl::user_get_tfa($userid, $realm);
my ($keyHandle, $publicKey) = $u2f->registration_verify($response);
PVE::AccessControl::user_set_tfa($userid, $realm, 'u2f', {
keyHandle => $keyHandle,
- publicKey => encode_base64($publicKey, ''),
+ publicKey => $publicKey, # already base64 encoded
});
} else {
die "invalid action: $action\n";
method => 'POST',
permissions => { user => 'all' },
protected => 1, # else we can't access shadow files
+ allowtoken => 0, # we don't want tokens to access TFA information
description => 'Finish a u2f challenge.',
parameters => {
additionalProperties => 0,
}
}});
+__PACKAGE__->register_method({
+ name => 'permissions',
+ path => 'permissions',
+ method => 'GET',
+ description => 'Retrieve effective permissions of given user/token.',
+ permissions => {
+ description => "Each user/token is allowed to dump their own permissions. A user can dump the permissions of another user if they have 'Sys.Audit' permission on /access.",
+ user => 'all',
+ },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ userid => {
+ type => 'string',
+ description => "User ID or full API token ID",
+ pattern => $PVE::AccessControl::userid_or_token_regex,
+ optional => 1,
+ },
+ path => get_standard_option('acl-path', {
+ description => "Only dump this specific path, not the whole tree.",
+ optional => 1,
+ }),
+ },
+ },
+ returns => {
+ type => 'object',
+ description => 'Map of "path" => (Map of "privilege" => "propagate boolean").',
+ },
+ code => sub {
+ my ($param) = @_;
+
+ my $rpcenv = PVE::RPCEnvironment::get();
+
+ my $userid = $param->{userid};
+ if (defined($userid)) {
+ $rpcenv->check($rpcenv->get_user(), '/access', ['Sys.Audit']);
+ } else {
+ $userid = $rpcenv->get_user();
+ }
+
+ my $res;
+
+ if (my $path = $param->{path}) {
+ my $perms = $rpcenv->permissions($userid, $path);
+ if ($perms) {
+ $res = { $path => $perms };
+ } else {
+ $res = {};
+ }
+ } else {
+ $res = $rpcenv->get_effective_permissions($userid);
+ }
+
+ return $res;
+ }});
+
1;