Introduction
Your problem
You want to restrict users' to only specific pages that their particular account allows access to.The solution
Implementing an access control list will allow you a great deal of control over what users can and cannot access on your site.If you view the demo, available with the downloadable source code, you will be greeted with an index page that tests the ACL for each user. You can select different links at the bottom to view the ACL for the different users. If you click on the 'Admin Screen' link near the top, you can view a sample of the admin interface that allows you to manage the users, roles, and permissions. NOTE: The admin system will perform a database restore every 30 minutes to make sure everything stays on the up and up. The download files also implement the ACL security on the admin site, so if user number one doesn't have the 'access admin' permission, you won't be able to access the admin site.
This system will enable you to create different groups of users (i.e. guests, premium members, contributors, and admins). We will be able to set unique permissions for each group, as well as for individual users. Let's get started by setting up our MySQL database.
Step 1: Create the Database
Our ACL will be stored in a relational database using six tables (including the table for users). You should already have a database set up in your host environment. We will create the following table structure:Step 2: Database Include
We need to create an include file so that we may connect to our database. Create a file called assets/php/database.php and add the following code to it (replace the variable values with the information appropriate for your hosting situation):
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
| <?php session_start(); ob_start(); $hasDB = false; $server = 'localhost' ; $user = 'root' ; $pass = 'mysql' ; $db = 'acl_test' ; $link = mysql_connect( $server , $user , $pass ); if (! is_resource ( $link )) { $hasDB = false; die ( "Could not connect to the MySQL server at localhost." ); } else { $hasDB = true; mysql_select_db( $db ); } ?> |
Lines 4-8 set up our variables. $hasDB is a boolean used to determine if we are connected. $server, $user, $pass, and $db are the connection arguments for the server. Line 9 connects to the server, while line 10 determines if the connection was successful. If it was, we select the database to use; if it wasn't, we display an error message using die().
Step 3: Create the ACL Class
This step is fairly long, as we are creating the ACL class that will form the basis of our system. I apologize in advance for the length of this step.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
| <?php class ACL { var $perms = array (); //Array : Stores the permissions for the user var $userID = 0; //Integer : Stores the ID of the current user var $userRoles = array (); //Array : Stores the roles of the current user function __constructor( $userID = '' ) { if ( $userID != '' ) { $this ->userID = floatval ( $userID ); } else { $this ->userID = floatval ( $_SESSION [ 'userID' ]); } $this ->userRoles = $this ->getUserRoles( 'ids' ); $this ->buildACL(); } function ACL( $userID = '' ) { $this ->__constructor( $userID ); } |
Analysis
After creating the class definition, we create the three class variables to store the information that will be used in the generation of the ACL.The Constructor Method
The __constructor() function is used to initialize the object when we want to load an ACL. It is called automatically when we call new ACL();. It is then passed a single, optional argument of the user to load the ACL for. Inside the constructor, we check to see if a user ID was passed in. If no ID was passed, we assume that we will load the ACL for the currently logged in user; so we read in the session variable for that. Alternatively, if we pass in a user ID, it allows us to read and edit the ACL for a user other than the one logged in (useful for your admin page).After we read in the user ID, we call getUserRoles() to generate an array of the roles the user is assigned to and store it in the $userRoles class variable. At the end of the constructor, we call buildACL() to generate the actual ACL. The method named ACL() is a crutch for PHP4 installs. When you call new ACL() in PHP5, the PHP interpreter runs the __constructor() method. However, when you run the same code in PHP4, the interpreter runs ACL(). By providing a method named the same as the class, we make the class PHP4 compatible.
Any time we create a new ACL object by passing in a user ID, that object will hold the permissions for the user who was passed in.
Helper Methods
Now, lets add some more helper methods to the same class file. These methods will provide support to the other methods by performing specialized tasks:
01
02
03
04
05
06
07
08
09
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
| function getUserRoles() { $strSQL = "SELECT * FROM `user_roles` WHERE `userID` = " . floatval ( $this ->userID) . " ORDER BY `addDate` ASC" ; $data = mysql_query( $strSQL ); $resp = array (); while ( $row = mysql_fetch_array( $data )) { $resp [] = $row [ 'roleID' ]; } return $resp ; } function getAllRoles( $format = 'ids' ) { $format = strtolower ( $format ); $strSQL = "SELECT * FROM `roles` ORDER BY `roleName` ASC" ; $data = mysql_query( $strSQL ); $resp = array (); while ( $row = mysql_fetch_array( $data )) { if ( $format == 'full' ) { $resp [] = array ( "ID" => $row [ 'ID' ], "Name" => $row [ 'roleName' ]); } else { $resp [] = $row [ 'ID' ]; } } return $resp ; } function buildACL() { //first, get the rules for the user's role if ( count ( $this ->userRoles) > 0) { $this ->perms = array_merge ( $this ->perms, $this ->getRolePerms( $this ->userRoles)); } //then, get the individual user permissions $this ->perms = array_merge ( $this ->perms, $this ->getUserPerms( $this ->userID)); } function getPermKeyFromID( $permID ) { $strSQL = "SELECT `permKey` FROM `permissions` WHERE `ID` = " . floatval ( $permID ) . " LIMIT 1" ; $data = mysql_query( $strSQL ); $row = mysql_fetch_array( $data ); return $row [0]; } function getPermNameFromID( $permID ) { $strSQL = "SELECT `permName` FROM `permissions` WHERE `ID` = " . floatval ( $permID ) . " LIMIT 1" ; $data = mysql_query( $strSQL ); $row = mysql_fetch_array( $data ); return $row [0]; } function getRoleNameFromID( $roleID ) { $strSQL = "SELECT `roleName` FROM `roles` WHERE `ID` = " . floatval ( $roleID ) . " LIMIT 1" ; $data = mysql_query( $strSQL ); $row = mysql_fetch_array( $data ); return $row [0]; } function getUsername( $userID ) { $strSQL = "SELECT `username` FROM `users` WHERE `ID` = " . floatval ( $userID ) . " LIMIT 1" ; $data = mysql_query( $strSQL ); $row = mysql_fetch_array( $data ); return $row [0]; } |
getUserRoles()
getUserRoles() will return an array of roles the current user it is assigned to. First, we will build the appropriate SQL statement and execute it. Using while(), we loop through all of the matching results, and finally return an array of the IDs. Likewise, getAllRoles() will return all of the available roles (not just the ones the user is assigned to). Based on the value of the argument, $format, it will return an array of IDs for all the roles, or an array of associative array with the ID and name of each role. This allows our function to do double duty. If we want to use the array of user roles in MySQL, we need an array of role IDs; but if we want to display the roles on our page, it would be helpful to have one array with all the info in it.buildACL
buildACL() generates the permissions array for the user, and is the heart of the system. First, we check to see if the user is assigned to any roles. If they are, we use array_merge() to combine the existing permissions array with the new array returned from the call to getRolePerms() (which gets all the permissions for all the roles the user is assigned to). Then we do the same for the individual user permissions, this time calling getUserPerms(). It is important that we read the user perms second because array_merge() overwrites duplicate keys. Reading the user permissions second ensures that the individual permissions will override any permissions inherited from the user's roles.All of the functions getPermKeyFromID(), getPermNameFromID(), getRoleNameFromID() and getUsername() are simply "lookup" functions. They allow us to pass in an ID and return the appropriate text value. You can see that we build the SQL statement, then execute it and return the result. Next we will add in the two functions which will pull the permissions from the database.
01
02
03
04
05
06
07
08
09
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
| function getRolePerms( $role ) { if ( is_array ( $role )) { $roleSQL = "SELECT * FROM `role_perms` WHERE `roleID` IN (" . implode( "," , $role ) . ") ORDER BY `ID` ASC" ; } else { $roleSQL = "SELECT * FROM `role_perms` WHERE `roleID` = " . floatval ( $role ) . " ORDER BY `ID` ASC" ; } $data = mysql_query( $roleSQL ); $perms = array (); while ( $row = mysql_fetch_assoc( $data )) { $pK = strtolower ( $this ->getPermKeyFromID( $row [ 'permID' ])); if ( $pK == '' ) { continue ; } if ( $row [ 'value' ] === '1' ) { $hP = true; } else { $hP = false; } $perms [ $pK ] = array ( 'perm' => $pK , 'inheritted' => true, 'value' => $hP , 'Name' => $this ->getPermNameFromID( $row [ 'permID' ]), 'ID' => $row [ 'permID' ]); } return $perms ; } function getUserPerms( $userID ) { $strSQL = "SELECT * FROM `user_perms` WHERE `userID` = " . floatval ( $userID ) . " ORDER BY `addDate` ASC" ; $data = mysql_query( $strSQL ); $perms = array (); while ( $row = mysql_fetch_assoc( $data )) { $pK = strtolower ( $this ->getPermKeyFromID( $row [ 'permID' ])); if ( $pK == '' ) { continue ; } if ( $row [ 'value' ] == '1' ) { $hP = true; } else { $hP = false; } $perms [ $pK ] = array ( 'perm' => $pK , 'inheritted' => false, 'value' => $hP , 'Name' => $this ->getPermNameFromID( $row [ 'permID' ]), 'ID' => $row [ 'permID' ]); } return $perms ; } function getAllPerms( $format = 'ids' ) { $format = strtolower ( $format ); $strSQL = "SELECT * FROM `permissions` ORDER BY `permName` ASC" ; $data = mysql_query( $strSQL ); $resp = array (); while ( $row = mysql_fetch_assoc( $data )) { if ( $format == 'full' ) { $resp [ $row [ 'permKey' ]] = array ( 'ID' => $row [ 'ID' ], 'Name' => $row [ 'permName' ], 'Key' => $row [ 'permKey' ]); } else { $resp [] = $row [ 'ID' ]; } } return $resp ; } |
Inside the while() loop, we perform several functions. First we generate the variable $pK, which we will use as the name of the array key. Because we will be looking for this value to determine if the user has a specific permission, it is important that we have it in a uniform format, which is why we are using strtolower(). If the key value is blank, we skip to the next iteration using continue;. Next, we look at $row['value'] to set an implicit boolean value for the permission. This ensures that only an actual value of '1' in the table will equate with true (i.e. the user has the permission), and is important for security. Otherwise we set the permission to false. At the end of the function, we create an array with several named keys so we can get all of the information about a permission. That array is assign to a new named key in the $perms array we created earlier. Note that we use $pK to create an appropriately named index. Finally we return the array.
You can see that in the returned array, there is an index name 'inherited'. This has a special significance for the ACL. If a user receives a permission because it belongs to a role the user is assigned to, it is said to be inherited. If the permissions is assigned to the user manually, it is not inherited.
In getAllPerms(), we build a list of all available permissions. Similar to getAllRoles() we can pass in a format argument to determine how the results will be returned. Now for the last part of the class:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| function userHasRole( $roleID ) { foreach ( $this ->userRoles as $k => $v ) { if ( floatval ( $v ) === floatval ( $roleID )) { return true; } } return false; } function hasPermission( $permKey ) { $permKey = strtolower ( $permKey ); if ( array_key_exists ( $permKey , $this ->perms)) { if ( $this ->perms[ $permKey ][ 'value' ] === '1' || $this ->perms[ $permKey ][ 'value' ] === true) { return true; } else { return false; } } else { return false; } } } ?> |
Step 4: User Admin
The first part of our admin section will deal with managing users. We need to create four different interfaces to deal with the aspects of managing users: List the users so we can select one to edit, viewing a detail user listing, assign users to roles, and grant users permissions.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
| <?php include ( "../assets/php/database.php" ); include ( "../assets/php/class.acl.php" ); $myACL = new ACL(); if ( $myACL ->hasPermission( 'access_admin' ) != true)<br />{<br /> header( "location: ../index.php" );<br />} ?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" > <head> <meta http-equiv= "Content-Type" content= "text/html; charset=utf-8" /> <title>ACL Test</title> <link href= "../assets/css/styles.css" rel= "stylesheet" type= "text/css" /> </head> <body> <div id= "header" ></div> <div id= "adminButton" ><a href= "../" >Main Screen</a> | <a href= "index.php" >Admin Home</a></div> <div id= "page" > <!-- PAGE CONTENT --> </div> </body> </html> |
NOTE: If you change the ACL permissions so that user #1 no longer has the 'access_admin' permission, you won't be able to access the admin site. Also, you must first go to /index.php before you go to any of the admin pages, as index.php sets the session variable assigning you userID #1.
Right now this is just the basic layout of the page. In the next steps, we will replace <!-- PAGE CONTENT --> above with some code to manage the users. We will use the querystring variable $action to determine which of the user interfaces we should display. There are four possible values that we will address: If it is null, we display a list of the current users. If it is set to 'user', we display the form for a single user. If it is set to 'roles', we display the form to assign a user. If it is set to 'perms', we display the form to give the user permissions.
List Users
Add this code inside the div with the id 'page':
01
02
03
04
05
06
07
08
09
10
| <? if ( $_GET [ 'action' ] == '' ) { ?> <h2>Select a User to Manage:</h2> <? $strSQL = "SELECT * FROM `users` ORDER BY `Username` ASC" ; $data = mysql_query( $strSQL ); while ( $row = mysql_fetch_assoc( $data )) { echo "<a href=\"?action=user&userID=" . $row [ 'ID' ] . "\">" . $row [ 'username' ] . "</a><br/ >" ; } } ?> |
Edit Individual User
Now, add this code directly under the previous code block:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| <? if ( $_GET [ 'action' ] == 'user' ) { $userACL = new ACL( $_GET [ 'userID' ]); ?> <h2>Managing <?= $myACL ->getUsername( $_GET [ 'userID' ]); ?>:</h2> ... Some form to edit user info ... <h3>Roles for user: (<a href= "users.php?action=roles&userID=<?= $_GET['userID']; ?>" >Manage Roles</a>)</h3> <ul> <? $roles = $userACL ->getUserRoles(); foreach ( $roles as $k => $v ) { echo "<li>" . $userACL ->getRoleNameFromID( $v ) . "</li>" ; } ?> </ul> <h3>Permissions for user: (<a href= "users.php?action=perms&userID=<?= $_GET['userID']; ?>" >Manage Permissions</a>)</h3> <ul> <? $perms = $userACL ->perms; foreach ( $perms as $k => $v ) { if ( $v [ 'value' ] === false) { continue ; } echo "<li>" . $v [ 'Name' ]; if ( $v [ 'inheritted' ]) { echo " (inheritted)" ; } echo "</li>" ; } ?> </ul> <? } ?> |
Assign Roles
Our assign roles form will end up looking like this:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| <? if ( $_GET [ 'action' ] == 'roles' ) { ?> <h2>Manage User Roles: (<?= $myACL ->getUsername( $_GET [ 'userID' ]); ?>)</h2> <form action= "users.php" method= "post" > <table border= "0" cellpadding= "5" cellspacing= "0" > <tr><th></th><th>Member</th><th>Not Member</th></tr> <? $roleACL = new ACL( $_GET [ 'userID' ]); $roles = $roleACL ->getAllRoles( 'full' ); foreach ( $roles as $k => $v ) { echo "<tr><td><label>" . $v [ 'Name' ] . "</label></td>" ; echo "<td><input type=\"radio\" name=\"role_" . $v [ 'ID' ] . "\" id=\"role_" . $v [ 'ID' ] . "_1\" value=\"1\"" ; if ( $roleACL ->userHasRole( $v [ 'ID' ])) { echo " checked=\"checked\"" ; } echo " /></td>" ; echo "<td><input type=\"radio\" name=\"role_" . $v [ 'ID' ] . "\" id=\"role_" . $v [ 'ID' ] . "_0\" value=\"0\"" ; if (! $roleACL ->userHasRole( $v [ 'ID' ])) { echo " checked=\"checked\"" ; } echo " /></td>" ; echo "</tr>" ; } ?> </table> <input type= "hidden" name= "action" value= "saveRoles" /> <input type= "hidden" name= "userID" value= "<?= $_GET['userID']; ?>" /> <input type= "submit" name= "Submit" value= "Submit" /> </form> <form action= "users.php" method= "post" > <input type= "button" name= "Cancel" onclick= "window.location='?action=user&userID=<?= $_GET['userID']; ?>'" value= "Cancel" /> </form> <? } ?> |
Inside the foreach() loop, we do the following: We start a new row and print out a label with the name of the role. Then we print out a radio button input. The name and id of the radio buttons is made unique for each role by using the format "role_[roleID]" (i.e. role_0012). Lines 13 and 16 determine which of the radio buttons should be checked. The first one will be checked if the user is already assigned to the group, while the second one will be checked if they are not. Notice that one has a value of '1' (for assign), and the other has a value of '0' (for don't assign). Then we end the row.
After all that, we add in some hidden elements that tell us what we are saving, and what user ID to save. Then we add a submit and cancel button.
Assign Permissions
The assign permissions form is similar to the roles form, but with different inputs, so let's add this code:
01
02
03
04
05
06
07
08
09
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
| <? if ( $_GET [ 'action' ] == 'perms' ) { ?> <h2>Manage User Permissions: (<?= $myACL ->getUsername( $_GET [ 'userID' ]); ?>)</h2> <form action= "users.php" method= "post" > <table border= "0" cellpadding= "5" cellspacing= "0" > <tr><th></th><th></th></tr> <? $userACL = new ACL( $_GET [ 'userID' ]); $rPerms = $userACL ->perms; $aPerms = $userACL ->getAllPerms( 'full' ); foreach ( $aPerms as $k => $v ) { echo "<tr><td>" . $v [ 'Name' ] . "</td>" ; echo "<td><select name=\"perm_" . $v [ 'ID' ] . "\">" ; echo "<option value=\"1\"" ; if ( $rPerms [ $v [ 'Key' ]][ 'value' ] === true && $rPerms [ $v [ 'Key' ]][ 'inheritted' ] != true) { echo " selected=\"selected\"" ; } echo ">Allow</option>" ; echo "<option value=\"0\"" ; if ( $rPerms [ $v [ 'Key' ]][ 'value' ] === false && $rPerms [ $v [ 'Key' ]][ 'inheritted' ] != true) { echo " selected=\"selected\"" ; } echo ">Deny</option>" ; echo "<option value=\"x\"" ; if ( $rPerms [ $v [ 'Key' ]][ 'inheritted' ] == true || ! array_key_exists ( $v [ 'Key' ], $rPerms )) { echo " selected=\"selected\"" ; if ( $rPerms [ $v [ 'Key' ]][ 'value' ] === true ) { $iVal = '(Allow)' ; } else { $iVal = '(Deny)' ; } } echo ">Inherit $iVal</option>" ; echo "</select></td></tr>" ; } ?> </table> <input type= "hidden" name= "action" value= "savePerms" /> <input type= "hidden" name= "userID" value= "<?= $_GET['userID']; ?>" /> <input type= "submit" name= "Submit" value= "Submit" /> <input type= "button" name= "Cancel" onclick= "window.location='?action=user&userID=<?= $_GET['userID']; ?>'" value= "Cancel" /> </form> <? } ?> |
Line 23-32 enhance the inherit option. If the permission is inherited, it makes it selected. Then it determines the value of the inherited permission and sets the variable $iVal so we can use the text value in the option label on line 33. After ending the select input and the table, we add in the hidden inputs to set up the save options, and add submit and cancel buttons.
Once this code is run, we will end up with a row for each available permission, and a drop down indicating whether or not the user has it.
Saving the Data
Add this code to /admin/users.php right above the doc type tag:
01
02
03
04
05
06
07
08
09
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
| <? if (isset( $_POST [ 'action' ])) { switch ( $_POST [ 'action' ]) { case 'saveRoles' : $redir = "?action=user&userID=" . $_POST [ 'userID' ]; foreach ( $_POST as $k => $v ) { if ( substr ( $k ,0,5) == "role_" ) { $roleID = str_replace ( "role_" , "" , $k ); if ( $v == '0' || $v == 'x' ) { $strSQL = sprintf( "DELETE FROM `user_roles` WHERE `userID` = %u AND `roleID` = %u" , $_POST [ 'userID' ], $roleID ); } else { $strSQL = sprintf( "REPLACE INTO `user_roles` SET `userID` = %u, `roleID` = %u, `addDate` = '%s'" , $_POST [ 'userID' ], $roleID , date ( "Y-m-d H:i:s" )); } mysql_query( $strSQL ); } } break ; case 'savePerms' : $redir = "?action=user&userID=" . $_POST [ 'userID' ]; foreach ( $_POST as $k => $v ) { if ( substr ( $k ,0,5) == "perm_" ) { $permID = str_replace ( "perm_" , "" , $k ); if ( $v == 'x' ) { $strSQL = sprintf( "DELETE FROM `user_perms` WHERE `userID` = %u AND `permID` = %u" , $_POST [ 'userID' ], $permID ); } else { $strSQL = sprintf( "REPLACE INTO `user_perms` SET `userID` = %u, `permID` = %u, `value` = %u, `addDate` = '%s'" , $_POST [ 'userID' ], $permID , $v , date ( "Y-m-d H:i:s" )); } mysql_query( $strSQL ); } } break ; } header( "location: users.php" . $redir ); } ?> |
If we just submitted the roles form, the following happens:
- We build a $redir querystring which is where we will be sent after the form processes.
- We loop through all of the $_POST variables.
- Using substr() we find out if the first 5 digits of the variable name are "role_". This way we only get the permission inputs in the following steps.
- If the value for the current input is equal to '0' or 'x' (i.e. we don't want the user to have this role), we perform the delete query. If we delete the role from the user_roles table, the user is no longer assigned to the role.
- If the value is not '0' or 'x' (line 14), we perform the replace query.
- For either query, we are using sprintf() for security (sprintf() forces variable typing and helps protect against SQL injection attacks more info).
- We execute the SQL using mysql_query().
If we just submitted the permissions form, the process is the same, except we are looking for a different prefix on the input names, and using a different database table. Once any operations are done, we use header("location:...") to redirect back to the page we were on, and we append the $redir querystring variable we made.
Step 5: Roles Admin
Now that we have finished the forms to manage our users, we need to manage our roles. The roles will be more simple, there are only two actions: view a list of roles, or edit a role. Create /admin/roles.php with the following code:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
| <?php include ( "../assets/php/database.php" ); include ( "../assets/php/class.acl.php" ); $myACL = new ACL(); if ( $myACL ->hasPermission( 'access_admin' ) != true)<br />{<br /> header( "location: ../index.php" );<br />} ?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" > <head> <meta http-equiv= "Content-Type" content= "text/html; charset=utf-8" /> <title>ACL Test</title> <link href= "../assets/css/styles.css" rel= "stylesheet" type= "text/css" /> </head> <body> <div id= "header" ></div> <div id= "adminButton" ><a href= "../" >Main Screen</a> | <a href= "index.php" >Admin Home</a></div> <div id= "page" > <!-- PAGE CONTENT --> </div> </body> </html> |
List Roles
Like the users page, we start off with the includes, creating the ACL object, and the page format. Our default action (page loaded with no querystring) is to list the available roles, so insert this code in place of <!-- PAGE CONTENT -->:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
| <? if ( $_GET [ 'action' ] == '' ) { ?> <h2>Select a Role to Manage:</h2> <? $roles = $myACL ->getAllRoles( 'full' ); foreach ( $roles as $k => $v ) { echo "<a href=\"?action=role&roleID=" . $v [ 'ID' ] . "\">" . $v [ 'Name' ] . "</a><br/ >" ; } if ( count ( $roles ) < 1) { echo "No roles yet.<br/ >" ; } ?> <input type= "button" name= "New" value= "New Role" onclick= "window.location='?action=role'" /> <? } ?> |
Edit Role
Add this code in /admin/roles.php under the previous block:
01
02
03
04
05
06
07
08
09
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
| <? if ( $_GET [ 'action' ] == 'role' ) { if ( $_GET [ 'roleID' ] == '' ) { ?> <h2>New Role:</h2> <? } else { ?> <h2>Manage Role: (<?= $myACL ->getRoleNameFromID( $_GET [ 'roleID' ]); ?>)</h2><? } ?> <form action= "roles.php" method= "post" > <label for = "roleName" >Name:</label><input type= "text" name= "roleName" id= "roleName" value= "<?= $myACL->getRoleNameFromID($_GET['roleID']); ?>" /> <table border= "0" cellpadding= "5" cellspacing= "0" > <tr><th></th><th>Allow</th><th>Deny</th><th>Ignore</th></tr> <? $rPerms = $myACL ->getRolePerms( $_GET [ 'roleID' ]); $aPerms = $myACL ->getAllPerms( 'full' ); foreach ( $aPerms as $k => $v ) { echo "<tr><td><label>" . $v [ 'Name' ] . "</label></td>" ; echo "<td><input type=\"radio\" name=\"perm_" . $v [ 'ID' ] . "\" id=\"perm_" . $v [ 'ID' ] . "_1\" value=\"1\"" ; if ( $rPerms [ $v [ 'Key' ]][ 'value' ] === true && $_GET [ 'roleID' ] != '' ) { echo " checked=\"checked\"" ; } echo " /></td>" ; echo "<td><input type=\"radio\" name=\"perm_" . $v [ 'ID' ] . "\" id=\"perm_" . $v [ 'ID' ] . "_0\" value=\"0\"" ; if ( $rPerms [ $v [ 'Key' ]][ 'value' ] != true && $_GET [ 'roleID' ] != '' ) { echo " checked=\"checked\"" ; } echo " /></td>" ; echo "<td><input type=\"radio\" name=\"perm_" . $v [ 'ID' ] . "\" id=\"perm_" . $v [ 'ID' ] . "_X\" value=\"X\"" ; if ( $_GET [ 'roleID' ] == '' || ! array_key_exists ( $v [ 'Key' ], $rPerms )) { echo " checked=\"checked\"" ; } echo " /></td>" ; echo "</tr>" ; } ?> </table> <input type= "hidden" name= "action" value= "saveRole" /> <input type= "hidden" name= "roleID" value= "<?= $_GET['roleID']; ?>" /> <input type= "submit" name= "Submit" value= "Submit" /> </form> <form action= "roles.php" method= "post" > <input type= "hidden" name= "action" value= "delRole" /> <input type= "hidden" name= "roleID" value= "<?= $_GET['roleID']; ?>" /> <input type= "submit" name= "Delete" value= "Delete" /> </form> <form action= "roles.php" method= "post" > <input type= "submit" name= "Cancel" value= "Cancel" /> </form> <? } ?> |
In each row, we print the permission name, and 3 radio buttons. The radios use the same nomenclature as the user form ("perm_[permID]"). 'Allow' or 'Deny' are selected depending on the value of the permission stored (thanks to lines 19 and 22). If you select 'ignore', no value is stored for that role/permission combo. Notice that the first two if() block have && $_GET['roleID'] != '' in them. This ensures that if no user ID is passed (that we are creating a new role), ignore is selected by default. Then we add the hidden inputs to set the save options, and close the form. We also add another form with hidden inuts to delete the role, and another form with a cancel button that will return us to the roles page.
If everything went according to plan, we should get the following when we try to edit the permissions for a role:
Saving the Data
Insert this code in /admin/roles.php right before the doc type tag:
01
02
03
04
05
06
07
08
09
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
| <? if (isset( $_POST [ 'action' ])) { switch ( $_POST [ 'action' ]) { case 'saveRole' : $strSQL = sprintf( "REPLACE INTO `roles` SET `ID` = %u, `roleName` = '%s'" , $_POST [ 'roleID' ], $_POST [ 'roleName' ]); mysql_query( $strSQL ); if (mysql_affected_rows() > 1) { $roleID = $_POST [ 'roleID' ]; } else { $roleID = mysql_insert_id(); } foreach ( $_POST as $k => $v ) { if ( substr ( $k ,0,5) == "perm_" ) { $permID = str_replace ( "perm_" , "" , $k ); if ( $v == 'X' ) { $strSQL = sprintf( "DELETE FROM `role_perms` WHERE `roleID` = %u AND `permID` = %u" , $roleID , $permID ); mysql_query( $strSQL ); continue ; } $strSQL = sprintf( "REPLACE INTO `role_perms` SET `roleID` = %u, `permID` = %u, `value` = %u, `addDate` = '%s'" , $roleID , $permID , $v , date ( "Y-m-d H:i:s" )); mysql_query( $strSQL ); } } header( "location: roles.php" ); break ; case 'delRole' : $strSQL = sprintf( "DELETE FROM `roles` WHERE `ID` = %u LIMIT 1" , $_POST [ 'roleID' ]); mysql_query( $strSQL ); $strSQL = sprintf( "DELETE FROM `user_roles` WHERE `roleID` = %u" , $_POST [ 'roleID' ]); mysql_query( $strSQL ); $strSQL = sprintf( "DELETE FROM `role_perms` WHERE `roleID` = %u" , $_POST [ 'roleID' ]); mysql_query( $strSQL ); header( "location: roles.php" ); break ; } } ?> |
- Perform a replace query on the roles table. This will update/insert the role name. Lines 8-13 perform an important function for saving roles. If we are performing an update, we already have an ID for the role. However if we are inserting one, we don't know the role ID. When we perform the replace query, the number of rows affected are returned. If the number of rows affected was greater than 1, a row was updated, so we should use the role id from the form. If the rows affected was not greater than 1, the row was inserted, so we use mysql_insert_id() to get the ID for the last inserted row.
- Then we loop through the $_POST variables and line 16 ensures that we only process rows where the input name starts with "perm_".
- Line 18 gets the floatval() of the permission so we end up with just the integer ID of the perm (so we know which permission we are dealing with).
- if ($v == 'x') {...} will run if we selected 'Ignore' for a permission on the form. It will attempt to delete the row from the table where the row ID and permission ID are right. If this happens, we use continue; to go to the next variable.
- If we have gotten to this point, we assume that we want to add or update a permission for this role. So, we use the 'replace into' syntax that we used in the user form. It's important that we have the roleID and permID in there so the database can check for an existing row.
- Finally we execute the SQL and redirect to the roles page.
Step 6: Permissions Admin
Like the roles admin, the permissions admin will have two functions: list the available permissions, and editing permissions. Start with this code in /admin/perms.php:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
| <?php include ( "../assets/php/database.php" ); include ( "../assets/php/class.acl.php" ); $myACL = new ACL(); if ( $myACL ->hasPermission( 'access_admin' ) != true)<br />{<br /> header( "location: ../index.php" );<br />} ?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" > <head> <meta http-equiv= "Content-Type" content= "text/html; charset=utf-8" /> <title>ACL Test</title> <link href= "../assets/css/styles.css" rel= "stylesheet" type= "text/css" /> </head> <body> <div id= "header" ></div> <div id= "adminButton" ><a href= "../" >Main Screen</a> | <a href= "index.php" >Admin Home</a></div> <div id= "page" > <!-- PAGE CONTENT --> </div> </body> </html> |
List Permissions
Place this code in the page div (in place of <!-- PAGE CONTENT -- >):
01
02
03
04
05
06
07
08
09
10
11
12
13
14
| <? if ( $_GET [ 'action' ] == '' ) { ?> <h2>Select a Permission to Manage:</h2> <? $roles = $myACL ->getAllPerms( 'full' ); foreach ( $roles as $k => $v ) { echo "<a href=\"?action=perm&permID=" . $v [ 'ID' ] . "\">" . $v [ 'Name' ] . "</a><br />" ; } if ( count ( $roles ) < 1) { echo "No permissions yet.<br />" ; } ?> <input type= "button" name= "New" value= "New Permission" onclick= "window.location='?action=perm'" /> <? } ?> |
Edit Permission
To edit/add an individual permission, we need to add this code immediately after the previous block:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
| <? if ( $_GET [ 'action' ] == 'perm' ) { if ( $_GET [ 'permID' ] == '' ) { ?> <h2>New Permission:</h2> <? } else { ?> <h2>Manage Permission: (<?= $myACL ->getPermNameFromID( $_GET [ 'permID' ]); ?>)</h2><? } ?> <form action= "perms.php" method= "post" > <label for = "permName" >Name:</label><input type= "text" name= "permName" id= "permName" value= "<?= $myACL->getPermNameFromID($_GET['permID']); ?>" maxlength= "30" /><br /> <label for = "permKey" >Key:</label><input type= "text" name= "permKey" id= "permKey" value= "<?= $myACL->getPermKeyFromID($_GET['permID']); ?>" maxlength= "30" /><br /> <input type= "hidden" name= "action" value= "savePerm" /> <input type= "hidden" name= "permID" value= "<?= $_GET['permID']; ?>" /> <input type= "submit" name= "Submit" value= "Submit" /> </form> <form action= "perms.php" method= "post" > <input type= "hidden" name= "action" value= "delPerm" /> <input type= "hidden" name= "permID" value= "<?= $_GET['permID']; ?>" /> <input type= "submit" name= "Delete" value= "Delete" /> </form> <form action= "perms.php" method= "post" > <input type= "submit" name= "Cancel" value= "Cancel" /> </form> <? } ?> |
At the end of the form, we add the hidden inputs, and the submit button. Then we have the delete and cancel forms.
Save the Data
Finally, we need to save the permission form, so add this code to the top of /admin/perms.php right above the doc type.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
| if (isset( $_POST [ 'action' ])) { switch ( $_POST [ 'action' ]) { case 'savePerm' : $strSQL = sprintf( "REPLACE INTO `permissions` SET `ID` = %u, `permName` = '%s', `permKey` = '%s'" , $_POST [ 'permID' ], $_POST [ 'permName' ], $_POST [ 'permKey' ]); mysql_query( $strSQL ); break ; case 'delPerm' : $strSQL = sprintf( "DELETE FROM `permissions` WHERE `ID` = %u LIMIT 1" , $_POST [ 'permID' ]); mysql_query( $strSQL ); break ; } header( "location: perms.php" ); } |
Step 7: Admin hub
We need a jumping off point for our ACL admin. We'll just create something simple with links to the 3 pages. Here is a preview and the code for it:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| <? php include("../assets/php/database.php"); include("../assets/php/class.acl.php"); $ myACL = new ACL(); if ($myACL->hasPermission('access_admin') != true)< br />{< br /> header("location: ../index.php");< br />} ?><! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> < head > < meta http-equiv = "Content-Type" content = "text/html; charset=utf-8" /> < title >ACL Test</ title > < link href = "../assets/css/styles.css" rel = "stylesheet" type = "text/css" /> </ head > < body > < div id = "header" ></ div > < div id = "adminButton" >< a href = "../" >Main Screen</ a ></ div > < div id = "page" > < h2 >Select an Admin Function:</ h2 > < a href = "users.php" >Manage Users</ a >< br /> < a href = "roles.php" >Manage Roles</ a >< br /> < a href = "perms.php" >Manage Permissions</ a >< br /> </ div > </ body > </ html > |
Step 8: Implementing the ACL on Your Site
As an example, say you had set up a permission with the key 'access_admin' and wanted to use it to control access to the admin interface. At the top of your page you could use this script to check it:
1
2
3
4
5
6
7
8
9
| <?php include ( "assets/php/database.php" ); include ( "assets/php/class.acl.php" ); $myACL = new ACL(); if ( $myACL ->hasPermission( 'access_admin' ) != true) { header( "location: insufficientPermission.php" ); } ?> |
In the provided source files, I have provided an index file that provides a simple test of the ACL based on the example code above. The sample index displays a list of all the permissions, and icons representing whether or not the current user can access each. There is also a list of the users that allows you to change the user that the ACL is displayed for. Here is the code for the sample index:
01
02
03
04
05
06
07
08
09
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
45
46
47
48
49
| <?php include ( "assets/php/database.php" ); include ( "assets/php/class.acl.php" ); $userID = $_GET [ 'userID' ]; $_SESSION [ 'userID' ] = 1; $myACL = new ACL(); ?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" > <head> <meta http-equiv= "Content-Type" content= "text/html; charset=utf-8" /> <title>ACL Test</title> <link href= "assets/css/styles.css" rel= "stylesheet" type= "text/css" /> </head> <body> <div id= "header" ></div> <div id= "adminButton" ><a href= "admin/" >Admin Screen</a></div> <div id= "page" > <h2>Permissions for <?= $myACL ->getUsername( $userID ); ?>:</h2> <? $userACL = new ACL( $userID ); $aPerms = $userACL ->getAllPerms( 'full' ); foreach ( $aPerms as $k => $v ) { echo "<strong>" . $v [ 'Name' ] . ": </strong>" ; echo "<img src=\"assets/img/" ; if ( $userACL ->hasPermission( $v [ 'Key' ]) === true) { echo "allow.png" ; $pVal = "Allow" ; } else { echo "deny.png" ; $pVal = "Deny" ; } echo "\" width=\"16\" height=\"16\" alt=\"$pVal\" /><br />" ; } ?> <h3>Change User:</h3> <? $strSQL = "SELECT * FROM `users` ORDER BY `Username` ASC" ; $data = mysql_query( $strSQL ); while ( $row = mysql_fetch_assoc( $data )) { echo "<a href=\"?userID=" . $row [ 'ID' ] . "\">" . $row [ 'username' ] . "</a><br />" ; } ?> </div> </body> </html> |
No comments:
Post a Comment