[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