]> git.proxmox.com Git - pve-access-control.git/blob - src/PVE/API2/AccessControl/RealmSync.pm
f5334b8bdb68e51c8e55a7e072fbae06c3f2f994
[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
106 my $calendar_event = Proxmox::RS::CalendarEvent->new($schedule);
107 my $next_run = $calendar_event->compute_next_event($last_run);
108 $job->{'next-run'} = $next_run if defined($next_run);
109 }
110
111 push @$res, $job;
112 }
113
114 return $res;
115 }});
116
117 __PACKAGE__->register_method({
118 name => 'read_job',
119 path => '{id}',
120 method => 'GET',
121 description => "Read realm-sync job definition.",
122 permissions => {
123 check => ['perm', '/', ['Sys.Audit']],
124 },
125 parameters => {
126 additionalProperties => 0,
127 properties => {
128 id => {
129 type => 'string',
130 format => 'pve-configid',
131 },
132 },
133 },
134 returns => {
135 type => 'object',
136 },
137 code => sub {
138 my ($param) = @_;
139
140 my $jobs = cfs_read_file('jobs.cfg');
141 my $id = $param->{id};
142 my $job = $jobs->{ids}->{$id};
143 return $job if $job && $job->{type} eq 'realm-sync';
144
145 raise_param_exc({ id => "No such job '$id'" });
146
147 }});
148
149 __PACKAGE__->register_method({
150 name => 'create_job',
151 path => '{id}',
152 method => 'POST',
153 protected => 1,
154 description => "Create new realm-sync job.",
155 permissions => {
156 description => "'Realm.AllocateUser' on '/access/realm/<realm>' and "
157 ."'User.Modify' permissions to '/access/groups/'.",
158 check => [ 'and',
159 ['perm', '/access/realm/{realm}', ['Realm.AllocateUser']],
160 ['perm', '/access/groups', ['User.Modify']],
161 ],
162 },
163 parameters => PVE::Jobs::RealmSync->createSchema(),
164 returns => { type => 'null' },
165 code => sub {
166 my ($param) = @_;
167
168 my $id = extract_param($param, 'id');
169
170 cfs_lock_file('jobs.cfg', undef, sub {
171 my $data = cfs_read_file('jobs.cfg');
172
173 die "Job '$id' already exists\n"
174 if $data->{ids}->{$id};
175
176 my $plugin = PVE::Job::Registry->lookup('realm-sync');
177 my $opts = $plugin->check_config($id, $param, 1, 1);
178
179 my $realm = $opts->{realm};
180 my $cfg = cfs_read_file('domains.cfg');
181
182 raise_param_exc({ realm => "No such realm '$realm'" })
183 if !defined($cfg->{ids}->{$realm});
184
185 my $realm_type = $cfg->{ids}->{$realm}->{type};
186 raise_param_exc({ realm => "Only LDAP/AD realms can be synced." })
187 if $realm_type ne 'ldap' && $realm_type ne 'ad';
188
189 $data->{ids}->{$id} = $opts;
190
191 cfs_write_file('jobs.cfg', $data);
192 });
193 die "$@" if ($@);
194
195 return undef;
196 }});
197
198 __PACKAGE__->register_method({
199 name => 'update_job',
200 path => '{id}',
201 method => 'PUT',
202 protected => 1,
203 description => "Update realm-sync job definition.",
204 permissions => {
205 description => "'Realm.AllocateUser' on '/access/realm/<realm>' and 'User.Modify'"
206 ." permissions to '/access/groups/'.",
207 check => [ 'and',
208 ['perm', '/access/realm/{realm}', ['Realm.AllocateUser']],
209 ['perm', '/access/groups', ['User.Modify']],
210 ],
211 },
212 parameters => PVE::Jobs::RealmSync->updateSchema(),
213 returns => { type => 'null' },
214 code => sub {
215 my ($param) = @_;
216
217 my $id = extract_param($param, 'id');
218 my $delete = extract_param($param, 'delete');
219 $delete = [PVE::Tools::split_list($delete)] if $delete;
220
221 cfs_lock_file('jobs.cfg', undef, sub {
222 my $jobs = cfs_read_file('jobs.cfg');
223
224 die "no options specified\n" if !scalar(keys %$param);
225
226 my $plugin = PVE::Job::Registry->lookup('realm-sync');
227 my $opts = $plugin->check_config($id, $param, 0, 1);
228
229 my $job = $jobs->{ids}->{$id};
230 die "no such realm-sync job\n" if !$job || $job->{type} ne 'realm-sync';
231
232 my $options = $plugin->options();
233 PVE::SectionConfig::delete_from_config($job, $options, $opts, $delete);
234
235 $job->{$_} = $param->{$_} for keys $param->%*;
236
237 cfs_write_file('jobs.cfg', $jobs);
238
239 return;
240 });
241 die "$@" if ($@);
242 }});
243
244
245 __PACKAGE__->register_method({
246 name => 'delete_job',
247 path => '{id}',
248 method => 'DELETE',
249 description => "Delete realm-sync job definition.",
250 permissions => {
251 check => ['perm', '/', ['Sys.Modify']],
252 },
253 protected => 1,
254 parameters => {
255 additionalProperties => 0,
256 properties => {
257 id => {
258 type => 'string',
259 format => 'pve-configid',
260 },
261 },
262 },
263 returns => { type => 'null' },
264 code => sub {
265 my ($param) = @_;
266
267 my $id = $param->{id};
268
269 cfs_lock_file('jobs.cfg', undef, sub {
270 my $jobs = cfs_read_file('jobs.cfg');
271
272 if (!defined($jobs->{ids}->{$id}) || $jobs->{ids}->{$id}->{type} ne 'realm-sync') {
273 raise_param_exc({ id => "No such job '$id'" });
274 }
275 delete $jobs->{ids}->{$id};
276
277 cfs_write_file('jobs.cfg', $jobs);
278 PVE::Jobs::RealmSync::save_state($id, undef);
279 });
280 die "$@" if $@;
281
282 return undef;
283 }});
284
285 1;