Deprecated: preg_replace(): The /e modifier is deprecated, use preg_replace_callback instead in /share/CACHEDEV1_DATA/Web/www/libraries/UBBcode/text_parser.class.php on line 228

Deprecated: preg_replace(): The /e modifier is deprecated, use preg_replace_callback instead in /share/CACHEDEV1_DATA/Web/www/libraries/UBBcode/text_parser.class.php on line 228

Deprecated: preg_replace(): The /e modifier is deprecated, use preg_replace_callback instead in /share/CACHEDEV1_DATA/Web/www/libraries/UBBcode/text_parser.class.php on line 228

Deprecated: preg_replace(): The /e modifier is deprecated, use preg_replace_callback instead in /share/CACHEDEV1_DATA/Web/www/libraries/UBBcode/text_parser.class.php on line 228

Deprecated: preg_replace(): The /e modifier is deprecated, use preg_replace_callback instead in /share/CACHEDEV1_DATA/Web/www/libraries/UBBcode/text_parser.class.php on line 228

Deprecated: preg_replace(): The /e modifier is deprecated, use preg_replace_callback instead in /share/CACHEDEV1_DATA/Web/www/libraries/UBBcode/text_parser.class.php on line 228

Deprecated: preg_replace(): The /e modifier is deprecated, use preg_replace_callback instead in /share/CACHEDEV1_DATA/Web/www/libraries/UBBcode/text_parser.class.php on line 228
Bitmask access levels

Comments Blog About Development Research Sites

Bitmask access levels

Aug 5, 2009
Any website with more than one user capable of modifying content will, sooner or later, want to create a system to determine what user can perform what action. A division in usergroups then tends to work, but requires crosstables if a user can belong to several groups and several groups have access to the same action. In essence, this becomes an N:N relationship, which means you will need 4 tables just to handle access rights.

A simpeler, faster and in some regards more beautifull solution is to incorporate bitmasks. Consider the binary representations of four access levels, and some imaginary usergroup each represents:
Code (php) (nieuw venster):
1
2
3
4
1  = 0001  // user
2  = 0010  // news poster
4  = 0100  // moderator
8  = 1000  // administrator

Now say you wish to grand someone rank 1 and 4, ie, make them both a user and a moderator:
Code (php) (nieuw venster):
1
2
3
$rank = 1 | 4; // Set all bits that are on in 1 or 4.

echo $rank; // Outputs 5, which is 0101 in binary

It really is that easy! If you are not familiar with the bitwise operators I suggest you check out the page on them in the manual.

The beauty is, you can also do this already in your SQL query:
Code (php) (nieuw venster):
1
2
3
SELECT  `username`
FROM    `accounts`
WHERE  (`accessMask` & 4)

(This selects someone who will at least have access mask 4, thus someone who belongs to the moderator user group).

Little more complicated: someone who is both an access mask 4 and mask 2, in other words, only give us the username if the person is both a moderator as well as a news poster:
Code (php) (nieuw venster):
1
2
3
SELECT  `username`
FROM    `accounts`
WHERE  (`accessMask` & (4 | 2))


Or the other way around, someone who does not have access right 8, ie everyone who is not an administrator:
Code (php) (nieuw venster):
1
2
3
SELECT  `username`
FROM    `accounts`
WHERE  ((`accessMask` ^ 8) & 8)


The adventage over other methods is that you can suffice with a single bitwise operation per access level, which is much, much faster than string comparisons for example and also faster than joining up several other tables. Additionally, you can simply obtain access rights directly from your database instead of having a PHP script parse them.

On a sidenote: for added readability you should define the various access masks as a class constant. In a truely OOP scenario this means creating a separate mask object:
Code (php) (nieuw venster):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Mask {
  const   USER           = 1;
  const   NEWS_POSTER    = 2;
  const   MODERATOR      = 4;
  const   ADMINISTRATOR  = 8;

  private
$mask;    // Access mask asociated with this object.


  /**
   *  Constructor; instantiates a new Mask object.
   *
   *  Main purpose of this is to set the access mask
   *  associated with this class to the value given.
   *
   *  @param  mask    Int     The access mask for this.
  **/

  public function __construct ($mask) {
    $this -> mask = (int) $mask;
  }



  /**
   *  Determine whether this Mask contains the user
   *  access level, ie whether the client belongs to
   *  the user usergroup.
   *
   *  @return Int     Either 0 (not a user) or self::USER.
  **/

  public function isUser () {
    return (
$this -> mask & self::USER);
  }



  /**
   *  Test of this Mask contains the given access group(s).
   *
   *  @param  mask    Int     The mask to test against.
   *  @return Int     Either 0 (not contained) or $mask.
  **/

  public function isA ($mask) {
    return (
$this -> mask & $mask);
  }
}

Note that several methods are omitted, such as tests for other access masks, the ability to obtain the set mask and the option to set a new mask in this object, but the general idea should be clear.

How this could be used:
Code (php) (nieuw venster):
1
2
3
4
5
6
7
$user -> mask = new Mask(Mask::USER | Mask::MODERATOR);

if (
$user -> mask -> isUser())
  echo
"This is a user!";

if (
$user -> mask -> isA(Mask::ADMINISTRATOR))
  echo
"This is an administrator!";

This will output the first line since the Mask contains the USER mask, but not the second one since it does not contain the ADMINISTRATOR mask.

Note: the names used here can be a bit confusing and weird, I do apologise but it was 3am at the time of writing.

FragFrog out!

Nov 24, 2009 jMerliN

How about regex's and systems with more fine grained permission controls? Bitmasks are only good if you have roughly less than 64 possible on or off flags for permissions (unless you make multiple columns). Even so, they don't really provide fine grained control and the bits aren't intuitive and must be referenced in a union of some type.

Perhaps you could look into working with strings of characters that describe permissions (say comma separated permissions with a + or - in front), then someone might have a permission string like:

"+a,+m" or "-p"
or similarly if your regex only matched '+a' or '+m' you could use a more descriptive permission rule:

"+admin,+moderator" or "-post"

The first of which grants admin/mod permissions and the second denies a granted ability of posting.

The only two advantages to this are readability and size of expression, you can make an arbitrarily long string to contain permissions. You can also make an arbitrary long binary field and consider each bit an on/off permission, and while bitfields are more efficient, it may make more sense to use a string-based permission system.

New comment

Your name:
Comment: