]> git.proxmox.com Git - pve-access-control.git/blob - src/PVE/API2/AccessControl/RealmSync.pm
realm sync: cleanup imports
[pve-access-control.git] / src / PVE / API2 / AccessControl / RealmSync.pm
1 package PVE::API2::AccessControl::RealmSync;
2
3 use strict;
4 use warnings;
5
6
7 use PVE::Cluster qw(cfs_lock_file cfs_read_file cfs_write_file);
8 use PVE::Exception qw(raise_param_exc);
9 use PVE::JSONSchema qw(get_standard_option);
10 use PVE::Job::Registry ();
11 use PVE::SectionConfig ();
12 use PVE::Tools qw(extract_param);
13
14 use PVE::Jobs::RealmSync ();
15
16 use base qw(PVE::RESTHandler);
17
18 my $get_cluster_last_run = sub {
19 my ($jobid) = @_;
20
21 my $state = eval { PVE::Jobs::RealmSync::get_state($jobid) };
22 die "error on getting state for '$jobid': $@\n" if $@;
23
24 if (my $upid = $state->{upid}) {
25 if (my $decoded = PVE::Tools::upid_decode($upid)) {
26 return $decoded->{starttime};
27 }
28 } else {
29 return $state->{time};
30 }
31
32 return undef;
33 };
34
35 __PACKAGE__->register_method ({
36 name => 'syncjob_index',
37 path => '',
38 method => 'GET',
39 description => "List configured realm-sync-jobs.",
40 permissions => {
41 check => ['perm', '/', ['Sys.Audit']],
42 },
43 parameters => {
44 additionalProperties => 0,
45 properties => {},
46 },
47 returns => {
48 type => 'array',
49 items => {
50 type => "object",
51 properties => {
52 id => {
53 description => "The ID of the entry.",
54 type => 'string'
55 },
56 enabled => {
57 description => "If the job is enabled or not.",
58 type => 'boolean',
59 },
60 comment => {
61 description => "A comment for the job.",
62 type => 'string',
63 optional => 1,
64 },
65 schedule => {
66 description => "The configured sync schedule.",
67 type => 'string',
68 },
69 realm => get_standard_option('realm'),
70 scope => get_standard_option('sync-scope'),
71 'remove-vanished' => get_standard_option('sync-remove-vanished'),
72 'last-run' => {
73 description => "Last execution time of the job in seconds since the beginning of the UNIX epoch",
74 type => 'integer',
75 optional => 1,
76 },
77 'next-run' => {
78 description => "Next planned execution time of the job in seconds since the beginning of the UNIX epoch.",
79 type => 'integer',
80 optional => 1,
81 },
82 },
83 },
84 links => [ { rel => 'child', href => "{id}" } ],
85 },
86 code => sub {
87 my ($param) = @_;
88
89 my $rpcenv = PVE::RPCEnvironment::get();
90 my $user = $rpcenv->get_user();
91
92 my $jobs_data = cfs_read_file('jobs.cfg');
93 my $order = $jobs_data->{order};
94 my $jobs = $jobs_data->{ids};
95
96 my $res = [];
97 for my $jobid (sort { $order->{$a} <=> $order->{$b} } keys %$jobs) {
98 my $job = $jobs->{$jobid};
99 next if $job->{type} ne 'realm-sync';
100
101 $job->{id} = $jobid;
102 if (my $schedule = $job->{schedule}) {
103 $job->{'last-run'} = eval { $get_cluster_last_run->($jobid) };
104 my $last_run = $job->{'last-run'} // time(); # current time as fallback
105 my $calendar_event = Proxmox::RS::CalendarEvent->new($schedule);
106 my $next_run = $calendar_event->compute_next_event($last_run);
107 $job->{'next-run'} = $next_run if defined($next_run);
108 }
109
110 push @$res, $job;
111 }
112
113 return $res;
114 }});
115
116 __PACKAGE__->register_method({
117 name => 'read_job',
118 path => '{id}',
119 method => 'GET',
120 description => "Read realm-sync job definition.",
121 permissions => {
122 check => ['perm', '/', ['Sys.Audit']],
123 },
124 parameters => {
125 additionalProperties => 0,
126 properties => {
127 id => {
128 type => 'string',
129 format => 'pve-configid',
130 },
131 },
132 },
133 returns => {
134 type => 'object',
135 },
136 code => sub {
137 my ($param) = @_;
138
139 my $jobs = cfs_read_file('jobs.cfg');
140 my $id = $param->{id};
141 my $job = $jobs->{ids}->{$id};
142 return $job if $job && $job->{type} eq 'realm-sync';
143
144 raise_param_exc({ id => "No such job '$id'" });
145
146 }});
147
148 __PACKAGE__->register_method({
149 name => 'create_job',
150 path => '{id}',
151 method => 'POST',
152 protected => 1,
153 description => "Create new realm-sync job.",
154 permissions => {
155 description => "'Realm.AllocateUser' on '/access/realm/<realm>' and "
156 ."'User.Modify' permissions to '/access/groups/'.",
157 check => [ 'and',
158 ['perm', '/access/realm/{realm}', ['Realm.AllocateUser']],
159 ['perm', '/access/groups', ['User.Modify']],
160 ],
161 },
162 parameters => PVE::Jobs::RealmSync->createSchema(),
163 returns => { type => 'null' },
164 code => sub {
165 my ($param) = @_;
166
167 my $id = extract_param($param, 'id');
168
169 cfs_lock_file('jobs.cfg', undef, sub {
170 my $data = cfs_read_file('jobs.cfg');
171
172 die "Job '$id' already exists\n"
173 if $data->{ids}->{$id};
174
175 my $plugin = PVE::Job::Registry->lookup('realm-sync');
176 my $opts = $plugin->check_config($id, $param, 1, 1);
177
178 my $realm = $opts->{realm};
179 my $cfg = cfs_read_file('domains.cfg');
180
181 raise_param_exc({ realm => "No such realm '$realm'" })
182 if !defined($cfg->{ids}->{$realm});
183
184 my $realm_type = $cfg->{ids}->{$realm}->{type};
185 raise_param_exc({ realm => "Only LDAP/AD realms can be synced." })
186 if $realm_type ne 'ldap' && $realm_type ne 'ad';
187
188 $data->{ids}->{$id} = $opts;
189
190 cfs_write_file('jobs.cfg', $data);
191 });
192 die "$@" if ($@);
193
194 return undef;
195 }});
196
197 __PACKAGE__->register_method({
198 name => 'update_job',
199 path => '{id}',
200 method => 'PUT',
201 protected => 1,
202 description => "Update realm-sync job definition.",
203 permissions => {
204 description => "'Realm.AllocateUser' on '/access/realm/<realm>' and "
205 ." 'User.Modify' permissions to '/access/groups/'.",
206 check => [ 'and',
207 ['perm', '/access/realm/{realm}', ['Realm.AllocateUser']],
208 ['perm', '/access/groups', ['User.Modify']],
209 ],
210 },
211 parameters => PVE::Jobs::RealmSync->updateSchema(),
212 returns => { type => 'null' },
213 code => sub {
214 my ($param) = @_;
215
216 my $id = extract_param($param, 'id');
217 my $delete = extract_param($param, 'delete');
218 if ($delete) {
219 $delete = [PVE::Tools::split_list($delete)];
220 }
221
222 cfs_lock_file('jobs.cfg', undef, sub {
223 my $jobs = cfs_read_file('jobs.cfg');
224
225 die "no options specified\n" if !scalar(keys %$param);
226
227 my $plugin = PVE::Job::Registry->lookup('realm-sync');
228 my $opts = $plugin->check_config($id, $param, 0, 1);
229
230 my $job = $jobs->{ids}->{$id};
231 die "no such realm-sync job\n" if !$job || $job->{type} ne 'realm-sync';
232
233 my $options = $plugin->options();
234 PVE::SectionConfig::delete_from_config($job, $options, $opts, $delete);
235
236 $job->{$_} = $param->{$_} for keys $param->%*;
237
238 cfs_write_file('jobs.cfg', $jobs);
239
240 return;
241 });
242 die "$@" if ($@);
243 }});
244
245
246 __PACKAGE__->register_method({
247 name => 'delete_job',
248 path => '{id}',
249 method => 'DELETE',
250 description => "Delete realm-sync job definition.",
251 permissions => {
252 check => ['perm', '/', ['Sys.Modify']],
253 },
254 protected => 1,
255 parameters => {
256 additionalProperties => 0,
257 properties => {
258 id => {
259 type => 'string',
260 format => 'pve-configid',
261 },
262 },
263 },
264 returns => { type => 'null' },
265 code => sub {
266 my ($param) = @_;
267
268 my $id = $param->{id};
269
270 cfs_lock_file('jobs.cfg', undef, sub {
271 my $jobs = cfs_read_file('jobs.cfg');
272
273 if (!defined($jobs->{ids}->{$id}) || $jobs->{ids}->{$id}->{type} ne 'realm-sync') {
274 raise_param_exc({ id => "No such job '$id'" });
275 }
276 delete $jobs->{ids}->{$id};
277
278 cfs_write_file('jobs.cfg', $jobs);
279 PVE::Jobs::RealmSync::save_state($id, undef);
280 });
281 die "$@" if $@;
282
283 return undef;
284 }});
285
286 1;