App-Dochazka-REST
view release on metacpan or search on metacpan
- 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 )