In almost every project I need to setup some sort of login, and usually it requires group access to certain data depending on different criteria.
For example I may have groups Admin+Member and I want Members to only be able to see other Users Profiles if the other Profile is active.
Little Helper
This function checks if the user is in a group.
config/bootstrap.phpThe Tables
usersCREATE TABLE IF NOT EXISTS `users` (
`id` int(11) NOT NULL auto_increment,
`username` char(50) NOT NULL,
`password` char(50) NOT NULL,
`active` tinyint(4) NOT NULL default '0',
`email` varchar(255) NOT NULL,
`created` datetime default NULL,
`modified` datetime default NULL,
PRIMARY KEY (`id`)
);
`id` int(11) NOT NULL auto_increment,
`username` char(50) NOT NULL,
`password` char(50) NOT NULL,
`active` tinyint(4) NOT NULL default '0',
`email` varchar(255) NOT NULL,
`created` datetime default NULL,
`modified` datetime default NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE IF NOT EXISTS `groups` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(128) NOT NULL,
`created` datetime default NULL,
`modified` datetime default NULL,
PRIMARY KEY (`id`)
);
`id` int(11) NOT NULL auto_increment,
`name` varchar(128) NOT NULL,
`created` datetime default NULL,
`modified` datetime default NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE IF NOT EXISTS `groups_to_users` (
`group_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
PRIMARY KEY (`group_id`,`user_id`)
);
`group_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
PRIMARY KEY (`group_id`,`user_id`)
);
The Models
Before we get started, lets setup the models.
I have added some validation to get you started.
app_model.phpclass AppModel extends Model {
function validUnique($value,$field){
if (is_array($field)) {
foreach($field as $v) $field = $v;
}
$conditions = array();
$conditions["{$this->name}.{$field}"] = $value;
if (isset($this->id) && $this->id) {
$conditions["{$this->name}.{$this->primaryKey}"] = "!={$this->id}";
}
$this->recursive = -1;
return !$this->hasAny($conditions);
}
function required($field) {
foreach($field as $k=>$v){}
return $v?true:false;
}
}
function validUnique($value,$field){
if (is_array($field)) {
foreach($field as $v) $field = $v;
}
$conditions = array();
$conditions["{$this->name}.{$field}"] = $value;
if (isset($this->id) && $this->id) {
$conditions["{$this->name}.{$this->primaryKey}"] = "!={$this->id}";
}
$this->recursive = -1;
return !$this->hasAny($conditions);
}
function required($field) {
foreach($field as $k=>$v){}
return $v?true:false;
}
}
class User extends AppModel {
var $name = 'User';
var $displayField = 'username';
var $validate = array(
'username' => array(
'not unique' => array(
'rule'=>array('validUnique','username'),
'required' => true,
),
'must be alpha-numeric' => array(
'rule' => 'alphaNumeric',
'required' => true,
),
'required field' => array(
'rule' => 'required',
'required' => true,
),
),
'email' => array(
'not unique' => array(
'rule'=>array('validUnique','email'),
'required' => true,
),
'invalid email' => array(
'rule'=>'email',
'required' => true,
),
'required field' => array(
'rule' => 'required',
'required' => true,
),
),
'password_confirm' => array(
'does not match' => array(
'rule' => 'validPasswordConfirm',
'required' => false,
),
),
);
var $hasAndBelongsToMany = array(
'Group' => array(
'className' => 'Group',
'joinTable' => 'groups_to_users',
'foreignKey' => 'user_id',
'associationForeignKey' => 'group_id',
'with' => 'GroupToUser',
),
);
function validPasswordConfirm($value){
if ($this->data[$this->alias]['password_change'] != $this->data[$this->alias]['password_confirm']) {
return false;
}
return true;
}
}
var $name = 'User';
var $displayField = 'username';
var $validate = array(
'username' => array(
'not unique' => array(
'rule'=>array('validUnique','username'),
'required' => true,
),
'must be alpha-numeric' => array(
'rule' => 'alphaNumeric',
'required' => true,
),
'required field' => array(
'rule' => 'required',
'required' => true,
),
),
'email' => array(
'not unique' => array(
'rule'=>array('validUnique','email'),
'required' => true,
),
'invalid email' => array(
'rule'=>'email',
'required' => true,
),
'required field' => array(
'rule' => 'required',
'required' => true,
),
),
'password_confirm' => array(
'does not match' => array(
'rule' => 'validPasswordConfirm',
'required' => false,
),
),
);
var $hasAndBelongsToMany = array(
'Group' => array(
'className' => 'Group',
'joinTable' => 'groups_to_users',
'foreignKey' => 'user_id',
'associationForeignKey' => 'group_id',
'with' => 'GroupToUser',
),
);
function validPasswordConfirm($value){
if ($this->data[$this->alias]['password_change'] != $this->data[$this->alias]['password_confirm']) {
return false;
}
return true;
}
}
The Controllers
The app_controller will do the work of setting the related Groups into the Auth data.
app_controller.phpclass AppController extends Controller {
var $components = array('Auth');
function beforeFilter() {
$this->__setAuthUser();
// allow pages to be displayed to anyone
if ($this->name=='Pages') {
$this->Auth->allow('display');
}
parent::beforeFilter();
}
function __setAuthUser() {
$this->Auth->userScope = array('User.active = 1');
// read the auth info
$auth = $this->Session->read('Auth');
// set the auth users groups
if (!isset($auth['GroupUser']) || $auth['GroupUser']!=$auth['User']['id']) {
$User = ClassRegistry::init('User');
$auth = $User->find('first',array('conditions'=>array('User.id'=>$this->Auth->user('id'))));
$auth['GroupUser'] = $this->Auth->user('id');
}
// check permissions for admin access
if(isset($this->params[Configure::read('Routing.admin')])) {
if (isset($auth['User']['id']) && !in_group('admin',$auth)) {
$this->_flash(__('You do not have permission to access the administration.',true),'error');
$this->redirect(array('admin'=>false,'controller'=>'users','action'=>'login'));
}
}
// save and set the auth info
$this->Session->write('Auth',$auth);
$this->set('auth',isset($auth['User'])?$auth:false);
}
function _flash($message,$type='message') {
$messages = (array)$this->Session->read('Message.multiFlash');
$messages[] = array('message'=>$message, 'layout'=>'default', 'params'=>array('class'=>$type));
$this->Session->write('Message.multiFlash', $messages);
}
}
var $components = array('Auth');
function beforeFilter() {
$this->__setAuthUser();
// allow pages to be displayed to anyone
if ($this->name=='Pages') {
$this->Auth->allow('display');
}
parent::beforeFilter();
}
function __setAuthUser() {
$this->Auth->userScope = array('User.active = 1');
// read the auth info
$auth = $this->Session->read('Auth');
// set the auth users groups
if (!isset($auth['GroupUser']) || $auth['GroupUser']!=$auth['User']['id']) {
$User = ClassRegistry::init('User');
$auth = $User->find('first',array('conditions'=>array('User.id'=>$this->Auth->user('id'))));
$auth['GroupUser'] = $this->Auth->user('id');
}
// check permissions for admin access
if(isset($this->params[Configure::read('Routing.admin')])) {
if (isset($auth['User']['id']) && !in_group('admin',$auth)) {
$this->_flash(__('You do not have permission to access the administration.',true),'error');
$this->redirect(array('admin'=>false,'controller'=>'users','action'=>'login'));
}
}
// save and set the auth info
$this->Session->write('Auth',$auth);
$this->set('auth',isset($auth['User'])?$auth:false);
}
function _flash($message,$type='message') {
$messages = (array)$this->Session->read('Message.multiFlash');
$messages[] = array('message'=>$message, 'layout'=>'default', 'params'=>array('class'=>$type));
$this->Session->write('Message.multiFlash', $messages);
}
}
Next I have provided a fully working users_controller, complete with account register, account management and lost password email.
I have also included some extra goodies such as habtm checkbox, breadcrumbs, search and multi-actions.
controllers/users_controller.phpclass UsersController extends AppController {
var $name = 'Users';
var $components = array('Email');
var $multiActions = array(
'multi_delete',
);
function beforeFilter() {
parent::beforeFilter();
$this->Auth->allow('register','password');
if (isset($this->passedArgs['key'])) {
$this->Auth->allow('account');
}
}
//
// PUBLIC ACTIONS
//
function index() {
$title = 'My Account';
// set breadcrumb
$breadcrumb = array();
$breadcrumb[] = array(
'name' => 'Home',
'href' => '/',
);
$breadcrumb[] = array(
'name' => $title,
);
$this->set(compact('title','breadcrumb'));
}
function account() {
// load account from key
if (isset($this->passedArgs['key'])) {
$this->recursive = -1;
$user = $this->User->findByPassword($this->passedArgs['key']);
if (!$user) {
$this->_flash(__('Invalid Key.', true),'error');
$this->redirect(array('action'=>'index'));
}
$this->Auth->login($user);
}
// load account from auth user
$id = $this->Auth->user('id');
if (!$id) {
$this->_flash(__('Invalid User.', true),'error');
$this->redirect(array('action'=>'index'));
}
// process submit
if (!empty($this->data)) {
$error = false;
$this->User->create();
$this->User->id = $id;
if ($this->data['User']['password_change']) {
$this->data['User']['password'] = $this->Auth->password($this->data['User']['password_change']);
}
if ($this->User->save($this->data,true,array('email','username','password','password_change','password_confirm'))) {
$this->_flash(__('Your Account has been saved.', true),'success');
$this->redirect(array('action'=>'index'));
}
$this->_flash(__('Your Account could not be saved. Please, try again.', true),'warning');
}
if (empty($this->data)) {
$this->data = $this->User->read(null, $id);
}
$title = 'Modify Details';
// set breadcrumb
$breadcrumb = array();
$breadcrumb[] = array(
'name' => 'Home',
'href' => '/',
);
$breadcrumb[] = array(
'name' => 'My Account',
'href' => array('controller'=>'users','action'=>'index'),
);
$breadcrumb[] = array(
'name' => $title,
);
$this->set(compact('title','breadcrumb'));
}
function register() {
$title = 'Register';
// process submit
if (!empty($this->data)) {
if (!$this->data['User']['password_confirm']) {
$this->data['User']['password_confirm']=' ';
}
$this->data['User']['password'] = $this->Auth->password($this->data['User']['password_change']);
$this->data['User']['active'] = 1;
$this->User->create();
if ($this->User->save($this->data,true,array('email','username','active','password','password_change','password_confirm'))) {
$this->Auth->login($this->data);
$this->_flash(__('Your Account has been created.', true),'success');
$this->redirect(array('action'=>'index'));
}
$this->_flash(__('Your Account could not be created. Please, try again.', true),'warning');
}
// set breadcrumb
$breadcrumb = array();
$breadcrumb[] = array(
'name' => 'Home',
'href' => '/',
);
$breadcrumb[] = array(
'name' => 'My Account',
'href' => array('controller'=>'users','action'=>'index'),
);
$breadcrumb[] = array(
'name' => $title,
);
$this->set(compact('title','breadcrumb'));
}
function login() {
$title = 'Login';
// set breadcrumb
$breadcrumb = array();
$breadcrumb[] = array(
'name' => 'Home',
'href' => '/',
);
$breadcrumb[] = array(
'name' => 'My Account',
'href' => array('controller'=>'users','action'=>'index'),
);
$breadcrumb[] = array(
'name' => $title,
);
$this->set(compact('title','breadcrumb'));
}
function logout() {
$this->Session->delete('Auth.GroupUser');
$this->redirect($this->Auth->logout());
}
function password() {
$error = false;
if (!$error) {
if (!isset($this->data['User']['lost_password']) || !$this->data['User']['lost_password']) {
$this->_flash(__('Please enter an email address or username.', true),'warning');
$error = true;
}
}
if (!$error) {
$this->User->recursive = -1;
$user = $this->User->findByUsername($this->data['User']['lost_password']);
if (!$user) {
$user = $this->User->findByEmail($this->data['User']['lost_password']);
}
if (!$user) {
$this->_flash(__('Account not found.', true),'error');
$error = true;
}
}
if (!$error) {
$this->Email->to = $user['User']['email'];
$this->Email->subject = 'New Password for '.Configure::read('Settings.app.name');
$this->Email->replyTo = Configure::read('Settings.app.email');
$this->Email->from = Configure::read('Settings.app.name').' <'.Configure::read('Settings.app.email').'>';
$this->Email->template = 'password';
$this->Email->sendAs = 'both';
$this->Email->delivery = Configure::read('Settings.smtp.delivery');
$this->Email->smtpOptions = array(
'port'=> 25,
'host' => 'localhost',
'timeout' => 30,
'username' => Configure::read('Settings.smtp.username'),
'password' => Configure::read('Settings.smtp.password'),
);
$this->set(compact('user'));
if (!$this->Email->send()) {
debug($this->Email);
$this->_flash(__('Email could not be sent.', true),'error');
$error = true;
}
}
if (!$error) {
$this->_flash(__(sprintf('New password has been sent to %s.',$user['User']['email']), true),'success');
$this->redirect(array('action'=>'login'));
}
$this->login();
$this->render('login');
}
//
// ADMIN ACTIONS
//
function admin_index() {
if (!isset($this->passedArgs['sort'])) {
$this->paginate['order'] = array('User.id'=>'desc');
}
// keywords
if(isset($this->passedArgs['keywords'])) {
$keywords = $this->passedArgs['keywords'];
$this->paginate['conditions'][] = array(
'OR' => array(
'User.id' => $keywords,
'User.username LIKE' => "%$keywords%",
'User.email LIKE' => "%$keywords%",
)
);
$this->data['Search']['keywords'] = $keywords;
}
// username
if(isset($this->passedArgs['username'])) {
$this->paginate['conditions'][]['User.username LIKE'] = str_replace('*','%',$this->passedArgs['username']);
$this->data['Search']['username'] = $this->passedArgs['username'];
}
// email
if(isset($this->passedArgs['email'])) {
$this->paginate['conditions'][]['User.email LIKE'] = str_replace('*','%',$this->passedArgs['email']);
$this->data['Search']['email'] = $this->passedArgs['email'];
}
// filter by group_id
if (isset($this->passedArgs['group_id'])) {
$this->User->bindModel(array(
'hasOne' => array(
'GroupToUser' => array(
'className' => 'GroupToUser',
'foreign_key' => 'user_id',
),
),
),false);
$this->paginate['conditions'][]['GroupToUser.group_id'] = $this->passedArgs['group_id'];
$this->data['Search']['group_id'] = $this->passedArgs['group_id'];
}
// load the data
$users = $this->paginate();
// set related data
$groups = $this->User->Group->find('list');
$this->set(compact('users','groups'));
}
function admin_search
var $name = 'Users';
var $components = array('Email');
var $multiActions = array(
'multi_delete',
);
function beforeFilter() {
parent::beforeFilter();
$this->Auth->allow('register','password');
if (isset($this->passedArgs['key'])) {
$this->Auth->allow('account');
}
}
//
// PUBLIC ACTIONS
//
function index() {
$title = 'My Account';
// set breadcrumb
$breadcrumb = array();
$breadcrumb[] = array(
'name' => 'Home',
'href' => '/',
);
$breadcrumb[] = array(
'name' => $title,
);
$this->set(compact('title','breadcrumb'));
}
function account() {
// load account from key
if (isset($this->passedArgs['key'])) {
$this->recursive = -1;
$user = $this->User->findByPassword($this->passedArgs['key']);
if (!$user) {
$this->_flash(__('Invalid Key.', true),'error');
$this->redirect(array('action'=>'index'));
}
$this->Auth->login($user);
}
// load account from auth user
$id = $this->Auth->user('id');
if (!$id) {
$this->_flash(__('Invalid User.', true),'error');
$this->redirect(array('action'=>'index'));
}
// process submit
if (!empty($this->data)) {
$error = false;
$this->User->create();
$this->User->id = $id;
if ($this->data['User']['password_change']) {
$this->data['User']['password'] = $this->Auth->password($this->data['User']['password_change']);
}
if ($this->User->save($this->data,true,array('email','username','password','password_change','password_confirm'))) {
$this->_flash(__('Your Account has been saved.', true),'success');
$this->redirect(array('action'=>'index'));
}
$this->_flash(__('Your Account could not be saved. Please, try again.', true),'warning');
}
if (empty($this->data)) {
$this->data = $this->User->read(null, $id);
}
$title = 'Modify Details';
// set breadcrumb
$breadcrumb = array();
$breadcrumb[] = array(
'name' => 'Home',
'href' => '/',
);
$breadcrumb[] = array(
'name' => 'My Account',
'href' => array('controller'=>'users','action'=>'index'),
);
$breadcrumb[] = array(
'name' => $title,
);
$this->set(compact('title','breadcrumb'));
}
function register() {
$title = 'Register';
// process submit
if (!empty($this->data)) {
if (!$this->data['User']['password_confirm']) {
$this->data['User']['password_confirm']=' ';
}
$this->data['User']['password'] = $this->Auth->password($this->data['User']['password_change']);
$this->data['User']['active'] = 1;
$this->User->create();
if ($this->User->save($this->data,true,array('email','username','active','password','password_change','password_confirm'))) {
$this->Auth->login($this->data);
$this->_flash(__('Your Account has been created.', true),'success');
$this->redirect(array('action'=>'index'));
}
$this->_flash(__('Your Account could not be created. Please, try again.', true),'warning');
}
// set breadcrumb
$breadcrumb = array();
$breadcrumb[] = array(
'name' => 'Home',
'href' => '/',
);
$breadcrumb[] = array(
'name' => 'My Account',
'href' => array('controller'=>'users','action'=>'index'),
);
$breadcrumb[] = array(
'name' => $title,
);
$this->set(compact('title','breadcrumb'));
}
function login() {
$title = 'Login';
// set breadcrumb
$breadcrumb = array();
$breadcrumb[] = array(
'name' => 'Home',
'href' => '/',
);
$breadcrumb[] = array(
'name' => 'My Account',
'href' => array('controller'=>'users','action'=>'index'),
);
$breadcrumb[] = array(
'name' => $title,
);
$this->set(compact('title','breadcrumb'));
}
function logout() {
$this->Session->delete('Auth.GroupUser');
$this->redirect($this->Auth->logout());
}
function password() {
$error = false;
if (!$error) {
if (!isset($this->data['User']['lost_password']) || !$this->data['User']['lost_password']) {
$this->_flash(__('Please enter an email address or username.', true),'warning');
$error = true;
}
}
if (!$error) {
$this->User->recursive = -1;
$user = $this->User->findByUsername($this->data['User']['lost_password']);
if (!$user) {
$user = $this->User->findByEmail($this->data['User']['lost_password']);
}
if (!$user) {
$this->_flash(__('Account not found.', true),'error');
$error = true;
}
}
if (!$error) {
$this->Email->to = $user['User']['email'];
$this->Email->subject = 'New Password for '.Configure::read('Settings.app.name');
$this->Email->replyTo = Configure::read('Settings.app.email');
$this->Email->from = Configure::read('Settings.app.name').' <'.Configure::read('Settings.app.email').'>';
$this->Email->template = 'password';
$this->Email->sendAs = 'both';
$this->Email->delivery = Configure::read('Settings.smtp.delivery');
$this->Email->smtpOptions = array(
'port'=> 25,
'host' => 'localhost',
'timeout' => 30,
'username' => Configure::read('Settings.smtp.username'),
'password' => Configure::read('Settings.smtp.password'),
);
$this->set(compact('user'));
if (!$this->Email->send()) {
debug($this->Email);
$this->_flash(__('Email could not be sent.', true),'error');
$error = true;
}
}
if (!$error) {
$this->_flash(__(sprintf('New password has been sent to %s.',$user['User']['email']), true),'success');
$this->redirect(array('action'=>'login'));
}
$this->login();
$this->render('login');
}
//
// ADMIN ACTIONS
//
function admin_index() {
if (!isset($this->passedArgs['sort'])) {
$this->paginate['order'] = array('User.id'=>'desc');
}
// keywords
if(isset($this->passedArgs['keywords'])) {
$keywords = $this->passedArgs['keywords'];
$this->paginate['conditions'][] = array(
'OR' => array(
'User.id' => $keywords,
'User.username LIKE' => "%$keywords%",
'User.email LIKE' => "%$keywords%",
)
);
$this->data['Search']['keywords'] = $keywords;
}
// username
if(isset($this->passedArgs['username'])) {
$this->paginate['conditions'][]['User.username LIKE'] = str_replace('*','%',$this->passedArgs['username']);
$this->data['Search']['username'] = $this->passedArgs['username'];
}
if(isset($this->passedArgs['email'])) {
$this->paginate['conditions'][]['User.email LIKE'] = str_replace('*','%',$this->passedArgs['email']);
$this->data['Search']['email'] = $this->passedArgs['email'];
}
// filter by group_id
if (isset($this->passedArgs['group_id'])) {
$this->User->bindModel(array(
'hasOne' => array(
'GroupToUser' => array(
'className' => 'GroupToUser',
'foreign_key' => 'user_id',
),
),
),false);
$this->paginate['conditions'][]['GroupToUser.group_id'] = $this->passedArgs['group_id'];
$this->data['Search']['group_id'] = $this->passedArgs['group_id'];
}
// load the data
$users = $this->paginate();
// set related data
$groups = $this->User->Group->find('list');
$this->set(compact('users','groups'));
}
function admin_search
