Catalyst Advent - Day 24 - Authorization

Introduction

Authorization is the step that comes after authentication. Authentication establishes that the user agent is really representing the user we think it's representing, and then authorization determines what this user is allowed to do.

Role Based Access Control

Under role based access control each user is allowed to perform any number of roles. For example, at a zoo no one but specially trained personnel can enter the moose cage (Mynd you, moose bites kan be pretty nasti!). For example:

    package Zoo::Controller::MooseCage;

    sub feed_moose : Local {
        my ( $self, $c ) = @_;

        $c->model( "Moose" )->eat( $c->req->param("food") );
    }

With this action, anyone can just come into the moose cage and feed the moose, which is a very dangerous thing. We need to restrict this action, so that only a qualified moose feeder can perform that action.

The Authorization::Roles plugin let's us perform role based access control checks. Let's load it:

    use Catalyst qw/
        Authentication # yadda yadda
        Authorization::Roles
    /;

And now our action should look like this:

    sub feed_moose : Local {
        my ( $self, $c ) = @_;

        if ( $c->check_user_roles( "moose_feeder" ) ) {
            $c->model( "Moose" )->eat( $c->req->param("food") );
        } else {
            $c->stash->{error} = "unauthorized";
        }
    }

This checks $c->user, and only if the user has all the roles in the list, a true value is returned.

check_user_roles has a sister method, assert_user_roles, which throws an exception if any roles are missing.

Some roles that might actually make sense in, say, a forum application:

  • administrator
  • moderator

each with a distinct task (system administration versus content administration).

Access Control Lists

Checking for roles all the time can be tedious and error prone.

The Authorization::ACL plugin let's us declare where we'd like checks to be done automatically for us.

For example, we may want to completely block out anyone who isn't a moose_feeder from the entire MooseCage controller:

    Zoo->deny_access_unless( "/moose_cage", [qw/moose_feeder/] );

The role list behaves in the same way as check_user_roles. However, the ACL plugin isn't limited to just interacting with the Roles plugin. We can use a code reference instead. For example, to allow either moose trainers or moose feeders into the moose cage, we can create a more complex check:

    Zoo->deny_access_unless( "/moose_cage", sub {
        my $c = shift;
        $c->check_user_roles( "moose_trainer" ) ||
		$c->check_user_roles( "moose_feeder" );
    });

The more specific a role, the earlier it will be checked. Let's say moose feeders are now restricted to only the feed_moose action, while moose trainers get access everywhere:

    Zoo->deny_access_unless( "/moose_cage", [qw/moose_trainer/] );
    Zoo->allow_access_if( "/moose_cage/feed_moose", [qw/moose_feeder/]);

When the feed_moose action is accessed the second check will be made. If the user is a moose_feeder, then access will be immediately granted. Otherwise, the next rule in line will be tested - the one checking for a moose_trainer. If this rule is not satisfied, access will be immediately denied.

Rules applied to the same path will be checked in the order they were added.

Lastly, handling access denial events is done by creating an access_denied private action:

    sub access_denied : Private {
        my ( $self, $c, $action ) = @_;

        


    }

This action works much like auto, in that it is inherited across namespaces (not like object oriented code). This means that the access_denied action which is nearest to the action which was blocked will be triggered.

If this action does not exist, an error will be thrown, which you can clean up in your end private action instead.

Also, it's important to note that if you restrict access to "/" then end, default, etc will also be restricted.

   MyApp->acl_allow_root_internals;

will create rules that permit access to end, begin, and auto in the root of your app (but not in any other controller).

More Information