Using Bitmasks in a Groups Permission System

October 1, 2008 – 8:00 pm

As promised in my previous post, this article will describe a practical application of bitmasks, in this particular case for usage in a permission system on a webapplication. This particular application is a social network site, in which a user can create a joinable group. Each group has four usergroups - Guests, Members, Moderators, and Administrators, and as a special group with no specific traits of its own, Creator(s) (i.e. the person(s) that created the group).

Each usergroup has a set of permissions in each group - so basically, you have to be able to store a usergroup’s permissions on a per-group basis. In this article, I’ll explain how I’ve used bitmasks and the associated operators as explained in the previous article to store those permissions, as well as the usergroup(s) a particular user belongs to in a specifc group. The bottom line is that by storing the permission a usergroup has in a bitmask, you end up with a lot less database usage. But that’ll be explained in this article.

First though, a case study. This is actually a good idea for pretty much every software application, both as a whole and its parts - analyze what you’re trying to do. In this case, I wanted to create a social network site of sorts (using the Zend Framework) with, next to the obvious user profiles and user-to-user communication, a ‘groups’ system, where users can create groups, clubs, or whatnot, each with its own discussions (or ‘threads’, you could basically also see it as a forum where users can create their own sections). More specifically, the owner / Administrator of a Group should be able to determine the access people have to their own groups - for example, a private group, an invite-only group, a read-only group, etcetera.

To implement this, a permission system would have to be setup. I was already pretty sure I’d use a default set of usergroups (Creators, Administrators, Moderators, Members and groupless, Guests), also called Roles, which more properly describes the per-group Role a User has - in group A, a User may just be a visiting Guest, whilst in group B the user would be a Moderator, and in C a Member, etc.

Next to that, I also figured that one User may fulfill more than one Role - in first instance the Creator / Administrator combination, where a Creator in its own doesn’t have any particular rights or duties, but when combined with the Administrator role, a Creator rises above the ‘regular’ Administrator rank, in that he cannot be removed from the group by other Administrators. In retrospect, allowing every user to have more than one Role at a time might’ve been a little overkill, but it’s quite manageable in terms of the database storage required to associate a User with a Group.

In the database, there’s a link table, GroupUsers, that contains the group ID, the user ID, and the Role, the latter being a regular integer value. Just one column to indicate the user’s Role, no matter how many Roles the user has.

Next to assigning Roles to Users on a per-group basis, a Role would also need to have a set of Permissions, also on a per-group basis. In other words, in one group a Member could, for example, create new threads, whilst in another a Member could only reply to threads, all based on the choice of the Administrator(s) of that particular group.

So another requirement was that per Group, the permissions of a Role would have to be stored.

The Permissions are basically a list of sorts, containing items such as

(users with this Role can:)

  • View the group
  • View threads
  • Edit threads
  • Create new threads
  • Reply to threads
  • Edit own posts
  • Edit other people’s posts
  • etc

The permissions each Role has can be determined and set with the above list of permissions. Next to that, it’s relatively easy to extend these permissions.

The problem however is storage. In a regular system, one might say that the Role has a list of boolean permission values in that Group - canViewGroup, canViewThreads, canEditThreads, etcetera. Translating that to a database schema is also easy - just add boolean columns canViewGroup, canViewThreads, etcetera, with values 0 or 1 in them.

However, this makes the Groups table, in which we can store the permissions for each group, needlessly long - for each role + permission, a column is needed, so with just the above 7 permissions, 28 columns would be needed to store these. One of the base rules of database design is to make your database tables as narrow as possible - as few columns as possible. The narrower the table, the less data has to be processed and searched through, the faster access is.

In order to solve this particular problem, bitmasks were used. Instead of storing each permission as its own boolean value, each permission has its own unique mask - as described in the previous article. The permissions itself is stored in a numerical value, both in the program’s code itself (the Group object) and in the database. Instead of having the amount of roles times the amount of permissions in database columns for each Group, you can now do with just one column per usergroup. Here’s how it’s done. We keep everything in the Group object, since that’s where the Roles’  Permissions are stored. You could opt to put the storage of Roles in a separate object, but it makes no functional or logical difference (there’s a 1-to-1 relationship between a Group and its permissions).

In the Group object, we put the numerical values that store the permissions for each usergroup, and we define a list of constants that represent the bitmasks (or ‘flags’) for each permission. Note again that all permissions are powers of 2, as explained in the other article. Note also the usage of class-level constants, introduced in PHP 5 (I believe).

class Group
{
	const PERM_VIEW_GROUP = 1;
	const PERM_VIEW_THREADS = 2;
	const PERM_EDIT_THREADS = 4;
	const PERM_CREATE_THREADS = 8;
	const PERM_REPLY_THREADS = 16;
	const PERM_EDIT_OWN_POSTS = 32;
	const PERM_EDIT_OTHER_POSTS = 64;

	private $administratorPermissions = 0;
	private $moderatorPermissions = 0;
	private $memberPermissions = 0;
	private $guestPermissions = 0;

}

For each Role, methods can be defined that set, unset, add or remove permissions to a certain usergroup. 

 

	public function setAdministratorPermissions($perms)
	{
		$this->administratorPermissions = $perms;
	}

	// returns the Administrator role's permissions
	public function getAdministratorPermissions()
	{
		return $this->administratorPermissions;
	}

	// Adds a permission to the Administrator role.
	public function addAdministratorPermission($perm)
	{
		// we use the |= operator, which is equal to $value = $value | $perm
		$this->administratorPermissions |= $perm;
	}

	// Removes / unsets the given permission from the Administrator role's permissions.
	public function removeAdministratorPermission($perm)
	{
		$this->administratorPermissions &= ~$perm
	}

	// checks if the Administrator role has the given permission
	public function getAdministratorPermission($perm)
	{
		return (($this->administratorPermissions & $perm) != 0);
	}

Rinse and repeat for the other usergroups. Of course, all this could be condensed into even smaller chunks. You could also, to further redue the storage used for permission, create additional permission masks, the full set for each usergroup - i.e. MODERATOR_PERM_CAN_VIEW_THREAD, etc. However, you'll probably run up against the limitations of the system - i.e. that a number can only contain so much bits before it overflows. If you have only a few permissions, you could chuck it all into a single value, but if you have more (more than 8), use separate variables.

Obligatory list of social network links:

These icons link to social bookmarking sites where readers can share and discover new web pages.
  • bodytext
  • del.icio.us
  • Google
  • E-mail this story to a friend!
  • Print this article!
  • Reddit
  • Slashdot
  • StumbleUpon

Post a Comment