App-Dochazka-REST

 view release on metacpan or  search on metacpan

lib/App/Dochazka/REST/Shared.pm  view on Meta::CPAN

=head2 shared_first_pass_lookup

Takes two scalar arguments, "key" and "value" and determines whether or not the
database contains an object answering to that description.

This should be used only for resources that require an exact match.

=cut

sub shared_first_pass_lookup {
    my ( $d_obj, $key, $value ) = @_;
    $log->debug( "Entering " . __PACKAGE__ . "::shared_first_pass_lookup with key $key, value $value" );

    my $conn = $d_obj->context->{'dbix_conn'};
    my ( $status, $thing );

    if ( uc($key) eq 'AID' ) {
        $thing = 'activity';
        $status = App::Dochazka::REST::Model::Activity->load_by_aid( $conn, $value );
    } elsif ( $key eq 'code' ) {
        $thing = 'activity';
        $status = App::Dochazka::REST::Model::Activity->load_by_code( $conn, $value );
    } elsif ( uc($key) eq 'CID' ) {
        $thing = 'component';
        $status = App::Dochazka::REST::Model::Component->load_by_cid( $conn, $value );
    } elsif ( $key eq 'path' ) {
        $thing = 'component';
        $status = App::Dochazka::REST::Model::Component->load_by_path( $conn, $value );
    } elsif ( uc($key) eq 'EID' ) {
        $thing = 'employee';
        $status = App::Dochazka::REST::Model::Employee->load_by_eid( $conn, $value );
    } elsif ( $key eq 'nick' ) {
        $thing = 'employee';
        $status = App::Dochazka::REST::Model::Employee->load_by_nick( $conn, $value );
    } elsif ( $key eq 'sec_id' ) {
        $thing = 'employee';
        $status = App::Dochazka::REST::Model::Employee->load_by_sec_id( $conn, $value );
    } elsif ( uc($key) eq 'IID' ) {
        $thing = 'interval';
        $status = App::Dochazka::REST::Model::Interval->load_by_iid( $conn, $value );
    } elsif ( uc($key) eq 'LID' ) {
        $thing = 'lock';
        $status = App::Dochazka::REST::Model::Lock->load_by_lid( $conn, $value );
    } elsif ( uc($key) eq 'PHID' ) {
        $thing = 'privilege history record';
        $status = App::Dochazka::REST::Model::Privhistory->load_by_phid( $conn, $value );
    } elsif ( uc($key) eq 'SHID' ) {
        $thing = 'schedule history record';
        $status = App::Dochazka::REST::Model::Schedhistory->load_by_shid( $conn, $value );
    } elsif ( uc($key) eq 'SID' ) {
        $thing = 'schedule';
        $status = App::Dochazka::REST::Model::Schedule->load_by_sid( $conn, $value );
    } elsif ( $key eq 'scode' ) {
        $thing = 'schedule';
        $status = App::Dochazka::REST::Model::Schedule->load_by_scode( $conn, $value );
    } else {
        die "shared_first_pass_lookup could not do anything with key $key!";
    }

    if ( $status->level eq 'NOTICE' and $status->code eq 'DISPATCH_NO_RECORDS_FOUND' ) {
        $d_obj->mrest_declare_status( code => 404,
            explanation => 'DISPATCH_SEARCH_EMPTY',
            args => [ $thing, "$key equals $value" ],
        );
        return;
    }
    if ( $status->not_ok ) {
        $d_obj->mrest_declare_status( code => 500, explanation => $status->code,
            args => $status->args 
        );
        return;
    }
    return $status->payload;
}


=head2 shared_entity_check

Check request entity for presence of properties

=cut

sub shared_entity_check {
    my ( $d_obj, @props ) = @_;
    $log->debug( "Entering " . __PACKAGE__ . "::shared_entity_check with properties " . 
        join( ' ', @props ) . " and entity: " . Dumper( $d_obj->context->{'request_entity'} ) );

    my $entity = $d_obj->context->{'request_entity'};
    if ( not $entity ) {
        $d_obj->mrest_declare_status( code => 400, 
            explanation => 'DISPATCH_ENTITY_MISSING'
        );
        return $fail;
    }
    if ( ref( $entity ) ne 'HASH' ) {
        $d_obj->mrest_declare_status( code => 400, 
            explanation => 'DISPATCH_ENTITY_NOT_KEY_VALUE'
        );
        return $fail;
    }
    foreach my $p ( @props ) {
        if ( not $entity->{$p} ) {
            $d_obj->mrest_declare_status( code => 400, 
                explanation => 'DISPATCH_PROP_MISSING_IN_ENTITY', args => [ $p ] 
            );
            return $fail;
        }
    }
    return $CELL->status_ok;
}


=head2 shared_get_employee_pass1

=cut

sub shared_get_employee_pass1 {
    my ( $d_obj, $pass, $key, $value ) = @_;
    $log->debug( "Entering " . __PACKAGE__ . "::shared_get_employee_pass1" ); 

    #
    # ACL checks
    #
    if (
            ! acl_check_is_my_report( $d_obj, ( lc $key ) => $value ) and
            ! acl_check_is_me( $d_obj, ( lc $key ) => $value )
       )
    {
        $d_obj->mrest_declare_status( code => 403, explanation => "DISPATCH_KEEP_TO_YOURSELF" );
        return 0;
    }
    #
    # 404 check
    #
    my $emp = shared_first_pass_lookup( $d_obj, $key, $value );
    return 0 unless $emp;
    $d_obj->context->{'stashed_employee_object'} = $emp;
    return 1;
}


=head2 shared_get_employee

=cut

sub shared_get_employee {
    my ( $d_obj, $pass, $key, $value ) = @_;
    $log->debug( "Entering " . __PACKAGE__ . "::shared_get_employee" ); 

    # first pass
    if ( $pass == 1 ) {
        return shared_get_employee_pass1(
            $d_obj, $pass, $key, $value
        );
    }

    # second pass
    return $CELL->status_ok( 'DISPATCH_EMPLOYEE_FOUND',
        payload => $d_obj->context->{'stashed_employee_object'},
    );
}


=head2 shared_update_employee

Takes three arguments:

    - $d_obj is the App::Dochazka::REST::Dispatch object
    - $emp is an employee object (blessed hashref)
    - $over is a hashref with zero or more employee properties and new values

The values from $over replace those in $emp

=cut

sub shared_update_employee {
    my ( $d_obj, $emp, $over ) = @_;
    $log->debug("Entering " . __PACKAGE__ . "::shared_update_employee" );
    $log->debug("Updating employee: " . Dumper( $emp ) );
    $log->debug("With key:value pairs " . Dumper( $over ) );

    ACL: {
        my $explanation = "Update operations require at least one key:value pair in the request entity";
        if ( ref( $over ) ne 'HASH' ) {
            $d_obj->mrest_declare_status( code => 400, explanation => $explanation );
            return $fail;
        }
        delete $over->{'eid'};
        if ( $over == {} ) {
            $d_obj->mrest_declare_status( code => 400, explanation => $explanation );
            return $fail;
        } 
    }

lib/App/Dochazka/REST/Shared.pm  view on Meta::CPAN

=cut

sub shared_history_init {
    my $context = shift;

    my $method = $context->{'method'};
    $log->debug( "Method is $method" );

    my $mapping = $context->{'mapping'};
    my $tsrange = $mapping->{'tsrange'};
    my $ts = $mapping->{'ts'};
    my ( $key, $value );
    if ( defined( my $nick = $mapping->{'nick'} ) ) {
        $key = 'nick';
        $value = $nick;
    } elsif ( defined ( my $eid = $mapping->{'eid'} ) ) {
        $key = 'EID';
        $value = $eid;
    } else {
        die "AAFAAAGAGAGGAGAGGGGGGH! mapping contains neither nick nor eid property: " . Dumper( $mapping );
    }

    return ( $context, $method, $mapping, $tsrange, $ts, $key, $value );
}


=head2 shared_get_privsched

Shared GET handler for 'priv' and 'schedule' lookups. Takes four arguments:

=over

=item C<$d_obj> - dispatch object

=item C<$t> - either 'priv' or 'schedule'

=item C<$pass> - either 1 or 2

=item C<$key> - either 'EID' or 'nick'

=item C<$value> - EID or nick value to lookup

=over

=cut

sub shared_get_privsched {
    my ( $d_obj, $t, $pass, $key, $value ) = @_;
    $log->debug( "Entering " . __PACKAGE__ . ":shared_get_privsched" ); 

    # first pass
    if ( $pass == 1 ) {
        #
        # 403 (ACL) check - passerby can only look up him- or herself
        #
        if ( ! acl_check_is_me( $d_obj, ( lc $key ) => $value ) ) {
            $d_obj->mrest_declare_status( code => 403, explanation => "DISPATCH_KEEP_TO_YOURSELF" );
            return 0;
        }
        # 
        # 404 check
        #
        my $emp = shared_first_pass_lookup( $d_obj, $key => $value );
        return 0 unless $emp;
        $d_obj->context->{'stashed_employee_object'} = $emp;
        return 1;
    }
    
    # second pass

    # - initialization
    my $status;
    my %dispatch = (
        'priv' => \&priv_by_eid,
        'schedule' => \&schedule_by_eid,
    );
    my $emp = $d_obj->context->{'stashed_employee_object'};
    my $eid = $emp->eid;
    my $nick = $emp->nick;
    my $ts = $d_obj->context->{'mapping'}->{'ts'};
    my $conn = $d_obj->context->{'dbix_conn'};

    # - run priv_by_eid or schedule_by_eid, as appropriate
    my $return_value = $dispatch{$t}->( $conn, $eid, $ts );

    # on success, $return_value will be a SCALAR like 'inactive' (priv) or 8 (SID of schedule)
    if ( ref( $return_value ) ne 'App::CELL::Status' ) {

        if ( $return_value and $t eq 'schedule' ) {
            # $return_value is SID of the schedule, but we want the schedule itself
            my $status = App::Dochazka::REST::Model::Schedule->load_by_sid( $conn, $return_value );
            $return_value = $status->payload;
        }

        my @privsched = ( $t, $return_value );
        if ( $ts ) {
            if ( ! $return_value ) {
                $d_obj->mrest_declare_status( 
                    code => 404, 
                    explanation => "Employee $nick (EID $eid) has no $t assigned as of $ts" 
                );
                return $CELL->status_not_ok;
            }
            my $code;
            if ( 'PRIV' eq uc( $t ) ) {
                $code = 'DISPATCH_EMPLOYEE_PRIV_AS_AT';
            } elsif ( 'SCHEDULE' eq uc( $t ) ) {
                $code = 'DISPATCH_EMPLOYEE_SCHEDULE_AS_AT';
            } else {
                die "AGHNEVERNEVERNEVERPRIVSCHED1";
            }
            return $CELL->status_ok( $code,
                args => [ $ts, $emp->nick, $return_value ],
                payload => {
                    eid => $eid += 0,  # "numify"
                    nick => $emp->nick,
                    timestamp => $ts,
                    @privsched,
                },
            );
        } else {
            if ( ! $return_value ) {
                $d_obj->mrest_declare_status( 
                    code => 404, 
                    explanation => "Employee $nick (EID $eid) has no $t assigned"
                );
                return $CELL->status_not_ok;
            }
            my $code;
            if ( 'PRIV' eq uc( $t ) ) {
                $code = 'DISPATCH_EMPLOYEE_PRIV';
            } elsif ( 'SCHEDULE' eq uc( $t ) ) {
                $code = 'DISPATCH_EMPLOYEE_SCHEDULE';
            } else {
                die "AGHNEVERNEVERNEVERPRIVSCHED2";
            }
            return $CELL->status_ok( $code,
                args => [ $emp->nick, $return_value ],
                payload => {
                    eid => $eid += 0,  # "numify"
                    nick => $emp->nick,
                    @privsched,
                },
            );
        }
    }

    # There was a DBI error
    return $return_value;
}


=head2 shared_employee_acl_part1

ACL check -- 'inactive' and 'active' employees can only operate on their own
EID. Returns boolean 1 or 0, where 1 means "ACL check passed".

=cut

sub shared_employee_acl_part1 {
    my ( $d_obj, $this_emp ) = @_;
    $log->debug( "Entering " . __PACKAGE__ . "::shared_employee_acl_part1" );

    my $context = $d_obj->context;
    my $cp = $context->{'current_priv'} || "none";

    # insert
    if ( ! defined( $this_emp ) ) {
        if ( $cp ne 'admin' ) {
            $d_obj->mrest_declare_status( code => 403,
                explanation => "Only administrators can insert new employee records"
            );
            return 0;
        }
    }

    # update
    if ( $cp eq 'admin' ) {
        return 1;
    } else {
        if ( $this_emp->eid == $context->{'current'}->{'eid'} ) {
            return 1;
        }
    }



( run in 0.923 second using v1.01-cache-2.11-cpan-39bf76dae61 )