A quick guide to using bitmasks for permissions in PHP.

What is a bitmask?

A bitmask is a pattern of binary values.

What’s the simplest example?

//Define our bitmask
define("allowEverythingBitmask",1); //In binary 0001
//Our user
class User {
	private $permissionBitmask = 1; //In binary 0001

	public function setPermissionBitmask($bitmask)
	{
		$this->permissionBitmask = $bitmask;
	}

	public function getPermissionBitmask()
	{
		return $this->permissionBitmask;
	}

}

$user = new User;
//Test if user can do everything
if((allowEverythingBitmask & $user->getPermissionBitmask()))
{
	echo "I can do everything!";
}

Notice the single ampersand. This means we’re doing a bitwise AND

What is a bitwise AND?

1 & 1 === 1
1 & 0 === 0
0 & 1 === 0
0 & 0 === 0

Why?

http://en.wikipedia.org/wiki/Logic_gate

Let’s get more complicated

define("allowViewBitMask",1);        //In binary 0001
define("allowEditBitMask",2);        //In binary 0010
define("allowCreateBitMask",4);      //In binary 0100
define("allowDeleteBitMask",8);      //In binary 1000

You can see here that the binary pattern for each of our bitmasks has a 1 in a different column.

Let’s create a user with the permissions to view and edit, but not create and delete.

$user2 = new User();
$user2->setPermissionBitmask(allowViewBitMask | allowEditBitMask);

What the hell did | do?

Good question let’s go back to our logic gates, not only do we have AND ( & ) we also have OR ( | ). The truth table for OR looks like this.

1 | 1 === 1
1 | 0 === 1
0 | 1 === 1
0 | 0 === 0

So when we did | behind the scenes we did this

0001
0010
====
0011

Then we assigned that value to our user.

$user->setPermissionBitmask(0011); // In decimal 3

Let’s test it

$user2 = new User();
$user2->setPermissionBitmask(allowViewBitMask | allowEditBitMask);

if((allowViewBitMask & $user2->getPermissionBitmask()))
{
	echo "I can view!";
}
if((allowEditBitMask & $user2->getPermissionBitmask()))
{
	echo "I can edit!";
}
else
{
	echo "I can't edit :( ";
}

Try removing the | allowEditBitMask and see what happens.

If at a later date we wanted to give this user the create bitmask we could do this

$userPermissions = $user->getPermissionBitMask();
$userPermissions |= allowCreateBitMask;

$user->setPermissionBitMask($userPermissions);

0011 //Current users bitmask
0100 //The create bitmask
====
0111
Which would be the same as doing

$user->setPermissionBitMask(0111) //In decimal 7

What about removing permissions?

I’m glad you asked! It’s back to our truth table and this time we’re looking for an Xor or a ^ in PHP.

1 ^ 1 === 0
1 ^ 0 === 1
0 ^ 1 === 1
0 ^ 0 === 0

Let’s test everything

$user ->setPermissionBitMask(allowViewBitMask | allowEditBitMask | allowCreateBitMask | allowDeleteBitMask);
checkPermissions($user);

$userPermissions = $user->getPermissionBitMask();
$userPermissions ^= allowCreateBitMask;
$user->setPermissionBitMask($userPermissions);
checkPermissions($user);

function checkPermissions($user) {

	if((allowViewBitMask & $user->getPermissionBitmask()))
	{
		echo "I can view!";
	}
	else
	{
		echo "I can't view :( ";
	}
	if((allowEditBitMask & $user->getPermissionBitmask()))
	{
		echo "I can edit!";
	}
	else
	{
		echo "I can't edit :( ";
	}
	if((allowCreateBitMask & $user->getPermissionBitmask()))
	{
		echo "I can create!";
	}
	else
	{
		echo "I can't create :( ";
	}
}

Why is this better than strings which I normally use?

Well for one we’re not having to store a bunch of strings against the user, two it’s a lot faster, and three adding and removing permissions is much simpler!

I hope this helps! You can find a full gist of the code here. https://gist.github.com/2955913

Updates!

I received some good feedback on Reddit and thought I’d post some of the information / answer some the of the questions here.

What the disadvantages?

This is a copy and paste of Balrock‘s answers on Reddit, Reddit gold coming your way soon. :)

It isn’t human readable -> hard to debug

In my personal experience the first thing I do when looking at a permissions problem is set the permissions of my login to be that of the user with the problem. I’d argue that setting one integer against my user in the database, is easier than having to set a lot of strings. I have to agree thought that bitmaks do make it harder to see exactly what permissions a user has.

You can only store 31 permissions in an int.

Unless you’re using 64 bit integers. I’d argue that by the time you have 31 permissions anyway you’re probably doing something wrong. Permissions should be broken down into manageable chunks

You can’t do anything more than store boolean permissions – if you need more advanced (upload size) you have to encode this boolean too

This is very true, and something, I’ll be honest, I hadn’t thought about.

The way he defines the permissions doesn’t allow simple reordering or logical grouping and with his implementation you expose internals (usage of boolean operator) of the class to the outside which for example won’t allow you to later fix the maximum 31 permissions bug

Again nothing to argue here, this is more a demonstration of how to use bitmasks, not how to use them specifically in your application.

Posted

by & filed under php, programming

Leave a Reply

(will not be published)