[cgiapp] Security, Authentication and Authorization for CGI::App
Mark Rajcok
mrajcok at gmail.com
Mon Mar 8 20:54:37 EST 2010
On Fri, Mar 5, 2010 at 12:44 PM, Brad Van Sickle <bvansickle3 at gmail.com>wrote:
> I'd be very interested in checking that out once it's available. I'm
> not sure I like your philosophy of making each module responsible for
> it's own security, I like to push as much up to the base class as
> possible, but I do love your attention to DB resources.
>
> >>> 2) Runmode authorization is a little trickier, but still manageable. I
>
> > I recently wrote an Authorization Plugin (it is on my todo list to get it
> up
> > on CPAN) to handle "runmode authorization". It uses attribute handles to
> > specify which runmodes require authorization:
>
Hi Brad,
I looked at my authorization plugin, and it is very far from being reworked
as a CPAN module. Since it is currently less than 40 lines of code, here it
is:
package CGI::Application::Plugin::Authz;
# requires runmodes authz_error() and authz_db_error() to be defined
# for handling authorization errors.
use strict;
use warnings;
use base 'Exporter';
use Carp;
use Attribute::Handlers;
our @EXPORT = qw(authorize);
our $VERSION = '1.0';
my %runmodes; # runmodes is a misnomer -- it should be something like
authz_methods
# that's what happens when you copy code and don't take the time to adapt
it properly
sub CGI::Application::Authz : ATTR(CODE, RAWDATA) {
my ($package, $symbol, $referent, $attr, $data) = @_;
$runmodes{*{$symbol}{NAME}} = $data || undef;
}
sub authorize { # prerun check
my($self, $rm) = @_;
# Note, this method can not be installed as a hook/callback, since we
need
# to execute BaseCgiApp::cgiapp_prerun() first. When I installed it as
# a callback, it got called before cgiapp_prerun() -- no good.
return if !exists $runmodes{$rm};
# determine authorization method to call
my $authz_check_rm = 'authz_check_' . $runmodes{$rm};
croak "authz method $authz_check_rm() not defined?!" if !
$self->can($authz_check_rm);
if($self->$authz_check_rm) {
# passed authorization check
} else {
if(defined $self->param('authz_db_error')) {
$self->prerun_mode('authz_db_error');
} else { # normal authz error
$self->prerun_mode('authz_error');
}
}
}
1
In my base CGI::App, I have this code (which calls authorize() during the
prerun stage):
sub cgiapp_prerun { # overrides; called after setup(), before
runmode
my ($self, $rm) = @_;
if($self->authen->is_protected_runmode($rm)) {
if(defined $self->authen->username) {
$self->_check_account_state;
$self->authorize($rm) if $self->can('authorize');
}
# else, Sign In form will be presented (or already is presented)
}
}
Method _check_account_state does a SELECT on the DB and stores u_state and
u_privileges data into $self->param(), for use by the authorization
methods. (I'd prefer that the Authentication Plugin could do this instead.)
In my Controller classes, I define the authorization methods, and indicate
which runmodes require which authorization:
package AdminManager;
sub authz_check_admin {
my $self = shift;
return UserGlobals::is_admin($self->param('u_privileges'))
# more complicated authorization may query the DB here, and store
# data in $self->param() for later use by the runmodes, so that that
# runmodes don't hit the DB again
}
sub assign_roles :Runmode :Authen :Authz(admin) { # display all users
... normal runmode stuff here ...
}
So, in all, it works like this:
hash %runmodes (bad name for the hash) gets populated via the attribute
handlers on the controller methods.
When a runmode is called (let's say it is assign_roles), cgiapp_prerun()
will call authorize('assign_roles'). authorize() will perform a hash lookup
and determine that authz_check_admin() is the authorization routine that is
associated with assign_roles, so it calls it. If authorization fails, we
switch the runmode to authz_error (or authz_db_error -- DB errors became so
common to check for, that I gave them a separate error runmode). If
authorization succeeds, assign_roles() runs normally.
Methods like authz_check_admin() could be put up into the base class instead
-- if they are shared by multiple controller classes, or if you prefer a
centralized authorization approach.
-- Mark R.
More information about the cgiapp
mailing list