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.
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.
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' )
Following the standard, recommended procedure, you will have a login-logout 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'); } }
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(...)); } }
Total 4 comments
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.
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...
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.
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 login to leave your comment.