In Cake 2.0.5 when logging in using the Auth component, it would seem Cake is retrieving all related models; and with many associations, logging in takes a long time.
This problem was first identified here in this ticket but the "solution" given doesn't mean a lot, and I can't find anything else in the documentation.
Using the FormAuthenticate class in 2.0 you can subclass and add
whatever recursive level you feel is appropriate fairly easily.
Has anyone experienced this, and have a fix?
Below - sample code:
Standard login method:
public function login() {
$this->User->recursive = -1; // does nothing
if ($this->Auth->login()) {
$this->redirect($this->Auth->redirect());
} else {
$this->Session->setFlash('Invalid username or password.');
}
}
And the query cake is producing for my app:
SELECT `User`.`id`, `User`.`username`, `User`.`password`, `User`.`role`, `User`.`created`, `User`.`modified`, `Band`.`id`, `Band`.`name`, `Band`.`genre`, `Band`.`location`, `Band`.`influences`, `Band`.`founded`, `Band`.`bio`, `Band`.`created`, `Band`.`modified`, `Band`.`status`, `Band`.`website`, `Band`.`email`, `Band`.`contact_number`, `Band`.`user_id`, `Member`.`id`, `Member`.`user_id`, `Member`.`first_name`, `Member`.`last_name`, `Member`.`display_name`, `Member`.`dob`, `Member`.`gender`, `Member`.`bio`, `Member`.`influences`, `Member`.`band_id` FROM `users` AS `User` LEFT JOIN `bands` AS `Band` ON (`Band`.`user_id` = `User`.`id`) LEFT JOIN `members` AS `Member` ON (`Member`.`user_id` = `User`.`id`) WHERE `User`.`username` = 'admin' AND `User`.`password` = 'dcec839a9258631138974cbccd81219f1d5dfcfa' LIMIT 1
As you can see it's retrieving every field, and joining every model. My app only has 2 additional associations, but you can see how this might be an issue with very complex apps.
When really, it should just be the users table. Setting recursive appears to do absolutely nothing.
You can use recursive option for Auth component.
public $components = array(
'Auth' => array(
'authenticate' => array(
'Form' => array('recursive' => -1)
)
)
or in beforeFilter method:
$this->Auth->authenticate = array('Form' => array('recursive' => -1));
What Mark is suggesting is to extend the FormAuthenticate class, or essentially override it.
Create a new file app/Controller/Component/Auth/ExtendedFormAuthenticate.php
This is the basic structure of the code - I've left in the important bit where the recursive level is set in the _findUser method:
App::uses('FormAuthenticate', 'Controller/Component/Auth');
class ExtendedFormAuthenticate extends FormAuthenticate
{
public function authenicate(CakeRequest $request, CakeResponse $response) {
// foo
}
protected function _findUser($username, $password)
{
// bar
$result = ClassRegistry::init($userModel)->find('first', array(
'conditions' => $conditions,
'recursive' => -1
));
// fooBar
}
}
I've created a Gist with the whole lot in: https://gist.github.com/1565672
Oh, almost forgot, you'll need to setup the AuthComponent to use the extended class.
public $components = array(
'Auth'=> array(
'authenticate' => array(
'ExtendedForm'
)
),
);
What about using Containable and override find at model?
Modifying Containable fields required in beforeFind callback?
Related
I'm building an application using CakePHP and trying to incorporate a custom authentication object but it does not seem to be able to find it. I get the following error when I try to log in: "Authentication adapter "LdapAuthorize" was not found". I have created the file app/Controller/Component/Auth/LdapAuthorize.php with my code for my authentication. Near the top of "AppController.php" I have
App::uses('LdapAuthroize', 'Controller/Component/Auth/LdapAuthorize');
and within the AppController class I have
public $components = array(
'Session',
'Auth' => array(
'loginRedirect' => array('controller' => 'pendings', 'action' => 'index'),
'logoutRedirect' => array('controller' => 'users', 'action' => 'login'),
'authorize' => array('Controller'),
'authenticate' => array('LdapAuthorize')
)
);
and then in my UsersController.php I have the following login function.
public function login() {
if($this->request->is('post')) {
if($this->Auth->login()) {
// My Login stuff...
}
else
$this->redirect(array('controller'=>'someController', 'action'=>'someAction'));
}
}
If anyone has any idea why it can't seem to load my custom authentication object that would be awesome. Thanks!
I put my custom authentication class inside Controller/Component/Auth. For example, the name of my class is CustomUserAuthenticate and the path to the file is,
Controller/Component/Auth/CustomUserAuthenticate.php.
Then in my AppController I added the following to the authenticate array,
class AppController extends Controller {
public $components = array(
'Auth' => array(
/** Any other configuration like redirects can go here */
'authenticate' => array(
'CustomUser'
)
)
);
}
The string in the authenticate array must match the name of the class except for the Authenticate word.
My CustomUserAuthenticate class extends CakePHP's Controller/Component/Auth/BaseAuthenticate and overrides the authenticate method. CakePHP's documentation states that this is not required. I haven't tried that way.
I think your App::uses() is wrong so it can't find the class. Your current code:
App::uses('LdapAuthroize', 'Controller/Component/Auth/LdapAuthorize');
Is trying to find Controller/Component/Auth/LdapAuthorize/LdapAuthroize.php
The first parameter is the class name (you have a typo with that), the second is just the path to the directory containing the class, you don't need to add the class name again.
Try this:
App::uses('LdapAuthorize', 'Controller/Component/Auth');
After setting up the simple Cakephp login concept I would like to let CakePHP use a different table to check the users and the login. I can't figure out how to change the table name within the Auth-component.
Below my basic Controller. How can I let Cakephp know it has to look into a different database table?
class AppController extends Controller {
public $components = array(
'Session',
'Auth'=>array(
'loginRedirect'=>array('controller'=>'users', 'action'=>'index'),
'logoutRedirect'=>array('controller'=>'users', 'action'=>'index'),
'authError'=>"You can't access that page",
'authorize'=>array('Controller')
)
);
public function isAuthorized($user) {
return true;
}
public function beforeFilter() {
//$this->Auth->allow('index', 'view');
$this->set('siteCategory', 'home');
$this->set('logged_in', $this->Auth->loggedIn());
$this->set('current_user', $this->Auth->user());
}
}
Best Solution:
This is something that's done in the User model with the useTable property.
I.e. in app/Model/User.php you should have something like this:
class User extends AppModel {
public $useTable = 'table_name';
//... rest of Model stuff here
}
Alternative:
Alternatively you can specify a different model to be used for the user, although I don't think that's what you're asking for. If I'm wrong there though, just set the userModel value like this:
public $components = array(
'Auth'=>array(
'authenticate'=>array(
'Form' => array('userModel' => 'ADifferentUserModel')
)));
Since a long time as per my knowledge I was using recursive to control my model relationship. If I make any relation between my models it would surely be autoconnected with paginate. To control that I need to use recursive. By default its value is 1 and to contro; that I have to use it as -1 or 0. Yes I read about Containable behaviour that how it automatically control fetching result from other Models Though relationships are made.
I went through same as writing
public $actsAs = array('Containable');
In my controller I wrote
$this->Album->Behaviors->load('Containable', array('autoFields' => false, 'recursive'=>false));
But then also my default paginate called data from other Model as well as fetch queries with other Models.
$this->paginate['Album'] = array('conditions' => $condition, 'limit' => '50', 'order' => array('Album.id' => 'DESC'));
$this->set('albums', $this->paginate('Album'));
My default pagination code as per my expectation data would be only from Album Model and to get from other Model I have to describe it in Pagination but when I checked it in Debug Kit it shows this.
As well as fetch data from all variables.
What should I do ?? Where I am wrong ??
You don’t want the data of related models, correct me if I’m wrong.
For this you need to set contain property to false. This will only bring the data of Album model
$this->paginate['Album'] = array('conditions' => $condition,'contain' => false 'limit' => '50', 'order' => array('Album.id' => 'DESC'));
$this->set('albums', $this->paginate('Album'));
Contain will be helpful when you want to attach multiple model with your query like
$this->paginate['Album'] = array('conditions' => $condition,'contain' => array('model1','model2'), 'limit' => '50', 'order' => array('Album.id' => 'DESC'));
I hope this will work for you. Thanks
Even if your model is set to be containable, you can still set the recursive to false to prevent the fetching of associated data.
Sample model:
<?php
class Article extends AppModel {
public $actsAs = array('Containable');
public $belongsTo = array('Category');
}
Sample controller:
<?php
class ArticlesController extends AppController {
public function index() {
$this->Article->recursive = 0;
$this->set('articles', $this->paginate('Article'));
}
public function view($id) {
$this->Article->recursive = 0;
$this->set('article', $this->Article->findById($id));
}
}
after I tried finding a solution using Google's search, I still haven't found anything which helped me. The problem is simple, I want to use another Model for the user authentication. The way the manual shows us does, somehow, not work.
My AppController looks like the follow:
public $components = array(
'Auth',
'DebugKit.Toolbar'
);
public function beforeFilter()
{
parent::beforeFilter();
if (isset($this->request->params["intranet"]) && $this->request->params["intranet"] == 1) {
$this->Auth = array(
"loginAction" => array(
"intranet" => true,
"controller" => "employees",
"action" => "login"
),
"authenticate" => array(AuthComponent::ALL => array("userModel" => "Employee"))
);
$this->layout = "intranet";
}
}
It does not matter what url I open, CakePHP always redirects me to /users/login. Of course I run parent::beforeFilter() in the Controllers.
Edit: Okay seems like I missunderstand userModel, loginAction seem to be the right keyword here, but after I changed it to array("controller" => "employees", "action" => "login") it still redirects me to /users/login...
Oh my god I'm so dumb... I guess the most programmers already have a facepalm after reading this, but for the newer CakePHP/PHP developers who may struggle with the same problem:
In the code above, I override $this->Auth with the array. The solution:
$this->Auth->loginAction = array(
"intranet" => true,
"controller" => "employees",
"action" => "login"
);
$this->Auth->authenticate = array(AuthComponent::ALL => array("userModel" => "Employee"));
I have a Cake website and it needs to have two separate logins, each one will have their own login form and see different pages, it would be nice to have two different tables because there are no similarities between the two types of people.
Each login form will only be used by certain people and they will never login to the other form, and vice versa.
Also, the two login tables have a relationship between them, which requires 2 tables?
Is this possible?
First, add a couple of empty custom authenticate objects. We'll reuse the same logic that FormAuthenticate uses (that is, uses POST data to check the database for a user), but simply change the model within the object settings (later).
app/Controller/Component/Auth/ModelOneAuthenticate.php
<?php
App::uses('FormAuthenticate', 'Controller/Component/Auth');
class ModelOneAuthenticate extends FormAuthenticate {
}
app/Controller/Component/Auth/ModelTwoAuthenticate.php
<?php
App::uses('FormAuthenticate', 'Controller/Component/Auth');
class ModelTwoAuthenticate extends FormAuthenticate {
}
Then tell your app to use these objects to authenticate, and tell it what model to use. You can also customize the fields here. In your AppController:
public $components = array(
'Auth' => array(
'authenticate' => array(
'ModelOne' => array(
'userModel' => 'ModelOne',
'fields' => array(
'username' => 'my_custom_username_field',
'password' => 'some_password_field'
)
),
'ModelTwo' => array(
'userModel' => 'ModelTwo'
)
)
)
);
The first authentication object would check the model_ones table for a username in my_custom_username_field and password in some_password_field, while the second one would check model_twos using the standard username and password fields.
The simplest way to do this is to just set a different session key for each login type:
if ($loginTypeOne) {
$this->Auth->authenticate = array(
'Form'=> array(
'userModel'=> 'TypeOne',
)
);
AuthComponent::$sessionKey = 'Auth.TypeOne';
} else {
$this->Auth->authenticate = array(
'Form'=> array(
'userModel'=> 'TypeTwo',
)
);
AuthComponent::$sessionKey = 'Auth.TypeTwo';
}
When they have to login there is a similarity: Both will require it to enter credentials, usually an username/email and password. So a users table and a foo_profiles table and a bar_profiles table depending on the user type should work also.
If you really want to go with two total different tables and the MVC stack for them, then simply use two different controllers FooUsers and BarUsers and inside of each create a customized login method.
I have done this previously by writing custom Authentication components that extend from BaseAuthenticate. As long as they implement the authenticate() method then you'll be able to do whatever you want to each of the different types of user.
In your AppController you need to register the different components by doing something like
public $components = array(
"Session",
"Auth" => array(
'authenticate' => array("UserType1", "UserType2"),
)
);
Check out the cookbook for the rest of it.
You can have a look on this.
Define Model for both login member and then define table which you want to use for the user.
set variable in model.
class SearchedCategory extends AppModel {
var $name = 'SearchedCategory';
Var useTable = 'give your table name here.';
var $primaryKey = 'id';
}