NAME

SPOPS::Secure - Implement security across one or more classes of SPOPS objects

SYNOPSIS

# In the configuration for your object, add security to objects
# created by this class:

$spops = {
  myobject => {
       class => 'My::Object',
       isa   => [ qw/ SPOPS::Secure SPOPS::DBI / ],
  },
};

DESCRIPTION

By adding this module into the 'isa' configuration key for your SPOPS class, you implement a mostly transparent per-object security system. This security system relies on a few things being implemented:

  • A SPOPS class implementing users

  • A SPOPS class implementing groups

  • A SPOPS class implementing security objects

Easy, eh? Fortunately, SPOPS comes with all three, although you are free to modify them as you see fit. (As of version 0.42, see the 'eg/My' directory in the source distribution for the sample classes.)

Most people interested in security should not be reading the docs for this class. Instead, look at SPOPS::Manual::Security which offers a broad view of security as well as how to use, implement and extend it.

METHODS

The methods that this class implements can be used by any SPOPS class. The variable $item below refers to the fact that you can either do an object method call or a class method call. If you do a class method call, you must pass in the ID of the object for which you want to get or set security.

However, you may also implement security on the class level as well. For instance, if your application uses classes to implement modules within an application, you might wish to restrict the module by security very similar to the security implemented for individual objects. In this case, you would have a class name and no object ID ($object_id) value. (See SPOPS::Manual::Security for more information.)

check_security( [ \%params ] )

The method check_security() returns a code corresponding to the LEVEL constants exported from this package. This code tells you what permissions the logged in (or passed in) user has. You can pass user and group parameters to check security for other items as well.

Note that you can check security for multiple groups but only one user at a time. Passing an arrayref of user objects for the 'user' parameter will result in the first user object being checked and the remainder discarded. This is probably not what you want.

Examples:

# Find the permission for the currently logged-in user for $item
$item->check_security();

# Get the security for this $item for a particuar
# user; note that this *does* find the groups this
# user belongs to and checks those as well

$item->check_security({ user => $user });

# Find the security for this item for either of the
# groups specified

$item->check_security({ group => [ $group, $group ] });

get_security( [ \%params ] )

Returns a hashref of security information about the particular class or object. The keys of the hashref are the constants, SEC_SCOPE_WORLD, SEC_SCOPE_GROUP and SEC_SCOPE_USER. The value corresponding to the SEC_SCOPE_WORLD key is simply the WORLD permission for the object or class. Similarly, the value of SEC_SCOPE_USER is the permission for the user specified. The SEC_SCOPE_GROUP key has as its value a hashref with the IDs of the group as keys. (Examples below)

Note that if the user specified does not have permissions for the class/object, then its entry is blank.

The parameters correspond to check_security. The default is to retrieve the security for the currently logged-in user and groups (plus WORLD), but you can restrict the output if necessary.

Note that the WORLD key is always set, no matter how much you restrict the user/groups.

Finally: this will not be on the test, since you will probably not need to use this very often unless you are subclassing this class to create your own custom security checks. The check_security() and set_security() methods are likely the only interfaces you need with security whether it be object or class-based. The get_security() method is used primarily for internal purposes, but you might also need it if you are writing security administration tools.

Examples:

# Return a hashref using the currently logged-in
# user and the groups the user belongs to
#
# Sample of what $perm looks like:
# $perm = { 'u' => 4, 'w' => 1, 'g' => { 5162 => 4, 7182 => 8 } };
#
# Which means that the user has a permission of SEC_LEVEL_READ,
# the user belongs to two groups with IDs 5162 and 7182 which have
# permissions of READ and WRITE, respectively, and the WORLD
# permission is NONE.
my $perm = $item->get_security();

# Find the security for a particular user object and its groups
my $perm = $item->get_security({ user => $that_user });

# Find the security for two groups, no user objects.
my $perm = $item->get_security({ group => [ $group1, $group2 ] });

get_security_scopes( \%params )

Called by get_security() to determine which user object and which group objects to use to check security on an object.

Returns: two-item list, the first is the $user object and the second is an arrayref of $group objects.

set_security( \%params )

The method set_security() returns a status as to whether the permission has been set to what you requested.

The default is to operate on one item at a time, but you can specify many items at once with the 'multiple' parameter.

Examples:

# Set $item security for WORLD to READ

my $wrv =  $item->set_security({ scope => SEC_SCOPE_WORLD,
                                 level => SEC_LEVEL_READ });
unless ( $wrv ) {
  # error! security not set properly
}

# Set $item security for GROUP $group to WRITE

my $grv =  $item->set_security({ scope => SEC_SCOPE_GROUP,
                                 scope_id => $group->id,
                                 level => SEC_LEVEL_WRITE });
unless ( $grv ) {
  # error! security not set properly
}

# Set $item security for USER objects whose IDs are the keys in the
# hash %multiple and whose values are the levels corresponding to the
# ID.
#
# (Note that this is a contrived example for setting up the %multiple
# hash - you should always do some sort of validation/checking before
# passing user-specified information to a method.)

my %multiple = (
 $user1->id => $cgi->param( 'level_' . $user1->id ),
 $user2->id => $cgi->param( 'level_' . $user2->id )
);
my $rv = $item->set_security({ scope => SEC_SCOPE_USER,
                               level => \%multiple });
if ( $rv != scalar keys %multiple ) {
  # error! security not set properly for all items
}

# Set $item security for multiple scopes whose values
# are in the hash %multiple; note that the hash %multiple
# has a separate layer now since we're specifying multiple
# scopes within it.

my %multiple = (
 SEC_SCOPE_USER() => {
    $user1->id => $cgi->param( 'level_' . $user1->id ),
    $user2->id => $cgi->param( 'level_' . $user2->id ),
 },
 SEC_SCOPE_GROUP() => {
    $group1->id  => $cgi->param( 'level_group_' . $group1->id ),
 },
);
my $rv = $item->set_security({ scope => [ SEC_SCOPE_USER, SEC_SCOPE_GROUP ],
                               level => \%multiple });

create_initial_security( \%params )

Creates the security for a newly created object. Generally this entails looking at the creation_security key of an object configuration and mapping the permissions there to the object.

Parameters:

  • class: Specify the class you want to use to create the initial security.

  • object_id: Specify the object ID you want to use to create the initial security.

SUPERUSER METHODS

A handful of methods enable SPOPS to implement superuser/group checking. A superuser is a user who can perform any action, and a member of the supergroup can do the same.

If your class does not use the supergroup, just setup a function:

sub is_supergroup { return undef }

_check_superuser( $user_object, \@group_object )

Checks whether the given user and group listing has superuser status. Returns a hashref suitable for passing to check_security().

NOTE: We may rename this to check_superuser() in the future.

is_superuser( $user_id )

Returns true if $user_id is the superuser, false if not. Default is for the value 1 to be the superuser ID, but subclasses can easily override.

is_supergroup( @group_id )

Returns true if one of @group_id is the supergroup, false if not. Default is for the value 1 to be the supergroup ID, but subclasses can easily override.

TAGS FOR SCOPE/LEVEL

This module exports nothing by default. You can import specific tags that refer to the scope and level, or you can import groups of them.

Note that you should always use these tags. They may seem unwieldly, but they make your program easier to read and allow us to modify the values for these behind the scenes without you modifying any of your code. If you use the values directly, you will get what is coming to you.

You can import individual tags like this:

use SPOPS::Secure qw( SEC_SCOPE_WORLD );

Or you can import the tags in groups like this:

use SPOPS::Secure qw( :scope );

Scope Tags

  • SEC_SCOPE_WORLD

  • SEC_SCOPE_GROUP

  • SEC_SCOPE_USER

Level Tags

  • SEC_LEVEL_NONE

  • SEC_LEVEL_SUMMARY

  • SEC_LEVEL_READ

  • SEC_LEVEL_WRITE

Verbose Level Tags

These tags return a text value for the different security levels.

  • SEC_LEVEL_VERBOSE_NONE (returns 'NONE')

  • SEC_LEVEL_VERBOSE_SUMMARY (returns 'SUMMARY')

  • SEC_LEVEL_VERBOSE_READ (returns 'READ')

  • SEC_LEVEL_VERBOSE_WRITE (returns 'WRITE')

Groups of Tags

  • scope: brings in all SEC_SCOPE tags

  • level: brings in all SEC_LEVEL tags

  • verbose: brings in all SEC_LEVEL_VERBOSE tags

  • all: brings in all tags

TO DO

Refactor create_initial_security()

This method is too long and confusing -- break it into pieces.

Sort out the different set_* methods

The different set_* methods are currently quite confusing.

Add caching

Gotta gotta gotta get a caching interface done, where we simply say:

$object->cache_security_level( $user );

And cache the security level for that object for that user. **Any** security modifications to that object wipe out the cache for that object.

BUGS

None known, besides girth.

COPYRIGHT

Copyright (c) 2001-2004 intes.net, inc.. All rights reserved.

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

AUTHORS

Chris Winters <chris@cwinters.com>