SPOPS::Secure::Hierarchy - Define hierarchical security


 # In your SPOPS configuration
 'myobj' => {
    'class' => 'My::FileObject',
    'isa' => [ qw/ ... SPOPS::Secure::Hierarchy  ... / ],
    'hierarchy_separator' => '/',
    'hierarchy_field'     => 'myobj_id',

 # Every normal SPOPS security check will now go through a hierarchy
 # check using '/' as a separator on the value of the object parameter
 # 'myobj_id'

  my $file_object = eval { My::FileObject->fetch(
                             '/docs/release/devel-only/v1.3/mydoc.html' ) };

 # You can also use it as a standalone service. Note that the 'class'
 # in this example is controlled by you and used as an identifier
 # only.

 my $level = eval { SPOPS::Secure::Hierarchy->check_security({
                      class                 => 'My::Nonexistent::File::Class',
                      user                  => $my_user,
                      group                 => $my_group_list,
                      security_object_class => 'My::SecurityObject',
                      object_id             => '/docs/release/devel-only/v1.3/mydoc.html',
                      hierarchy_separator   => '/' }) };


The existing SPOPS security framework relies on a one-to-one mapping of security value to object. Sometimes you need security to filter down from a parent to any number of children, such as in a pseudo-filesystem of objects.

To accomplish this, every record needs to have an identifier that can be manipulated into a parent identifier. With filesystems (or URLs) this is simple. Given the pseudo-file:


You have the following parents:

 <ROOT OBJECT> (explained below)

What this module does is check the security of each parent in the hierarchy. If no security settings are found for an item, the module tries to find security of its parent. This continues until either the parent hierarchy is exhausted or one of the parents has a security setting.

If the security were defined like this:

(Note: this is pseudo-code, and not necessarily the internal representation):

      { world => SEC_LEVEL_READ,
        group => { admin => SEC_LEVEL_WRITE } }

 /docs/release/devel-only =>
      { world => SEC_LEVEL_NONE,
        group => { devel => SEC_LEVEL_WRITE } }

And our sample file is:


And our users are:

  • racerx is a member of groups 'public', 'devel' and 'mysteriouscharacters'

  • chimchim is a member of groups 'public', 'sidekicks'

  • speed is a member of groups 'public' and 'devel'

Then both the users racerx and speed would have SEC_LEVEL_WRITE access to the file while chimchim would have no access at all.

For the file:


All three users would have SEC_LEVEL_READ access since the permissions inherit from the ROOT OBJECT.

What is the ROOT OBJECT?

If you have a hierarchy of security, you are going to need one object from which all security flows. No matter what kind of identifiers, separators, etc. that you are using, the root object always has the same object ID (For the curious, this object ID is available as the exported scalar $ROOT_OBJECT_NAME from this module.)

If you do not create security for the root object manually, SPOPS::Secure::Hierarchy will do so for you. However, you should be aware that it will create the most stringent permissions for such an object and that you might have a difficult time creating/updating objects once this happens.

Here is how to create such security:

          scope => [ SEC_SCOPE_WORLD, SEC_SCOPE_GROUP ],
          level => { SEC_SCOPE_WORLD() => SEC_LEVEL_READ,
                     SEC_SCOPE_GROUP() => { 3 => SEC_LEVEL_WRITE } }

Now, every object created in your class will default to having READ permissions for WORLD and WRITE permissions for group ID 3.


Most of the functionality in this class is found in SPOPS::Secure. We override one of its methods and add another to implement the functionality of this module.

get_hierarchy_levels( \%params )

Retrieve security for each level of the hierarchy. Returns a list -- the first element is a hashref with the keys as hierarchy elements and the values as the security settings for that element (like what you would get back if you checked only one item). The second element is a scalar with the key of the first item encountered which actually had security.


 my ( $all_levels, $first ) = $obj->get_hierarchy_levels();
 print "Level Info:\n", Data::Dumper::Dumper( $all_levels );

 >Level Info:
 > $VAR1 = {
 >  '/docs/release/devel-only/v1.3/mydoc.html' => undef,
 >  '/docs/release/devel-only/v1.3' => undef,
 >  '/docs/release/devel-only' => { u => 4, g => undef, w => 8 },
 >  '/docs/release/' => undef,
 >  '/docs/' => undef,
 >  'ROOT_OBJECT' => { u => undef, g => undef, w => 4 }

 print "First Level: ", $first;

 > First Level: /docs/release/devel-only

get_security( \%params )

Returns: hashref of security information indexed by the scopes.


  • class ($) (not required if calling from object)

    Class (or generic identifier) for which we would like to check security

  • object_id ($) (not required if calling from object)

    Unique identifier for the object (or generic thing) needing to be checked.

  • hierarchy_field ($) (only required if calling from object with no configuration)

    Field to be used for the hierarchy check. Most (all?) of the time this will be the same as your configured 'id_field' in your SPOPS configuration.

  • hierarchy_separator ($) (not required if calling from object with configuration)

    Character or characters used to split the hierarchy value into pieces.

  • hierarchy_manip (optional)

    Code reference that, given the value to be broken into chunks, will return a hashref of information that describe the ways the hierarchy information can be used.


This is overridden and a no-op, since we do not want SPOPS::Secure to create the default WORLD settings for us and mess up our inheritance.

create_root_object_security( \%params )

If you are trying to retrofit this security system into a class with already existing objects, you will need a way to bootstrap it so that you can perform the actions you like. This method will create initial security for you.


  • scope (\@ or $)

    One or more SPOPS::Secure SEC_SCOPE_* constants that define the scopes that you are defining security for.

  • level (\% or $)

    If you have specified more than one item in the 'scope' parameter, this is a hashref, the keys of which are the scopes defined. The value may be a SPOPS::Secure LEVEL constant if the matching scope is WORLD, or a hashref of object-id - LEVEL pairs if the matching scope is USER or GROUP. (See "What is the ROOT OBJECT?" above.)


None known.


Revisit when hierarchy field != primary key

the _get_hierarchy_parameters has an assumption that the object ID will always be the hierarchy value. Fix this. (Putting off because this is unlikely.)


Security for Each Parent not Required

Note that each parent as we go up the hierarchy does not have to exist in terms of security. That is, since an object can be both a child and a parent, and a child can inherit from a parent, then the inheritance needs to be able to flow through more than one generation.




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

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


Chris Winters <>

Christian Lemburg <>