App-Dochazka-REST

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

- Model/: fix some lingering migration-related issues
- Test.pm, t/: migration broke lots of tests; we are now expected to send the
  request context, but with Plack::Test there is none(!); fortunately, it is
  trivial to simulate such a context ($faux_context)
- dispatch_Message_en.conf: get rid of
  DISPATCH_EMPLOYEE_{INSERT,UPDATE,DELETE}_OK message (DOCHAZKA_CUD_OK is
  sufficient)

0.343  2014-12-10 16:43 CET
- dbinit_Config.pm: fix names of parameters used to store PostgreSQL superuser
  credentials for testing purposes
- ConnBank.pm: refactor the entire module
- REST.pm: adapt to the new ConnBank.pm code; make 'init' routine return a
  status object with the Plack application in the payload
- Dispatch/, Model/, Test.pm, t/: adapt to the new ConnBank.pm code

0.344  2014-12-10 18:01 CET
- bin/dochazka-rest: adapt to recent modifications to REST.pm->init
- REST_Message_en.conf: add DOCHAZKA_NO_DBIX_CONNECTOR
- dbinit_Config.pm: sum(numbackends) is not working for some reason; work
  around the problem

config/REST_Config.pm  view on Meta::CPAN

        { code => 'OVERTIME_WORK', long_desc => 'Overtime work' },
        { code => 'PAID_VACATION', long_desc => 'Paid vacation' },
        { code => 'UNPAID_LEAVE', long_desc => 'Unpaid leave' },
        { code => 'DOCTOR_APPOINTMENT', long_desc => 'Doctor appointment' },
        { code => 'CTO', long_desc => 'Compensation Time Off' },
        { code => 'SICK_DAY', long_desc => 'Discretionary sick leave' },
        { code => 'MEDICAL_LEAVE', long_desc => 'Statutory medical leave' },
    ] );   

# DOCHAZKA_BASIC_AUTH_REALM
#     message displayed to user when she is asked to enter her credentials
set( 'DOCHAZKA_BASIC_AUTH_REALM', 
     'ENTER YOUR DOCHAZKA CREDENTIALS (e.g., demo/demo)' );

# DOCHAZKA_LDAP
#     Enable/disable LDAP authentication
set( 'DOCHAZKA_LDAP', 0 );

# DOCHAZKA_LDAP_AUTOCREATE
#     Autocreate unknown users if found in LDAP
set( 'DOCHAZKA_LDAP_AUTOCREATE', 0 );

config/sql/dbinit_Config.pm  view on Meta::CPAN

#
# sql/dbinit_Config.pm
#
# database initialization SQL

#
# DBINIT_CONNECT_SUPERUSER
# DBINIT_CONNECT_SUPERAUTH
#
# These should be overrided in Dochazka_SiteConfig.pm with real
# superuser credentials (but only for testing - do not put production
# credentials in any configuration file!!!!)
#
set( 'DBINIT_CONNECT_SUPERUSER', 'postgres' );
set( 'DBINIT_CONNECT_SUPERAUTH', 'bogus_password_to_be_overrided' );

#
# DBINIT_CREATE
# 
#  A list of SQL statements that are executed when the database is first
#  created, to set up the table structure, etc. -- see the create_tables
#  subroutine in REST.pm 

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

        } );
    } catch {
        $status = $CELL->status_err( 'DOCHAZKA_DBI_ERR', args => [ $_ ] );
    };
    return $status;
}


=head2 reset_db

Drop and re-create a Dochazka database. Takes superuser credentials as
arguments. 

Be very, _very_, _VERY_ careful with this function.

=cut

sub reset_db {

    my $status;
    my $dbname = $site->DOCHAZKA_DBNAME;
    my $dbuser = $site->DOCHAZKA_DBUSER;
    my $dbpass = $site->DOCHAZKA_DBPASS;
    $log->debug( "Entering " . __PACKAGE__ . "::reset_db to initialize database $dbname with credentials $dbuser / $dbpass" );

    # PGTZ *must* be set
    $ENV{'PGTZ'} = $site->DOCHAZKA_TIMEZONE;

    # create:
    # - audit schema (see config/sql/audit_Config.pm)
    # - public schema (all application-specific tables, functions, triggers, etc.)
    # - the 'root' and 'demo' employees
    # - privhistory record for root
    print "Getting database connection...";

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

        $log->error( "Session expired!" );
        return 0;
    }
    return 1;
}


=head3 _authenticate

Authenticate the nick associated with an incoming REST request.  Takes a nick
and a password (i.e., a set of credentials). Returns a status object, which
will have level 'OK' on success (with employee object in the payload), 'NOT_OK'
on failure. In the latter case, there will be a declared status.

=cut

sub _authenticate {
    my ( $self, $nick, $password ) = @_;
    my ( $status, $emp );
    $log->debug( "Entering " . __PACKAGE__ . "::_authenticate" );

    # empty credentials: fall back to demo/demo
    if ( $nick ) {
        $log->notice( "Login attempt from $nick" );
    } else {
        $log->notice( "Login attempt from (anonymous) -- defaulting to demo/demo" );
        $nick = 'demo'; 
        $password = 'demo'; 
    }

    $log->debug( "\$site->DOCHAZKA_LDAP is " . $site->DOCHAZKA_LDAP );

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

    set( 'DOCHAZKA_REST_LOG_FILE_RESET', 1);
    EOF
    #

Where 'mypass' is the PostgreSQL password you set in the 'ALTER
ROLE' command, above.

The C<DBINIT_CONNECT_SUPERAUTH> setting is only needed for database
initialization (see below), when L<App::Dochazka::REST> connects to PostgreSQL
as user 'postgres' to drop/create the database. Once the database is created,
L<App::Dochazka::REST> connects to it using the PostgreSQL credentials of the
current user.


=head2 Database initialization

To initialize the database or reset it to a pristine state:

    $ dochazka-resetdb

Note that this is a two-step process. The first step is to create the database,

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

Some resources (those that use the GET method) are accessible using a web
browser. That said, if we are only interested in displaying information
from the database, GET requests are all we need and using a web browser can
be convenient.  

To start exploring, fire up a standard web browser and point it to the base URI
of your L<App::Dochazka::REST> installation:

    http://dochazka.site

and entering one's credentials in the Basic Authentication dialog.

=head2 With a command-line HTTP client

To access all the resources, you will need a client that is capable of
generating POST, PUT, and DELETE requests as well as GET requests. Also, since
some of the information L<App::Dochazka::REST> provides is in the response
headers, the client needs to be capable of displaying those as well.

One such client is Daniel Stenberg's B<curl>.

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

One of the first things the server looks at, when it receives a request, is 
the method. Only certain HTTP methods, such as 'GET' and 'POST', are accepted.
If this test fails, a "405 Method Not Allowed" response is sent.

=item * B<Internal and external authentication, session management>

This takes place when L<Web::Machine> calls the C<is_authorized> method,
our implementation of which is in L<App::Dochazka::REST::Auth>.

Though the method is called C<is_authorized>, what it really does is
authenticate the request - i.e., validate the user's credentials to 
determine his or her identity. B<Authorization> - determination whether the
user has sufficient privileges to make the request - takes place one step
further on. (The HTTP standard uses the term "authorized" to mean
"authenticated"; the name of this method is a nod to that usage.)

In C<is_authorized>, the user's credentials are authenticated
against an external database (LDAP), an internal database (PostgreSQL
'employees' table), or both. Session management techniques are utilized
to minimize external authentication queries, which impose latency. The
authentication and session management algorithms are described in
L<"AUTHENTICATION AND SESSION MANAGEMENT">. If authentication fails, a "401
Unauthorized" response is sent. 

Since this is the first time that the PostgreSQL database is needed, this
is also where the L<DBIx::Connector> object is attached to the request
context. (The request context is a hashref that accompanies the request 
as it undergoes processing.) For details, see
L<App::Dochazka::REST::Auth/"is_authorized">.

In a web browser, repeated failed authentication attempts are typically
associated with repeated display of the credentials dialog (and no other
indication of what is wrong, which can be confusing to users but is probably a
good idea, because any error messages could be abused by attackers).

=item * B<Authorization/ACL check>

After the request is authenticated (associated with a known employee), the
server examines the ACL profile of the resource being requested and compares it
with the employee's privilege level. If the privilege level is too low for the
requested operation, a "403 Forbidden" response is sent.

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


If any of these are missing, or the difference between C<last_seen> and the
current date/time is greater than the time interval defined in the
C<DOCHAZKA_REST_SESSION_EXPIRATION_TIME>, the request is rejected with 401
Unauthorized. 


=head2 New session

Requests for a new session are subject to HTTP Basic Authentication. To protect
employee credentials from network sniffing attacks, the HTTP traffic
must be encrypted. This can be accomplished using an SSL-capable HTTP
server or transparent proxy such as L<nginx|http://nginx.org/en/>.

If the C<DOCHAZKA_LDAP> site parameter is set to a true value, the
C<_authenticate> routine of L<App::Dochazka::REST::Resource> will attempt to 
authenticate the request against an external resource using the LDAP protocol.

LDAP authentication takes place in two phases:

=over

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

=item * authentication phase

=back

The purpose of the lookup phase is to determine if the user exists in the 
LDAP resource and, if it does exist, to get its 'cn' property. In the second
phase, the password entered by the user is compared with the password stored
in the LDAP resource.

If the LDAP lookup phase fails, or if LDAP is disabled, L<App::Dochazka::REST>
falls back to "internal authentication", which means that the credentials are
compared against the C<nick>, C<passhash>, and C<salt> fields of the
C<employees> table in the database.

To protect user credentials from snooping, the actual passwords are not stored
in the database, Instead, they are run through a one-way hash function and
the hash (along with a random "salt" string) is stored in the database instead
of the password itself. Since some "one-way" hashing algorithms are subject to
brute force attacks, the Blowfish algorithm was chosen to provide the best
known protection.

If the request passes Basic Authentication, a session ID is generated and 
stored in a cookie. 


t/dispatch/001-resource.t  view on Meta::CPAN


note( 'request for HTML' );
my $r = GET '/', 'Accept' => 'text/html';
isa_ok( $r, 'HTTP::Request' );
$r->authorization_basic( 'root', 'immutable' );
my $resp = $test->request( $r );
isa_ok( $resp, 'HTTP::Response' );
is( $resp->code, 200 );
like( $resp->content, qr/<html>/ );

note( 'request with bad credentials (401)' );
req( $test, 401, 'fandango', 'GET', '/' );

note( 'request that doesn\'t pass ACL check (403)' );
req( $test, 403, 'demo', 'GET', '/forbidden' );

note( 'GET request for non-existent resource (400)' );
req( $test, 400, 'demo', 'GET', '/HEE HAW!!!/non-existent/resource' );

note( 'PUT request for non-existent resource (400)' );
req( $test, 400, 'demo', 'PUT', '/HEE HAW!!!/non-existent/resource' );



( run in 0.273 second using v1.01-cache-2.11-cpan-a5abf4f5562 )