Article

The PHP Anthology Volume 2, Chapter 1 - Access Control

Page: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Next

How do I build a permissions system?

So far, you've already got an authentication system, which provides a global security system for your site. But are all your site's members equal? You probably don't want all of your users to have access to edit and delete articles, for example. To deal with this, you need to add to the security system further complexity that allows you to assign "permissions" to limited groups of members, permitting only these users to perform particular actions.

Rather than assigning specific permissions to particular accounts, which would quickly become a nightmare to administer, the way we'll build a permissions system is to think in terms of Users, Groups and Permissions. Users (login accounts) will be assigned to Groups, which will have names like "Administrators," "Authors," "Managers," and so on. Permissions reflect actions that users will be allowed to perform within the site, and they will also be assigned to Groups. From an administration perspective, managing all this will be easy, as it will be a simple matter to see which Permissions a particular Group has, and which users are assigned to that Group.

To build the relationships I've described requires the construction of many-to-many relationships between tables. This is explained as follows:

  • A User can belong to many Groups.
  • A Group may have many Users.
  • A Permission can be assigned to many Groups.
  • A Group may have many Permissions.

In practical terms, the way to build many-to-many relationships in MySQL is to use a lookup table, which relates to two other tables. The lookup table stores a two column index, each column being the key of one of the two related tables. For example, here's the definition of the user2collection lookup table:

CREATE TABLE user2collection (              
 user_id INT(11) NOT NULL DEFAULT '0',              
 collection_id INT(11) NOT NULL DEFAULT '0',              
 PRIMARY KEY (user_id, collection_id)              
)

Notice that the primary key for the table uses both columns. In doing so, it makes sure that no combination of user_id and collection_id can appear more than once.

Note that I use "collection" to refer to "group" in MySQL; the use of "group" would confuse the current versions of the mysqldump utility, which, as you know from Chapter 3, PHP and MySQL, is a helpful tool for backing up a database.

The table below presents sample data from the user2group table:

1279_table1

This tells us that User 1 is a member of Group 2, User 2 is a member of Groups 1 and 2, User 3 is a member of Group 2, etc.

With the lookup tables defined (the other being called collection2permission), we can now perform queries across the tables to identify the permissions a particular user has been allowed. For example, the following query returns all the permissions for the user with user_id 1.

SELECT              
 p.name as permission              
FROM              
 user2collection uc, collection2permission cp, permission p              
WHERE              
 uc.user_id = '1' AND              
 uc.collection_id = cp.collection_id AND              
 cp.permission_id = p.permission_id

Note that I've used aliases for table names, such as user2collection ug, to make writing the query easier.

Armed with that knowledge, it's time we put together a class for users. The class will allow us to fetch all the information we need about users and, in particular, to check their permissions.

First, we need to define constants for the table and column names, so that we can use the class against different table structures:

Example 1.47. AccessControl/User.php (in SPLIB) (excerpt)              
             
/**              
* Constants defining table and column names              
*/              
# Modify this constants to match the session variable names              
// Name to use for login variable              
@define('USER_LOGIN_VAR', 'login');              
             
# Modify these constants to match your user login table              
// Name of users table              
@define('USER_TABLE', 'user');              
// Name of ID column in usre              
@define('USER_TABLE_ID', 'user_id');              
// Name of login column in table              
@define('USER_TABLE_LOGIN', 'login');              
// Name of email column in table              
@define('USER_TABLE_EMAIL', 'email');              
// Name of firstname column in table              
@define('USER_TABLE_FIRST', 'firstName');              
// Name of lastname column in table              
@define('USER_TABLE_LAST', 'lastName');              
// Name of signature column in table              
@define('USER_TABLE_SIGN', 'signature');              
             
// Name of Permission table              
@define('PERM_TABLE', 'permission');              
// Permission table id column              
@define('PERM_TABLE_ID', 'permission_id');              
// Permission table name column              
@define('PERM_TABLE_NAME', 'name');              
             
// Name of Permission table              
@define('PERM_TABLE', 'permission');              
// Permission table id column              
@define('PERM_TABLE_ID', 'permission_id');              
// Permission table name column              
@define('PERM_TABLE_NAME', 'name');              
             
// Name of User to Collection lookup table              
@define('USER2COLL_TABLE', 'user2collection');              
// User to Collection table user_id column              
@define('USER2COLL_TABLE_USER_ID', 'user_id');              
// User to Collection table collection_id column              
@define('USER2COLL_TABLE_COLL_ID', 'collection_id');              
             
// Name of Collection to Permission lookup table              
@define('COLL2PERM_TABLE', 'collection2permission');              
// Collection to Permission table collection id              
@define('COLL2PERM_TABLE_COLL_ID', 'collection_id');              
// Collection to Permission table permission id              
@define('COLL2PERM_TABLE_PERM_ID', 'permission_id');

With the constants defined, we can get down to the meat of the class:

Example 1.48. AccessControl/User.php (in SPLIB) (excerpt)              
             
/**              
* User Class<br />              
* Used to store information about users, such as permissions              
* based on the session variable "login"<br />              
* <b>Note:</b> you will need to modify the populate() and              
* checkPermission() methods if your database table structure              
* is different to that used here.              
* @access public              
* @package SPLIB              
*/              
class User {              
 /**              
  * Database connection              
  * @access private              
  * @var  object              
  */              
 var $db;              
 /**              
  * The id which identifies this user              
  * @access private              
  * @var int              
  */              
 var $userId;              
 /**              
  * The users email              
  * @access private              
  * @var string              
  */              
 var $email;              
 /**              
  * First Name              
  * @access private              
  * @var string              
  */              
 var $firstName;              
 /**              
  * Last Name              
  * @access private              
  * @var string              
  */              
 var $lastName;              
 /**              
  * Signature              
  * @access private              
  * @var string              
  */              
 var $signature;              
 /**              
  * Permissions              
  * @access private              
  * @var array              
  */              
 var $permissions;              
 /**              
  * User constructor              
  * @param object instance of database connection              
  * @access public              
  */              
 function User(&$db)              
 {              
   $this->db = &$db;              
   $this->populate();              
 }

To begin, we've defined some data members as well as the constructor, which takes an instance of the MySQL class. The constructor calls the method, populate:

Example 1.49. AccessControl/User.php (in SPLIB) (excerpt)              
             
 /**              
  * Determines the user's id from the login session variable              
  * @return void              
  * @access private              
  */              
 function populate()              
 {              
   $session = new Session();              
   $sql = "SELECT              
             " . USER_TABLE_ID . ", " . USER_TABLE_EMAIL . ",              
             " . USER_TABLE_FIRST . ", " . USER_TABLE_LAST . ",              
             " . USER_TABLE_SIGN . "              
           FROM              
             " . USER_TABLE . "              
           WHERE              
             " . USER_TABLE_LOGIN . " =              
             '" . $session->get(USER_LOGIN_VAR) . "'";              
   $result = $this->db->query($sql);              
   $row = $result->fetch();              
   $this->userId = $row[USER_TABLE_ID];              
   $this->email = $row[USER_TABLE_EMAIL];              
   $this->firstName = $row[USER_TABLE_FIRST];              
   $this->lastName = $row[USER_TABLE_LAST];              
   $this->signature = $row[USER_TABLE_SIGN];              
 }

The populate method pulls this user's record from the database and stores various useful pieces of information in the object's variables, so we can easily get to them when, for example, we want to display that user's name on the page. Most important is gathering the user_id value from the database, for use in checking permissions.

If you liked this article, share the love:
Print-Friendly Version Suggest an Article

Sponsored Links