There are two ways to control authentication to an action in Maypole.
The first is to create an authenticate
method in the
relevant model class; if you want everyone to be able to mess with
beers but only certain people to be able to edit the breweries list,
then your should write BeerDB::Brewery::authenticate
. The second way is to provide authentication globally in your driver class.
Maypole will check authentication in that order - first, in the model class,
then globally. If there's no method found in the model class hierarchy (with
inheritance) then Maypole checks for BeerDB::authenticate
. There
is no fallback; if you provide a table-specific authentication mechanism and
that method fails, then the global authentication method is not called.
In either case, you need to decide whether or not a request is permissible,
and to do that, you need to examine the request to see what the user is
trying to do. Maypole passes you the Maypole
request object
to examine:
sub authenticate { my ($self, $r) = @_; return OK; }
(This might feel a bit weird if you're overriding
authenticate
in your handler class, because $r
is $self
. But if you don't worry about that,
everything will be fine.)
It's up to you what you decide to do if you don't want to
authenticate the request. You could just deny access, returning an
Apache 403, and letting the server sort it out; to do this, return
FORBIDDEN
. (If things don't work whether you return
OK
or FORBIDDEN
, make sure you're importing the
status code constants from the Apache::Constants
module.) Or you could use the Maypole framework itself to send the user
elsewhere.
For instance, one common set-up is to not allow the user to do
anything unless they're logged in. We'll pretend that we've already
implemented a
get_user
method to determine if they are or not - this
populates $r->{user}
with an appropriate object. If
$r->{user}
isn't there, they're not coming in! We set
the template to login
, which pops up a login form and
has them try again.
sub authenticate { my ($self, $r) = @_; $r->get_user; return OK if $r->{user}; $r->template("login"); return OK; }
Or we could allow them to only do specific things if they're not logged in. Such as register an account, for instance, so they actually can log in:
sub authenticate { my ($self, $r) = @_; $r->get_user; return OK if $r->{user}; return OK if $r->action eq "register" and $r->table eq "user"; $r->template("login"); return OK; }
Maybe we want to be really polite and not just completely derail them to force them to log in, but allow them to continue where they were going once they successfully submit credentials. Actually, this is really trivial. All we need to do is reconstruct the URL in our template, and have the login form post the credentials back to that URL.
When Maypole goes through the authentication phase for the second time, hopefully this time the username and password will be right, we'll be able to get a user object, and we'll sail straight into the right action: (I didn't realise it was going to be that easy, either. We're learning together, here.)
[% SET args = request.args.join("/"); %] <h2> You need to log in </h2> <FORM ACTION="[% base %]/[%request.table%]/[% request.action %]/[%args%]"> Username: <INPUT TYPE="text" NAME="username"> <BR> Password: <INPUT TYPE="password" NAME="password"> <BR> </FORM>
Now, what if we want to tell them that they got the password wrong?
Assuming that our get_user
method returns some useful status
code, we can pass a message onto the template:
sub authenticate { my ($self, $r) = @_; $r->{template_args}{error} = "Bad username or password" unless $r->get_user; return OK if $r->{user}; return OK if $r->action eq "register" and $r->table eq "user"; $r->template("login"); return OK; }
Which gets picked up like so:
[% SET args = request.args.join("/"); %] <h2> You need to log in </h2> <FORM ACTION="[% base %]/[%request.table%]/[% request.action %]/[%args%]"> [% IF error %] <FONT COLOR="#FF0000"> [%error %] <FONT> [% END %] Username: <INPUT TYPE="text" NAME="username"> <BR> Password: <INPUT TYPE="password" NAME="password"> <BR> </FORM>
How get_user
actually works is up to you; here's one I
made earlier:
sub get_user { my $r = shift; my $ar = $r->{ar}; my $sid; my %jar = Apache::Cookie->new($ar)->parse; if (exists $jar{sessionid}) { $sid = $jar{sessionid}->value(); } $sid = undef unless $sid; # Clear it, as 0 is a valid sid. my %session = (); my $new = !(defined $sid); my ($uid, $user); if ($new) { # Go no further unless login credentials are right. ($uid, $r->{user}) = $r->check_credentials; return 0 unless $uid; } tie %session, 'Apache::Session::File', $sid, { Directory => "/tmp/sessions", LockDirectory => "/tmp/sessionlock", }; if ($new) { # Store the userid, and bake the cookie $session{uid} = $uid; $ar->log->debug("Returning session ".$session{_session_id}." to the cookie jar"); my $cookie = Apache::Cookie->new($ar, -name => "sessionid", -value => $session{_session_id}, -path => "/" ); $cookie->bake(); } else { # Grab the user object from the session data. $r->{user} = Flox::User->retrieve($session{uid}); } untie %session; return 1; }
The referenced check_credentials
routine just looks up the
username and password provided in $r->{params}
and returns
the ID and object of a Flox::User
entry corresponding to
the right user. This is probably overkill, but it works for me - you can
use it as the Maypole::Authentication::UserSessionCookie
module.