]> git.proxmox.com Git - pve-access-control.git/blob - src/PVE/API2/Jobs/RealmSync.pm
bump version to 8.1.4
[pve-access-control.git] / src / PVE / API2 / Jobs / RealmSync.pm
1 package PVE::API2::Jobs::RealmSync;
2
3 use strict;
4 use warnings;
5
6 use PVE::Cluster qw(cfs_lock_file cfs_read_file cfs_write_file);
7 use PVE::Exception qw(raise_param_exc);
8 use PVE::JSONSchema qw(get_standard_option);
9 use PVE::Job::Registry ();
10 use PVE::SectionConfig ();
11 use PVE::Tools qw(extract_param);
12
13 use PVE::Jobs::RealmSync ();
14
15 use base qw(PVE::RESTHandler);
16
17 my $get_cluster_last_run = sub {
18 my ($jobid) = @_;
19
20 my $state = eval { PVE::Jobs::RealmSync::get_state($jobid) };
21 die "error on getting state for '$jobid': $@\n" if $@;
22
23 if (my $upid = $state->{upid}) {
24 if (my $decoded = PVE::Tools::upid_decode($upid)) {
25 return $decoded->{starttime};
26 }
27 } else {
28 return $state->{time};
29 }
30
31 return undef;
32 };
33
34 __PACKAGE__->register_method ({
35 name => 'syncjob_index',
36 path => '',
37 method => 'GET',
38 description => "List configured realm-sync-jobs.",
39 permissions => {
40 check => ['perm', '/', ['Sys.Audit']],
41 },
42 parameters => {
43 additionalProperties => 0,
44 properties => {},
45 },
46 returns => {
47 type => 'array',
48 items => {
49 type => "object",
50 properties => {
51 id => {
52 description => "The ID of the entry.",
53 type => 'string'
54 },
55 enabled => {
56 description => "If the job is enabled or not.",
57 type => 'boolean',
58 },
59 comment => {
60 description => "A comment for the job.",
61 type => 'string',
62 optional => 1,
63 },
64 schedule => {
65 description => "The configured sync schedule.",
66 type => 'string',
67 },
68 realm => get_standard_option('realm'),
69 scope => get_standard_option('sync-scope'),
70 'remove-vanished' => get_standard_option('sync-remove-vanished'),
71 'last-run' => {
72 description => "Last execution time of the job in seconds since the beginning of the UNIX epoch",
73 type => 'integer',
74 optional => 1,
75 },
76 'next-run' => {
77 description => "Next planned execution time of the job in seconds since the beginning of the UNIX epoch.",
78 type => 'integer',
79 optional => 1,
80 },
81 },
82 },
83 links => [ { rel => 'child', href => "{id}" } ],
84 },
85 code => sub {
86 my ($param) = @_;
87
88 my $rpcenv = PVE::RPCEnvironment::get();
89 my $user = $rpcenv->get_user();
90
91 my $jobs_data = cfs_read_file('jobs.cfg');
92 my $order = $jobs_data->{order};
93 my $jobs = $jobs_data->{ids};
94
95 my $res = [];
96 for my $jobid (sort { $order->{$a} <=> $order->{$b} } keys %$jobs) {
97 my $job = $jobs->{$jobid};
98 next if $job->{type} ne 'realm-sync';
99
100 $job->{id} = $jobid;
101 if (my $schedule = $job->{schedule}) {
102 $job->{'last-run'} = eval { $get_cluster_last_run->($jobid) };
103 my $last_run = $job->{'last-run'} // time(); # current time as fallback
104
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 'User.Modify'"
205 ." 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 $delete = [PVE::Tools::split_list($delete)] if $delete;
219
220 die "no job options specified\n" if !scalar(keys %$param);
221
222 cfs_lock_file('jobs.cfg', undef, sub {
223 my $jobs = cfs_read_file('jobs.cfg');
224
225 my $plugin = PVE::Job::Registry->lookup('realm-sync');
226 my $opts = $plugin->check_config($id, $param, 0, 1);
227
228 my $job = $jobs->{ids}->{$id};
229 die "no such realm-sync job\n" if !$job || $job->{type} ne 'realm-sync';
230
231 my $options = $plugin->options();
232 PVE::SectionConfig::delete_from_config($job, $options, $opts, $delete);
233
234 $job->{$_} = $param->{$_} for keys $param->%*;
235
236 cfs_write_file('jobs.cfg', $jobs);
237
238 return;
239 });
240 die "$@" if ($@);
241 }});
242
243
244 __PACKAGE__->register_method({
245 name => 'delete_job',
246 path => '{id}',
247 method => 'DELETE',
248 description => "Delete realm-sync job definition.",
249 permissions => {
250 check => ['perm', '/', ['Sys.Modify']],
251 },
252 protected => 1,
253 parameters => {
254 additionalProperties => 0,
255 properties => {
256 id => {
257 type => 'string',
258 format => 'pve-configid',
259 },
260 },
261 },
262 returns => { type => 'null' },
263 code => sub {
264 my ($param) = @_;
265
266 my $id = $param->{id};
267
268 cfs_lock_file('jobs.cfg', undef, sub {
269 my $jobs = cfs_read_file('jobs.cfg');
270
271 if (!defined($jobs->{ids}->{$id}) || $jobs->{ids}->{$id}->{type} ne 'realm-sync') {
272 raise_param_exc({ id => "No such job '$id'" });
273 }
274 delete $jobs->{ids}->{$id};
275
276 cfs_write_file('jobs.cfg', $jobs);
277 PVE::Jobs::RealmSync::save_state($id, undef);
278 });
279 die "$@" if $@;
280
281 return undef;
282 }});
283
284 1;