NAME
   Authorize::Rule - Rule-based authorization mechanism

VERSION
   version 0.007

SYNOPSIS
   This is an extensive example, showing various options:

       my $auth = Authorize::Rule->new(
           rules => {
               dev => {
                   Payroll => [ [0] ], # always deny
                   ''      => [ [1] ], # default allow for unknown resources
               },

               tester => {
                   '' => [
                       # labeled rulesets
                       'check tester' => [
                           # all rules must apply
                           # key 'is_test' with value 1
                           # and keys test_name/test_id must exist
                           1, { is_test => 1 }, 'test_name', 'test_id'
                       ],
                       'default' => [0],
                   ]
               },

               admin => {
                   '' => [
                       # the admin does *not* have a passwordless ssh key
                       [ 1, { passwordless_ssh_key => undef } ],
                   ],
               },

               ceo => {
                   '' => [
                       [
                           # decide on the value of the return ourselves
                           # based on the resource
                           sub {
                               my $res = shift;
                               return 'Access Granted for ' . $res->{'resource'};
                           },

                           # a rule that is itself a subroutine
                           # with access to the parameters
                           sub { has_permission( $_[0]->{'resource'} ) },

                           # a rule with a key that matches the result of a sub
                           { now => sub { correct_relative_time() } },
                       ],
                   ],
               },

               biz_rel => {
                   Graphs    => [ [0] ],
                   Databases => [
                       # access to reservations table
                       [ 1, { table => 'Reservations' } ],
                   ],

                   Invoices => [
                       [ 0, 'user' ],
                       [ 1         ],
                   ],

                   Payroll => [ [1] ],
                   Revenue => [ [1] ],
                   ''      => [ [0] ],
               },

               support => {
                   Databases => [
                       [ 1, { table => 'Complaints' } ],
                   ],

                   Invoices => [ [1] ],
                   ''       => [ [0] ],
               },

               sysadmins => {
                   Graphs => [ [1] ],
                   ''     => [ [0] ],
               },
           },

           entity_groups => {
               sysadmins => [ qw<John Jim Goat> ],
           },

           resource_groups => {
               Graphs => [ 'ThisGraphs', 'ThoseGraphs' ],
           },
       );

   *(this example is not taken from any actual code)*

DESCRIPTION
   Authorize::Rule allows you to provide a set of rulesets, each containing
   rules, for authorizing access of entities to resources. This does not
   cover authentication, or fine-grained parameter checking.

   While authentication asks "who are you?", authorization asks "what are
   you allowed to do?"

   The system is based on decisions per entities, resources, and any
   optional parameters.

ALPHA CODE
   I can't promise some of this won't change in the next few versions.

   Stay tuned.

SPECIFICATION
   The specification covers several elements:

   *   Entity

   *   Resource

   *   Action

   *   Optional parameters

   *   Optional label

   The general structure is:

       {
           ENTITY => {
               RESOURCE => [
                   OPTIONAL_LABEL => [ ACTION, RULE_1, RULE_2, ...RULE_N ],
               ]
           }

       }

   Allowed rules are:

       # parameters must have this key with this value
       [ $action, { key  => 'value' } ]
       [ $action, { name => 'Marge' } ]

       # parameters must not have this key
       [ $action, { key => undef } ]

       # parameters must have these keys, values aren't checked
       [ $action, 'key1', 'key2', ... ]

       # they can be seamlessly mixed
       [ $action, { Company => 'Samsung' }, { Product => 'Phone' }, 'model_id' ]

       # and yes, this is the equivalent of:
       [ $action, { Company => 'Samsung', Product => 'Phone' }, 'model_id' ]

       # a mix of keys with expected values and keys expected not to exist
       [ $action, { name => 'Marge', holding_knife => undef } ]

       # labels can be applied to rulesets:
       'verifying test account' => [ $action, { username => 'tester' } ]

       # rules can be a subroutine
       [ $action, sub { my $params = shift; ... } ]

       # keys can match to a subroutine result
       [ $action, { Company => sub { get_company( $_[0]->{'company'} ) } } ]

       # and lastly, actions can be subroutines
       [ sub { my $result_hash = shift; return 'OK' if ... }, { %params } ]

   An action is either true, false, or a code reference which returns one.
   The recommended values for true or false are 1 or 0. Traditionally these
   will be 1 or 0:

       [ 1, RULES... ]
       [ 0, RULES... ]
       [ 'FAILURE', RULES... ]

       my $result = $auth->is_allowed( $entity, $resource );
       if ( $result eq 'FAILURE' ) {
           ...
       }

   Or as a code reference:

       [ sub {...}, RULES... ]

   The code reference receives the entire result hash as a parameter:

       [ sub {
           my $result = shift;

           # $result = {
           #     ruleset_idx => 1,
           #     params      => $PARAMS,
           #     entity      => $ENTITY,
           #     resource    => $RESOURCE,
           # }
       }, RULES... ]

   Rules and rulesets are read consecutively, so you might want to position
   your rules in order to exit early. When a ruleset is matches, the
   execution or rulesets stops and the action is returned.

EXAMPLES
 All resources
   Cats think they can do everything, and they can:

       my $rules = {
           Cat => {
               # default rule for any unmatched resource
               '' => [
                   # only 1 ruleset with no actual rules, just an action
                   [1]
               ],
           }
       }

       my $auth = Authorize::Rule->new( rules => $rules );
       $auth->is_allowed( cats => 'kitchen' ); # 1, success
       $auth->is_allowed( cats => 'bedroom' ); # 1, success

   If you don't like the example of cats (what's wrong with you?), try to
   think of a department (or person) given all access to all resources in
   your company:

       $rules = {
           Sysadmins => {
               '' => [ [1] ],
           },

           CEO => {
               '' => [ [1] ],
           },
       }

 Per resource
   Dogs, however, provide less of a problem. Mostly if you tell them they
   aren't allowed somewhere, they will comply. Dogs can't get on the table.
   Other than the table, we do want them to have access everywhere.

       $rules = {
           Cat => {
               '' => [ [1] ],
           },

           Dog => {
               Table => [ [0] ], # can't go on the table
               ''    => [ [1] ], # otherwise, allow everything
           },
       }

   A corporate example might refer to some departments (or persons) having
   access to some resources while denied everything else, or a certain
   resource not available to some while all others are.

       $rules = {
           CEO => {
               Payrolls => [ [0] ], # no access to Payrolls
               ''       => [ [1] ], # access to everything else
           },

           Support => {
               UserPreferences      => [ [1] ], # has access to this
               UserComplaintHistory => [ [1] ], # and this
               ''                   => [ [0] ], # but that's it
           },
       }

 Per resource and per conditions
   This is the most extensive control you can have. This allows you to set
   permissions based on conditions, such as specific parameters per
   resource.

   Suppose we have no problem for the dogs to walk on that one table we
   don't like?

       my $rules => {
           Dog => {
               Table => [
                   # if the table is owned by someone else, it's okay
                   [ 1, { owner => 'someone-else' } ],

                   # otherwise, no
                   [0],
               ],

               '' => [ [1] ], # but generally dogs can go everywhere
           }
       };

       my $auth = Authorize::Rule->new( rules => $rules );
       $auth->is_allowed( Dog => 'Table', { owner => 'me' } ); # 0, fails

   You can also specify just the existence (and "define"ss) of keys:

       my $rules = {
           Support => {
               ClientTable => [
                   [ 1, 'user_id' ], # must have a user id to access the table
                   [0],              # otherwise, access denied
               ]
           }
       };

 OR conditions
   If you want to create an OR condition, all you need is to provide
   another ruleset:

       my $rules = {
           Dog => {
               Table => [
                   [ 1, { carer => 'Jim'  } ], # if Jim takes care of the dog
                   [ 1, { carer => 'John' } ], # or if John does
                   [0],                        # otherwise, no
               ]
           }
       };

       $auth->is_allowed( Dog => 'Table', { owner => 'me'   } ); # 0, fails
       $auth->is_allowed( Dog => 'Table', { owner => 'Jim'  } ); # 1, succeeds
       $auth->is_allowed( Dog => 'Table', { owner => 'John' } ); # 1, succeeds

 AND conditions
   If you want to create an AND condition, just add more rules to the
   ruleset:

       my $rules = {
           Dog => {
               Table => [
                   [
                       1,                     # allow if...
                       { carer => 'John'   }, # john is the carer
                       { day   => 'Sunday' }, # it's Sunday
                       { clean => 1        }, # you're clean
                       'tag_id',              # and you have a tag id
                   # otherwise, no
                   [0],
               ]
           }
       };

   As shown in other examples above, any hash rules can be put in the same
   hash, so this is equivalent:

       my $rules = {
           Dog => {
               Table => [
                   [
                       1,                     # allow if...
                       {
                           carer => 'John',   # john is the carer
                           day   => 'Sunday', # it's Sunday
                           clean => 1,        # you're clean
                       },
                       'tag_id',              # and you have a tag id
                   # otherwise, no
                   [0],
               ]
           }
       };

   The order of rules does not change anything, except how quickly it might
   mismatch. If you have insane amounts of rules and conditions, it could
   make a difference, but unlikely.

 labeling
   Optional labels can be applied in order to help structure rulesets and
   understand which ruleset matched.

       my $rules = {
           Tester => {
               # Tester's rulesets for any resource
               '' => [
                   # regular ruleset
                   [ 1, 'test_mode' ], # if we're in test_mode

                   # labeled ruleset
                   'has test ID' => [ 1, 'test_id' ], # has a test ID
               ],
           },
       };

   Labeled and unlabeled rulesets can be interchanged freely.

 Catch all
   You might ask *what if there is no last rule at the end for any other
   resource?*

   The answer is simple: the "default" clause will be used. You can find an
   explanation of it under *ATTRIBUTES*.

 Callbacks
  As rule
   A rule can be a callback:

       my $rules = {
           Marge => {
               '' => [
                   [ 1, sub {
                       my $params = shift;

                       time - $params->{'now'} < 10
                           and return 1;

                       return 0;
                   } ]
               ],
           }
       };

       $auth->is_allowed( 'Marge', 'Anywhere', { now => time } );

  As parameter
   You can compare a parameter value to the result of a callback:

       my $rules = {
           Marge => {
               '' => [
                   [ 1, { name => sub { get_name( $_[0]->{'entity'} ) } } ]
               ]
           }
       };

   This will compare the "name" value in the parameters to whatever will be
   returned by "get_name", which gets as a first argument the "entity" that
   is used - in this case, *Marge*.

  As action
   You can also set the action to be a callback, which allows to do two
   interesting things:

   *   Change the action

   *   Investigate the result hash

       my $rules = {
           Marge => {
               '' => [
                   [
                       sub {
                           my $result = shift;
                           return 'SucceededAt' . $result->{'resource'};
                       },
                       { time => 'now' }, # rule
                   ],
               ]
           }
       };

       my $auth   = Authorize::Rule->new( rules => $rules );
       my $action = $auth->is_allowed( 'Marge', 'Somewhere', %params );
       # $action = 'SucceededAtSomewhere'

   The result hash will contain information on the request and the
   matching, such as the ruleset which was matched.

ATTRIBUTES
 default
   In case there is no matching rule for the entity/resource/conditions,
   what would you like to do. The default is to deny (0), but you can
   change it to allow by default if there is no match.

       Authorize::Rule->new(
           default => 1, # allow by default
           rules   => {...},
       );

       Authorize::Rule->new(
           default => -1, # to make sure it's the catch-all
           rules   => {...},
       );

 entity_groups
   Entity groups allow you to group entities onto their own label. This
   means you can set up multiple entities at the same time, while still
   matching them by the entity name instead of group name.

       my $auth = Authorize::Rule->new(
           rules => {
               'My Group' => {
                   Desk => [ [1] ],
               },
           },

           entity_groups => {
               'My Group' => [ qw<Sawyer Mickey> ],
           },
       );

       # OK
       $auth->is_allowed( 'Sawyer', 'Desk' );

 resource_groups
   Resource groups allow you to group resources onto their own label, much
   like *entity_groups*. You can set up multiple resources at the same
   time, while still matching them by the resource name instead of the
   group name.

       my $auth = Authorize::Rule->new(
           rules => {
               Person => {
                   Home => [ [1] ],
               },
           },

           resource_groups => {
               Home => [ 'Bedroom', 'Living Room', ... ],
           },
       );

       # OK
       $auth->is_allowed( 'Person', 'Bedroom' );

 rules
   Rules can be either:

   *
        A hash reference of your permissions, defined by the specification explained
       above.

   *   A key name (string) indicating this key must exist with no
       restriction to the value other than it must be defined.

   *   A callback with a result that provides the success or fail in
       boolean context.

METHODS
 is_allowed
   Returns the action for the entity and resource.

   Effectively, this is the "action" key in the result coming from the
   "allowed" method described below.

 allowed
       my $result = $auth->allowed( $entity, $resource, $params );

   Returns an entire hash containing every piece of information that might
   be helpful:

   *   entity

   *   resource

   *   params

   *   action

   *   label

   *   ruleset_idx

       The index of the ruleset, starting from 1.

AUTHORS
   *   Sawyer X <[email protected]>

   *   Mickey Nasriachi <[email protected]>

COPYRIGHT AND LICENSE
   This software is copyright (c) 2014 by Sawyer X.

   This is free software; you can redistribute it and/or modify it under
   the same terms as the Perl 5 programming language system itself.