]>
Commit | Line | Data |
---|---|---|
dc982c4b DC |
1 | package PVE::API2::AccessControl::RealmSync; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
dc982c4b | 6 | |
51ae7bbb | 7 | use PVE::Cluster qw(cfs_lock_file cfs_read_file cfs_write_file); |
dc982c4b DC |
8 | use PVE::Exception qw(raise_param_exc); |
9 | use PVE::JSONSchema qw(get_standard_option); | |
51ae7bbb TL |
10 | use PVE::Job::Registry (); |
11 | use PVE::SectionConfig (); | |
dc982c4b DC |
12 | use PVE::Tools qw(extract_param); |
13 | ||
51ae7bbb | 14 | use PVE::Jobs::RealmSync (); |
dc982c4b DC |
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 | |
c0210e3c | 105 | |
dc982c4b DC |
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 => { | |
c0210e3c TL |
205 | description => "'Realm.AllocateUser' on '/access/realm/<realm>' and 'User.Modify'" |
206 | ." permissions to '/access/groups/'.", | |
dc982c4b DC |
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'); | |
c0210e3c | 219 | $delete = [PVE::Tools::split_list($delete)] if $delete; |
dc982c4b DC |
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; |