CakePHP : Check authorizations in views - cakephp

I am using CakePHP in my project and I am looking for a proper way to check advanced user rights in my views.
I have several pages in which the contents depend of your rights (you can view some blocks or not, edit some infos or not, etc...)
I searched and the only way I found is to implement an Auth Helper, but I thought the best way to to that is to implement methods in my "UserController" (such as canPerformAction($action, $controller = 'default_controller')), am I wrong ? And if I'm right, how to call that methods properly ?
Thanks.
EDIT : More precisions
For example I have an action "editEventProducts" that a user can perform only if he's the event owner and if the event status is <= 2.
I check that in my controller "isAuthorized" function, works like a charm.
But I have a page called "eventDetails", form which you can perfom several actions such as this one, and I want to show the edit button, only if you can do it.
If fact what I need is the output of the "isAuthorized" function for each action that you can call, but can I properly get it from a view ?
Solution
I implemented a Auth helper who does several check such as this one, which is finally a whitelist check, depending of the status of my event, hope it will help, the code :
App::uses('AppHelper', 'View/Helper');
class AuthHelper extends AppHelper {
var $helpers = array('Session');
private $_whitelist = array(
'controller1' => array(
'events' => array(
'action1' => array(1 => true, 2 => true),
'action2' => array(1 => true, 2 => true),
'action3' => array(3 => true),
'action4' => array(6 => true)
)
),
'user' => array(
'controller1' => array(
'action1' => array(1 => true, 2 => true),
'action2' => array(1 => true, 2 => true)
)
)
);
public function canPerformAction ($action, $event_infos, $controller = 'events') {
return isset($this->_whitelist[$this->Session->read('Auth.User.role')][$controller][$action][$event_infos['Event']['state_id']]);
}
}

It sounds to me like you just want to render some parts of a view based on the permissions of the user. Well, in this case I think a helper is the right choice. The user should already have all the permissions he has loaded - except they're very fine grained and you got thousands of permissions.
Check this AuthHelper, it allows you to check if the user is logged in, for a role or a set of roles in a field. Alternatively implement your own solution to match whatever your permission system is.
Note that the helper relies on passing the user data to the view in a view variable. It can be also configured to read the data from the auth part of the session directly.
Here is the example taken from it's documentation:
if ($this->Auth->isLoggedIn()) {
echo __('Hello %s!', $this->Auth->user('username'));
}
if ($this->Auth->isMe($record['Record']['user_id']) {
// or your edit button here
echo '<h2>' . __('Your records') . '</h2>';
}
if ($this->Auth->hasRole('admin') {
echo $this->Html->link(__('delete'), array('action' => 'delete'));
}

What you need is called authorization, and is the process of granting/denying actions usually built on top of an authentication step, which maps HTTP requests to logical users.
The authorization scheme can be implemented in a number of ways, for example with simple role-based rules, where users are grouped exactly for the purpose of assigning rights, or with more complex ACL (access control lists). Both can be adopted at the same time for different parts of the system, depending on your needs.
Whatever scheme you pick, you absolutely need to query it at the beginning of your controllers actions (if applicable, you may and up with a standardized authorization filter in your AppController), because the HTTP request doesn't need to come from a previously sent HTTP page, but could be a (possibly) malicious, hand-craften one. Also, you'll likely need to adjust the UI after the user rights. Maybe you'll better start with a bunch of if statements, and then after some days of work you'll be able to identify your needs and build your libraries/helpers/blocks/whatever to avoid code duplication and easing reading the templates.

If you have predefined user permissions (like 'admin', 'moderator', 'editor', 'publisher'...) you can just read the user role and current action in the controller function isAuthorized and set it to true or false.
If you want custom permissions per user, you can store those values in the database, read them in the isAuthorized function and make your logic to determine if you should allow him or not.
My solution to this was a separate table user_permissions that was something like this:
user_id | action
where action would be `controller/action' or 'view/block' or whatever you want to save there.
I would read all values for current user in the controller and if the current controller/action was found in the array, i'd set isAuthorized to true. You can apply your logic to the blocks also.

You can call function of controller from view using
requestAction(string $url, array $options)
Or you can create your custom Helper which will do this for you!

Related

CakePHP 3.5 Auth use multiple tables

I have an Auth process which works fine with one userModel. But not only because of my DB schema I need to have one login method/action which works with multiple models.
So far I've tried everything I was able to think of or find online - for example editing this Cake 1.3 solution into Cake 3 and a few more hints I was able to find.
However, I'm not able to figure it out.
Thank you for any answer.
My AppController component load:
$this->loadComponent('ExtendedAuth', [
'authenticate' => [
'Form' => [
//'userModel' => 'Admins',
'fields' => [
'username' => 'email',
'password' => 'password'
]
]
],
'loginAction' => [
'controller' => 'Admins',
'action' => 'login'
],
// If unauthorized, return them to page they were just on
'unauthorizedRedirect' => $this->referer(),
]);
My ExtendedAuthComponent:
class ExtendedAuthComponent extends AuthComponent
{
function identify($user = null, $conditions = null) {
$models = array('Admins', 'Users');
foreach ($models as $model) {
//$this->userModel = $model; // switch model
parent::setConfig('authenticate', [
AuthComponent::ALL => [
'userModel' => $model
]
]);
$result = parent::identify(); // let cake do its thing
if ($result) {
return $result; // login success
}
}
return null; // login failure
}
}
EDIT1: Description of situation
I have two separate tables (Admins, Users). I need just one login action which tries to use Admins table prior to Users. Because of the application logic I can't combine them to one table with something like 'is_admin' flag. So basically what I need is instead of one specific userModel set in Auth config, I need a set of models. Sounds simple and yet I'm not able to achieve it.
EDIT2: Chosen solution
Based on the answer below, I decided to update my schema. Auth users table is just simplified table with login credentials and role and other role-specific fields are then in separate tables which are used as a connection for other role-specific tables. Even though the answer is not exactly a solution for the asked question, it made me think more about any possible changes of the schema and I found this solution because of it so I'm marking it as a solution. I appreciate all comments as well.
As Mark already said in a comment: Don't use two users tables. Add a type field or role or whatever else and associated data in separate tables if it's different like admin_profiles and user_profiles.
Don't extend the Auth component. I wouldn't recommend to use it anymore any way because it's going to get deprecated in the upcoming 3.7 / 4.0 release. Use the new official authentication and authorization plugins instead.
If you insist on the rocky path and want to make your life harder, well go for it but then you should still not extend the auth component but instead write a custom authentication adapter. This is the right place to implement your custom 2-table-weirdness. Read this section of the manual on how to do it.

Simplified and manageable ACL implementation in cakephp

I went through complete lesson on cakephp's ACL component, but gigantic ACL component do not seem to meet my very simple requirements.
I have only group based access control, three groups are users, managers and administrators the fourth is a anonymous users without logins for which I am not creating any group.
from acl concept it creates three table
aros -> this looks somewhat redundant data copied from groups table, I dont even need to have a group table but just field group_id in users table.
acos -> this is a list of public methods in controllers, I had to use AclExtra plugin to populate over 250+ actions in table, now this is the part which I think un-manageable, I noticed that tool used to populate acos table cannot reliably sync everytime when I do changes in controllers, the same work must be done at remote site for each changes that means terrible thing! this also mean i have to have a database backup during updates and migration.
Other side if I use php file based acos that is again un-manageable because we have to make sure syncing between controller and acl file.
aros_acos -> obviously
can we have a simpler mechanism something like i deny all actions using Auth component and then inside each action or maybe in beforeRender method i can specify what methods are open to what group ?
Thanks
There is an undocumented acl class PhpAcl it is much simpler to use than then the database driven ACL and more dynamic than the ini bassed ACL.
In Config/core.php
/**
* The class name and database used in CakePHP's
* access control lists.
*/
Configure::write('Acl.classname', 'PhpAcl');
// Configure::write('Acl.database', 'default');
This tells your ACL to use the PhpAcl
Then open up Config/acl.php
There are some good instructions there
Assumptions:
In your application you created a User model with the following properties: username, group_id, password, email, firstname,
lastname and so on.
You configured AuthComponent to authorize actions via $this->Auth->authorize = array('Actions' => array('actionPath' =>
'controllers/'),...)
Now, when a user (i.e. jeff) authenticates successfully and requests a
controller action (i.e. /invoices/delete) that is not allowed by
default (e.g. via $this->Auth->allow('edit') in the Invoices
controller) then AuthComponent will ask the configured ACL interface
if access is granted. Under the assumptions 1. and 2. this will be
done via a call to Acl->check() with
array('User' => array('username' => 'jeff', 'group_id' => 4, ...))
as ARO and
'/controllers/invoices/delete'
as ACO.
I wanted to use static names for Groups or Roles so you can add a role field to your user table, and then set up the $map like this:
**
* The role map defines how to resolve the user record from your application
* to the roles you defined in the roles configuration.
*/
$config['map'] = array(
'User' => 'User/username',
'Role' => 'User/role',
);
For my app we aren't using user based permissions only role, so we could remove the User from the $map.
Then you need to set up some roles:
/**
* role configuration
*/
$config['roles'] = array(
'Role/admin' => null,
);
Any role not in this array will get 'Role/default'
Now just set up your permissions, they are pretty self explanatory.
/**
* rule configuration
*/
$config['rules'] = array(
'allow' => array(
'*' => 'Role/admin',
'controllers/Reports/*' => 'Role/default',
'controllers/EurRates/*' => 'Role/default',
'controllers/Posts/index' => 'Role/default',
'controllers/Users/(edit|index)' => 'Role/default',
),
'deny' => array(
'controllers/ProtectedController/*' => 'Role/default',
'controllers/EurRates/(edit|add|delete)' => 'Role/default',
'controllers/Reports/(edit|add|delete)' => 'Role/default',
),
);
That's it, now you can allow or deny permission to actions based on role.

CakePHP: Scaffolding after having written edit/view/add

I have an application in which we give a very friendly interface for managing data. This is done through many controllers' add/edit/view functions. But now the requirement has come that we should have "super admins" able to edit anything, and scaffolding will give them a quick and dirty manner of changing data. Since scaffolding uses add/edit/view by default, I've unintentionally overwritten the ability to scaffold.
I can't just go and change all my calls to edit/add for our "user friendly" data managing. So I want to essentially ignore the add/edit/view when, for example, a user has a flag of "yes, please let me scaffold". I imagined it would be something like:
public function edit($id) {
if (admin_user) {
$scaffold;
} else {
[user-friendly version code]
}
}
But no dice. How can I achieve what I want?
suppose you already have admin users and you want to scaffold only super-user:
Also suppose you store the information about beeing a super-user or not in a column named super in the users table
in your core.php
Configure::write('Routing.prefixes', array('admin', 'super));
in your appController
public $scaffold = 'super';
beforFilter() {
if($this->Auth->user('super') && !isset($this->params['super'])
$this->redirect(array('super' => true));
}
Now I can't try this code but the idea should work.
edit: we need to check if we are already in a super_action to avoid infinite redirect

Clearing all prefixes

I have several prefixes in play in an existing CakePHP app. I also have a bit of primary navigation in the layout that points to shared methods. I know I can explicitly set each prefix to false to avoid linking with the prefix, but is there a shortcut path that simply tells Cake to no use any prefixes no matter which one's context may currently exist?
For example, I'm on a page where a realtor can register (/realtor/users/register). I have a similar prefix for inspectors and contractors because the registration process is slightly different. Since I'm not authenticated, there's a Login link in the primary nav, but the login action is shared by all user types and should be accessed without any prefix.
<?php echo $this->Html->link( 'Login', array( 'controller' => 'users', 'action' => 'login', 'realtor' => false, 'inspector' => false, 'contractor' => false ) ) ?>
I'd like to be able to, in the link, just turn off all prefixing rather than turning off each possible prefix independently. Possible?
I know it's been 2 years since the question above was answered, though I think I found an even less intrusive way to accomplish what you want.
Set the prefix name dynamically by taking the current prefix value from $this->params and set it to false, like so
$this->Html->link('hello', array($this->params['prefix']=>false, 'controller'=>'posts','action'=>'index'));
The value of $this->params['prefix'] will be the one and relevant at that moment to set to false.
cheers
If loosing the routing capabilities is not a problem for you, you could pass a string instead of an array to the link() method:
<?php
echo $this->Html->link('Login', '/users/login');
?>
EDIT
To keep routing mechanism, here is a little Helper that would do the trick:
class MyHtmlHelper extends HtmlHelper
{
public function link($title, $url = null, $options = array(), $confirmMessage = false)
{
$prefixes = Router::prefixes();
foreach($prefixes as $prefix)
{
$url[$prefix] = false;
}
return parent::link($title, $url, $options, $confirmMessage);
}
}
Off course you could change the method name if you want to keep the standard link() method. I tested this with Cake2, but this should work with Cake1.3

CakePHP strange behavior with beforeFilter: I cannot set the variables to the view

Okay, this will require some setup:
I'm working on a method of using nice post title "slugs" in the URL's of my cakePHP powered blog.
For example: /blog/post-title-here instead of /blog/view_post/123.
Since I'm obviously not going to write a new method for every post, I'm trying to be slick and use CakePHP callbacks to emulate the behavior of PHP 5's __call() magic method. For those who do not know, CakePHP's dispatcher checks to see if a method exists and throws a cakePHP error before __call() can be invoked in the controller.
What I've done so far:
In the interest of full disclosure ('cause I have no Idea why I'm having a problem) I've got two routes:
Router::connect('/blog/:action/*', array('controller' => 'blog_posts'));
Router::connect('/blog/*', array('controller' => 'blog_posts'));
These set up an alias for the BlogPostsController so that my url doesn't look like /blog_posts/action
Then in the BlogPostsController:
public function beforeFilter() {
parent::beforeFilter();
if (!in_array($this->params['action'], $this->methods)) {
$this->setAction('single_post', $this->params['action']);
}
}
public function single_post($slug = NULL) {
$post = $this->BlogPost->get_post_by_slug($slug);
$this->set('post', $post);
//$this->render('single_post');
}
The beforeFilter catches actions that do not exist and passes them to my single_post method. single_post grabs the data from the model, and sets a variable $post for the view.
There's also an index method that displays the 10 most recent posts.
Here's the confounding part:
You'll notice that there is a $this->render method that is commented-out above.
When I do not call $this->render('single_post'), the view renders once, but the $post variable is not set.
When I do call $this->render('single_post'), The view renders with the $post variable set, and then renders again with it not set. So in effect I get two full layouts, one after the other, in the same document. One with the content, and one without.
I've tried using a method named single_post and a method named __single_post and both have the same problem. I would prefer the end result to be a method named __single_post so that it cannot be accessed directly with the url /blog/single_post.
Also
I've not yet coded error handling for when the post does not exist (so that when people type random things in the url they don't get the single_post view). I plan on doing that after I figure out this problem.
This doesn't explicitly answer your question, but I'd just forego the whole complexity by solving the problem using only routes:
// Whitelist other public actions in BlogPostsController first,
// so they're not caught by the catch-all slug rule.
// This whitelists BlogPostsController::other() and ::actions(), so
// the URLs /blog/other/foo and /blog/actions/bar still work.
Router::connect('/blog/:action/*',
array('controller' => 'blog_posts'),
array('action' => 'other|actions'));
// Connect all URLs not matching the above, like /blog/my-frist-post,
// to BlogPostsController::single_post($slug). Optionally use RegEx to
// filter slug format.
Router::connect('/blog/:slug',
array('controller' => 'blog_posts', 'action' => 'single_post'),
array('pass' => array('slug') /*, 'slug' => 'regex for slug' */));
Note that the above routes depend on a bug fix only recently, as of the time of this writing, incorporated into Cake (see http://cakephp.lighthouseapp.com/projects/42648/tickets/1197-routing-error-when-using-regex-on-action). See the edit history of this post for a more compatible solution.
As for the single_post method being accessible directly: I won't. Since the /blog/:slug route catches all URLs that start with /blog/, it'll catch /blog/single_post and invoke BlogPostsController::single_post('single_post'). You will then try to find a post with the slug "single_post", which probably won't exist. In that case, you can throw a 404 error:
function single_post($slug) {
$post = $this->BlogPost->get_post_by_slug($slug);
if (!$post) {
$this->cakeError('error404');
}
// business as usual here
}
Error handling: done.

Resources