File Coverage

blib/lib/App/Dochazka/REST/Shared.pm
Criterion Covered Total %
statement 54 361 14.9
branch 0 138 0.0
condition 0 21 0.0
subroutine 18 40 45.0
pod 22 22 100.0
total 94 582 16.1


line stmt bran cond sub pod time code
1             # *************************************************************************
2             # Copyright (c) 2014-2017, SUSE LLC
3             #
4             # All rights reserved.
5             #
6             # Redistribution and use in source and binary forms, with or without
7             # modification, are permitted provided that the following conditions are met:
8             #
9             # 1. Redistributions of source code must retain the above copyright notice,
10             # this list of conditions and the following disclaimer.
11             #
12             # 2. Redistributions in binary form must reproduce the above copyright
13             # notice, this list of conditions and the following disclaimer in the
14             # documentation and/or other materials provided with the distribution.
15             #
16             # 3. Neither the name of SUSE LLC nor the names of its contributors may be
17             # used to endorse or promote products derived from this software without
18             # specific prior written permission.
19             #
20             # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21             # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22             # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23             # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
24             # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25             # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26             # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27             # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28             # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29             # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30             # POSSIBILITY OF SUCH DAMAGE.
31             # *************************************************************************
32              
33             # ------------------------
34             # Shared dispatch functions
35             # ------------------------
36              
37             package App::Dochazka::REST::Shared;
38              
39 41     41   698 use strict;
  41         96  
  41         1180  
40 41     41   198 use warnings;
  41         82  
  41         1490  
41              
42 41     41   479 use App::CELL qw( $CELL $log $site );
  41         59458  
  41         5604  
43 41     41   595 use App::Dochazka::REST::ACL qw( acl_check_is_me acl_check_is_my_report );
  41         95  
  41         1809  
44 41     41   577 use App::Dochazka::REST::ConnBank qw( conn_status );
  41         86  
  41         1420  
45 41     41   500 use App::Dochazka::REST::Model::Activity;
  41         88  
  41         1155  
46 41     41   196 use App::Dochazka::REST::Model::Employee;
  41         83  
  41         1168  
47 41     41   522 use App::Dochazka::REST::Model::Interval;
  41         79  
  41         1154  
48 41     41   210 use App::Dochazka::REST::Model::Lock;
  41         81  
  41         1059  
49 41     41   486 use App::Dochazka::REST::Model::Privhistory;
  41         72  
  41         1097  
50 41     41   456 use App::Dochazka::REST::Model::Schedhistory;
  41         82  
  41         1077  
51 41     41   474 use App::Dochazka::REST::Model::Schedule;
  41         74  
  41         1184  
52 41     41   206 use App::Dochazka::REST::Model::Shared qw( priv_by_eid schedule_by_eid );
  41         78  
  41         1722  
53 41     41   10666 use App::Dochazka::REST::Util qw( hash_the_password pre_update_comparison );
  41         131  
  41         2871  
54 41     41   291 use Data::Dumper;
  41         120  
  41         1823  
55 41     41   273 use Params::Validate qw( :all );
  41         91  
  41         7433  
56 41     41   288 use Try::Tiny;
  41         102  
  41         2572  
57              
58             my $fail = $CELL->status_not_ok;
59              
60              
61             =head1 NAME
62              
63             App::Dochazka::REST::Dispatch::Shared - Shared dispatch functions
64              
65              
66              
67              
68             =head1 DESCRIPTION
69              
70             This module provides code that is, or may be, used by more than one resource
71             handler method.
72              
73              
74              
75              
76             =head1 EXPORTS
77              
78             =cut
79              
80 41     41   239 use Exporter qw( import );
  41         90  
  41         130233  
81             our @EXPORT_OK = qw(
82             shared_first_pass_lookup
83             shared_entity_check
84             shared_get_employee
85             shared_get_employee_pass1
86             shared_insert_employee
87             shared_update_employee
88             shared_update_schedule
89             shared_get_class_prop_id
90             shared_history_init
91             shared_get_privsched
92             shared_employee_acl_part1
93             shared_employee_acl_part2
94             shared_update_activity
95             shared_update_component
96             shared_update_history
97             shared_insert_activity
98             shared_insert_component
99             shared_insert_interval
100             shared_insert_lock
101             shared_update_intlock
102             shared_process_quals
103             );
104             our %EXPORT_TAGS = ( ALL => [ @EXPORT_OK ] );
105              
106              
107             =head1 PACKAGE VARIABLES
108              
109             The package variable C<%f_dispatch> is used in C<fetch_by_eid>, C<fetch_by_nick>,
110             and C<fetch_own>.
111              
112             =cut
113              
114             my %f_dispatch = (
115             "attendance" => \&App::Dochazka::REST::Model::Interval::fetch_by_eid_and_tsrange,
116             "lock" => \&App::Dochazka::REST::Model::Lock::fetch_by_eid_and_tsrange,
117             );
118             my %id_dispatch = (
119             "attendance" => "App::Dochazka::REST::Model::Interval",
120             "lock" => "App::Dochazka::REST::Model::Lock",
121             );
122              
123              
124             =head1 FUNCTIONS
125              
126             =cut
127              
128             =head2 shared_first_pass_lookup
129              
130             Takes two scalar arguments, "key" and "value" and determines whether or not the
131             database contains an object answering to that description.
132              
133             This should be used only for resources that require an exact match.
134              
135             =cut
136              
137             sub shared_first_pass_lookup {
138 0     0 1   my ( $d_obj, $key, $value ) = @_;
139 0           $log->debug( "Entering " . __PACKAGE__ . "::shared_first_pass_lookup with key $key, value $value" );
140              
141 0           my $conn = $d_obj->context->{'dbix_conn'};
142 0           my ( $status, $thing );
143              
144 0 0         if ( uc($key) eq 'AID' ) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
145 0           $thing = 'activity';
146 0           $status = App::Dochazka::REST::Model::Activity->load_by_aid( $conn, $value );
147             } elsif ( $key eq 'code' ) {
148 0           $thing = 'activity';
149 0           $status = App::Dochazka::REST::Model::Activity->load_by_code( $conn, $value );
150             } elsif ( uc($key) eq 'CID' ) {
151 0           $thing = 'component';
152 0           $status = App::Dochazka::REST::Model::Component->load_by_cid( $conn, $value );
153             } elsif ( $key eq 'path' ) {
154 0           $thing = 'component';
155 0           $status = App::Dochazka::REST::Model::Component->load_by_path( $conn, $value );
156             } elsif ( uc($key) eq 'EID' ) {
157 0           $thing = 'employee';
158 0           $status = App::Dochazka::REST::Model::Employee->load_by_eid( $conn, $value );
159             } elsif ( $key eq 'nick' ) {
160 0           $thing = 'employee';
161 0           $status = App::Dochazka::REST::Model::Employee->load_by_nick( $conn, $value );
162             } elsif ( $key eq 'sec_id' ) {
163 0           $thing = 'employee';
164 0           $status = App::Dochazka::REST::Model::Employee->load_by_sec_id( $conn, $value );
165             } elsif ( uc($key) eq 'IID' ) {
166 0           $thing = 'interval';
167 0           $status = App::Dochazka::REST::Model::Interval->load_by_iid( $conn, $value );
168             } elsif ( uc($key) eq 'LID' ) {
169 0           $thing = 'lock';
170 0           $status = App::Dochazka::REST::Model::Lock->load_by_lid( $conn, $value );
171             } elsif ( uc($key) eq 'PHID' ) {
172 0           $thing = 'privilege history record';
173 0           $status = App::Dochazka::REST::Model::Privhistory->load_by_phid( $conn, $value );
174             } elsif ( uc($key) eq 'SHID' ) {
175 0           $thing = 'schedule history record';
176 0           $status = App::Dochazka::REST::Model::Schedhistory->load_by_shid( $conn, $value );
177             } elsif ( uc($key) eq 'SID' ) {
178 0           $thing = 'schedule';
179 0           $status = App::Dochazka::REST::Model::Schedule->load_by_sid( $conn, $value );
180             } elsif ( $key eq 'scode' ) {
181 0           $thing = 'schedule';
182 0           $status = App::Dochazka::REST::Model::Schedule->load_by_scode( $conn, $value );
183             } else {
184 0           die "shared_first_pass_lookup could not do anything with key $key!";
185             }
186              
187 0 0 0       if ( $status->level eq 'NOTICE' and $status->code eq 'DISPATCH_NO_RECORDS_FOUND' ) {
188 0           $d_obj->mrest_declare_status( code => 404,
189             explanation => 'DISPATCH_SEARCH_EMPTY',
190             args => [ $thing, "$key equals $value" ],
191             );
192 0           return;
193             }
194 0 0         if ( $status->not_ok ) {
195 0           $d_obj->mrest_declare_status( code => 500, explanation => $status->code,
196             args => $status->args
197             );
198 0           return;
199             }
200 0           return $status->payload;
201             }
202              
203              
204             =head2 shared_entity_check
205              
206             Check request entity for presence of properties
207              
208             =cut
209              
210             sub shared_entity_check {
211 0     0 1   my ( $d_obj, @props ) = @_;
212             $log->debug( "Entering " . __PACKAGE__ . "::shared_entity_check with properties " .
213 0           join( ' ', @props ) . " and entity: " . Dumper( $d_obj->context->{'request_entity'} ) );
214              
215 0           my $entity = $d_obj->context->{'request_entity'};
216 0 0         if ( not $entity ) {
217 0           $d_obj->mrest_declare_status( code => 400,
218             explanation => 'DISPATCH_ENTITY_MISSING'
219             );
220 0           return $fail;
221             }
222 0 0         if ( ref( $entity ) ne 'HASH' ) {
223 0           $d_obj->mrest_declare_status( code => 400,
224             explanation => 'DISPATCH_ENTITY_NOT_KEY_VALUE'
225             );
226 0           return $fail;
227             }
228 0           foreach my $p ( @props ) {
229 0 0         if ( not $entity->{$p} ) {
230 0           $d_obj->mrest_declare_status( code => 400,
231             explanation => 'DISPATCH_PROP_MISSING_IN_ENTITY', args => [ $p ]
232             );
233 0           return $fail;
234             }
235             }
236 0           return $CELL->status_ok;
237             }
238              
239              
240             =head2 shared_get_employee_pass1
241              
242             =cut
243              
244             sub shared_get_employee_pass1 {
245 0     0 1   my ( $d_obj, $pass, $key, $value ) = @_;
246 0           $log->debug( "Entering " . __PACKAGE__ . "::shared_get_employee_pass1" );
247              
248             #
249             # ACL checks
250             #
251 0 0 0       if (
252             ! acl_check_is_my_report( $d_obj, ( lc $key ) => $value ) and
253             ! acl_check_is_me( $d_obj, ( lc $key ) => $value )
254             )
255             {
256 0           $d_obj->mrest_declare_status( code => 403, explanation => "DISPATCH_KEEP_TO_YOURSELF" );
257 0           return 0;
258             }
259             #
260             # 404 check
261             #
262 0           my $emp = shared_first_pass_lookup( $d_obj, $key, $value );
263 0 0         return 0 unless $emp;
264 0           $d_obj->context->{'stashed_employee_object'} = $emp;
265 0           return 1;
266             }
267              
268              
269             =head2 shared_get_employee
270              
271             =cut
272              
273             sub shared_get_employee {
274 0     0 1   my ( $d_obj, $pass, $key, $value ) = @_;
275 0           $log->debug( "Entering " . __PACKAGE__ . "::shared_get_employee" );
276              
277             # first pass
278 0 0         if ( $pass == 1 ) {
279 0           return shared_get_employee_pass1(
280             $d_obj, $pass, $key, $value
281             );
282             }
283              
284             # second pass
285             return $CELL->status_ok( 'DISPATCH_EMPLOYEE_FOUND',
286 0           payload => $d_obj->context->{'stashed_employee_object'},
287             );
288             }
289              
290              
291             =head2 shared_update_employee
292              
293             Takes three arguments:
294              
295             - $d_obj is the App::Dochazka::REST::Dispatch object
296             - $emp is an employee object (blessed hashref)
297             - $over is a hashref with zero or more employee properties and new values
298              
299             The values from $over replace those in $emp
300              
301             =cut
302              
303             sub shared_update_employee {
304 0     0 1   my ( $d_obj, $emp, $over ) = @_;
305 0           $log->debug("Entering " . __PACKAGE__ . "::shared_update_employee" );
306 0           $log->debug("Updating employee: " . Dumper( $emp ) );
307 0           $log->debug("With key:value pairs " . Dumper( $over ) );
308              
309             ACL: {
310 0           my $explanation = "Update operations require at least one key:value pair in the request entity";
  0            
311 0 0         if ( ref( $over ) ne 'HASH' ) {
312 0           $d_obj->mrest_declare_status( code => 400, explanation => $explanation );
313 0           return $fail;
314             }
315 0           delete $over->{'eid'};
316 0 0         if ( $over == {} ) {
317 0           $d_obj->mrest_declare_status( code => 400, explanation => $explanation );
318 0           return $fail;
319             }
320             }
321              
322             # for password hashing, we will assume that $over might contain
323             # a 'password' property, which is converted into 'passhash' + 'salt' via
324             # Authen::Passphrase
325 0           hash_the_password( $over );
326              
327 0 0         return $emp->update( $d_obj->context ) if pre_update_comparison( $emp, $over );
328 0           $log->notice( "Update operation would not change database; skipping it" );
329 0           return $CELL->status_ok( 'DISPATCH_UPDATE_NO_CHANGE_OK' );
330             }
331              
332              
333             =head2 shared_insert_employee
334              
335             Called from handlers in L<App::Dochazka::REST::Dispatch>. Takes three arguments:
336              
337             - $d_obj is the App::Dochazka::REST::Dispatch object
338             - $ignore_me will be undef
339             - $new_emp_props is a hashref with employee properties and their values (guaranteed to contain 'nick')
340              
341             =cut
342              
343             sub shared_insert_employee {
344 0     0 1   $log->debug( "Entered " . __PACKAGE__ . "::shared_insert_employee" );
345 0           my ( $d_obj, $ignore_me, $new_emp_props ) = validate_pos( @_,
346             { isa => 'App::Dochazka::REST::Dispatch' },
347             { type => UNDEF },
348             { type => HASHREF },
349             );
350 0           $log->debug( "Arguments are OK, about to insert new employee: " . Dumper( $new_emp_props ) );
351              
352             # If there is a "password" property, transform it into "passhash" + "salt"
353 0           hash_the_password( $new_emp_props );
354              
355             # spawn an object, filtering the properties first
356 0           my @filtered_args = App::Dochazka::Common::Model::Employee::filter( %$new_emp_props );
357 0           my %proplist_after = @filtered_args;
358 0           $log->debug( "Properties after filter: " . join( ' ', keys %proplist_after ) );
359 0           my $emp = App::Dochazka::REST::Model::Employee->spawn( @filtered_args );
360              
361             # execute the INSERT db operation
362 0           return $emp->insert( $d_obj->context );
363             }
364              
365              
366             =head2 shared_update_schedule
367              
368             Takes three arguments:
369              
370             - $d_obj is the dispatch (App::Dochazka::REST::Dispatch) object
371             - $sched is a schedule object (blessed hashref)
372             - $over is a hashref with zero or more schedule properties and new values
373              
374             The values from C<$over> replace those in C<$emp>.
375              
376             =cut
377              
378             sub shared_update_schedule {
379 0     0 1   my ( $d_obj, $sched, $over ) = validate_pos( @_,
380             { isa => 'App::Dochazka::REST::Dispatch' },
381             { isa => 'App::Dochazka::REST::Model::Schedule' },
382             { type => HASHREF },
383             );
384 0           $log->debug("Entering " . __PACKAGE__ . "::shared_update_schedule" );
385              
386 0 0         delete $over->{'sid'} if exists $over->{'sid'};
387 0 0         delete $over->{'schedule'} if exists $over->{'schedule'};
388 0 0         if ( pre_update_comparison( $sched, $over ) ) {
389 0           $log->debug( "After pre_update_comparison: " . Dumper $sched );
390 0           return $sched->update( $d_obj->context );
391             }
392              
393             $d_obj->mrest_declare_status(
394 0           code => 400,
395             explanation => "Cannot update schedule due to invalid input",
396             );
397 0           return $fail;
398             }
399              
400              
401             =head2 shared_get_class_prop_id
402              
403             For 'priv' and 'schedule' resources. Given the request context, extract the
404             first component, which will always be either 'priv' or 'schedule'. Based on
405             that, generate the object class, property name, and ID property name for
406             use in the resource handler.
407              
408             =cut
409              
410             sub shared_get_class_prop_id {
411 0     0 1   my ( $context ) = @_;
412 0           my $class = 'App::Dochazka::REST::Model::';
413 0           my ( $prop, $id );
414 0 0         if ( $context->{'components'}->[0] eq 'priv' ) {
    0          
415 0           $class .= 'Privhistory';
416 0           $prop = 'priv';
417 0           $id = 'phid';
418             } elsif ( $context->{'components'}->[0] eq 'schedule' ) {
419 0           $class .= 'Schedhistory';
420 0           $prop = 'sid';
421 0           $id = 'shid';
422             } else {
423 0           die "AGAGAGAGGAGGGGGAAAAAAAHHHH!!!!!";
424             }
425 0           return ( $class, $prop, $id );
426             }
427              
428              
429             =head2 shared_history_init
430              
431             For 'priv/history' and 'schedule/history' resources. Given the request context,
432             extract or generate values needed by the resource handler.
433              
434             =cut
435              
436             sub shared_history_init {
437 0     0 1   my $context = shift;
438              
439 0           my $method = $context->{'method'};
440 0           $log->debug( "Method is $method" );
441              
442 0           my $mapping = $context->{'mapping'};
443 0           my $tsrange = $mapping->{'tsrange'};
444 0           my $ts = $mapping->{'ts'};
445 0           my ( $key, $value );
446 0 0         if ( defined( my $nick = $mapping->{'nick'} ) ) {
    0          
447 0           $key = 'nick';
448 0           $value = $nick;
449             } elsif ( defined ( my $eid = $mapping->{'eid'} ) ) {
450 0           $key = 'EID';
451 0           $value = $eid;
452             } else {
453 0           die "AAFAAAGAGAGGAGAGGGGGGH! mapping contains neither nick nor eid property: " . Dumper( $mapping );
454             }
455              
456 0           return ( $context, $method, $mapping, $tsrange, $ts, $key, $value );
457             }
458              
459              
460             =head2 shared_get_privsched
461              
462             Shared GET handler for 'priv' and 'schedule' lookups. Takes four arguments:
463              
464             =over
465              
466             =item C<$d_obj> - dispatch object
467              
468             =item C<$t> - either 'priv' or 'schedule'
469              
470             =item C<$pass> - either 1 or 2
471              
472             =item C<$key> - either 'EID' or 'nick'
473              
474             =item C<$value> - EID or nick value to lookup
475              
476             =over
477              
478             =cut
479              
480             sub shared_get_privsched {
481 0     0 1   my ( $d_obj, $t, $pass, $key, $value ) = @_;
482 0           $log->debug( "Entering " . __PACKAGE__ . ":shared_get_privsched" );
483              
484             # first pass
485 0 0         if ( $pass == 1 ) {
486             #
487             # 403 (ACL) check - passerby can only look up him- or herself
488             #
489 0 0         if ( ! acl_check_is_me( $d_obj, ( lc $key ) => $value ) ) {
490 0           $d_obj->mrest_declare_status( code => 403, explanation => "DISPATCH_KEEP_TO_YOURSELF" );
491 0           return 0;
492             }
493             #
494             # 404 check
495             #
496 0           my $emp = shared_first_pass_lookup( $d_obj, $key => $value );
497 0 0         return 0 unless $emp;
498 0           $d_obj->context->{'stashed_employee_object'} = $emp;
499 0           return 1;
500             }
501            
502             # second pass
503              
504             # - initialization
505 0           my $status;
506 0           my %dispatch = (
507             'priv' => \&priv_by_eid,
508             'schedule' => \&schedule_by_eid,
509             );
510 0           my $emp = $d_obj->context->{'stashed_employee_object'};
511 0           my $eid = $emp->eid;
512 0           my $nick = $emp->nick;
513 0           my $ts = $d_obj->context->{'mapping'}->{'ts'};
514 0           my $conn = $d_obj->context->{'dbix_conn'};
515              
516             # - run priv_by_eid or schedule_by_eid, as appropriate
517 0           my $return_value = $dispatch{$t}->( $conn, $eid, $ts );
518              
519             # on success, $return_value will be a SCALAR like 'inactive' (priv) or 8 (SID of schedule)
520 0 0         if ( ref( $return_value ) ne 'App::CELL::Status' ) {
521              
522 0 0 0       if ( $return_value and $t eq 'schedule' ) {
523             # $return_value is SID of the schedule, but we want the schedule itself
524 0           my $status = App::Dochazka::REST::Model::Schedule->load_by_sid( $conn, $return_value );
525 0           $return_value = $status->payload;
526             }
527              
528 0           my @privsched = ( $t, $return_value );
529 0 0         if ( $ts ) {
530 0 0         if ( ! $return_value ) {
531 0           $d_obj->mrest_declare_status(
532             code => 404,
533             explanation => "Employee $nick (EID $eid) has no $t assigned as of $ts"
534             );
535 0           return $CELL->status_not_ok;
536             }
537 0           my $code;
538 0 0         if ( 'PRIV' eq uc( $t ) ) {
    0          
539 0           $code = 'DISPATCH_EMPLOYEE_PRIV_AS_AT';
540             } elsif ( 'SCHEDULE' eq uc( $t ) ) {
541 0           $code = 'DISPATCH_EMPLOYEE_SCHEDULE_AS_AT';
542             } else {
543 0           die "AGHNEVERNEVERNEVERPRIVSCHED1";
544             }
545 0           return $CELL->status_ok( $code,
546             args => [ $ts, $emp->nick, $return_value ],
547             payload => {
548             eid => $eid += 0, # "numify"
549             nick => $emp->nick,
550             timestamp => $ts,
551             @privsched,
552             },
553             );
554             } else {
555 0 0         if ( ! $return_value ) {
556 0           $d_obj->mrest_declare_status(
557             code => 404,
558             explanation => "Employee $nick (EID $eid) has no $t assigned"
559             );
560 0           return $CELL->status_not_ok;
561             }
562 0           my $code;
563 0 0         if ( 'PRIV' eq uc( $t ) ) {
    0          
564 0           $code = 'DISPATCH_EMPLOYEE_PRIV';
565             } elsif ( 'SCHEDULE' eq uc( $t ) ) {
566 0           $code = 'DISPATCH_EMPLOYEE_SCHEDULE';
567             } else {
568 0           die "AGHNEVERNEVERNEVERPRIVSCHED2";
569             }
570 0           return $CELL->status_ok( $code,
571             args => [ $emp->nick, $return_value ],
572             payload => {
573             eid => $eid += 0, # "numify"
574             nick => $emp->nick,
575             @privsched,
576             },
577             );
578             }
579             }
580              
581             # There was a DBI error
582 0           return $return_value;
583             }
584              
585              
586             =head2 shared_employee_acl_part1
587              
588             ACL check -- 'inactive' and 'active' employees can only operate on their own
589             EID. Returns boolean 1 or 0, where 1 means "ACL check passed".
590              
591             =cut
592              
593             sub shared_employee_acl_part1 {
594 0     0 1   my ( $d_obj, $this_emp ) = @_;
595 0           $log->debug( "Entering " . __PACKAGE__ . "::shared_employee_acl_part1" );
596              
597 0           my $context = $d_obj->context;
598 0   0       my $cp = $context->{'current_priv'} || "none";
599              
600             # insert
601 0 0         if ( ! defined( $this_emp ) ) {
602 0 0         if ( $cp ne 'admin' ) {
603 0           $d_obj->mrest_declare_status( code => 403,
604             explanation => "Only administrators can insert new employee records"
605             );
606 0           return 0;
607             }
608             }
609              
610             # update
611 0 0         if ( $cp eq 'admin' ) {
612 0           return 1;
613             } else {
614 0 0         if ( $this_emp->eid == $context->{'current'}->{'eid'} ) {
615 0           return 1;
616             }
617             }
618 0           $d_obj->mrest_declare_status( code => 403, explanation => "DISPATCH_KEEP_TO_YOURSELF" );
619 0           return 0;
620             }
621              
622              
623             =head2 shared_employee_acl_part2
624              
625             Apply ACL rules on which fields can be updated.
626             If privlevel is inactive or active, analyze which fields the user wants to update
627             (passerbies will be rejected earlier in Resource.pm, and admins can edit any field)
628              
629             Returns boolean 1 or 0, where 1 means "ACL check passed".
630              
631             =cut
632              
633             sub shared_employee_acl_part2 {
634 0     0 1   my ( $d_obj ) = @_;
635 0           $log->debug( "Entering " . __PACKAGE__ . "::shared_employee_acl_part2" );
636              
637 0           my $context = $d_obj->context;
638 0   0       my $cp = $context->{'current_priv'} || 'none';
639              
640 0 0         if ( $cp eq 'admin' ) {
    0          
641 0           return 1;
642             } elsif ( $cp =~ m/^(inactive)|(active)$/i ) {
643 0           delete $context->{'request_entity'}->{'eid'};
644 0           my %lut;
645 0           map { $lut{$_} = ''; } @{ $site->DOCHAZKA_PROFILE_EDITABLE_FIELDS->{$cp} };
  0            
  0            
646 0           foreach my $prop ( keys %{ $context->{'request_entity'} } ) {
  0            
647 0 0         next if exists $lut{$prop};
648             $d_obj->mrest_declare_status(
649             $CELL->status_err(
650             'DISPATCH_ACL_VIOLATION',
651             args => [ $cp, "update $prop property" ],
652             http_code => 403,
653 0           uri_path => $context->{'uri_path'},
654             )
655             );
656 0           return 0;
657             }
658 0           return 1;
659             }
660             $d_obj->mrest_declare_status(
661 0           $CELL->status_err(
662             'DISPATCH_ACL_VIOLATION',
663             args => [ $cp, "update employee profiles" ],
664             http_code => 403,
665             )
666             );
667 0           return 0;
668             }
669              
670              
671             =head2 shared_update_activity
672              
673             Takes three arguments:
674              
675             - $d_obj is the dispatch object
676             - $act is an activity object (blessed hashref)
677             - $over is a hashref with zero or more activity properties and new values
678              
679             The values from $over replace those in $act
680              
681             =cut
682              
683             sub shared_update_activity {
684 0     0 1   my ( $d_obj, $act, $over ) = @_;
685 0           $log->debug("Entering " . __PACKAGE__ . "::shared_update_activity" );
686 0 0         delete $over->{'aid'} if exists $over->{'aid'};
687 0 0         return $act->update( $d_obj->context ) if pre_update_comparison( $act, $over );
688 0           $log->notice( "Update operation would not change database; skipping it" );
689 0           return $CELL->status_ok( 'DISPATCH_UPDATE_NO_CHANGE_OK' );
690             }
691              
692              
693             =head2 shared_update_component
694              
695             Takes three arguments:
696              
697             - $d_obj is the dispatch object
698             - $comp is a component object (blessed hashref)
699             - $over is a hashref with zero or more component properties and new values
700              
701             The values from $over replace those in $comp
702              
703             =cut
704              
705             sub shared_update_component {
706 0     0 1   my ( $d_obj, $comp, $over ) = @_;
707 0           $log->debug("Entering " . __PACKAGE__ . "::shared_update_component" );
708 0 0         delete $over->{'cid'} if exists $over->{'cid'};
709 0 0         if ( pre_update_comparison( $comp, $over ) ) {
710 0           my $status = $comp->update( $d_obj->context );
711 0 0 0       return $status unless $status->level eq 'ERR' and $status->code eq 'DOCHAZKA_MALFORMED_400';
712             }
713 0           $d_obj->mrest_declare_status( code => 400, explanation => "DISPATCH_ILLEGAL_ENTITY" );
714 0           return $fail;
715             }
716              
717              
718             =head2 shared_update_history
719              
720             Takes three arguments:
721              
722             - $d_obj is the dispatch object
723             - $obj is a (priv/schedule) history object (blessed hashref)
724             - $over is a hashref with zero or more history properties and new values
725              
726             The values from $over replace those in $obj
727              
728             =cut
729              
730             sub shared_update_history {
731 0     0 1   my ( $d_obj, $obj, $over ) = @_;
732 0           $log->debug("Entering " . __PACKAGE__ . "::shared_update_history" );
733 0 0         delete $over->{'eid'} if exists $over->{'eid'};
734 0 0         return $obj->update( $d_obj->context ) if pre_update_comparison( $obj, $over );
735 0           $log->notice( "Update operation would not change database; skipping it" );
736 0           return $CELL->status_ok( 'DISPATCH_UPDATE_NO_CHANGE_OK' );
737             }
738              
739              
740             =head2 shared_insert_activity
741              
742             Takes two arguments: the dispatch object and the properties that are supposed
743             to be an activity object to be inserted.
744              
745             =cut
746              
747             sub shared_insert_activity {
748 0     0 1   my ( $d_obj, $code, $props ) = validate_pos( @_,
749             { isa => 'App::Dochazka::REST::Dispatch' },
750             { type => SCALAR },
751             { type => HASHREF },
752             );
753 0           $log->debug("Reached " . __PACKAGE__ . "::shared_insert_activity" );
754              
755 0           my %proplist_before = %$props;
756 0           $proplist_before{'code'} = $code; # overwrite whatever might have been there
757 0           $log->debug( "Properties before filter: " . join( ' ', keys %proplist_before ) );
758            
759             # spawn an object, filtering the properties first
760 0           my @filtered_args = App::Dochazka::Common::Model::Activity::filter( %proplist_before );
761 0           my %proplist_after = @filtered_args;
762 0           $log->debug( "Properties after filter: " . join( ' ', keys %proplist_after ) );
763 0           my $act = App::Dochazka::REST::Model::Activity->spawn( @filtered_args );
764              
765             # execute the INSERT db operation
766 0           return $act->insert( $d_obj->context );
767             }
768              
769              
770             =head2 shared_insert_component
771              
772             Takes two arguments: the dispatch object and the properties that are supposed
773             to be a component object to be inserted.
774              
775             =cut
776              
777             sub shared_insert_component {
778 0     0 1   my ( $d_obj, $path, $props ) = validate_pos( @_,
779             { isa => 'App::Dochazka::REST::Dispatch' },
780             { type => SCALAR },
781             { type => HASHREF },
782             );
783 0           $log->debug("Reached " . __PACKAGE__ . "::shared_insert_component" );
784              
785 0           my %proplist_before = %$props;
786 0           $proplist_before{'path'} = $path; # overwrite whatever might have been there
787 0           $log->debug( "Properties before filter: " . join( ' ', keys %proplist_before ) );
788              
789             # spawn an object, filtering the properties first
790 0           my @filtered_args = App::Dochazka::Common::Model::Component::filter( %proplist_before );
791 0           my %proplist_after = @filtered_args;
792 0           $log->debug( "Properties after filter: " . join( ' ', keys %proplist_after ) );
793 0           my $comp = App::Dochazka::REST::Model::Component->spawn( @filtered_args );
794              
795             # execute the INSERT db operation
796 0           my $status = $comp->insert( $d_obj->context );
797 0 0 0       return $status unless $status->level eq 'ERR' and $status->code eq 'DOCHAZKA_MALFORMED_400';
798 0           $d_obj->mrest_declare_status( code => 400, explanation => 'DISPATCH_ILLEGAL_ENTITY' );
799 0           return $fail;
800             }
801              
802              
803             =head2 shared_insert_interval
804              
805             Shared routine for inserting attendance intervals.
806              
807             =cut
808              
809             sub shared_insert_interval {
810 0     0 1   my ( $d_obj ) = @_;
811 0           $log->debug("Reached " . __PACKAGE__ . "::shared_insert_interval" );
812              
813 0           return shared_insert_intlock( $d_obj, 'Interval' );
814             }
815              
816              
817             =head2 shared_insert_lock
818              
819             Shared routine for inserting lock intervals.
820              
821             =cut
822              
823             sub shared_insert_lock {
824 0     0 1   my ( $d_obj ) = @_;
825 0           $log->debug("Reached " . __PACKAGE__ . "::shared_insert_lock" );
826              
827 0           return shared_insert_intlock( $d_obj, 'Lock' );
828             }
829              
830              
831             =head2 shared_insert_intlock
832              
833             =cut
834              
835             sub shared_insert_intlock {
836 0     0 1   my ( $d_obj, $intlock ) = @_;
837 0           $log->debug("Reached " . __PACKAGE__ . "::shared_insert_intlock with $intlock" );
838              
839 0           my $context = $d_obj->context;
840              
841 0           my %proplist_before = %{ $context->{'request_entity'} };
  0            
842 0           $log->debug( "Properties before filter: " . join( ' ', keys %proplist_before ) );
843              
844             # dispatch
845 0           my %dispatch = (
846             'Interval' => \&App::Dochazka::Common::Model::Interval::filter,
847             'Lock' => \&App::Dochazka::Common::Model::Lock::filter,
848             );
849              
850             # spawn an object, filtering the properties first
851 0           my @filtered_args = $dispatch{$intlock}->( %proplist_before );
852 0           my %proplist_after = @filtered_args;
853 0           $log->debug( "Properties after filter: " . join( ' ', keys %proplist_after ) );
854              
855 0           my $obj;
856 0 0         if ( $intlock eq 'Interval' ) {
    0          
857 0           $obj = App::Dochazka::REST::Model::Interval->spawn( @filtered_args );
858             } elsif ( $intlock eq 'Lock' ) {
859 0           $obj = App::Dochazka::REST::Model::Lock->spawn( @filtered_args );
860             } else {
861 0           die "Dying a horrible death";
862             }
863              
864             # execute the INSERT db operation
865 0           return $obj->insert( $context );
866             }
867              
868              
869             =head2 shared_update_intlock
870              
871             Takes three arguments:
872              
873             - $d_obj is the dispatch object
874             - $int is an interval or lock object (blessed hashref)
875             - $over is a hashref with zero or more interval properties and new values
876              
877             The values from $over replace those in $int
878              
879             =cut
880              
881             sub shared_update_intlock {
882 0     0 1   my ( $d_obj, $int, $over ) = @_;
883 0           $log->debug("Entering " . __PACKAGE__ . "::shared_update_intlock" );
884              
885 0           my $context = $d_obj->context;
886              
887             # determine whether we have been passed an interval or lock and set $idv accordingly
888 0           my $class = ref( $int );
889 0           my $idv;
890 0 0         if ( $class eq 'App::Dochazka::REST::Model::Interval' ) {
    0          
891 0           $idv = 'iid';
892             } elsif ( $class eq 'App::Dochazka::REST::Model::Lock' ) {
893 0           $idv = 'lid';
894             } else {
895 0           $log->crit( "Bad interval class! " . Dumper( $class ) );
896 0           die "Bad interval class";
897             }
898              
899 0 0         delete $over->{$idv} if exists $over->{$idv}; # IID/LID cannot be changed, so get rid of it
900              
901             # make sure $over does not contain any non-kosher fields, and merge
902             # $over into $int
903 0 0         return $int->update( $context ) if pre_update_comparison( $int, $over );
904 0           $log->notice( "Update operation would not change database; skipping it" );
905 0           return $CELL->status_ok( 'DISPATCH_UPDATE_NO_CHANGE_OK' );
906             }
907              
908              
909             =head2 shared_process_quals
910              
911             Parses qualifiers string into a hashref. Checks values for sanity; returns a status object.
912              
913             =cut
914              
915             sub shared_process_quals {
916 0   0 0 1   my $qualifiers = shift || '';
917 0           $qualifiers =~ s/\s//g;
918 0           $log->debug( "Entering " . __PACKAGE__ . "::shared_process_qualifiers with $qualifiers" );
919              
920 0           my @qtokens = split(',', $qualifiers);
921            
922 0           my %pl = ();
923 0           my $status = $CELL->status_ok;
924 0           TOKEN: foreach my $t ( @qtokens ) {
925 0           $log->debug( "Processing token $t\n" );
926 0           foreach my $prop ( qw( nick eid month ) ) {
927 0 0         if ( $t =~ m/^$prop=/ ) {
928             #$log->debug( "Found property $prop" );
929 0           $t =~ s/^$prop=//;
930             #$log->debug( "Value is $t" );
931 0           $pl{$prop} = $t;
932 0           next TOKEN;
933             }
934             }
935 0           $status = $CELL->status_err( 'DOCHAZKA_MALFORMED_400' );
936 0           last TOKEN;
937             }
938 0 0         return $status unless $status->ok;
939 0           my %well_formed = (
940             'nick' => qr/^[[:alnum:]_][[:alnum:]_-]+$/,
941             'eid' => qr/^\d{1,9}$/,
942             'month' => qr/^\d{1,6}$/,
943             );
944 0           WELL_FORMED: foreach my $prop ( keys %pl ) {
945 0 0         if ( ! ( $pl{$prop} =~ $well_formed{$prop} ) ) {
946 0           $status = $CELL->status_err( 'DOCHAZKA_MALFORMED_400' );
947 0           last WELL_FORMED;
948             }
949             }
950 0 0         return $status unless $status->ok;
951 0 0         my $payload = ( %pl ) ? \%pl : undef;
952 0           return $CELL->status_ok( 'DISPATCH_PROCESSED_QUALIFIERS', payload => $payload );
953             }
954              
955              
956             1;