I have two cakephp2 applications running on same database, but having different Auth tables and different $this->Auth->userModel values accordingly.
Authentication works well and users from one app can't log into other.
BUT.. as apps uses same CAKEPHP session cookie, this happens:
when user from app 'one' logs in, it can access any Auth protected action in app 'two'!
I will probably use different user roles and cookie names.
But still, why Auth component is ignoring Auth->userModel settings when checking the session? Is there a way to configure it to work right in this situation?
Thanks in advance for any suggestions.
If not configured otherwise, AuthComponent will write the authenticated user record to the Auth.User session key in CakePHP 2. But it can be changed:
AuthComponent::sessionKey
The session key name where the record of the current user is stored. If unspecified, it will be "Auth.User".
(In CakePHP 1.3 this was different: Auth.{$userModel name})
So, if your apps share a Session, which they do, if cookie name and Security.salt match, the logged in record will be shared.
There are two possibilities to solve this:
Separate the logins
Simply set a different AuthComponent::sessionKey for your two models. This will allow them to keep the logged in user separately
Separate the sessions
Configure different Cookie names and Salts for both apps, so their sessions cannot override each other. This is probably the cleaner solution, because it also covers the risk of other session keys being double-used.
I have a similar issue which is why I've started a bounty on this question. Basically I have a public facing part of the application which lets users login from one table and an administrative part of the application which lets admins login using a different table. My AppController looks something like this:
public $components = array(
'Session',
'Auth' => array(
'autoRedirect' => false,
'authenticate' => array(
'Form' => array(
'userModel' => 'User'
)
),
'loginAction' => array('controller' => 'users', 'action' => 'login'),
'loginRedirect' => array('controller' => 'users', 'action' => 'overview'),
'logoutRedirect' => array('controller' => 'users', 'action' => 'loggedout')
)
);
and I have another AdminController where I have this:
public $components = array(
'Session',
'Auth' => array(
'authenticate' => array(
'CustomForm' => array(
'userModel' => 'Admin'
)
),
'loginAction' => array('controller' => 'admin', 'action' => 'login'),
'loginRedirect' => array('controller' => 'admin', 'action' => 'index'),
'logoutRedirect' => array('controller' => 'home', 'action' => 'index')
)
);
But as mentioned in this question, sessions from the two don't get along and overwrite each other. What's the best way to overcome this?
Extend the Model/Datasource/Session/DatabaseSession.php session handler with something like MyDatabaseSession and overwrite the write and read methods. Maybe simply copy the existing code of both methods and add something like
'app_id' => Configure::read('App.appId')
to the read() conditions and do the same in the write method. And do not forget to add the field to your session database schema and to configure the session to use your handler.
<?php
App::uses('DatabaseSession', 'Model/Datasource/Session');
class ExtendedDatabaseSession extends DatabaseSession {
public function read($id) {
$row = $this->_model->find('first', array(
'conditions' => array(
'app_id' => Configure::read('App.appId'),
$this->_model->primaryKey => $id)));
if (empty($row[$this->_model->alias]['data'])) {
return false;
}
return $row[$this->_model->alias]['data'];
}
public function write($id, $data) {
if (!$id) {
return false;
}
$expires = time() + $this->_timeout;
$record = compact('id', 'data', 'expires');
$record[$this->_model->primaryKey] = $id;
$record['app_id'] = Configure::read('App.appId');
return $this->_model->save($record);
}
}
I do not know your app, so were you write the app id to the config data is up to you, bootstrap or beforeFilter() maybe. You should add it before the session gets initialized I think or you'll need to re-init the session or something. I leave it up to you to look the right point up. :)
Related
In my CakePHP application, I have applied Url validations so that admin can access only those actions which are defined for admin and same as with users.
In my application, "surveylist" is the action of admin and when any user directly access that action(surveylist), URL validations work(Unauthorized access msg is displayed).
But below that message ctp file of surveylist executes forcefully and show errors because I have validated URL through the try-catch block and it cannot get the set variables of action.
I want that ctp file should not execute if unauthorize error comes.
My code for surveylist is:-
public function surveylist($pg=null){
try{
if($this->checkPageAccess($this->params['controller'] . '/' . $this->params['action'])){
$this->Paginator->settings = array(
'Survey' => array(
'limit' => 5,
'order' => 'created desc',
'conditions'=>array('is_deleted'=> 0),
'page' => $pg
)
);
$numbers = $this->Paginator->paginate('Survey');
$this->set(compact('numbers'));
}else{
$this->Flash->set(__('Unauthorised access'));
}
}catch(Exception $e){
$this->Flash->set(__($e->getMessage()));
}
}
I don't want the ctp file of surveylist to execute if control comes to else.
Plz, help me out......
Thanx in advance...
I suppose you are using prefix to separate admin and users, if not please do that it is great way to handle and restrict methods.
After doing that you have to make condition to check which prefix(admin, user) is currently active and according that load Auth component and allow action in allow() method of Auth.
Example:
$this->loadComponent('Auth',[
/*'authorize' => [
'Acl.Actions' => ['actionPath' => 'controllers/']
],*/
'loginRedirect' => [
'controller' => 'Users',
'action' => 'index'
],
'authenticate' => [
'Form' => [
'fields' => [
'username' => 'email',
'password' => 'password'
]
]
],
'loginAction' => [
'controller' => 'Users',
'action' => 'login'
],
'unauthorizedRedirect' => [
'controller' => 'Users',
'action' => 'login',
'prefix' => false
],
'authError' => 'You are not authorized to access that location.',
]);
if ($this->request->params['prefix']=='admin') {
// Put actions you want to access to admin in allow method's array
$this->Auth->allow(array('add', 'edit', etc...));
} else if ($this->request->params['prefix']=='user') {
// Put actions you want to access to user in allow method's array
$this->Auth->allow(array('login', 'view', etc...));
}
This way you can restrict actions for particular role.
Hope this helps!
I use Entrust before to control ACL in Laravel when my project is still Laravel 4, and now with Laravel 5.2 Entrust no longer work, especialy in route filtering.
And then I find this package and trying to use it, but still got a lot of question, so to make it more simple I will explain my use case when I use entrust:
First I want to make a permission for create, view, update and delete for article, in Entrust I will create permission like create_article, view_article, update_article and delete_article.
But now in Kodeine when I create permission there is "slug" so I tried to do this like in documentation say
$permUser = $permission->create([
'name' => 'article',
'slug' => [ // pass an array of permissions.
'create' => true,
'view' => true,
'update' => true,
'delete' => true
],
'description' => 'Manange article'
]);
So from what I read it will be just grouping all of my article permission into one place and there is slug with each parameters view, create, update, delete.
The problem I see is, if I want to make my users to only can view article, how to do that based on permission that I created up there?
Since from documentation the to assignPermission is only give permission name and that mean it will include all slug in there and it will be all true?
So if I want to make users only can view article I need to create something like
$permUser = $permission->create([
'name' => 'article_view',
'slug' => [ // pass an array of permissions.
'view' => true,
],
'description' => 'view article'
]);
And if I want to make users only can create article then I will mean I need to create
$permUser = $permission->create([
'name' => 'article_create',
'slug' => [ // pass an array of permissions.
'create' => true,
],
'description' => 'create article'
]);
then what's the point of slug - is it just pretty much the same like role but with parameter in slug?
As I wrote in your github issue, I suggest you to keep all your article permissions as you alredy have it, in one big group but all of them set to false (keep in mind that you may need to change your 'most_permissive_wins' variable in your acl config file). You can create a "child" group permission for your users role using Inheritance, setting to true all of those permissions your users need. You can then asign that child group to your users role (not the big one) and the user role tou your specific user. To clarify my answer, lets say you have this group:
$permArticles = $permission->create([
'name' => 'articles',
'slug' => [ // pass an array of permissions.
'create' => false,
'view' => false,
'update' => false,
'delete' => false,
],
'description' => 'All articles module permissions'
]);
then you can create something like:
$articlesPermUser = Permission::create([
'name' => 'articles.user',
'slug' => [ // an array of permissions only for student
'view' => true,
],
// we use permission inheriting.
'inherit_id' => $permArticles->getKey(),
'description' => 'user articles permissions'
]);
then you assign your new permission to your user role (I am assuming you alredy have a role name 'user'):
$userRole = Role::where('slug', 'user')->first();
$userRole->assignPermission('articles.user');
And finally you assign that role to... let say your logged user:
Auth::user()->assignRole($userRole);
You can also solve this problem by overwriting the permission, this could be done assigning a specific permission value to a user (but yes, you would need to do this for every single user in your app if needed, so I dont like this solution at all).
Lets say we keep our big group:
$permArticles = $permission->create([
'name' => 'articles',
'slug' => [ // pass an array of permissions.
'create' => false,
'view' => false,
'update' => false,
'delete' => false,
],
'description' => 'All articles module permissions'
]);
As this group says, any rol with your article permission assgined will not be able to do anything in your articles module. Lets say your user role alredy has this permission, but you want a certain user (lets say the logged one) be able to update an article. You can set the specific update permission value to true like so:
Auth::user()->addPermission('update.articles', true);
//or
Auth::user()->addPermission('articles', [
'update' => true,
]);
Thank you for the answer but over the time, I already find a perfect solution that match what I need. It is not much different from what I do in entrust.
So first I will just create a permission like this for view article
$class = 'article';
$permission = new Kodeine\Acl\Models\Eloquent\Permission();
$permUser = $permission->create([
'name' => $class.'_view',
'slug' => [
'view' => true,
],
'description' => 'View '.$class
]);
and then another one for example create article
$class = 'article';
$permission = new Kodeine\Acl\Models\Eloquent\Permission();
$permUser = $permission->create([
'name' => $class.'_create',
'slug' => [
'create' => true,
],
'description' => 'Create '.$class
]);
and later just assign those permission to user role, for example I want to make this user role to be can view article
$roleAdmin = Kodeine\Acl\Models\Eloquent\Role::where('name','=','user_1');
$roleAdmin->assignPermission('article_view');
I still don't understand about Inheritance feature, and I needed to do this quickly. It maybe not an ideal way, but it's works for me.
i am using " romanbican - bicon roles ", i don't see sufficient information for the " Creating Roles ",
code is available but i don't know where i paste this code, please suggest quick steps to implement permissions.
I used this same laravel package and simply created a new controller and route pointing to the the following controller methods:
public function getRoleAdmin()
{
$adminRole = Role::create([
'name' => 'Admin',
'slug' => 'admin',
'description' => 'System Administrator', // optional
'level' => 1, // optional, set to 1 by default
]);
}
public function getRoleModerator()
{
$moderatorRole = Role::create([
'name' => 'Forum Moderator',
'slug' => 'forum.moderator',
'description' => 'Forum Moderator',
'level' => 1,
]);
}
I then created a simple view with a button for each calling the appropriate route/controller/method to create either a new moderator or administrator role. You will see that calling Role::create simply creates a new record in the roles table with these attributes which you could easily perform with a standard DB call to insert into the table. I used the same approach for creating/deleting permissions.
I cannot understand how to choose which user data to save after login. I have noticed that I can only change the recursivity of the model, but I cannot choose individual fields to use.
For example, normally Cakephp saves in session all user fields except the password, even the data that I don't need and I do not want stored.
If I increase the recursion, Cakephp saves all the fields of related models.
Is there a way as for the "fields" parameter of the Model find method?
I know that after login I can recover the data that I miss and add them in session, merging to those already stored, but I want to avoid making another query and find a more elegant solution, if it exists.
Thanks.
As of Cake 2.2, you can add a contain key to your authentication options to pull related data. Since the contain key accepts a fields key, you can restrict the fields there:
public $components = array(
'Auth' => array(
'authenticate' => array(
'Form' => array(
'contain' => array(
'Profile' => array(
'fields' => array('name', 'birthdate')
)
)
)
)
)
);
If you want to change the fields the user model searches for, you can extend the authentication object you're using. Generally the users table contains a minimal amount of information, so this isn't usually necessary.
However, I'll give an example anyway. We'll use the FormAuthenticate object here, and use most of the _findUser method code from the BaseAuthenticate class. This is the function that Cake's authentication system uses to identify the user.
App::uses('FormAuthenticate', 'Controller/Component/Auth');
class MyFormAuthenticate extends FormAuthenticate {
// overrides BaseAuthenticate::_findUser()
protected function _findUser($username, $password) {
$userModel = $this->settings['userModel'];
list($plugin, $model) = pluginSplit($userModel);
$fields = $this->settings['fields'];
$conditions = array(
$model . '.' . $fields['username'] => $username,
$model . '.' . $fields['password'] => $this->_password($password),
);
if (!empty($this->settings['scope'])) {
$conditions = array_merge($conditions, $this->settings['scope']);
}
$result = ClassRegistry::init($userModel)->find('first', array(
// below is the only line added
'fields' => $this->settings['findFields'],
'conditions' => $conditions,
'recursive' => (int)$this->settings['recursive']
));
if (empty($result) || empty($result[$model])) {
return false;
}
unset($result[$model][$fields['password']]);
return $result[$model];
}
}
Then use that authentication and pass our new setting:
public $components = array(
'Auth' => array(
'authenticate' => array(
'MyForm' => array(
'findFields' => array('username', 'email'),
'contain' => array(
'Profile' => array(
'fields' => array('name', 'birthdate')
)
)
)
)
)
);
I just spent a while on this problem, only to find out that a 'userFields' option has been implemented as of Cake 2.6
Have a look at the docs here:
http://book.cakephp.org/2.0/en/core-libraries/components/authentication.html
I'm building a user panel, and having some problems with data validation. As an example, the page where you change your password (custom validation rule comparing string from two fields (password, confirm password)):
Route:
Router::connect('/profile/password', array('controller' => 'users', 'action' => 'profile_password'));
Controller:
function profile_password()
{
$this->User->setValidation('password'); // using the Multivalidatable behaviour
$this->User->id = $this->Session->read('Auth.User.id');
if (empty($this->data))
{
$this->data = $this->User->read();
} else {
$this->data['User']['password'] = $this->Auth->password($this->data['User']['password_change']);
if ($this->User->save($this->data))
{
$this->Session->setFlash('Edytowano hasło.', 'default', array('class' => 'success'));
$this->redirect(array('action' => 'profile'));
}
}
}
The problem is, that when I get to http://website.com/profile/password and mistype in one of the fields, the script goes back to http://website.com/users/profile_password/5 (5 being current logged users' id). When I type it correctly then it works, but I don't really want the address to change.
It seems that routes aren't supported by validation... (?) I'm using Cake 1.3 by the way.
Any help would be appreciated,
Paul
EDIT 1:
Changing the view from:
echo $form->create(
'User',
array(
'url' => array('controller' => 'users', 'action' => 'profile_password'),
'inputDefaults' => array('autocomplete' => 'off')
)
);
to:
echo $form->create(
'User',
array(
'url' => '/profile/password',
'inputDefaults' => array('autocomplete' => 'off')
)
);
does seem to do the trick, but that's not ideal.
Check the URL of the form in the profile_password.ctp view file.
Try the following code:
echo $this->Form->create('User', array('url' => array('controller' => 'users', 'action' => 'profile_password')));
Also, I think your form might be a little vulnerable. Try using Firebug or something similar to POST a data[User][id] to your action. If I'm right, you should be setting:
$this->data['User']['id'] = $this->Auth->user('id');
instead of:
$this->User->id = $this->Session->read('Auth.User.id');
because your id field is set in $this->data.
HTH.