NAME

Tree::Authz - inheritance-based authorization scheme

VERSION

0.01

DEVELOPER RELEASE

There's only a few methods in this class, so the interface should stabilise pretty quickly. Feel free to suggest changes. For the moment, keep a close eye on the CHANGES file when updating.

SYNOPSIS

use Tree::Authz;

my $groups = { superuser    => [ qw( spymasters politicians ) ],
               spymasters   => [ qw( spies moles ) ],
               spies        => [ 'informants' ],
               informants   => [ 'base' ],
               moles        => [ 'base' ],
               politicians  => [ 'citizens' ],
               citizens     => [ 'base' ],
               };

my $authz = Tree::Authz->setup_hierarchy( $groups, 'SpyLand' );

my $superuser = $authz->get_group( 'superuser' );
my $spies     = $authz->get_group( 'spies' );
my $citizens  = $authz->get_group( 'citizens' );
my $base      = $authz->get_group( 'base' );

$spies   ->setup_permissions( [ qw( read_secrets wear_disguise ) ] );
$citizens->setup_permissions( 'vote' );
$base    ->setup_permissions( 'breathe' );

foreach my $group ( $superuser, $spies, $citizens, $base ) {
    foreach my $ability ( qw( unspecified_ability
                              spy
                              spies
                              read_secrets
                              wear_disguise
                              vote
                              breathe
                              can ) ) {

        if ( $group->can( $ability ) ) {
            printf "%s can '%s'\n", $group->group_name, $ability;
        }
        else {
            printf "%s cannot '%s'\n", $group->group_name, $ability;
        }
    }
}

# prints:

superuser can 'unspecified_ability'     # superpowers!
superuser can 'spy'
superuser can 'spies'
superuser can 'read_secrets'
superuser can 'wear_disguise'
superuser can 'vote'
superuser can 'breathe'
superuser can 'can'
spies cannot 'unspecified_ability'
spies can 'spy'
spies can 'spies'
spies can 'read_secrets'
spies can 'wear_disguise'
spies can 'vote'
spies can 'breathe'
spies can 'can'
citizens cannot 'unspecified_ability'
citizens cannot 'spy'
citizens cannot 'spies'
citizens cannot 'read_secrets'
citizens cannot 'wear_disguise'
citizens can 'vote'
citizens can 'breathe'
citizens can 'can'
base cannot 'unspecified_ability'
base cannot 'spy'
base cannot 'spies'
base cannot 'read_secrets'
base cannot 'wear_disguise'
base cannot 'vote'
base cannot 'breathe'                   # !
base cannot 'can'                       # !!

# storing code on the nodes of the tree
$spies->setup_abilities( read_secret => $coderef );

print $spies->read_secret( '/path/to/secret/file' );

$spies->setup_plugins( 'My::Spies::Skills' );

$spies->fly( $jet ); # My::Spies::Skills::fly

DESCRIPTION

Class for inheritable, groups-based permissions system (Access Control List).

Custom methods can be placed on group objects. Authorization can be performed either by checking whether the group name matches the required name, or by testing (via can) whether the group can perform the method required.

Two groups are specified by default. At the top, superusers can do anything ($superuser->can( $action ) always returns a coderef). At the bottom, the base group can do nothing ($base->can( $action ) always returns undef).

All groups are automatically capable of authorizing actions named for the singular and plural of the group name.

METHODS

Methods can be called on classes or instances, except where stated otherwise.

Namespaces and class methods

This class is designed to work in environments where multiple applications run within the same process (i.e. websites under mod_perl). If the optional namespace parameter is supplied to setup_hierarchy, the groups are isolated to the specified namespace. All class methods should be called through the class returned from setup_hierarchy.

If your program is not operating in such an environment (e.g. CGI scripts), then you can completely ignore this parameter, and call class methods either through Tree::Authz, or through the string returned from setup_hierarchy (which, funnily enough, will be 'Tree::Authz').

get_group( $group_name )

Factory method, returns a Tree::Authz subclass object.

Sets up two permitted actions on the group - the singular and plural of the group name. This might be too cute, and could change to just the group name in a near future release. Opinions welcome.

new( $group_name )

Alias for new.

group_name()

Instance method.

Returns the name of the group.

group_exists( $group_name )

Returns true if the specified group exists anywhere within the hierarchy.

subgroup_exists( $subgroup_name, [ $group_name ] )

Method not implemented yet.

Give me a nudge if this would be useful.

Returns true if the specified group exists anywhere in the hierarchy underneath the current or specified group.

list_groups()

Returns an arrayref of all the group names in the hierarchy, sorted by name.

dump_hierarchy( [ $namespace ] )

Get a simple printout of the structure of your hierarchy.

This method requires Devel::Symdump.

If you find yourself parsing the output and using it somehow in your code, let me know, and I'll find a Better Way to provide the data. This method is just intended for quick and dirty printouts and could change at any time.

setup_hierarchy( $groups, [ $namespace ] )

Class method.

$groups has:

keys   - group names
values - arrayrefs of subgroup name(s)

Sets up a hierarchy of Perl classes representing the group structure.

The hierarchy will be contained within the $namespace top level if supplied. This makes it easy to set up several independent hierarchies to use within the same process, e.g. for different websites under mod_perl.

Returns a class name through which group objects can be retrieved and other class methods called. This will be 'Tree::Authz' if no namespace is specified.

setup_permissions( $cando )

Instance method.

Adds methods to the class representing the group. $cando is a single method name, or arrayref of method names. No-op methods are added to the class representing the group:

my $spies = $authz->get_group( 'spies' );

my $cando = [ qw( read_secret wear_disguise ) ];

$spies->setup_permissions( $cando );

if ( $spies->can( 'read_secret' ) ) {
    warn 'Compromised!';
}

warn 'Trust no-one' if $spies->can( 'wear_disguise' );
setup_permissions_on_group( $group_name, $cando )

Class method version of setup_permissions.

setup_abilities( $name => $coderef, [ $name2 => $coderef2 ], ... )

Instance method.

Adds methods to the class representing the group. Keys give method names and values are coderefs that will be installed as methods on the group class:

my $spies = $authz->get_group( 'spies' );

my %able = ( read_secret => sub {
                my ($self, $file) = @_;
                open( SECRET, $file );
                local $/;
                <SECRET>;
                },

             find_moles => sub { ... },

            );

$spies->setup_abilities( %able );

if ( $spies->can( 'read_secret' ) ) {
    print $spies->read_secret( '/path/to/secret/file' );
}

# or

if ( my $read = $spies->can( 'read_secret' ) ) {
    print $spies->$read( '/path/to/secret/file' );
}

# with an unknown $group
my $get_secret = $group->can( 'read_secret' )       ||     # spy
                 $group->can( 'steal_document' )    ||     # mole
                 $group->can( 'create_secret' )     ||     # spymaster
                 $group->can( 'do_illicit_thing' )  ||     # politician
                 sub {};                                   # boring life

my $secret = $group->$get_secret;
setup_abilities_on_group( $group_name, %code )

Class method version of setup_abilities.

setup_plugins( $plugins )

Instance method.

Instead of adding a set of coderefs to a group's class, this method adds a class to the @ISA array of the group's class.

package My::Spies;

sub wear_disguise {}

sub read_secret {
    my ($self, $file) = @_;
    open( SECRET, $file );
    local $/;
    <SECRET>;
}

package main;

my $spies = $authz->get_group( 'spies' );

$spies->setup_plugins( 'My::Spies' );

if ( $spies->can( 'read_secret' ) ) {
    warn 'Compromised!';
    print $spies->read_secret( '/path/to/secret/file' );
}

warn 'Trust no-one' if $spies->can( 'wear_disguise' );
setup_plugins_on_group( $group_name, $plugins )

Class method version of setup_plugins.

TODO

More methods for returning meta information, e.g. immediate subgroups of a group, all subgroups of a group, list available actions of a group and its subgroups.

Might be nice to register users with groups.

Make group objects be singletons - not necessary if the only data they carry is their own name.

Under mod_perl, all setup of hierarchies and permissions must be completed during server startup, before the startup process forks off Apache children. It would be nice to have some way of communicating updates to other processes. Alternatively, you could run the full startup sequence every time you need to access a Tree::Authz group, but that seems sub-optimal.

DEPENDENCIES

Lingua::EN::Inflect::Number, Class::Data::Inheritable.

Optional - Devel::Symdump.

Sub::Override for the test suite.

BUGS

Please report all bugs via the CPAN Request Tracker at http://rt.cpan.org/NoAuth/Bugs.html?Dist=Tree-Authz.

COPYRIGHT AND LICENSE

Copyright 2004 by David Baird.

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

AUTHOR

David Baird, cpan@riverside-cms.co.uk

SEE ALSO

DBIx::UserDB, Data::ACL.