Just a Theory

Trans rights are human rights

TKP Permissions Design

So, I’m thinking of implementing permissions in the Kinetic Platform differently than they’re implemented in Bricolage. Bricolage has a number of fixed permissions: READ, EDIT, RECALL, CREATE, PUBLISH, and DENY. These permissions are cumulative, so that if you have EDIT permission, it implies READ, and if you have CREATE, it implies RECALL, EDIT, and READ.

This design was based on Windows NT permissions (roughly), and has worked reasonably, well, but is annoying for various reasons. The most serious drawback is that it’s difficult to understand. I always tell people who need to manage Bricolage permissions to read Bric::Security, and then read six more times. But aside from the impenetrability of the current permissions design, it’s also difficult to add new permissions: where should they fit into the hierarchy? This is what happened with RECALL and PUBLISH, which were added in a later version of Bricolage. To this day, it’s a bit confusing to some that, with RECALL permission, you can RECALL a story but not CREATE one.

So I’m looking around for other permissions patterns. Unix is nice, in that READ, WRITE, and EXECUTE permissions are all entirely independent, and apply to three classes of objects (file owner, file group, everyone). But Unix only needs to worry about files; Kinetic applications will have many more objects for which permissions will need to be specified. RT uses discreet permissions with names like “AdminQueue”, “CommentOnTicket”, “CreateTicket”, and “StealTicket” to be applied to every user or group. This strikes me as somewhat more useful, since the permissions are much more descriptive and can be targeted to particular objects. In fact, the permission names even indicate to what types of objects permissions apply!

So I’m thinking of leaving the cumulative permissions model behind and switching to more descriptive, discreet, and potentially numerous permissions. I’m not, however particularly fond of RT’s approach of storing the permissions as strings in the database. Now, I could keep them as numbers, where each permission has its own unique number. This is similar to how Bricolage permissions work. Only I’d have to always use List::MoreUtil’s any() function to see if a permission was in a list.

For example, say that an object had permissions with the numbers 1, 2, 5, 8, 12, 49, and 50. If these were stored in an array, then every time I had to check permissions, the can_do() function would have to search through those numbers:

sub can_do {
    my ($self, $to_check) = @_;
    my $perms = $self->perms;
    return any { $_ == $to_check } @$perms;
}

This isn’t the most efficient approach, unfortunately. If there are a lot of numbers, and you were always checking one that was towards the end of the list of permissions, it’d always take a long time. So, an alternate approach–one that conveniently works well with discreet permissions–is to use powers of two for the permissions numbers: 1, 2, 4, 8, 16, 32, 64, 128, 256, and so on. Then, for a given object, these can just be stored in a single number that’s created by bit-wise ORing them together: 1 | 2 | 8 | 16. Fortunately, PostgreSQL has a nice aggregate function for this, bit_or(). (I’ll have to create my own for SQLite.) Anyway, then the can_do() function becomes much simpler and more efficient:

sub can_do {
    my ($self, $to_check) = @_;
    return $self->perms & $to_check;
}

If the value of $to_check was in the list bit-ORed into their permissions number, it will be returned; otherwise, 0 will be returned. Not bad, eh?

So anyway, I’m soliciting feedback. Are discreet permissions better than cumulative permissions? And if so, are bit-wise ORed numbers the best way to represent an object ACLs?

Looking for the comments? Try the old layout.