]>
Commit | Line | Data |
---|---|---|
09281ad7 DM |
1 | package PVE::CLI::pveum; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
042eaa3d | 5 | |
66d1b615 | 6 | use PVE::AccessControl; |
09281ad7 DM |
7 | use PVE::RPCEnvironment; |
8 | use PVE::API2::User; | |
9 | use PVE::API2::Group; | |
10 | use PVE::API2::Role; | |
11 | use PVE::API2::ACL; | |
12 | use PVE::API2::AccessControl; | |
f28a69a0 | 13 | use PVE::API2::Domains; |
dc547a13 | 14 | use PVE::API2::TFA; |
9d06f603 | 15 | use PVE::Cluster qw(cfs_read_file cfs_write_file); |
369851ac | 16 | use PVE::CLIFormatter; |
09281ad7 | 17 | use PVE::CLIHandler; |
66d1b615 | 18 | use PVE::JSONSchema qw(get_standard_option); |
b34d76e7 | 19 | use PVE::PTY; |
369851ac | 20 | use PVE::RESTHandler; |
66d1b615 | 21 | use PVE::Tools qw(extract_param); |
09281ad7 DM |
22 | |
23 | use base qw(PVE::CLIHandler); | |
24 | ||
e623414a DM |
25 | sub setup_environment { |
26 | PVE::RPCEnvironment->setup_default_cli_env(); | |
27 | } | |
28 | ||
b34d76e7 DC |
29 | sub param_mapping { |
30 | my ($name) = @_; | |
98007830 | 31 | |
b34d76e7 DC |
32 | my $mapping = { |
33 | 'change_password' => [ | |
34 | PVE::CLIHandler::get_standard_mapping('pve-password'), | |
35 | ], | |
36 | 'create_ticket' => [ | |
37 | PVE::CLIHandler::get_standard_mapping('pve-password', { | |
38 | func => sub { | |
39 | # do not accept values given on cmdline | |
40 | return PVE::PTY::read_password('Enter password: '); | |
41 | }, | |
42 | }), | |
43 | ] | |
44 | }; | |
45 | ||
46 | return $mapping->{$name}; | |
98007830 DM |
47 | } |
48 | ||
369851ac FG |
49 | my $print_api_result = sub { |
50 | my ($data, $schema, $options) = @_; | |
51 | PVE::CLIFormatter::print_api_result($data, $schema, undef, $options); | |
52 | }; | |
53 | ||
66d1b615 FG |
54 | my $print_perm_result = sub { |
55 | my ($data, $schema, $options) = @_; | |
56 | ||
57 | if (!defined($options->{'output-format'}) || $options->{'output-format'} eq 'text') { | |
58 | my $table_schema = { | |
59 | type => 'array', | |
60 | items => { | |
61 | type => 'object', | |
62 | properties => { | |
63 | 'path' => { type => 'string', title => 'ACL path' }, | |
64 | 'permissions' => { type => 'string', title => 'Permissions' }, | |
65 | }, | |
66 | }, | |
67 | }; | |
68 | my $table_data = []; | |
69 | foreach my $path (sort keys %$data) { | |
70 | my $value = ''; | |
71 | my $curr = $data->{$path}; | |
72 | foreach my $perm (sort keys %$curr) { | |
73 | $value .= "\n" if $value; | |
74 | $value .= $perm; | |
75 | $value .= " (*)" if $curr->{$perm}; | |
76 | } | |
77 | push @$table_data, { path => $path, permissions => $value }; | |
78 | } | |
79 | PVE::CLIFormatter::print_api_result($table_data, $table_schema, undef, $options); | |
80 | print "Permissions marked with '(*)' have the 'propagate' flag set.\n"; | |
81 | } else { | |
82 | PVE::CLIFormatter::print_api_result($data, $schema, undef, $options); | |
83 | } | |
84 | }; | |
85 | ||
86 | __PACKAGE__->register_method({ | |
87 | name => 'token_permissions', | |
88 | path => 'token_permissions', | |
89 | method => 'GET', | |
90 | description => 'Retrieve effective permissions of given token.', | |
91 | parameters => { | |
92 | additionalProperties => 0, | |
93 | properties => { | |
94 | userid => get_standard_option('userid'), | |
95 | tokenid => get_standard_option('token-subid'), | |
96 | path => get_standard_option('acl-path', { | |
97 | description => "Only dump this specific path, not the whole tree.", | |
98 | optional => 1, | |
99 | }), | |
100 | }, | |
101 | }, | |
102 | returns => { | |
103 | type => 'object', | |
104 | description => 'Hash of structure "path" => "privilege" => "propagate boolean".', | |
105 | }, | |
106 | code => sub { | |
107 | my ($param) = @_; | |
108 | ||
109 | my $token_subid = extract_param($param, "tokenid"); | |
110 | $param->{userid} = PVE::AccessControl::join_tokenid($param->{userid}, $token_subid); | |
111 | ||
112 | return PVE::API2::AccessControl->permissions($param); | |
113 | }}); | |
114 | ||
9d06f603 WB |
115 | __PACKAGE__->register_method({ |
116 | name => 'delete_tfa', | |
117 | path => 'delete_tfa', | |
118 | method => 'PUT', | |
119 | description => 'Delete TFA entries from a user.', | |
120 | parameters => { | |
121 | additionalProperties => 0, | |
122 | properties => { | |
123 | userid => get_standard_option('userid'), | |
124 | id => { | |
125 | description => "The TFA ID, if none provided, all TFA entries will be deleted.", | |
126 | type => 'string', | |
127 | optional => 1, | |
128 | }, | |
129 | }, | |
130 | }, | |
131 | returns => { type => 'null' }, | |
132 | code => sub { | |
133 | my ($param) = @_; | |
134 | ||
135 | my $userid = extract_param($param, "userid"); | |
136 | my $tfa_id = extract_param($param, "id"); | |
137 | ||
9d06f603 WB |
138 | PVE::AccessControl::lock_tfa_config(sub { |
139 | my $tfa_cfg = cfs_read_file('priv/tfa.cfg'); | |
140 | if (defined($tfa_id)) { | |
141 | $tfa_cfg->api_delete_tfa($userid, $tfa_id); | |
142 | } else { | |
143 | $tfa_cfg->remove_user($userid); | |
144 | } | |
145 | cfs_write_file('priv/tfa.cfg', $tfa_cfg); | |
146 | }); | |
147 | return; | |
148 | }}); | |
149 | ||
9d299603 WB |
150 | __PACKAGE__->register_method({ |
151 | name => 'list_tfa', | |
152 | path => 'list_tfa', | |
153 | method => 'GET', | |
154 | description => "List TFA entries.", | |
155 | parameters => { | |
156 | additionalProperties => 0, | |
157 | properties => { | |
158 | userid => get_standard_option('userid', { optional => 1 }), | |
159 | }, | |
160 | }, | |
161 | returns => { type => 'null' }, | |
162 | code => sub { | |
163 | my ($param) = @_; | |
164 | ||
165 | my $userid = extract_param($param, "userid"); | |
166 | ||
9d299603 WB |
167 | my sub format_tfa_entries : prototype($;$) { |
168 | my ($entries, $indent) = @_; | |
169 | ||
170 | $indent //= ''; | |
171 | ||
172 | my $nl = ''; | |
173 | for my $entry (@$entries) { | |
174 | my ($id, $ty, $desc) = ($entry->@{qw/id type description/}); | |
c200d9fd | 175 | printf("${nl}${indent}%-9s %s\n${indent} %s\n", "$ty:", $id, $desc // ''); |
9d299603 WB |
176 | $nl = "\n"; |
177 | } | |
178 | }; | |
179 | ||
180 | my $tfa_cfg = cfs_read_file('priv/tfa.cfg'); | |
181 | if (defined($userid)) { | |
182 | format_tfa_entries($tfa_cfg->api_list_user_tfa($userid)); | |
183 | } else { | |
184 | my $result = $tfa_cfg->api_list_tfa('', 1); | |
185 | my $nl = ''; | |
aba03c96 | 186 | for my $entry (sort { $a->{userid} cmp $b->{userid} } @$result) { |
9d299603 WB |
187 | print "${nl}$entry->{userid}:\n"; |
188 | format_tfa_entries($entry->{entries}, ' '); | |
189 | $nl = "\n"; | |
190 | } | |
191 | } | |
192 | return; | |
193 | }}); | |
194 | ||
09281ad7 | 195 | our $cmddef = { |
1e41cdc9 PA |
196 | user => { |
197 | add => [ 'PVE::API2::User', 'create_user', ['userid'] ], | |
198 | modify => [ 'PVE::API2::User', 'update_user', ['userid'] ], | |
199 | delete => [ 'PVE::API2::User', 'delete_user', ['userid'] ], | |
369851ac | 200 | list => [ 'PVE::API2::User', 'index', [], {}, $print_api_result, $PVE::RESTHandler::standard_output_options], |
66d1b615 | 201 | permissions => [ 'PVE::API2::AccessControl', 'permissions', ['userid'], {}, $print_perm_result, $PVE::RESTHandler::standard_output_options], |
618d112b | 202 | tfa => { |
9d06f603 | 203 | delete => [ __PACKAGE__, 'delete_tfa', ['userid'] ], |
9d299603 | 204 | list => [ __PACKAGE__, 'list_tfa', ['userid'] ], |
ddf78dfb | 205 | unlock => [ 'PVE::API2::User', 'unlock_tfa', ['userid'] ], |
618d112b | 206 | }, |
084c149a FG |
207 | token => { |
208 | add => [ 'PVE::API2::User', 'generate_token', ['userid', 'tokenid'], {}, $print_api_result, $PVE::RESTHandler::standard_output_options ], | |
ccaecac1 | 209 | modify => [ 'PVE::API2::User', 'update_token_info', ['userid', 'tokenid'], {}, $print_api_result, $PVE::RESTHandler::standard_output_options ], |
084c149a FG |
210 | remove => [ 'PVE::API2::User', 'remove_token', ['userid', 'tokenid'], {}, $print_api_result, $PVE::RESTHandler::standard_output_options ], |
211 | list => [ 'PVE::API2::User', 'token_index', ['userid'], {}, $print_api_result, $PVE::RESTHandler::standard_output_options], | |
66d1b615 | 212 | permissions => [ __PACKAGE__, 'token_permissions', ['userid', 'tokenid'], {}, $print_perm_result, $PVE::RESTHandler::standard_output_options], |
084c149a | 213 | } |
1e41cdc9 PA |
214 | }, |
215 | group => { | |
216 | add => [ 'PVE::API2::Group', 'create_group', ['groupid'] ], | |
217 | modify => [ 'PVE::API2::Group', 'update_group', ['groupid'] ], | |
218 | delete => [ 'PVE::API2::Group', 'delete_group', ['groupid'] ], | |
369851ac | 219 | list => [ 'PVE::API2::Group', 'index', [], {}, $print_api_result, $PVE::RESTHandler::standard_output_options], |
1e41cdc9 PA |
220 | }, |
221 | role => { | |
222 | add => [ 'PVE::API2::Role', 'create_role', ['roleid'] ], | |
223 | modify => [ 'PVE::API2::Role', 'update_role', ['roleid'] ], | |
224 | delete => [ 'PVE::API2::Role', 'delete_role', ['roleid'] ], | |
369851ac | 225 | list => [ 'PVE::API2::Role', 'index', [], {}, $print_api_result, $PVE::RESTHandler::standard_output_options], |
1e41cdc9 PA |
226 | }, |
227 | acl => { | |
228 | modify => [ 'PVE::API2::ACL', 'update_acl', ['path'], { delete => 0 }], | |
229 | delete => [ 'PVE::API2::ACL', 'update_acl', ['path'], { delete => 1 }], | |
369851ac | 230 | list => [ 'PVE::API2::ACL', 'read_acl', [], {}, $print_api_result, $PVE::RESTHandler::standard_output_options], |
1e41cdc9 | 231 | }, |
f28a69a0 DC |
232 | realm => { |
233 | add => [ 'PVE::API2::Domains', 'create', ['realm'] ], | |
234 | modify => [ 'PVE::API2::Domains', 'update', ['realm'] ], | |
235 | delete => [ 'PVE::API2::Domains', 'delete', ['realm'] ], | |
236 | list => [ 'PVE::API2::Domains', 'index', [], {}, $print_api_result, $PVE::RESTHandler::standard_output_options], | |
673d2bf2 | 237 | sync => [ 'PVE::API2::Domains', 'sync', ['realm'], ], |
f28a69a0 DC |
238 | }, |
239 | ||
09281ad7 DM |
240 | ticket => [ 'PVE::API2::AccessControl', 'create_ticket', ['username'], undef, |
241 | sub { | |
242 | my ($res) = @_; | |
243 | print "$res->{ticket}\n"; | |
244 | }], | |
245 | ||
765305e2 | 246 | passwd => [ 'PVE::API2::AccessControl', 'change_password', ['userid'] ], |
09281ad7 | 247 | |
1e41cdc9 PA |
248 | useradd => { alias => 'user add' }, |
249 | usermod => { alias => 'user modify' }, | |
250 | userdel => { alias => 'user delete' }, | |
09281ad7 | 251 | |
1e41cdc9 PA |
252 | groupadd => { alias => 'group add' }, |
253 | groupmod => { alias => 'group modify' }, | |
254 | groupdel => { alias => 'group delete' }, | |
09281ad7 | 255 | |
1e41cdc9 PA |
256 | roleadd => { alias => 'role add' }, |
257 | rolemod => { alias => 'role modify' }, | |
258 | roledel => { alias => 'role delete' }, | |
09281ad7 | 259 | |
1e41cdc9 PA |
260 | aclmod => { alias => 'acl modify' }, |
261 | acldel => { alias => 'acl delete' }, | |
09281ad7 DM |
262 | }; |
263 | ||
3470fad8 TL |
264 | # FIXME: HACK! The pool API is in pve-manager as it needs access to storage guest and RRD stats, |
265 | # so we only add the pool commands if the API module is available (required for boots-trapping) | |
266 | my $have_pool_api; | |
267 | eval { | |
268 | require PVE::API2::Pool; | |
269 | PVE::API2::Pool->import(); | |
270 | $have_pool_api = 1; | |
271 | }; | |
272 | ||
273 | if ($have_pool_api) { | |
274 | $cmddef->{pool} = { | |
275 | add => [ 'PVE::API2::Pool', 'create_pool', ['poolid'] ], | |
276 | modify => [ 'PVE::API2::Pool', 'update_pool', ['poolid'] ], | |
277 | delete => [ 'PVE::API2::Pool', 'delete_pool', ['poolid'] ], | |
278 | list => [ 'PVE::API2::Pool', 'index', [], {}, $print_api_result, $PVE::RESTHandler::standard_output_options], | |
279 | }; | |
280 | } | |
281 | ||
09281ad7 | 282 | 1; |