Yii 1.1: Simple access control

12 followers

For those who feel, the Controller->accessRules() or RBAC (Role-Based Access Control) is too complicated or doesn't want the username(s) to be hard-coded in accessRules(), here is a very simple, easy-to-implement solution.

Considerations

As usual, you will have a table, holding the user's data, such as: username, password, email, real_name, etc. To store the user rights, you need an additional field, named admin_level. This will be an unsigned tinyint, and will hold the user's rights to do things around the site. You will define the admin levels, according to your needs. Now, for this example, let's define 4 levels: 0: REGULAR_USER - basic access 1: EDITOR - access to product editing, translations, etc. 2: MANAGER - Higher level control: site settings, statistics, etc. 3: ADMIN - Delete/modify users, modify critical site settings, handle money, etc.

Implementation

Step 1. Create/modify the user table:

CREATE TABLE `users` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
    `username` varchar(25) CHARACTER SET ascii NOT NULL,
    `password` varchar(128) COLLATE utf8_bin NOT NULL,
    `email` varchar(100) COLLATE utf8_bin NOT NULL,
    `admin_level` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '0: regular user; 1: editor; 2: manager; 3: admin'
)

Step 2. Create the user login-logout mechanism

Following the standard, recommended procedure, you will have a login-logout functionality.

Step 3. Define the user levels and create the access-control functionality.

Open the protected/components/Controller.php file and add the following to the beginning of the file:

define('REGULAR_USER', 0);
define('EDITOR', 1);
define('MANAGER', 2);
define('ADMIN', 3);

You will load and store the user's data every time a page is requested. To do this, modify the Controller class as follows:

Add a userData property:

public $userData; // Holds an activeRecord with current user. NULL if guest

Define (or extend) the init() function:

public function init() {
    ...
    // Load the user
    if (!Yii::app()->user->isGuest)
        $this->userData = AR_Users::model()->findByPk(Yii::app()->user->id);
    ...
}

Add the allowUser function:

public function allowUser($min_level) { //-1 no login required 0..3: admin level
    $current_level = -1;
    if ($this->userData !== null)
        $current_level = $this->userData->admin_level;
    if ($min_level > $current_level) {
        throw new CHttpException(403, 'You have no permission to view this content');
    }
}

Step 4. Begin your controller actions calling the allowUser() function.

If the current user will not have the required admin level, a CHttpException will be thrown. If you don't need any access control, don't use the allowUser() function.

class SomeController extends Controller {
 
    public function actionIndex() {
        // $this->allowUser(REGULAR_USER); // everybody can view index
        $this->render('index');
    }
 
    public function actionLogin() {
        // $this->allowUser(REGULAR_USER); // everybody can log in
        $this->render('login', array(...));
    }
 
    public function actionEditproduct($id) {
        $this->allowUser(EDITOR); // only editors, managers and admins can edit products
        ...
        $this->render('editproduct', array(...));
    }
 
    public function actionSitesettings($id) {
        $this->allowUser(MANAGER); // only managers and admins can change site settings
        ...
        $this->render('sitesettings', array(...));
    }
 
    public function actionEdituser($id) {
        $this->allowUser(ADMIN); // only admins can change user data
        ...
        $this->render('edituser', array(...));
    }
 
}

Final words

  • you can use the userData property to check, display or even modify user data. If userData === null, there is no user logged in, so you need to check first this.
  • the userData->admin_level can be edited along with the other user data, so everybody's rights can be changed (by an admin)
  • the user rights will be hard-coded. This will help not tech-guru site owners to manage their subscribed members, especially to allow/disallow employees to do/see certain tasks on the site.

Total 4 comments

#8707 report it
ferisoft at 2012/06/20 10:06am
accessRules()

Point by point: usernames you can put * to target all usernames and use expression to evaluate whatever, if your logic for access is complex you can still move the logic in a method and just add the method to the expressions key.

__it's very high the possibility to forget to add an action or change the action's name, etc.

Well it basically depends on how good you develop, you can forget many stuff, also if you want to change the access for the whole controller or for lotsa methods then using your method you should go by each action and do it one by one, instead of having a central location to do so.

Either way I am not saying one method is better than the other I guess depends on the site and the coding style. I would usually use the AccessRules just because it gives me a central location to define all my access logic for that controller and when i want to modify I know exactly where to look for it.

#8692 report it
szfjozsef at 2012/06/19 07:10pm
@ ferisoft, yugenek

Several problems with the accessRules() function:

  • user names are hard-coded... The user names are third-party data. The code should work with any given database, regardless of the user's names, especially if the names are changeable by the owners. The admin can be 'developer', 'dan', 'joe', etc.

  • it's very high the possibility to forget to add an action or change the action's name, etc.

  • a quick look at the controller's action doesn't tell anything about who can access/where is the access defined...

#8686 report it
ferisoft at 2012/06/19 02:26pm
expression ACL
public function accessRules()
    {
        return array(array('allow', 
            'actions' => array('login', 'logout'), 'users' => array('*'),), array('allow',
            'users' => array('*'), 'expression' => 'isset($user->role) && ($user->role==="admin")'), array('deny', 
            'users' => array('*'),),);
    }

you can also just use an expression in the accessRules method like the above. I have hardcoded admin but you can still define constants and use them too. I guess its a matter of preference if you want to define the access in each action or you prefer to use a central method where you define all the access rules.

#8679 report it
yugene at 2012/06/19 02:26am
use access control filter + accessRules() instead

Hi szfjozsef,
Your article is nicely written, but here's a couple of but, which are quite important from my point of view. I believe it's important information for newbies as well:
1. usage complexity of access control filter + accessRules() is the same as for the above described method.
2. using access control filter you'll have all your access rules written in one place, so you won't have to search trough your code everytime you need to update them.
3. access control filter and RBAC are framework standard approaches, thus any other yii-developer won't have to dig into the custom approach code to understand which way access control is performed, but will work with standard approaches right away.

Leave a comment

Please to leave your comment.

Write new article