Browse | Submit New Snippet | Create Package

 

othAuth :: Authentication Component

Type:
Full Script
Category:
Other
License:
GNU General Public License
Language:
PHP
 
Description:
othAuth :: a Authentication Component by othman ouahbi aka CraZyLeGs
Features:
=========
- Users Support
- Groups Support
- Permissions To Groups Mapping Support (controller/action/p/a/r/a/m/s
- Login/Logout

Prerequisites:
===============

You need to have:

1) 4 tables in your db (users, groups, permissions, groups_permissions):
------------------------------------------------------------------------

CREATE TABLE `groups` (
`id` int(10) unsigned NOT NULL auto_increment,
`name` varchar(50) NOT NULL default '',
`created` datetime NOT NULL default '0000-00-00 00:00:00',
`modified` datetime NOT NULL default '0000-00-00 00:00:00',
PRIMARY KEY (`id`)
);

CREATE TABLE `groups_permissions` (
`group_id` int(10) unsigned NOT NULL default '0',
`permission_id` int(10) unsigned NOT NULL default '0',
KEY `group_id` (`group_id`,`permission_id`)
);

CREATE TABLE `permissions` (
`id` int(10) unsigned NOT NULL auto_increment,
`name` varchar(50) NOT NULL default '',
`created` datetime NOT NULL default '0000-00-00 00:00:00',
`modified` datetime NOT NULL default '0000-00-00 00:00:00',
PRIMARY KEY (`id`),
KEY `name` (`name`)
);

CREATE TABLE `users` (
`id` int(10) unsigned NOT NULL auto_increment,
`username` varchar(50) NOT NULL default '',
`password` varchar(32) NOT NULL default '',
`name` varchar(50) NOT NULL default '',
`email` varchar(100) NOT NULL default '',
`last_visit` datetime NOT NULL default '0000-00-00 00:00:00',
`group_id` int(10) unsigned NOT NULL default '0',
`active` tinyint(1) unsigned NOT NULL default '0',
`created` datetime NOT NULL default '0000-00-00 00:00:00',
`modified` datetime NOT NULL default '0000-00-00 00:00:00',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`),
UNIQUE KEY `email` (`email`,`username`),
KEY `group_id` (`group_id`)
);


INSERT INTO `groups` VALUES (1, 'SuperAdmin', '0000-00-00 00:00:00', '0000-00-00 00:00:00');
INSERT INTO `groups` VALUES (2, 'Admin', '0000-00-00 00:00:00', '0000-00-00 00:00:00');
INSERT INTO `groups` VALUES (3, 'Member', '0000-00-00 00:00:00', '0000-00-00 00:00:00');

INSERT INTO `groups_permissions` VALUES (1, 1);
INSERT INTO `groups_permissions` VALUES (1, 2);

INSERT INTO `permissions` VALUES (1, 'users/add', '2006-03-13 23:19:31', '0000-00-00 00:00:00');
INSERT INTO `permissions` VALUES (2, 'users/test_cache', '2006-03-13 23:19:31', '0000-00-00 00:00:00');

INSERT INTO `users` VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 'fname lname', 'admin@exemple.com', '2006-03-14 06:55:08', 1, 1, '0000-00-00 00:00:00', '0000-00-00 00:00:00');
INSERT INTO `users` VALUES (2, 'oth', 'e10adc3949ba59abbe56e057f20f883e', 'fname lname', 'oth@exemple.com', '2005-07-15 00:49:47', 3, 1, '0000-00-00 00:00:00', '0000-00-00 00:00:00');



2) Models for your tables
-------------------------

class Group extends AppModel
{
var $name = 'Group';
var $hasMany = 'User';
var $hasAndBelongsToMany = array('Permission' =>
array('className' => 'Permission',
'joinTable' => 'groups_permissions'));
}

class Permission extends AppModel
{
var $name = 'Permission';
var $hasAndBelongsToMany = array('Group' =>
array('className' => 'Group',
'joinTable' => 'groups_permissions'));
}

class User extends AppModel
{
var $name = 'User';
var $belongsTo = 'Group';
var $recursive = 5;
}

Usage:
======

Pre
----
In your controller, you first add othAuth to your $Components
var $components = array('othAuth');
Next, you declare a member variable called $othAuthRestrictions.
This variable will hold the actions + params that need to be checked for permissions
ex:

var $othAuthRestrictions =
array('test_cache','add','edit','show/admins/1','restore/db');

Note:
It should be obvious that if you have Access to show/admins/1
you don't necessarly have access to show/admins or show.
but if you have access to show, you do automatically have access to show/admin
a deny, allow logic might be added in a future release

to ignore auth check on this controller just set it to null
var $othAuthRestrictions = null;
for overall controller auth check use:
var $othAuthRestrictions = "*";

now we need to initialize the Component, beforeFilter looks like the perfect place ex:
function beforeFilter()
{
$auth_conf = array('auto_redirect' => true,
'login_page' => 'users/login',
'logout_page' => 'users/logout',
'access_page' => 'users/login',
'hashkey' => 'MySEcEeTHaSHKeYz');

$this->othAuth->controller = &$this;
$this->othAuth->init($auth_conf);
$this->othAuth->check();
}

first argument is $auto_redirect
if set to true, second argument $redirect_page is used to automatically redirect the user
after login, after logout, a failure auth check etc..

Login
-----
very simple, create a form that has three fields:
USER_LOGIN_VAR, USER_PASSW_VAR, USER_GROUP_VAR
if USER_GROUP_VAR is not specified, group of id = 1 is assumed
now in your action you might have something like that:
function login()
{
if(isset($this->params['data']))
{
$auth_num = $this->othAuth->login($this->params['data']['User']);

$this->set('auth_msg', $this->othAuth->getMsg($auth_num));
}
}

if $auto_redirect is true, login will redirect automatically to $redirect_page

Logout
------
ex:
function logout()
{
$this->othAuth->logout();
$this->render("login");
}

Redirection
-----------
setting auto_redirect to false limits you alot, othAuth Handles auto-redirection for you.
you have three pages to setup,
login_page: Where the login form is
logout_page: Page you want othAuth to redirect to after logout
access_page: Page you want othAuth to redirect to after login

Redirect-Back
--------------
If you want to enable this feature, uncomment @define ( 'AUTH_URL_REDIRECT_VAR','from');
by default it's uncommented.
basically if your where in an permitted page and your session timeouts,
or you have insufficient Permissions on a page you'll be redirected to the login_page
this feature enables the users they re-login to be automatically redirected to the page
they were in or were trying to access

Centralizing Authentication
===========================

Use AppController' beforeFilter method instead of your controller' specific one

Possible Upcoming features
==========================

- Enhanced Deny/Allow Logic
- Group/User hasManyBelongsToMany Association
- Support Cake' built in ACL

-----------------------
Hope you find this Component useful
For Bugs, Enhancements, Please send me a mail to crazylegs@gmail.com

Versions Of This Snippet::

Othman Ouahbi
Snippet ID Download Version Date Posted Author Delete
930.22006-05-12 14:04Othman Ouahbi
Changes since last version::
Changement depuis la dernière version:
-Bug fixes.
-Full Support for CAKE_ADMIN.
you can have:
$othAuthRestriction = array('admin');
- added a helper and methods to the Component
to get information from the auth session.

PS: should be named oth_auth.php
790.1.22006-05-02 11:53Christian Trummer
Changes since last version::
fixed a bug in _checkPermission
strpos returned false or true (0) but 0 is interpreted like false. i added a underscore.
710.1.12006-04-08 17:05Othman Ouahbi
Changes since last version::
Fixed a bug in _validRestrictions, I'll include here the whole file tho
560.1.02006-03-18 16:56Othman Ouahbi

Download a raw-text version of this code by clicking on "Download Version"

 


Latest Snippet Version: :0.2

<?php


// othAuth By othman ouahbi.
// comments, bug reports are welcome crazylegs@gmail.com

class othAuthComponent extends Object
{
	
/**
* Constants to modify the behaviour of othAuth Component
*/
	var $user_login_var        = 'username';
	var $user_passw_var        = 'password';
	var $user_group_var        = 'group_id';
	
	var $user_table       	   = 'users';
	
	var $user_table_login      = 'username';
	var $user_table_passw      = 'password';
	var $user_table_gid        = 'group_id';
	var $user_table_active     = 'active';
	var $user_table_last_visit = 'last_visit';
	var $auth_url_redirect_var = 'from';
	var $user_model       = 'User';
	var $group_model      = 'Group';
	var $permission_model = 'Permission';

	/*
	 * Internals you don't normally need to edit those
	 */
	var $components = array('Session');
	var $controller = true;
	
	var $redirect_page;
	var $hashkey = "mYpERsOnALhaSHkeY";
	var $auto_redirect;
	var $gid = 1;
	var $login_page = '';
	var $logout_page = '';
	var $access_page = '';
	var $strict_gid_check = true;
	
	function init($auth_config = null) 
	{
		if(is_array($auth_config) && !is_null($auth_config) && !empty($auth_config))
		{
			$this->login_page       = isset($auth_config['login_page']) ? $auth_config['login_page']  : 'users/login';
			$this->logout_page      = isset($auth_config['logout_page'])? $auth_config['logout_page'] : 'users/logout';
			$this->access_page      = isset($auth_config['access_page'])? $auth_config['access_page'] : $this->login_page;
			$this->auto_redirect    = isset($auth_config['auto_redirect']) ? (boolean)$auth_config['auto_redirect']  : true;
			$this->hashkey          = isset($auth_config['hashkey'])? (string) $auth_config['hashkey'] : 'mYpERsOnALhaSHkeY';
			$this->strict_gid_check = isset($auth_config['strict_gid_check']) ? (boolean)$auth_config['strict_gid_check']  : true;
		}
		else
		{
			$this->login_page       = 'users/login';
			$this->logout_page      = 'users/logout';
			$this->auto_redirect    = true;
			$this->hashkey          = "mYpERsOnALhaSHkeY";
			$this->strict_gid_check = true;
		}
		
		// pass auth data to the view so it can be used by the helper
		$this->_passAuthData();
	}
	
	function login($params) // username,password,group
	{
		 
		 
		 if($this->Session->valid() && $this->Session->check('othAuth_'.$this->hashkey))
		 {
		 	return 1;
		 }
		 
		 if($params == null || 
		 	!isset($params[$this->user_login_var]) || 
		 	!isset($params[$this->user_passw_var]))
		 {
		 	return 0;
		 }
		 
		 uses('sanitize');
		 $login = Sanitize::paranoid($params[$this->user_login_var]);
		 $passw = Sanitize::paranoid($params[$this->user_passw_var]);
		 if(isset($params[$this->user_group_var]))
		 {
		 	
		 	$this->gid = (int) Sanitize::paranoid($params[$this->user_group_var]);
			if( $this->gid < 1)
			{
				$this->gid = 1;
			}
		 }
	 
		 if($login == "" || $passw == "") 
		 {
		 	return -1;
		 }
		 
		$passw = md5($passw);
		$gid_check_op = ($this->strict_gid_check)?'':'<=';
		$conditions = array(
							"{$this->user_model}.".$this->user_table_login => "$login",
							"{$this->user_model}.".$this->user_table_passw => "$passw",
							"{$this->user_model}.".$this->user_table_gid => "$gid_check_op{$this->gid}",
							"{$this->user_model}.".$this->user_table_active => 1); 
		
		$this->controller->{$this->user_model}->recursive = 5;			
		$row = $this->controller->{$this->user_model}->find($conditions);
		
		$num_users = (int) $this->controller->{$this->user_model}->findCount($conditions);
		
		if( empty($row) || $num_users != 1 )
		{
			
			return -2;
		}
		else
		{
			
			$this->_saveSession($row);
			
			// Update the last visit date to now
			if(isset($this->user_table_last_visit))
			{	
				$row["{$this->user_model}"][$this->user_table_last_visit] = date('Y-m-d h:i:s');
				$res = $this->controller->{$this->user_model}->save($row,true,array($this->user_table_last_visit)); 
			}
			if($this->auto_redirect == true)
			{
				$this->redirect($this->access_page);
			}
			
			return 1;
		}
		 
	}
	
	function _saveSession($row)
	{	
		 $login = $row[$this->user_model][$this->user_table_login];
		 $passw = $row[$this->user_model][$this->user_table_passw];
		 $gid   = $row[$this->user_model][$this->user_table_gid];
		 //$hk = md5($this->hashkey.$login.$passw.$this->gid);
		 $hk    = md5($this->hashkey.$login.$passw.$gid);
		 $row["{$this->user_model}"]['login_hash'] = $hk;
 		 $row["{$this->user_model}"]['hashkey']    = $this->hashkey;
 		 //$this->Session->write('othAuth_'.$this->gid,$row);
		 $this->Session->write('othAuth_'.$this->hashkey,$row);

	}
	
	function __notcurrent($page)
	{
		if($page == "") return false;
		
		$c = strtolower($this->controller->name);
		$a = strtolower($this->controller->action);
		
		$page = strtolower($page.'/');
		
		$c_a = $this->_handleCakeAdmin($c,$a);
		
		$not_current = strpos($page,$c_a);
		// !== is required, $not_current might be boolean(false)
		return ((!is_int($not_current)) || ($not_current !== 0));
	}
	
 	function redirect($page = "",$back = false)
	{	
		if($page == "") 
			//$page = $this->redirect_page;
			$page = $this->logout_page;
			
		if(isset($this->auth_url_redirect_var))
		{
			if(!isset($this->controller->params['url'][$this->auth_url_redirect_var]))
			{	
				//die("1");
				if($back == true)
				{
					$this->Session->write('othauth_from_page',$this->controller->params['url']['url']);
					$page .= "?".$this->auth_url_redirect_var."=".$this->controller->params['url']['url'];
				}
				else 
				{	
					if($this->Session->check('othauth_from_page'))
					{
						$page = $this->Session->read('othauth_from_page');
						$this->Session->del('othauth_from_page');
					}
				}
				if($this->__notcurrent($page))
					$this->controller->redirect($page);
			}
		}
		else
		{
			if($this->__notcurrent($page))
				$this->controller->redirect($page);
		}
    }
	
	// users/login users/logout
    // Logout the user
    function logout ()
	{	
		$us = 'othAuth_'.$this->hashkey;
		
		if($this->Session->valid($us))
		{
			$ses = $this->Session->read($us);
			
			if(!empty($ses) && is_array($ses))
			{
				// two logins of different hashkeys can exist
				if($this->hashkey == $ses["{$this->user_model}"]['hashkey'])
				{
					$this->Session->del($us);
					$this->Session->del('othauth_from_page');
					
				}
			}
		}
		
		if($this->auto_redirect == true) 
		{	
			
			$this->redirect($this->logout_page);
		}
    }
	

    // Confirms that an existing login is still valid
    function check()
	{
		// is there a restriction list && action is in
		if($this->_validRestrictions())
		{

			$us 	   = 'othAuth_'.$this->hashkey;
			// does session exists
			//if(!empty($ses) && is_array($ses))
			if($this->Session->valid() && 
			   $this->Session->check($us))
			{
				$ses 	   = $this->Session->read($us);
				$login     = $ses["{$this->user_model}"][$this->user_table_login];
				$password  = $ses["{$this->user_model}"][$this->user_table_passw];
				$gid       = $ses["{$this->user_model}"][$this->user_table_gid];
				$hk        = $ses["{$this->user_model}"]['login_hash'];
				
				// is user invalid
				
				if (md5($this->hashkey.$login.$password.$gid) != $hk)
				{
					/*
					if($this->auto_redirect == true) 
					{
						$this->logout();	
					}
					*/
					
					$this->logout();
					return false;
				}
				
				// check permissions on the current controller/action/p/a/r/a/m/s
				if(!$this->_checkPermission($ses))
				{
					if($this->auto_redirect == true) 
					{
						// should probably add $this->noaccess_page too or just flash
						$this->redirect($this->login_page,true);
					}
					return false;
				}
				
				return true;
				
			}
			if($this->auto_redirect == true) 
			{
				$this->redirect($this->login_page,true);
			}
			return false;	
		}
		
		return true;
    }
	
	function _validRestrictions()
	{
		
		$isset   = isset($this->controller->othAuthRestrictions);
		if($isset)
		{
			$oth_res = $this->controller->othAuthRestrictions;
			
			if(is_string($oth_res))
			{
				
				
				if(($oth_res === "*") ||(
				defined('CAKE_ADMIN') && (($oth_res === CAKE_ADMIN) || $this->isCakeAdminAction())))
				{
					if(
					   $this->__notcurrent($this->login_page) && 
					   $this->__notcurrent($this->logout_page))
					{
						//die('here');
						return true;
					}	
				}
				
			}
			elseif(is_array($oth_res))
			{
				if(defined('CAKE_ADMIN'))
				{
					if(in_array(CAKE_ADMIN,$oth_res))
					{
						if($this->isCakeAdminAction())
						{
							if($this->__notcurrent($this->login_page) && 
							   $this->__notcurrent($this->logout_page))
							{
								return true;
							}
						}
					}
				}
				foreach($oth_res as $r)
				{
					$pos = strpos($r."/",$this->controller->action."/");
					if($pos === 0)
					{
						return true;
					}
				}
			}
		}
		
		return false;
	}
	
	function _checkPermission(&$ses)
	{
		//die('c');
		$c   = strtolower($this->controller->name);
		$a   = strtolower($this->controller->action);
		$h   = strtolower($this->controller->here);
		$c_a = $this->_handleCakeAdmin($c,$a);// controller/admin_action -> admin/controller/action
		
		// extract params
		$aa  =  substr( $c_a, strpos($c_a,'/'));
		
		$params = isset($this->controller->params['pass'])?implode('/',$this->controller->params['pass']): '';
		
		$c_a_p = $c_a.$params;
		
		
		$return = false;
		
		if(!isset($ses[$this->group_model][$this->permission_model]))
		{
			return false;
		}
		$ses_perms = $ses[$this->group_model][$this->permission_model];
		
		
		// quickly check if the group has full access (*) or 
		// current_controller/* or CAKE_ADMIN/current_controller/*
		// full params check isn't supported atm
		foreach($ses_perms as $sp)
		{
			if($sp['name'] == '*')
			{
				return true;
			}else
			{
				$sp_name = strtolower($sp['name']);
				$perm_parts = explode('/',$sp_name);
				// users/edit/1 users/edit/*
				//  users/* users/*
				
				if(defined('CAKE_ADMIN'))
				{
					
					if((count($perm_parts) > 1)  && 
					   ($perm_parts[0] == CAKE_ADMIN) &&
					   ($perm_parts[1] == strtolower($c)) && 
					   ($perm_parts[2] == "*"))
					{
						return true;
					}
				}else
				{
					if((count($perm_parts) > 1)  && 
					   ($perm_parts[0] == strtolower($c)) && 
					   ($perm_parts[1] == "*"))
					{
						return true;
					}
				}

			}
		}
		
		
		if(is_string($this->controller->othAuthRestrictions))
		{
			$is_checkall   = $this->controller->othAuthRestrictions === "*";
			$is_cake_admin = defined('CAKE_ADMIN') && ($this->controller->othAuthRestrictions === CAKE_ADMIN);
			if($is_checkall || $is_cake_admin)
			{
				foreach($ses_perms as $p)
				{	
					if(strpos($c_a_p,strtolower($p['name'])) === 0)
					{
						$return = true;
						break;
					}
				}
			}
		}
		else 
		{
			$a_p_in_array = in_array($a.'/'.$params, $this->controller->othAuthRestrictions);
			
			// if current url is restricted, do a strict compare
			// ex if current url action/p and current/p is in the list
			// then the user need to have it in perms
			// current/p/s current/p
			if($a_p_in_array)
			{
				
				foreach($ses_perms as $p)
				{
					if($c_a_p == strtolower($p['name']))
					{
						$return = true;
						break;
					}
				}
			}
			// allow a user with permssion on the current action to access deeper levels
			// ex: user access = 'action', allow 'action/p'
			else 
			{
				foreach($ses_perms as $p)
				{
					if(strpos($c_a_p,strtolower($p['name'])) === 0)
					{
						$return = true;
						break;
					}
				}
			}
			
		}
		
		return $return;
	}
	
	function _handleCakeAdmin($c,$a)
	{
		if(defined('CAKE_ADMIN'))
		{
			$strpos = strpos($a,CAKE_ADMIN.'_');
			if($strpos === 0)
			{
				$function = substr($a,strlen(CAKE_ADMIN.'_'));
				if($c == null) return $function.'/';
				$c_a = CAKE_ADMIN.'/'.$c.'/'.$function.'/';
				return $c_a;
			}else
			{
				if($c == null) return $a.'/';
			}	
		}
		return $c.'/'.$a.'/';
	}
	
	function getSafeCakeAdminAction()
	{
		if(defined('CAKE_ADMIN'))
		{
			$a = $this->controller->action;
			$strpos = strpos($a,CAKE_ADMIN.'_');
			if($strpos === 0)
			{
				$function = substr($a,strlen(CAKE_ADMIN.'_'));
				
				return $function;
			}
		}
		return $this->controller->action;
	}
	
	function isCakeAdminAction()
	{
		if(defined('CAKE_ADMIN'))
		{
			$a = $this->controller->action;
			$strpos = strpos($a,CAKE_ADMIN.'_');
			if($strpos === 0)
			{
				return true;
			}
		}
		return false;
	}
	
	// helper methods
	function user($arg)
	{
		$us = 'othAuth_'.$this->hashkey;
		// does session exists
		if($this->Session->valid() && $this->Session->check($us))
		{
			$ses = $this->Session->read($us);
			if(isset($ses["{$this->user_model}"][$arg]))
			{
				return $ses["{$this->user_model}"][$arg];
			}
			else
			{
				return false;
			}
		}
		return false;	
	}
	
	// helper methods
	function group($arg)
	{
		$us = 'othAuth_'.$this->hashkey;
		// does session exists
		if($this->Session->valid() && $this->Session->check($us))
		{
			$ses = $this->Session->read($us);
			if(isset($ses["{$this->group_model}"][$arg]))
			{
				return $ses["{$this->group_model}"][$arg];
			}
			else
			{
				return false;
			}
		}
		return false;	
	}
	
	
	// helper methods
	function permission($arg)
	{
		$us = 'othAuth_'.$this->hashkey;
		// does session exists
		if($this->Session->valid() && $this->Session->check($us))
		{
			$ses = $this->Session->read($us);
			if(isset($ses[$this->group_model][$this->permission_model]))
			{
				$ret = array();
				if(is_array($ses[$this->group_model][$this->permission_model]))
				{
					for($i = 0; $i < count($ses[$this->group_model][$this->permission_model]); $i++ )
					{
						$ret[] = $ses[$this->group_model][$this->permission_model][$i][$arg];	
					}
				}
				return $ret;
			}
			else
			{
				return false;
			}
		}
		return false;	
	}
	
	function getData()
	{
		$us = 'othAuth_'.$this->hashkey;
		// does session exists
		if($this->Session->valid() && $this->Session->check($us))
		{
			return $this->Session->read($us);
		}
		return false;	
	}
	
	// passes data to the view to be used by the helper
	function _passAuthData()
	{

		$data['hashkey']                 = $this->hashkey;
		$data['user_model']              = $this->user_model;
		$data['group_model']             = $this->group_model;
		$data['permission_model']        = $this->permission_model;
		$data['login_page']              = $this->login_page;
		$data['logout_page']             = $this->logout_page;
		$data['access_page']             = $this->access_page;
		$data['auth_url_redirect_var']   = $this->auth_url_redirect_var;
		$data['strict_gid_check']        = $this->strict_gid_check;
		
		$this->controller->set('othAuth_data',$data);	
	}
	
	function getMsg($id) 
	{
		switch($id) {
		case 1:
			{
				return "You are Logged In !";
			}break;
		case 0:
			{
				return "Please Log In !";
			}break;
		case -1:
			{
				 return $this->user_login_var."/".$this->user_passw_var." Empty!";
			}break;
		case -2:
			{
				 return "Incorrect ".$this->user_login_var."/".$this->user_passw_var."!";
			}break;
		default:
			{
				 return "Invalid error ID";
			}break;
		
		}
	}
}
?>
		

Submit a new version

You can submit a new version of this snippet if you have modified it and you feel it is appropriate to share with others..