Why Authorize is to set to Controller in AuthComponent? - cakephp-3.0

Why authorize is to set to Controller while Authentication is done in AppController?
Like: I got it when Blog example is doing but did not get details explanation on it
$this->loadComponent('Auth', [
'authorize' => ['Controller']
]);
I read the Authorize Section but could not understand it. So Could someone please help making me understand it?

The book is describing how you would control your own authorization at the controller level.
Authentication identifies a valid user. If any logged in user may access any part of your app, then you don't need to implement any further authorization. But if you wish to restrict access to certain controllers, based on role for example, you could set 'authorize' => ['Controller'] in the Auth config as described, and then in each controller define your own isAuthorized() method, based on the role of the user.
For example, if in your InvoicesController you only want to let users whose role is 'Accounting' access the methods, you could include a test for that in an isAuthorized() method in the InvoicesController:
// src/Controller/InvoicesController.php
class InvoicesController extends AppController
{
public function isAuthorized($user)
{
if ($user['role'] === 'Accounting'){
return true;
}
return parent::isAuthorized($user);
}
// other methods
}

Related

Yii2 - module login in basic application template

Using the "basic" application template, what is the correct way of setting up a module login that is separate from the main site login?
For example I have an "admin" module which requires a login. I also need a user login for the main site.
I have done the following:
Created admin module using gii tool
Created models folder within the admin module folder
Placed LoginForm.php and User.php within this folder (also updated the namespace declarations in these files)
Added AccessControl behaviour and login/logout actions to modules\admin\controllers\DefaultController.php
Updated config\web.php as follows:
'modules' => [
'admin' => [
'class' => 'app\modules\admin\Module',
],
],
Updated app\modules\admin\Module.php as follows:
public function init()
{
parent::init();
Yii::$app->set('user', [
'class' => 'yii\web\User',
'identityClass' => 'app\modules\admin\models\User',
'enableAutoLogin' => true,
'loginUrl' => ['admin/default/login'],
]);
Yii::$app->set('session', [
'class' => 'yii\web\Session',
'name' => '_adminSessionId',
]);
}
The problem I am having is that if I try to access an admin page when I am not logged in, it shows the login form (this is correct). However upon logging in, it is just redirects me back to the main site. It should redirect me to the admin page I was trying to access.
In DefaultController.php, it has the following (default code):
if ($model->load(Yii::$app->request->post()) && $model->login())
return $this->goBack();
What is the correct way of doing this so I can have independent logins for the admin module and for the main site? I don't want to use the "advanced application template" as that adds some unnecessary complexity.
The User component allows you to set a returnUrl, the getter explained: This method reads the return URL from the session. It is usually used by the login action which may call this method to redirect the browser to where it goes after successful authentication.
Recommended: Before processing the data, set the returnUrl by calling Yii::$app->user->setReturnUrl(Url::current()); (this will store the page the user was on in the session, moreover, you can manipulate GET parameters using Url::current() before passing it to the session), and let the framework do its magic.
Not recommended: After processing the data, you can rely on referrer (which won't work in some cases) as following
return Yii::$app->request->referrer ? $this->redirect(Yii::$app->request->referrer) : $this->goHome(); or instead of goHome which basically redirects to app->homeUrl that can be set during Module init, you could say $this->redirect('your/admin/index');
I recommend setting the ['homeUrl' => 'your/admin/index'] during the initialization of the Module, as you might need it for other features as well and Yii2 uses it as a "fallback" to some redirects as well.
The best way is to create new controller in the admin module, which should have login, logout actions. Because, in future you may add there some extra logic.
In that login action you can use same LoginForm.
You can specify redirect url in that login action.
Admin module class can looks like this:
namespace app\modules\admin;
class Module extends \yii\base\Module
{
public $layout = 'main';
public $defaultRoute = 'main/index';
public function init()
{
parent::init();
Yii::$app->errorHandler->errorAction = '/admin/main/error';
Yii::$app->user->loginUrl = '/admin/main/login';
.....
}
}
goBack() defaults to the homeUrl if the returnUrl for the user hasn't been set.
Why not just redirect?
if ($model->load(Yii::$app->request->post()) && $model->login())
return $this->redirect(['myadminmodule']);
go to config/web.php and add it to the components
'backendUrlManager'=>[
'class' => 'yii\web\urlManager',
'enablePrettyUrl'=>true,
'showScriptName'=>false,
'baseUrl'=>'/admin',
],
In DefaultController.php, it has the following (default code):
if ($model->load(Yii::$app->request->post()) && $model->login())
return Yii::$app->getResponse()->redirect(Yii::$app->backendUrlManager->baseUrl);

Yii2 RBAC Rule Feedback / Message

This is a question about RBAC usage in Yii2.
So far I have found it to work rather well and satisfactory, however there is one key feature that I am missing: The ability for Yii2 Rules to provide "feedback" in a similar way as Yii2 Validators set Error messages to explain why the validation failed. I am looking for a way to provide some sort of feedback as to why the permission was not granted.
In particular, the can() method will return a boolean type, which is fine, but when checking for permission we have no idea why exactly the user was not granted that particular permission.
To give a more practical example. Let's say we are trying to determine whether the current user can submit a comment. We would typically do something like this:
if (Yii::$app->user->can('postComment',['comment'=>$comment])) {
$comment->post();
} else {
throw new ForbiddenHttpException('Sorry m8, you cant do this. No idea why tho!');
}
It works great, but as shown in the example we really have no idea why the user isn't able to post the comment. Could be any number of reasons, for example because the thread is locked or because they do not have permission to post in a certain category or because they dont have a high enough reputation etc. But we want to tell the user why! So my question is, how do we get that feedback from Yii2's RBAC?
You would want to create your own AccessRule and set the message exceptions from your scenarios by overriding the current methods in that class. matchRole would be the method you would be overriding. Yii2 doesn't have this in place so you would have to roll your own AccessRule to do so.
Then once its created attach it to your controllers:
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::className(),
'ruleConfig' => [
'class' => 'app\components\AccessRule'
],
'rules' => [
/* my normal rules */
],
],
];
}
So basically all I did was add
'message' => 'Current password cannot be blank.'
to my rules.
Make sure you seperate the correct rules, so you don't get that message on multiple fields, where it doesn't make sense. Also make sure you add it on the 'required' rule, unless you want that message to show when it's another rule..
I hope this helped you guys, as I spent a bit too much time searching for it.
The fastest way to get feedback from RBAC is to pass an object as a parameter. If the check fails then the error text is entered into this object. In this way, the reason for the negative test result can be obtained.
Let's say we want to prevent users from commenting on their posts.
Validation in Rule. This Rule is associated with postComment permission:
class PostCommentRule extends yii\rbac\Rule
{
public $name = 'PostCommentRuleName';
public function execute($user, $item, $params)
{
$allowed = $params['post']->owner_id !== $user;
if(!$allowed && isset($params['errorObject']))
$params['errorObject']->errorText = 'Comments to oneself are not allowed.';
return $allowed;
}
}
We check permission and in case of prohibition we have a reason:
$errorObject = new stdClass();
if (Yii::$app->user->can('postComment',['post'=>$post,'errorObject'=>$errorObject])) {
$comment->post();
} else {
throw new ForbiddenHttpException($errorObject->errorText);
}
In your case I would create a base permission class which will cover an abstraction for specific restriction message with one simple method and it will be extended by all your permissions.
This is the abstract permission blueprint.
abstract class AbstractPermission extends Permission
{
/**
* #return string
*/
abstract public function getRestrictionMessage(): string;
}
Creating custom database manager in order to check if retrieved permission has implement the abstraction.
class CustomDbManager extends DbManager
{
/**
* #throws \Exception
* #return AbstractPermission|null
*/
public function getPermission($name): ?AbstractPermission
{
$permission = parent::getPermission($name);
if ($permission === null) {
return null;
}
if (!$permission instanceof AbstractPermission) {
throw new \Exception(
'Your permission class should be derived from ' . AbstractPermission::class
);
}
return $permission;
}
}
Define CustomDbManager in your configuration file
'components' => [
'authManager' => [
'class' => CustomDbManager::class
],
...
];
Example with your PostCommentPermission.
class PostCommentPermission extends AbstractPermission
{
/**
* #return string
*/
public function getRestrictionMessage(): string
{
return 'You cannot post comments!';
}
}
And finally invoke your manager with specific permission check
$authManager = Yii::$app->getAuthManager();
$postCommentPermission = $authManager->getPermission('postComment');
if (Yii::$app->user->can($postCommentPermission->name, ['comment' => $comment])) {
$comment->post();
} else {
throw new ForbiddenHttpException($postCommentPermission->getRestrictionMessage());
}

Cakephp3 Auth - allow some controllers to work without authoriziation

Today i wanted to use Cakephp3 auth component.
I have studies cakephp3 blog tutorial and auth documentation.
I have followed Blog tutorial, and for now everything works fine, like login,logout etc...
I wanted to have UsersController to be protected and used only when i will logged in.
And this is ok, but now i saw that i need to login to other controllers actions, for example i have PagesController, which should be public.
I found this in docs:
// Allow only the view and index actions.
$this->Auth->allow(['view', 'index']);
But i have a lot of actions, and listing actions in this function may be problematic.
My question is: How can i globally set all actions of UsersController to be protected, and all the rest of controllers to be public ?
Thank You.
For UsersController
class UsersController extends AppController
{
public function beforeFilter(Event $event)
{
// allow only login, forgotpassword
$this->Auth->allow(['login', 'forgotpassword']);
}
}
For Other controller. (Example: PagesController)
class PagesController extends AppController
{
public function beforeFilter(Event $event)
{
// allow all action
$this->Auth->allow();
}
}
In your appsController, you can add below code. You must add all the view names in $this->Auth->allow('ViewName').
public function beforeFilter(Event $event)
{
parent::beforeFilter($event);
// Allow users to register and logout.
// You should not add the "login" action to allow list. Doing so would
// cause problems with normal functioning of AuthComponent.
$this->Auth->allow(['Index', 'View', 'Edit', 'CustomViewName']); // NOTE: DO NOT ADD VIEWS THAT ARE USED BY YOUR USERSCONTROLLER.
}
And from UsersController, you can remove $this->Auth->allow(['add', 'logout']);. This is how I would do it.
I think you can use ControllerAuthorize.It will allows you to handle authorization checks in a controller callback.Add this setting in your app component.
$this->loadComponent('Auth', [
'authorize' => 'Controller',
]);
Then you can prevent or allow access by isAuthorized()
example :
public function isAuthorized($user) {
if ( isset($user['role']) == 'yourRole' ) {
return TRUE;
}
else {
header("location: Router::url('/', true)");
exit();
}
}
For details
cake doc
Try:
$this->Auth->deny(['controllers name that you want to authorize']);

Meaning of Yii::$app->user->identity->role->role_name;

In a book called Yii2 for Beginners, which is mainly about the advanced template, I have encountered the following unexplained code, which seems relevant to RBAC:
$userHasRoleName = Yii::$app->user->identity->role->role_name;
What exactly does this mean? For example, I guess that this:
Yii::$app->user
refers to this file:
vendor\yiisoft\yii2\web\User.php
Is this correct?
In any case, what does the rest of the code refer to? Specifically:
->identity->role->role_name
In the above User.php file, I have not been able to find anything like "function identity()", so it can't be that. I have found numerous $identity variables, but I don't know which one the code might be referring to. And there is no $role variable at all.
What is this code referring to:
Yii::$app->user->identity->role->role_name;
Yii described magic methods like __get, __set and so on, to get access for inaccessible properties. Oftenly such methods begins from get or set (in Yii implementation it is). To get access to ->identity, \yii\web\User has method getIdentity. This method return identity wich you described in config with identityClass property for user component. Oftenly identityClass is a AR model which implements IdentityInterface.
'components' => [
'user' => [
'identityClass' => 'common\models\User',
]
]
To get access to ->role for example you must to create a new method
namespace common\models;
class User extends ActiveRecord implements IdentityInterface {
public function getRole(){
// if user can have only one role
return current( \Yii::$app->authManager->getRolesByUser( $this->id ) );
}
}
Btw implementation of ->role->role_name may be very different.

Execute my code before any action of any controller

I would like to check if my user have filled certain fields in his profile before he can access any action of any controller.
For example
if(empty(field1) && empty(field2))
{
header("Location:/site/error")
}
In yii1 I could do it in protected\components\Controller.php in init() function
But in yii2 I'm not sure where to put my code. I cannot modify core files, but not sure what to do in backend of my advanced application to make it work.
I know I can user beforeAction() but I have too many controllers to do that and to keep track of every controller
In case you need to execute a code before every controller and action, you can do like below:
1 - Add a component into your components directory, for example(MyGlobalClass):
namespace app\components;
class MyGlobalClass extends \yii\base\Component{
public function init() {
echo "Hi";
parent::init();
}
}
2 - Add MyGlobalClass component into your components array in config file:
'components' => [
'MyGlobalClass'=>[
'class'=>'app\components\MyGlobalClass'
],
//other components
3 - Add MyGlobalClass into bootstarp array in config file:
'bootstrap' => ['log','MyGlobalClass'],
Now, you can see Hi before every action.
Please note that, if you do not need to use Events and Behaviors you can use \yii\base\Object instead of \yii\base\Component
Just add in config file into $config array:
'on beforeAction' => function ($event) {
echo "Hello";
},
Create a new controller
namespace backend\components;
class Controller extends \yii\web\Controller {
public function beforeAction($event)
{
..............
return parent::beforeAction($event);
}
}
All your controllers should now extend backend\components\Controller and not \yii\web\Controller. with this, you should modify every controller. I would go for this solution.
I believe you might also replace 1 class with another (so no change to any controller necessary), something like
\Yii::$classMap = array_merge(\Yii::$classMap,[
'\yii\web\Controller'=>'backend\components\Controller',
]);
See more details here: http://www.yiiframework.com/doc-2.0/guide-tutorial-yii-integration.html and I took the code from here: https://github.com/mithun12000/adminUI/blob/master/src/AdminUiBootstrap.php
you can put this in your index.php file. However, make sure you document this change very well as somebody that will come and try to debug your code will be totally confused by this.
Just i think this code on config file can help you:
'on beforeAction' => function ($event) {
// To log all request information
},
'components' => [
'response' => [
'on beforeSend' => function($event) {
// To log all response information
},
],
];
Or, https://github.com/yiisoft/yii2/blob/master/docs/guide/security-authorization.md use RBAC, to restrict access to controllers actions one at a time based on rules. Why would you want to restrict access to controller actions based on user fields is beyond me. You will not be able to access anything (including the login form) if you put a restriction there.