linux454 has asked for the wisdom of the Perl Monks concerning the following question:

I am creating a permissions system for a large application. This application will have a very broad scope, and is very componentized. Which means that each component may have it's own set of permissions that it will need to add to the system. I have come up with what I believe are the two best solutions:
  1. Permission Bitmask
  2. Named Permissions

(Of course I am open to other solutions)
Let me elaborate...
Example of permissions bitmask:
... use constant READ_PERM => 0x04; use constant WRITE_PERM => 0x02; use constant EXECUTE_PERM => 0x01; use constant NO_PERM => 0x00; my $allowed_perms = READ_PERM | WRITE_PERM; # Check for permissions: if ( $allowd_perms == NO_PERM ) { print "PERMISSION DENIED\n" exit -1; } if ( $allowed_perms & READ_PERM ) print "READ Allowed\n"; if ( $allowed_perms & WRITE_PERM ) print "WRITE Allowed"; if ( $allowed_perms & EXECUTE_PERM ) print "EXECUTE Allowd\n"; ...
This method requires that we have 1 bit for every permission across the entire system. Thus a system with 256 distinct permissions will represent the integer 1.15792089237316e+77. To assign a user a permissinos mask we bitwise OR the permissions bits together. To check for a particular permission in the mask we bitwise AND the permission bit that we are looking for against the user's mask.
The pros:
The cons: The next approach is named permissions. This method implements permissions as a list of text tokens. For example:
use constant READ_PERM => READ_PERM use constant WRITE_PERM => WRITE_PERM; use constant EXECUTE_PERM => EXECUTE_PERM; my @allowed_perms = ( READ_PERM, EXECUTE_PERM ); # Check permissions if ( scalar(@allowed_perms) == 0 ) { print "PERMISSION DENIED\n"; exit -1; } if ( grep { $_ == READ_PERM } @allowed_perms ) print "READ Allowed\n"; if ( grep { $_ == WRITE_PERM } @allowed_perms ) print "WRITE Allowed\n"; if ( grep { $_ == EXECUTE_PERM } @allowed_perms ) print "EXECUTE Allowed\n";
The pros: The cons:

The question is which one is better in the opinion of this community and/or are there better ways of doing this than the two that I have laid out?

The objectives are to create an access control system that is flexible, maintainable, and easy to use.

Many of you would point out that by those virtues the named permission method would fit the bill more closely. Too true. However, I am hoping that some of you see something I've missed. I personally prefer the bitmask method, however the problem of having a large enough place to store the mask without stringifying it is a problem.

Any constructive criticism, comments and/or suggestions are greatly welcomed and desired.

Thank you all for your hard work and attention.

update (broquaint): fixed missing </ul> tag

Replies are listed 'Best First'.
Re: Bitmask or Named permissions
by idsfa (Vicar) on Oct 06, 2003 at 17:18 UTC

    Is it possible to place the permissions scheme inside of each component and just define the API? This gives you most of the pros of each system. Especially if the implementations of the API do not store permission data which matches the "default" (ie, if read-only is the default, no data needs to be stored for users with read-only access).

    Pros
    • Flexible - components can be added/removed/reshuffled
    • Maintainable - Permissions are packaged with the objects
    • Simple - if ( $obj->is_readable($id) ) is fairly clear
    Cons
    • Planning - More up-front work needed (is this a con?)
    • Insular - Permissions are opaque between components

    Probably more cons I haven't thought of.


    Remember, when you stare long into the abyss, you could have been home eating ice cream.
      Thanks for the feedback.
      Let me say that more up-front work is definitely not a con =]
      I am a firm believer that a little more work up-front can save a lot of work on the backend.
      Really, I think the biggest issue with a permissions system is how to store a user's assigned permissions. This will then generally dictate how permissions are stored. The plan is to create a class that does nothing but answer authorization questsions something like the following pseudo-code:
      class Authorize { constructor( string user ) { # get user permissions from persistent storage } hasPermission(PermissionPrimitive perm) { # compare user's permissions with given primitive # and return true or false accordingly } }

      Of course this scenario would probably have more methods, but those two are really the core functionality.
      Given this scenario we have a central place to store a user's permissions and a central place for querying the user's permissions. This allows for the system as a whole to be ignorant of permissions, and puts the power into the specific components that will actually need authorization information.
        Then you also have to either place business logic about how permissions react with each other in those methods, or rely on general statements about permissions -- like they are always additive or the minimalistic. Either way limits you on how permissions that are inherently tied together can work (think requisite permissions). also exclusive permissions are hard to deal with as well.


        -Waswas
Re: Bitmask or Named permissions
by halley (Prior) on Oct 06, 2003 at 18:00 UTC
    When I was writing C++ regularly, I developed a quick class that represented "modes" like an IRC channel or user. That is, the caller could manipulate modes with simple mode strings, and the application could configure legal modes and special modes ahead of time.

    From memory, C++ code would work something like:

    CMode mode = new CMode("abcd*eAB"); mode = "beB"; // mode.Clear(); mode.SetModes("beB"); mode += "a"; // mode.SetModes("a"); mode -= "b"; // mode.ClearModes("b"); mode += "d Jones"; // mode.SetModes("d", "Jones"); if (mode & "a") { ... } // mode.IsModeSet("a")

    The special handling of "d*" meant that mode "d" took an argument, and you could have any number of them. (Kinda like "/mode #chan +b n!i@h" on IRC.)

    Limits, similar to that of bitfields, are that you have a fixed and arbitrary set of fields. Benefits over bitfields are that it's fairly easy to expose the flags to the end user in a consistent way.

    --
    [ e d @ h a l l e y . c c ]

Re: Bitmask or Named permissions
by graff (Chancellor) on Oct 07, 2003 at 04:22 UTC
    If it's large (possibly growing?) set of components, where each component basically has to be set up for a small set of "standard" permission flags, then I should think that the perl coding best suited to this situation would be a hash, with compenent name as the hash key, and a string (e.g. "rwx" vs. "rw-" vs. "r--", etc) representing the basic permissions assigned to a given user for the given component. If you render this as an object class, it would be even easier and more maintainable.

    As for storing the hash to a database (and reading it back), there are lots of ways to do this, but perhaps the most practical would be a table entirely devoted to storing permissions: each row would hold "username", "componentname", and "permstring"; the "username, componentname" tuple would serve as a primary key (two components can't have the same name, and one user can't have two different permission settings for the same component).

    With such a table, you can "select * where componentname='X'" and see all the users and their permissions associated with that component, or "select * where username='Y'" and see all the components this person is registered for, and what his/her permissions are for each one.

    Maybe you want the permission information to be "global" in some way (all contained in one hash, accessible to all components), or maybe it should be set up so that only the database keeps the "global" picture, and each component only fetches and holds the permission data it needs. With a simple permissions table and an object class to manage it, you could go either way. You're also free to choose where to implement dependencies (e.g. users who can do X should all be able to do Y), though it will probably be best to handle this in the components, or in the code that glues components together -- keep the permissions object simple and general-purpose, rather than cluttering it up with stuff that is based on knowledge about what the components actually do.

    (Of course, some dependencies are generic no-brainers, like "if you can execute this, you have to be able to read it"; this sort of thing belongs in a "Perms" module.)

    update: This train of thought could just lead to a heavier reliance on the database, querying the permissions table whenever something needs to be checked rather than maintaining a perl-internal hash; in that case, you would probably want the different types of permissions (read, write, execute, etc) as separate columns in the table, to allow more efficent indexing and fetches. A "Perms" module would still be useful, to help integrate/migrate the table data for components and users. Keep in mind that it's easy to add columns to an existing table, in case you come up with new types of permissions later on.

Re: Bitmask or Named permissions
by blssu (Pilgrim) on Oct 07, 2003 at 14:36 UTC

    I don't understand why the 64 bit limit is a problem. The bits are used to identify operations. The user id should be stored in another field.

    There is no possible way your users will think about the security of 64 different operations. Maybe I'm missing something though. Could you list some of the "real" permissions instead of just the read/write/execute example?

    I designed a security module that stores object permissions in an ACL table. Each different operation is given a column in the ACL table. If the value of the column is even, permission is denied; if the value is odd, permission is granted.

    The data model looks like this:

    ACL ( item_id integer, group_id integer, read_access integer, write_access integer, create_access integer, delete_access integer ... ) -- All objects with security assignments have -- an entry in the item table. Common attributes -- such as owner, creation date, data retention -- schedule, dispose-by date, etc. are stored -- here too. ITEM ( id integer, ... ) -- The session table is updated when a person -- logs in. Hierarchical group memberships are -- flattened out and inserted into the session -- table. SESSION ( user_id integer, group_id integer )

    Each different object class can implement its own has_user_access method. The base class method does a simple ACL test using this query:

    select max(read_access) from acl, session where acl.group_id = session.group_id and acl.item_id = :ITEM and session.user_id = :USER

    The results of these security tests are cached only for the immediate operation (Apache request object). I use a bit-field, but since it is just a cache, the implementation can change without affecting any other code. After the operation is complete, the cache is thrown away so that security information can not leak between user sessions. (It is not as horrible as it sounds -- the database keeps its own cache so these checks rarely hit the disk.)

Re: Bitmask or Named permissions
by duffbeer703 (Novice) on Oct 06, 2003 at 19:10 UTC

    I work with a big, bad, broad application with similar permissions requirements.

    The handled it by creating an "administrative" object with permissions attributes and user attributes. These objects can work systemwide or be assigned to specific objects in the system.

    So for example the "root" user would be subscribed to an administrative group with global "superuser" permissions.

    A "peon" user is subscribed to an administrative group with global "user" permissions and admin permissions over their mailbox.

    It's cumbersome (the app I deal with does not let a user subscribe to multiple administrative objects) but effective.

Re: Bitmask or Named permissions
by IOrdy (Friar) on Oct 07, 2003 at 03:25 UTC
    I could be wrong but couldn't you store a seperate permission int for each componenent giving 64 possible permissions per component. That way you still only store an int but a user may have X amount of permission int's depending on the number of components.

    You could extend the basic permissions class when you create a component to provide methods(aliases) for your permissions that return a boolean value for each permission. I'm thinking along the lines of Class::DBI for your construction of objects, that way your classes/code need only grab the permissions for the components they are interested in.

    some psuedo perl.
    package main; use User::Permissions; use User::Permissions::Node; use User::Permissions::Page; my $up = User::Permissions->new(user_id => 1); my $up_page = User::Permissions::Page->new; warn "can read nodes" if User::Permissions::Node->read; # package call warn "can read pages" if $up_page->read; # object call # ... later ... package Foo; use User::Permissions; use User::Permissions::Baz; my $up = User::Permissions->new(user_id => 1); warn "can write baz" if User::Permissions::Baz->write;
Re: Bitmask or Named permissions
by Anonymous Monk on Oct 07, 2003 at 10:51 UTC
    Perl has powerful stringwise bit fiddling...use it. The below example allows for variable length bitstrings. If you can make sure they are all the same length (e.g. initialize to "\0"x(MAXBITS/8), then set bits), you can use eq and ne, which might make for clearer code.
    sub setpermbybitnum { my $vec=""; vec($vec,shift,1) = 1; $vec } # check if any perms are set sub no_perm { "$_[0]" =~ tr/\0//c == 0} # check if all specified perm(s) are set sub has_perms { no_perm( ("$_[0]" & "$_[1]") ^ "$_[1]") } # check if any specified perms are set sub one_or_more_of_perms { !no_perm("$_[0]" & "$_[1]") } use constant READ_PERM => setpermbybitnum(3); use constant WRITE_PERM => setpermbybitnum(2); use constant EXECUTE_PERM => setpermbybitnum(1); my $allowed = READ_PERM | WRITE_PERM; # Check for permissions: if ( no_perm($allowed) ) { print "PERMISSION DENIED\n"; exit -1; } if ( has_perms($allowed, READ_PERM) ) { print "READ Allowed\n"; } if ( has_perms($allowed, WRITE_PERM) ) { print "WRITE Allowed\n"; } if ( has_perms($allowed, EXECUTE_PERM) ) { print "EXECUTE Allowd\n"; } if ( has_perms($allowed, READ_PERM|WRITE_PERM ) ) { print "READ and WRITE Allowed\n"; } if ( has_perms($allowed, READ_PERM|EXECUTE_PERM) ) { print "READ and EXECUTE Allowed\n"; } if ( one_or_more_of_perms($allowed, READ_PERM|EXECUTE_PERM) ) { print "READ or EXECUTE Allowed\n"; }