App-Dochazka-REST
view release on metacpan or search on metacpan
lib/App/Dochazka/REST/Guide.pm view on Meta::CPAN
edit C<pg_hba.conf>. In SUSE distributions, this file is located in
C<data/> under the C<postgres> home directory. Using our favorite editor,
we change the METHOD entry for C<local> so it looks like this:
# TYPE DATABASE USER ADDRESS METHOD
local all all password
For the audit triggers to work (and the application will not run otherwise), we
must to add the following line to the end of C<postgresql.conf> (also
located in C<data/> in SUSE distros):
dochazka.eid = -1
Then, as root, we restart the postgresql service:
bash# systemctl restart postgresql.service
Lastly, check if you can connect to the C<postgres> database using the password:
bash$ psql --username postgres postgres
Password for user postgres: [...type 'mypass'...]
psql (9.2.7)
Type "help" for help.
postgres=#
To exit, type C<\q> at the postgres prompt:
postgres=# \q
bash$
=head2 Site configuration
Before the Dochazka REST database can be initialized, we will need to
tell L<App::Dochazka::REST> about the PostgreSQL superuser password
that we set in the previous step. This is done via a site parameter.
There may be other site params we will want to set, but the following
is sufficient to run the test suite.
First, create a sitedir:
bash# mkdir /etc/dochazka-rest
and, second, a file therein:
# cat << EOF > /etc/dochazka-rest/REST_SiteConfig.pm
set( 'MREST_DEBUG_MODE', 1 );
set( 'DBINIT_CONNECT_SUPERAUTH', 'mypass' );
set( 'DOCHAZKA_REST_LOG_FILE', "dochazka-rest.log" );
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,
role, extensions etc. - i.e., everything that requires database superuser
permissions. The second step is to create the schemas, etc. For this, the
ordinary "dochazka" role is used.
In a production setting, or whenever the two steps need to be done separately,
the database administrator can perform the first step using the "psql" command
in C<bin/dochazka-resetdb>. After that, the second step can be performed by
simply running
$ dochazka-dbinit
=head2 Start the server
The last step is to start the Dochazka REST server. In the future, this
will be possible using a command like C<systemctl start dochazka-rest.service>.
At the moment, however, we are still in development/testing phase and we
start the server like this:
$ dochazka-rest
Starting Web::MREST ver. 0.282
App distro is App-Dochazka-REST
App module is App::Dochazka::REST::Dispatch
Distro sharedir is
/usr/lib/perl5/site_perl/5.18.2/auto/share/dist/App-Dochazka-REST
Local site configuration directory is /etc/dochazka-rest
Loading configuration parameters from /etc/dochazka-rest
Setting up logging
Logging to /home/smithfarm/mrest.log
Calling App::Dochazka::REST::Dispatch::init()
Starting server
HTTP::Server::PSGI: Accepting connections at http://0:5000/
Note that the development web server L<HTTP::Server::PSGI> is used. To use
L<Starman> instead, use the following command:
$ dochazka-rest -- --server Starman
=head2 Take it for a spin
Point your browser to L<http://localhost:5000/>
=head1 BASIC PARAMETERS
=head2 UTF-8
The server assumes all incoming requests are encoded in UTF-8, and it encodes
all of its responses in UTF-8 as well.
=head2 HTTP(S)
In order to protect user passwords from network sniffing and other nefarious
activities, it is recommended that the server be set up to accept HTTPS
requests only.
=head2 Self-documenting
Another implication of REST is that the server provides "resources" and that
those resources are, to some extent at least, self-documenting.
=head1 EXPLORING THE SERVER
=head2 With a web browser
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>.
In the HTTP request, the client may provide an C<Accept:> header specifying
either HTML (C<text/html>) or JSON (C<application/json>). For the convenience
of those using a web browser, HTML is the default.
Here are some examples of how to use B<curl> (or a web browser) to explore
resources. These examples assume a vanilla installation of
L<App::Dochazka::REST> with the default root password. The same commands can be
used with a production server, but keep in mind that the resources you will see
may be limited by your privilege level.
=over
=item * GET resources
The GET method is used to search for and display information. The top-level
GET resources are listed at the top-level URI, either using B<curl>
$ curl -v -H 'Accept: application/json' http://demo:demo@dochazka.site/
Similarly, to display a list of sub-resources under the 'privhistory' top-level
resource, enter the command:
$ curl http://demo:demo@dochazka.site/employee -H 'Accept: application/json'
Oops - no resources are displayed because the 'demo' user has only passerby
privileges, but all the privhistory resources require at least 'active'. To
see all the available resources, we can authenticate as 'root':
$ curl http://root:immutable@dochazka.site/employee -H 'Accept: application/json'
=item * POST resources
With the GET method, we could only access resources for finding and displaying
information: we could not add, change, or delete information. For that we will
need to turn to some other client than the web browser -- a client like B<curl>
that is capable of generating HTTP requests with methods like POST (as well as
PUT and DELETE).
Here is an example of how we would use B<curl> to display the top-level POST
resources:
curl -v http://root:immutable@dochazka.site -X POST -H "Content-Type: application/json"
The "Content-Type: application/json" header is necessary because the server
only accepts JSON in the POST request body -- even though in this case we
did not send a request body, most POST requests will have one. For best
results, the request body should be a legal JSON string represented as a
sequence of bytes encoded in UTF-8.
=item * PUT resources
lib/App/Dochazka/REST/Guide.pm view on Meta::CPAN
Any time we need to delete information -- i.e., completely wipe it from
the database, we will need to use the DELETE method.
DELETE resources can be explored using a B<curl> command analogous to the one
given for the POST method.
Keep in mind that the data integrity constraints in the underlying PostgreSQL
database may make it difficult to delete a resource if any other resources
are linked to it. For example, an employee cannot be deleted until all
intervals, privhistory records, schedhistory records, locks, etc. linked to
that employee have been deleted. Intervals, on the other hand, can be
deleted as long as they are not subject to a lock.
=back
=head1 DOCUMENTATION OF REST RESOURCES
The definition of each resource includes an HTML string containing the
resource's documentation. This string can be accessed via POST request for
the C<docu> resource (provide the resource name in double quotes in the
request body).
In order to be "self-documenting", the definition of each REST resource
contains a "short" description and a "long" POD string. From time to time, the
entire resource tree is walked to generate a module,
L<App::Dochazka::REST::Docs::Resources>, containing all the resource
documentation.
=head1 REQUEST-RESPONSE CYCLE
Incoming HTTP requests are handled by L<App::Dochazka::REST::Resource>,
which inherits from L<Web::Machine::Resource>. The latter uses L<Plack> to
implement a PSGI-compliant stack.
L<Web::Machine> takes a "state-machine" approach to implementing the HTTP 1.1
standard. Requests are processed by running them through a state
machine, each "cog" of which is a L<Web::Machine::Resource> method that can
be overridden by a child module. In our case, this module is
L<App::Dochazka::REST::Resource>.
The behavior of the resulting web server can be characterized as follows:
=over
=item * B<Allowed methods test>
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.
The ACL profile is part of the resource definition. It can be specified either
as a single value for all HTTP methods, or as a hash, e.g.:
{
GET => 'passerby',
PUT => 'admin',
DELETE => 'admin',
}
In certain operations (i.e., combinations of HTTP method and resource), the
full range of functionality may be available only to administrators. See These
operations are special cases. Their ACL profile is either 'inactive' or
'active', but a non-administrator employee may still get a 403 Forbidden error
on the operation if they are trying to do something, such as update an interval
belonging to a different employee, that is reserved for administrators.
=item * B<Test for resource existence>
The next test a request undergoes on its quest to become a response is the
test of resource existence. If the request is asking for a non-existent resource,
e.g. L<http://dochazka.site/employee/curent>, it cannot be fulfilled and a "404
Not Found" response will be sent.
For GET requests, this is ordinarily the last cog in the state machine: if the
test passes, a "200 OK" response is typically sent, along with a response body.
(There are exceptions to this rule, however - see L<the AUTHORIZATION
chapter|"AUTHORIZATION">.) Requests using other methods (POST, PUT, DELETE) are
subject to further processing as described below.
=back
=head2 Additional processing (POST and PUT)
Because they are expected to have a request body, incoming POST and PUT
requests are subject to the following additional test:
=over
=item * B<malformed_request>
This test examines the request body. If it is non-existent, the test
passes. If the body exists and is valid JSON, the test passes. Otherwise,
it fails.
=item * B<known_content_type>
Test the request for the 'Content-Type' header. POST and PUT requests
should have a header that says:
Content-Type: application/json
lib/App/Dochazka/REST/Guide.pm view on Meta::CPAN
"[1964-12-31 23:15, 1965-01-01 03:10)"
for Dochazka that would mean that the employee with EID 1 has a weekly schedule
of "WED/22:05-THU/04:35" and "THU/23:15-FRI/03:10", because the dates in the
ranges fall on a Wednesday (1964-12-30), a Thursday (1964-12-31), and a
Friday (1964-01-01), respectively.
=head2 When history changes take effect
The C<effective> field of the C<privhistory> and C<schedhistory> tables
contains the effective date/time of the history change. This field takes a
timestamp, and a trigger ensures that the value is evenly divisible by five
minutes (by rounding). In other words,
'1964-06-13 14:45'
is a valid C<effective> timestamp, while
'2014-01-01 00:00:01'
will be rounded to '2014-01-01 00:00'.
=head1 AUTHENTICATION AND SESSION MANAGEMENT
Employees do not access the database directly, but only via HTTP requests.
For authorization and auditing purposes, L<App::Dochazka::REST> needs to
associate each incoming request to an EID.
The L<Plack::Middleware::Session> module associates each incoming request with
a session. Sessions are validated by examining the session state in the
L<App::Dochazka::REST::Auth> module.
=head2 Existing session
If the session state is valid, it will contain:
=over
=item * the Employee ID, C<eid>
=item * the IP address from which the session was first originated, C<ip_addr>
=item * the date/time when the session was last seen, C<last_seen>
=back
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
=item * lookup phase
=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.
=head1 AUTHORIZATION
=head1 CLIENT-SERVER COMMUNICATION
As stated above, communication between the server and its clients takes place
using the HTTP protocol. More abstractly, the communication takes the form of
requests (from client to server) and responses (from server back to client) to
those requests. In other words, communication is never initiated by the server,
but always by the clients.
=head2 HTTP request
An HTTP request has the following basic components:
=over
=item * Method
Dochazka supports GET, PUT, POST, and DELETE
=item * URI
Universal Resource Indicator specifying a Dochazka resource
=item * Headers
More on these below
=item * Request entity
Data accompanying the request - may or may not be present
=back
=head3 Method
The Dochazka REST server accepts the following HTTP methods:
C<GET>, C<PUT>, C<POST>, and C<DELETE>.
=over
=item C<GET>
A C<GET> request on a resource is a request for information - in other words,
it is "read-only": C<GET> requests never change the underlying data. In
Dochazka, C<GET> requests frequently map to C<SELECT> statements.
( run in 1.043 second using v1.01-cache-2.11-cpan-39bf76dae61 )