I'm trying to create an app where the authorization part is done by checking the input criteria against LDAP, on CakePHP. I'm following the text linked in this page but I'm having issues. I don't want to use any users table, I have no interest in storing the data. All I want to do, is grant the access to the users in case they have any LDAP credentials. I managed to estabilish the connection and check the data, but the app won't save the session, so the user is never logged in. Do you have any clue on what am I doing wrong?
This is the login function:
public function login() {
/*if($this->request->is('post')) {
if ($this->Auth->login()) {
$this->redirect($this->Auth->redirect());
} else {
$this->Session->setFlash("Invalid data provided");
}
}*/
App::import('Lib', 'ldap');
if ($this->Session->read('Auth.User')) {
$this->redirect(array('controller' => 'allocations', 'action' => 'index'));
} elseif (!empty($this->data)) {
$ldap = new ldap;
if ($ldap->auth($this->Auth->request->data['User']['username'], $this->Auth->request->data['User']['password'])) {
/*$userrow = $this->User->findByUsername($this->data['User']['username']);
if (!$userrow) {
$ldap_info = $ldap->getInfo($this->data['User']['username']);
$this->data['User']['user'] = $this->data['User']['username'];
$this->data['User']['name'] = $ldap_info['name'];
$this->data['User']['group_id'] = 3; //sets the default group
$this->add();
$userrow = $this->User->findByUsername($this->data['User']['user']);
}
$user = $userrow['User'];*/
$user = array()
$this->Auth->Session->write($this->Auth->sessionKey, $user);
$this->Auth->_loggedIn = true;
$this->redirect($this->Auth->redirect());
$this->Session->setFlash('You are logged in!');
} else {
$this->Session->setFlash(__('Login Failed', true));
}
}
}
I tried debugging the ldap->auth (which basically calls an ldap_bind function stored in an external library) and the result of the auth is correct (the method returns 'true'). So the problem has to be in the creation of the session. How do I make CakePHP know that the user has been correctly logged in and that it has to store the session? Thanks!
you can write user data from ldap into session by
$this->Session->write('Auth.User', array(
'id' => 1,
'username' => 'admin'
));
I use code above to set user in session. it's work with AuthComponent.
you can check/get user detail by AuthComponent::user()
Related
I have two tables in my database, one for admins (named Admins) and the other one for normal users, named : utilisateurs (in french). I know i have to use cakePHP's convention which says i must create a table named users, which has the fields username and password. But the problem is that i have two tables and not only one. Each one has its particular fields, so i really need them to stay separated. And even informations required to login are different depending on the user :
admins need their login + password
normal users need a specific id (in their id cards) + password
What i want to do, is create two login pages, one for admins and the other for normal user. After logging in, the user is redirected to the page he is supposed to see. But yet, if the user tries attempt a forbidden location. I want be able to stop him (beforeFilter + isAuthorized i think)
How can i make all this work ?
I'm not beginner in cakephp, i've already made an authentication system in anotehr app using Auth Component, but it was a little easier because needed only one table for users.
Your help would be much appreciated.
Assuming the following:
You have 2 tables associated with model User and Admin, where:
User has idcard and password fields.
Admin has login and passowrd field.
The passwords are hashed using User::hasPassword and Admin::hasPassword functions.
You login form is created as follows:
echo $this->Form->create(null, '') ;
echo $this->Form->input('login') ;
echo $this->Form->input('password') ;
echo $this->Form->end(__('Submit')) ;
You can create a new Authenticate component under App/Controller/Component/Auth/MyAuthenticate.php:
<?php
App::uses('FormAuthenticate', 'Controller/Component/Auth');
class MyAuthenticate extends FormAuthenticate {
public function authenticate(CakeRequest $request, CakeResponse $response) {
$username = $request->data['login'] ;
$password = $request->data['password'] ;
App::import('Model', 'User') ;
$userModel = new User () ;
/* Try to authenticate as a user... */
$user = $userModel->find('first', array(
'conditions' => array(
'idcard' => $username,
'password' => User::hashPassword($password) ;
)
)) ;
if ($user) {
$user = $user['User'] ; // Get only useful info
$user['type'] = 'user'; // Save user type
return $user ;
}
/* Same thing for admin. */
App::import('Model', 'Admin') ;
$adminModel = new Admin () ;
$user = $adminModel->find('first', array(
'conditions' => array(
'login' => $username,
'password' => Admin::hashPassword($password) ;
)
)) ;
if ($user) {
$user = $user['Admin'] ; // Get only useful info
$user['type'] = 'admin'; // Save user type
return $user ;
}
return null ;
}
};
You just need to be sure that that a admin cannot be authenticated as a user, and reverse.
In your AppController:
public $components = array(
'Auth' => array(
'authenticate' => array('My'), // The prefix in front of your component
'loginAction' => array(/* ... */),
'loginRedirect' => array(/* ... */),
'logoutRedirect' => array(/* ... */),
'authError' => "...",
'authorize' => 'Controller'
)
) ;
Your login action is the same that for normal tables:
public function login () {
if ($this->request->is('post')) {
if ($this->Auth->login()) {
$this->redirect($this->Auth->redirect());
}
else {
$this->request->data['password'] = "" ;
$this->Session->setFlash('Invalid login/id or password.');
}
}
}
Then, in beforeFilter or isAuthorized you can check $this->Auth->user('type');. For example, in AppController:
public function isAuthorized () {
/* Assuming you keep CakePHP convention where action starting with admin_ set admin params. */
if(isset($this->params['admin'])) {
return $this->Auth->user('type') == 'admin' ;
}
return true ;
}
Or if you want to disable access for non admin users to all action in AdminController, use beforeFilter:
class AdminController extends AppController {
public function beforeFilter () {
if (!$this->Auth->loggedIn()) {
$this->Session->setFlash('You need to be logged to access this page.');
$this->redirect(/* Login url. */) ;
return ;
}
if ($this->Auth->user('type') != 'admin') {
$this->Session->setFlash('You need to be admin to access this page.');
$this->redirect(/* Somewhere... */) ;
return ;
}
return parent::beforeFilter () ;
}
}
In my app I have 3 roles of Users (user, admin, non logged user), and I want to redirect them to different pages when access is denied. How to do that?
In time, what $this->Auth->authorize = array('Controller'); means? I didn't understand this in cake docs.
Thanks all.
I would have it in a IF statement, but perhaps someone else can suggest a more CakePHP specific method..
if($user === 'Admin') {
//Admin Redirect
$redirectController = 'admin';
$redirectMethod = 'admin_index';
} elseif ($user === 'User') {
//User Redirect
$redirectController = 'user';
$redirectMethod = 'index';
} else {
//Not logged in
$redirectController = 'SomeController';
$redirectMethod = 'someMethod';
}
$this->Auth->unauthorizedRedirect = array(
'controller' => $redirectController,
'action' => $redirectMethod
);
$this->Auth->authorize = array('Controller'); means that the Authorization is done at the Controller level. I believe you can change where authorization is carried out using this.
I have a user front end and an admin area. If a user is signed in and trys to go to the to the admin url they are redirected to the index page. I wish to redirect them to the admin login page with a message to login as administrator.
There may be a case where a admin is logged in as a user and then trys to login into the admin area. I have not been able to rediect to the admin login and give option to log out and log in as admin.
app_controller
function beforeFilter() {
$this->Auth->loginError = "Wrong credentials";
$this->Auth->authError = "This part of the website is protected.";
//Configure AuthComponent
$this->Auth->allow('display');
$this->Auth->authorize = 'actions';
$this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
$this->Auth->logoutRedirect = array('controller' => 'users', 'action' => 'login');
//$this->Auth->autoRedirect = false;
//$this->Auth->loginRedirect = array('controller' => 'reservatins', 'action' => 'index');
} // end before filter
users_controller
function beforeFilter() {
parent::beforeFilter();
$this->Auth->allowedActions = array('admin_login','admin_logout');
//$this->Auth->allowedActions = array('*');
$this->set('select_nav', array('admin','users'));
}
function admin_login() {
// $this->layout = 'admin'; // nothing required
$this->layout = 'blank'; // nothing required
}
I have done that on one of my projects. The user is ever logged in (as Anonymous, as User or as Admin) and, depending on from where is he coming, and the current permissions he have, I show different login errors.
To do that.. this is what I did...
First, you need to use the "controller" authorize method:
$this->Auth->authorize = 'controller';
From now on, all your actions will pass through the isAuthorized method of your current controller. As I have my users, groups and permissions on my database and every group have different permissions, I created the isAuthorized method on my app_controller:
public function isAuthorized()
{
if ( !$this->__permitted($this->name, $this->action) )
{
$this->cakeError('error403');
return false;
}
return true;
}
What I'm doing here is checking for user permissions through my AppController __permitted method (it simply checks for permissions on session; if we don't have them saved in session, I check for them on the DB and then I store them on the Session).
If the user don't have permissions, I show him the error 403. And here is the funny part.
In your AppError add a method called error403, and here you can control where to redirect the user and what kind of message to show to him.
Here is the code I've used (obviously you must create your own piece of code according to your needs):
public function error403()
{
// Extract params
extract($this->controller->params, EXTR_OVERWRITE);
// Store url to be redirected on success
if (!isset($url))
{
$url = $this->controller->here;
}
if (isset($url['url']))
{
$url = $url['url'];
}
$url = Router::normalize($url);
// The page is trying to access is an admin page?
$is_admin_page = isset($this->controller->params['admin']) && $this->controller->params['admin'] == true ? true : false;
if (!empty($url) && count($url) >= 2)
{
$query = $url;
unset($query['url'], $query['ext']);
$url .= Router::queryString($query, array());
}
// 403 header
$this->controller->header("HTTP/1.0 403 Forbidden");
// If my method is NOT an upload
if (!preg_match('/upload/', $url))
{
// Write referer to session, so we can use it later
$this->controller->Session->write('Auth.redirect', $url);
}
else exit; // else exit, so we prevent 302 header from redirect
// NOTE: we can't use $this->controller->Auth->loginAction because there's no controller loaded
$loginAction = array('controller' => 'users', 'action' => 'login');
// If is ajax...
if (isset($this->controller->params['isAjax']) && $this->controller->params['isAjax'] == true)
{
$this->controller->layout = 'ajax';
$message = __("No tens permisos per fer aquesta acció", true);
// If user is anonymous..
if ( $this->controller->ISession->isAnonymous() )
{
// AJAX Error Message
$message = __('La teva sessió no està iniciada.', true)
. ' <a href="'.Router::url($loginAction).'">'
. __('Fes clic aquí per iniciar-la', true) . '</a>';
}
$this->controller->set(compact('message'));
$this->controller->render('error403');
$this->controller->afterFilter();
echo $this->controller->output;
}
else
{
$message = __("No tens permisos per fer aquesta acció", true);
$redirect = $this->controller->referer();
// If is anonymous...
if ($this->controller->ISession->isAnonymous())
{
$message = __('La teva sessió no està iniciada.', true);
$redirect = $loginAction;
}
// If user can't access the requested page, we redirect him to login
if (!$this->controller->ISession->userCan($redirect))
{
$redirect = $loginAction;
}
// Show different auth messages for admin and user pages
$this->controller->Session->setFlash($message, $is_admin_page ? 'default' : 'gritter', array(), 'auth');
$this->controller->redirect($redirect, null, true);
}
}
Remember, this is the code for my case. You should create your own error403 page according to your needs. Of course, you can start with my method to get it :)
Using CakePHP's Auth Component, how do I allow users to authenticate by using either their "username" or "email" field as a username, and a "pass" field as their password?
what does "using (username and email) both as username " mean?
Edit: ok, so you want Auth to look in both username and email fields in the db to compare to the "username" that the user enters? then do this:
function beforeFilter() {
parent::beforeFilter();
$this->Auth->fields = array('username' => 'username', 'password' => 'pass');
$this->Auth->autoRedirect = false;
}
function login(){
if ($this->Auth->user()) {
$this->redirect($this->Auth->redirect());
} else if (!empty($this->data)) {
$this->Auth->fields = array('username' => 'email', 'password' => 'pass');
$this->data['User']['email'] = $this->data['User']['username'];
if($this->Auth->login($this->data))$this->redirect($this->Auth->redirect());
}
}
To do this you have to skip Auths autoredirect and manage it yourself. This the login action in your users_controller:
public function login() {
if(!empty($this->data)) { // Submitted form
// Try to login with Email
if(!$this->Auth->user() // if user wasn't logged in with username + pass
&& !empty($this->Auth->data['User']['username'])
&& !empty($this->Auth->data['User']['password'])
) {
$user = $this->User->find('first', array(
'conditions' => array(
'User.email' => $this->Auth->data['User']['username'],
'User.password' => $this->Auth->data['User']['password']
),
'recursive' => -1
));
if(!empty($user) && $this->Auth->login($user)) {
// They logged in, so kill the flash error message
$this->Session->delete('Message.auth');
} else {
$this->Session->setFlash($this->Auth->loginError, $this->Auth->flashElement, array(), 'auth');
}
}
if($this->Auth->user()) {
// Post login logic here
$this->redirect($this->Auth->redirect());
}
} else {
if($this->Auth->user()) {
$this->Session->setFlash(__d('users', 'You are already registered and logged in!', true));
//$this->redirect('/');
$this->redirect($this->Auth->redirect());
}
}
This was copied straight from my app, so may need a bit of tweaking for yours. Don't forget to set $this->Auth->autoRedirect = false; in your AppController:beforeFilter();
You have to remember that Auth will automatically check against username and password, so this action just picks up from that. The Session::remove() call is to delete the Auth error message automatically left when the username/password check fails ANd the email login succeeds (otherwise you get error messages with successful logins).
I have a users controller, where if I add a user I want to redirect based on the usertype a user selects on making his account
the situation:
users table
id
name
usertype_id
The user add form has a select box for user type, I have two types of users: teachers and students (each another table, model, controller) if the user chooses teacher I want to redirect to /teachers/add/$id if the user chooses student I want to redirect to: /students/add/$id
this is what I have atm, but that doesn't work obviously
<?php
class UsersController extends AppController {
var $name = 'Users';
function add() {
if (!empty($this->data)) {
$this->User->create();
if ($this->User->save($this->data)) {
$id = $this->User->id;
if ($this->User->usertype_id=='1')
{
$this->redirect(array('students/add/'.$id));
} elseif ($this->User->usertype_id=='2') {
$this->redirect(array('teachers/add/'.$id));
} else {
$this->Session->setFlash(__('The user could not be saved. Please, try again.', true));
}
} else {
$this->Session->setFlash(__('The user could not be saved. Please, try again.', true));
}
}
$usertypes = $this->User->Usertype->find('list');
$this->set(compact('usertypes'));
}
}
?>
I'm pretty sure the problem is the assumption that, because $this->User->id exists, $this->User->usertype_id must also exist, which it does not. I ran into that issue when I first started working with CakePHP, too. :)
If the user type was passed via the add form, you need to check the data array:
Change
if ($this->User->usertype_id=='1')
To
if ($this->data['User']['usertype_id'] == '1')
If that doesn't work (I can't remember if $this->data is emptied after a successful save) then you should store the value prior to the save, like so:
function add() {
if (!empty($this->data)) {
$usertype_id = $this->data['User']['usertype_id'];
$this->User->create();
if ($this->User->save($this->data)) {
$id = $this->User->id;
if ($usertype_id == '1') {
$this->redirect(array('students/add/'.$id));
} elseif ($usertype_id == '2') {
// the rest remains the same
Addendum
Rather than using the concatenation in your redirect, This looks cleaner to me:
$this->redirect(array('controller' => 'teachers', 'action' => 'add', $id));
But I guess that's just preference.
Addendum 2
I have some additional advice about cleaning up your controller and moving all of the logic to the model. This way you can re-use the code from other controllers in the future, and your current controller will be easier to read. I would change the entire method to look like this:
// this is in /controllers/users_controller.php
function add() {
if (!empty($this->data)) {
$saved_user = $this->User->save_user($this->data);
if ($saved_user['success']) {
$this->redirect(array(
'controller' => $saved_user['controller'],
'action' => 'add',
$this->User->id
));
}
}
$this->Session->setFlash(__('The user could not be saved. Please, try again.', true));
$usertypes = $this->User->Usertype->find('list');
$this->set(compact('usertypes'));
}
// this is in your model, /models/user.php
function save_user($data) {
$this->create;
$usertype_id = $data['User']['usertype_id'];
return array(
'controller' => ($usertype_id == '2') ? 'teachers': 'students';
'success' => $this->save($data),
);
}