From the security point of view, you can have 3 types of controllers:
Most of the time, the 3rd option will be used together with the 2nd one, which will result in a controller that requires an authenticated user and might allow only specific users to some of it’s actions. Please note that you might have a secure controller, and specify special privileges for only some of it’s actions - the rest of the actions will require just a logged on user.
The next examples will show how I set my login “workflow” for my intranet app. The default controller/action points to a secure controller which means that, upon accessing http://intra.net.site, the user will be redirected to another controller/action used for logging in. You can change the controller/action it’s redirected to in the config.ini.php file (see secureController/secureAction). When the user enters his/her password and submits the form, I check in another action if the user is ok, and let the framework know that the user has logged on (see User::login()), then I redirect him back to the default controller/action. This time, the user is logged on, and the page is displayed.
Create a controller (eg. secure_controller.php) that will be used to login.
<?php class Secure extends ActiveController { // I don't want to use the default layout for the login page. Mainly because it exposes what menus I have in my intranet app. var $layout = ""; // You want to use your custom users table to check if the user is ok. For this example, I'm using the Operators table. var $models = array('operator'); function login() { // show login form } function dologin() { $op = new Operator; // findRor always returns an array $ops_array = $op->findRoR('name', trim($_POST['username'])); if(is_array($ops_array)) { reset($ops_array); list($id, $op) = each($ops_array); if( ($op->id > 0) && (md5($_POST['password']) == $op->password) ) { // Set the user as logged on. Save the ID and username for further use. User::login($op->id, $op->name); // redirect to default page Framework::redirect(); return; } } // if the checks failed, redisplay the form Framework::redirect('secure', 'login'); } function logout() { User::logout(); Framework::redirect('secure', 'login'); } // used by dispatcher to show "privilege required" errors function noprivileges() { Framework::croak("No privileges to access this page"); } } ?>
To create a controller that can be accessed only by an authenticated user:
<?php class BillingReports extends ActiveController { var $secure = true; // require logged on user; will automatically redirect to login page otherwise } ?>
To restrict actions in the controller to users with specified privilege:
<?php class BillingReports extends ActiveController { var $secure = true; var $restrictions = array( 'action1' => 'billing-privilege1', 'action2' => 'billing-privilege2' ); function action1() {} function action2() {} } ?>
The ideas for this part are taken from the phpGACL project. The table structure is found in the taniphp/acl_aco_aro_tables.sql file.
In the aco table you should store the access control objects. For our BillingReports controller, the ACO table will look like this:
| id | name |
|---|---|
| 1 | billing-privilege1 |
| 2 | billing-privilege2 |
Here you should store the groups/users in your system. Please note that this means some of the information is duplicated in your users table. You might use the aro table for both purposes, though I haven’t tryed that. Let’s create the Management and Users groups, with 2 users in each group.
| id | name | left | right |
|---|---|---|---|
| 1 | Users | 1 | 12 |
| 2 | Management | 6 | 11 |
| 3 | simple_user1 | 2 | 3 |
| 4 | simple_user2 | 4 | 5 |
| 5 | manager1 | 7 | 8 |
| 6 | manager2 | 9 | 10 |
The left and right fields are used for storing the groups/users tree in the table. Please check the article about storing hierarchical data in a database.
Basically, the Users group contains all users in the system. Inside it, there are 2 users + the Management group, that contains another two users. If you set some privilege to the Users group, all the users, including the managers will have it. If you set a privilege to the Management group, only manager1 and manager2 will have that specific privilege.
Here comes the complicated part. We have to configure which users/groups have access to which objects (in other words, what privileges does each user/group has).
For this example, let’s implement this:
action1 in our BillingReports controller. simple_user1 is denied access to action1 (thus, he has no privileges at all, but he can still login and use both secure/unsecure controllers that don’t have any privileges attached to their actions). Management group has access to action2.
The ACO table will look like this:
| id | aro | aco | value | enabled | |
|---|---|---|---|---|---|
| 1 | 1 | 1 | 1 | 1 | Users(aro=1) have access to action1(aco=1) |
| 2 | 3 | 1 | 0 | 1 | simple_user1(aro=3) doesn’t have access to action1(aco=1) |
| 3 | 2 | 2 | 1 | 1 | Management(aro=2) has access to action2(aco=2) |
Please note that since Management group is in the Users group, all the managers will have access to both action1 and action2.
Having all 3 rows in the database sets the privileges to all the users in our system. When adding a new user, you just have to place it in the group you need and all permissions/restrictions for that group will apply to him/her too.