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