RBAC always return the exception - php

this is the entry for auth_itemi'm trying to use rbac in yii2-basic framework.
code is as follow:
config/web.php:
'authManager' => [
'class' => 'yii\rbac\DbManager',
],
controller:
public function actionCreate()
{
if(Yii::$app->user->can('countries/create')){
$chk = 'Can Do';
}else{
$chk = 'Can Not Do';
}
echo $chk;exit();
}

Make sure to check that all the points below are working as expected.
The information comes from the Yii2 guide pages and it is explained in more detail there.
Configure the application to use RBAC with the data stored on the database.
'authManager' => [
'class' => 'yii\rbac\DbManager',
],
Add and assign roles and permissions to the database.
One way, probably the simplest, is to use a console controller, but then you have to deal with permissions on user creation and update somewhere else in your code.
<?php
namespace app\commands;
use Yii;
use yii\console\Controller;
class RbacController extends Controller
{
public function actionInit()
{
$auth = Yii::$app->authManager;
$auth->removeAll();
// add "create country" permission better remove '/'
$createCountry = $auth->createPermission('createCountry');
$createCountry->description = 'Create a new country';
$auth->add($createCountry);
// add "admin" role and give this role the "createCountry" permission
$adminRole = $auth->createRole('admin');
$auth->add($adminRole);
$auth->addChild($adminRole, $createCountry);
// Assign roles to user by id, make sure this is the user that you
// are using when testing
$auth->assign($adminRole, 1);
}
}
Add some logs in your controller to check what is not working as expected.
public function actionCreate()
{
// Logs just to find what is wrong, remove them later
if (($user = Yii::$app->user->identity) === null) {
Yii::debug('No user logged in, the problem is there', __METHOD__);
} else {
Yii::debug("User $user->id logged in", __METHOD__);
if (!Yii::$app->user->can('createCountry') {
Yii::debug('User cannot create country', __METHOD__);
if (!Yii::$app->user->can('admin') {
Yii::debug('User does not have admin role', __METHOD__);
} else {
Yii::debug('Admin role does not have createCountry child', __METHOD__);
}
} else {
Yii::debug('User can create country, ALL DONE!', __METHOD__);
}
}
// Remove above this line after finding the problem
// You would keep the logic below this line after finding the problem
if(!Yii::$app->user->can('createCountry')) {
throw new ForbiddenHttpException('You are not allowed to do that');
}
// No 'else' after throwing, more readable code
// Your logic goes here, the user can create countries
}

I'm not sure, but I think:
Your code is almost ok
Missing addChild($adminRole, $createCountry);
The permission is checked against the table auth_item_child.
Parent Child
admin 'createCountry'
admin 'deleteCountry'
guest 'indexCountry'
....
Then you can
if(Yii::$app->user->can('createCountry')){

Related

Zend framework 2: How to redirect from a plugin?

I worte a plugin IdentityPlugin to check login status of a user. If the user session gets logout, I want to redirect them to login page. My code is given below.
public function checkLogin($logout=true,$next='login'){
if($this->auth->hasIdentity()){
}elseif ($logout){
return $this->getController()->redirect()->toRoute($next);
}
}
in my controller
// Check identity, if not found- redirect to login
$this->IdentityPlugin()->checkLogin();
Any idea?
You're returning a response to the controller but you're not returning it from the controller itself.
For example you could try this is your controller:
$check = $this->IdentityPlugin()->checkLogin();
if ($check instanceof Response) {
return $check;
}
A more complex solution could be to stop the propagation of the controller's MvcEvent, set whatever response you want, and return directly.
Hi you need to config you plugin in factories in module.config.php and pass service manager to __construct, like below:
'controller_plugins' => array(
'factories' => array(
'CheckLogin' => function($sm){
$checkLogin = new Application\Plugin\CheckLogin($sm);
return $checkLogin;
},
),
),
Then in your plugin you will be able to call all you need using service Manager:
namespace Application\Plugin;
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
class CheckLogin extends AbstractPlugin
{
public function __construct($sm)
{
$auth = $sm->getServiceLocator()->get("Zend\Authentication\AuthenticationService");
if( !$auth->hasIdentity()){
$sm->getController()->plugin('redirect')->toUrl('/login');
}
}
}

Authentication with Doctrine in Zend Framework 2 Good Practices

Lets say that my page has 10 sections, in 6 of them I have to check if the user is logged in, and if not, redirect him to the "login/register" page.
I found myself repeating this code in the controller of those 6 pages:
public function actionthatneedsauthAction()
{
$sl = $this->getServiceLocator();
$authService = $sl->get('doctrine.authenticationservice.orm_default');
$user = $authService->getStorage()->read(); //is the user logged in?
if ($user) { //auth successful
//-------------/*CODE FOR THIS SPECIFIC CONTROLLER GOES HERE*/--------
return new ViewModel(array(
'user' => $user,
'somethingelse' => $somethingelse
));
} else { //auth denied
return $this->redirect()->toRoute(user, array('action' => 'login'));
}
}
I tried to encapsulate that into a service, called islogged (this is a model, not a controller), but I couldn't make it work because I couldn't find a way to redirect to a controller from inside a model, I only know how to redirect to a controller via another controller.
So in my usermanager.php I had a function like this one:
public function islogged()
{
$sl = $this->getServiceLocator();
$authService = $sl->get('doctrine.authenticationservice.orm_default');
$user = $authService->getStorage()->read(); //is the user logged in?
if ($user) { //auth successful
return $user;
} else {
/*
redirect to the login screen, dont know how to do it,
this code doesnt work here:
return $this->redirect()->toRoute(NULL, array(
'controller' => 'user',
'action' => 'login'
));
*/
}
}
so the idea was that in my controllers I only had to write:
$user = islogged();
and all the code repetition I mentioned won't be necessary anymore.
Is it a good practice what I tried to do with the usermanager.php islogged function?
If it is a good practice, how am I supposed to redirect to a controller from inside a model?
If is not a good practice, which will be the way to avoid all that code repetition that I'm having in my controllers?
I know that I can put the authentication step into the onboostrap() but in that case the auth will be triggered for all of my pages, and I just want it in some of them.
I would advise you to implement Doctrine Authentication with official DoctrineModule Authentication described in the docs folder of the repo.
Read this - Link to DoctrineModule Authentication
Then you can handle your Authentication check via the zf2 own Controller and View Helpers identity. See example in the docs here.
I use this ACL Module on my apps: https://github.com/ZF-Commons/zfc-rbac
My Customer overview controller then looks as so:
<?php
namespace RoleBasedCustomer\Controller;
use RoleBasedUser\Service\AuthenticationService;
use RoleBasedUser\Service\UserService;
use RoleBasedUser\Controller\AbstractMultiModelController;
class OverviewController extends AbstractMultiModelController
{
public function __construct(
AuthenticationService $authService,
UserService $userService
) {
$this->authService = $authService;
$this->userService = $userService;
}
public function indexAction()
{
if ( ! $this->authService->hasIdentity() ) {
return $this->redirect()->toRoute('customer/login');
}
}
}
The only thing i had to do is replace these two lines:
$authService = $this->getServiceLocator()
->get('doctrine.authenticationservice.orm_default');
$user = $authService->getStorage()->read(); //is the user logged in?
with this one:
$user = $this->identity();

Can't update (but create new) objects in CakePHP with save()

Using CakePHP 2.4.9 on a LAMP setup (Ubuntu 13.10, Apache 2, MySQL Server 5.5, PHP 5.5.3).
Trying to package a model (User) into a plugin but I have run into a strange problem:
In two different actions I use save() to either create or update a User. This works:
if ($this->request->data) {
if ($this->User->create() && $this->User->save($this->request->data)) {
$this->Session->setFlash(_('<strong>Congratulations!</strong> Successfully created a new user.'), 'default', array('class' => 'alert alert-success'));
$this->redirect(array('action' => 'register'));
} else {
$this->Session->setFlash(_('<strong>Oops!</strong> Could not create a new user.'), 'default', array('class' => 'alert alert-danger'));
}
}
But this doesn't work:
if ($this->request->data) {
$this->User->id = $id;
if ($this->User->save($this->request->data)) {
$this->Session->setFlash(_('<strong>Congratulations!</strong> Successfully updated your user account.'), 'default', array('class' => 'alert alert-success'));
$this->redirect(array('action' => 'settings'));
} else {
$this->Session->setFlash(_('<strong>Oops!</strong> Could not update user account.'), 'default', array('class' => 'alert alert-danger'));
}
}
When I try to save the latter example (the "update action") it gives me the following error:
Fatal Error
Error: Call to a member function getColumnType() on a non-object
File: /home/johan/sites/userplugin/lib/Cake/Model/Model.php
Line: 1412
Notice: If you want to customize this error message, create app/View/Errors/fatal_error.ctp
The forms are pretty standard, I use the FormHelper to create form and fields. But in the update form I have a hidden field with the ID:
<?php echo $this->Form->hidden('id'); ?>
If I remove that, the update form goes through but creates a new object! So it's like $this->User->id = $id doesn't do anything. I currently set $id "manually", so $id = 1...
I tried searching for similar issues but didn't find anything. One discussion mentioned the ID field, maybe it's not correctly set up? But I couldn't find any solution in regards to that.
There's a bad model reference
The different code permutations are not directly related to the problem - or may simply indicate another different problem =).
The line of code that is failing is this:
$this->{$model}->getColumnType($column);
Where $this is the User model instance. What that probably means is that when it fails, the object is not the class you expect to check simply do:
debug(get_class($this->User));
In your controller, it is most likely AppModel.
What causes this
A typical cause of that kind of "sometimes it works, sometimes it doesn't" problem is that there are some references to the model using the plugin prefix, and there are some that don't i.e.
<?php
App::uses('MyPluginAppModel', 'MyPlugin.Model');
class Group extends MyPluginAppModel {
public $hasMany = array(
'User' => array(
'className' => 'User'
),
#'User' # Or like this
)
}
Whereas it should be like this:
<?php
App::uses('MyPluginAppModel', 'MyPlugin.Model');
class Group extends MyPluginAppModel {
public $hasMany = array(
'User' => array(
'className' => 'MyPlugin.User'
),
#'MyPlugin.User' # Or like this
)
}
What happens here is it depends upon how the relevant model is first accessed, as to what populates the User key in the class registry and is returned for all subsequent requests for that model reference.
To fix the problem, ensure that the model is always referenced with the correct plugin prefix (or of course no prefix if it's not a plugin model).
What I ultimately found out (and should've ruled out earlier, really) was that having an array defined in my AppModel, for configuring the plugin, named the same as my plugin was stupid, borderline retarded perhaps.
I.e. if your plugin is called MyPlugin, do not define a $myplugin array in your AppModel.
try this
//Controller
public function edit(){
if (!empty($this->request->data)) {
//if you want to save on User object
$this->User->id = $this->request->data['User']['id'];
if ($this->User->save($this->request->data)) {
.................................
$this->Session->setFlash(_('<strong>Congratulations!</strong> Successfully updated your user account.'), 'default', array('class' => 'alert alert-success'));
$this->redirect(array('action' => 'settings'));
} else {
$this->Session->setFlash(_('<strong>Oops!</strong> Could not update user account.'), 'default', array('class' => 'alert alert-danger'));
}
} else {
$this->request->data = $this->User->read(null, $id);
}
}
//edit view
<?php
echo $this->Form->create('User');
echo $this->Form->hidden('id');
echo $this->Form->input('first_name');
..
..
.
echo $this->Form->end(__('Save'));
?>

Getting An Error When Yii Tries To Read The User Role

I'am a new in Yii and I'm trying to do a RBAC control in my web application. I write an authorization and registration pages and everything working perfect. But when I trying to read current user's role using Yii::app()->user->role I'm getting an error with the following content:
PHP warning
include(User.php): failed to open stream: No such file or directory
I did everything like in official cookbook. Honestly, I don't know why this happens that's why if someone could help me to solve this problem I will be very appreciated.
Here I wrote the steps which I did when writing the role based user control.
As mentioned in official Yii cookbook I created a simple database table named users where I defined the role attribute. Then I write a WebUser class:
class WebUser extends CWebUser {
private $_model = null;
function getRole() {
if($user = $this->getModel()){
return $user->role;
}
}
private function getModel(){
if (!$this->isGuest && $this->_model === null){
$this->_model = User::model()->findByPk($this->id, array('select' => 'role'));
}
return $this->_model;
}
}
Next step I changed the default realization of the UserIdentity::authentificate() method, where I trying to assign role to the current user:
public function authenticate()
{
$users = Users::model()->find('LOWER(name)=?', array(strtolower($this->name)));
if($users === null)
$this->errorCode = self::ERROR_USERNAME_INVALID;
else if ($users->validatePassword(md5($this->password)))
$this->errorCode = self::ERROR_PASSWORD_INVALID;
else
{
$this->_id = $users->id;
$this->username = $users->name;
$auth=Yii::app()->authManager;
echo "user role = ".$users->role.", user id = ".$this->_id;
if(!$auth->isAssigned($users->role,$this->_id))
{
if($auth->assign($users->role,$this->_id))
{
Yii::app()->authManager->save();
}
}
$this->errorCode=self::ERROR_NONE;
}
return $this->errorCode == self::ERROR_NONE;
}
...
Finally, I declare all this components in the main web config file:
...
'import'=>array(
'application.models.*',
'application.components.*',
),
...
'components'=>array(
'user'=>array(
// enable cookie-based authentication
'class'=>'WebUser',
'allowAutoLogin'=>true,
),
'authManager' => array(
'class' => 'PhpAuthManager',
'defaultRoles' => array('guest'),
),
...
In getModel() you're using User::model(), but in authenticate() you're calling Users::model(), could it be that you're trying to call the wrong model class (it's Users instead of User)?

Secure Ajax Requests made by YII

i'm currently writing a Application based on YII.
My action for index:
public function actionIndex() {
$data = array();
$data['server'] = Server::model()->findByPk(1);
$data['dataProvider'] = new CActiveDataProvider('ServerUserPermission', array('criteria' => array('condition' => 'serverID=:id', 'params' => array(':id' => 1))));
$this->render('index', $data);
}
my ajax action:
public function actionAddPermission($server) {
if(Util::checkServerPower($server, Permission::MODIFY_SERVER)) {
$perm = new ServerUserPermission;
$perm->userID = 1;
$perm->serverID = $server;
$perm->power = 10;
try {
if ($perm->save()) {
echo "OK";
} else {
echo Util::print_r($perm->getErrors());
}
} catch (Exception $e) {
echo 'Critical Error Code: ' . $e->getCode();
}
} else {
echo 'No Permissions';
}
}
My view links to the addPermission action by using a button:
echo CHtml::ajaxButton("Insert New Player", array('addPermission', 'server' => $server->serverID), array('success'=>'refresh'));
My function Util::checkServerPower(...) checks the current User of the Application. Consequence: Ajax requests in YII are handled by an Guest AuthWeb User, but i need to check whether the User is actually allowed to add permissions or not. I currently cannot think of a secured solution to protect malicious data send by other guests or not. Is it somehow possible to get the (server-side) userID of the Ajax-call?
Thanks anyway
sincerly
I would do it by using the built in access control and extending CWebUser.
It might seem lengthy but I think it's a clean solution. (We already have Yii::app()->user->isGuest and the like, so why not check all permissions here?)
1) Activate the access control filter.
(In one controller or in /components/controller.php for all your controllers at once)
public function filters()
{
return array( 'accessControl' ); // Tell Yii to use access rules for this controller
}
2) Add an access rule
In the concerned controller. (Sorry, I didn't bother with your index-action.)
public function accessRules()
{
return array(
[
'allow',
'actions'=>['AddPermission'],
'expression'=>'$user->has(Permission::MODIFY_SERVER)'
],
['deny'], // Deny everything else.
);
}
3) Extend CWebUser
// components/WebUser.php
class WebUser extends CWebUser {
public function has( $permission)
{
// Check database for permissions using Yii::app()->user->id
...
}
}
4) Configure your app to use your new WebUser instead of CWebUser
// config/main.php
'components'=>[
'user'=>[
'class' => 'WebUser',
],
],

Categories