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)?
Related
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')){
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');
}
}
}
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();
I'm currently developing a restful/stateless api in cakephp which uses tokens (at the moment) and should use rolling tokens (like suggested here from g-shearer) in the future. My current implementation works, but i'm really concerned if i've implemented everything the right way (auth components especially custom auth components seem really confusing to me)
PS: I'm using the current version of cakephp (2.5.1).
1: I've created the file TokenAuthenticate.php in Controller/Component/Auth:
<?php
App::uses('BaseAuthenticate', 'Controller/Component/Auth');
class TokenAuthenticate extends BaseAuthenticate {
public function authenticate(CakeRequest $request, CakeResponse $response) {
}
public function getUser(CakeRequest $request) {
//set values from request headers
$publictoken = $request->header('Security-Public-Token');
$accesstoken = $request->header('Security-Access-Token');
$timestamp = $request->header('Security-Timestamp');
// check if required header fields are set
if (empty($publictoken) || empty($accesstoken) || empty($timestamp)) {
return false;
}
// init required token model
$Token = ClassRegistry::init('Token');
//check if token pair exists
if ($dbtoken = $Token->findByPublic($publictoken)) {
if ($accesstoken == md5($dbtoken['Token']['private'] . $timestamp)) {
//valid token - return user
$User = ClassRegistry::init('User');
$dbuser = $User->findById($dbtoken['Token']['user_id'])['User'];
return $dbuser;
} else {
//invalid token
return false;
}
} else {
//invalid token pair
return false;
}
}
public function unauthenticated(CakeRequest $request, CakeResponse $response) {
return true;
}
}
?>
then i've added the following to my controller:
class UsersController extends AppController {
public $uses = array('User', 'Token');
public $components = array('Auth' => array('authenticate' => array('Token')));
public function beforeFilter() {
parent::beforeFilter();
AuthComponent::$sessionKey = false;
$this->Auth->autoRedirect = false;
$this->Auth->allow('login', 'register');
}
in my actions i check the status like so:
if (!$this->Auth->loggedIn()) {
$this->set(array('error' => 'INVALID_AUTHENTIFICATION'));
$this->render('view');
}
So I can set a custom error and output it without being redirected to the login action (note the unauthenticated function in my tokenauthentication file which returns true - so cakephp does not redirect you)
I think the login process should happen in the authenticate function of my TokenAuthenticate file and not in the login action of my controller, or am i wrong? What is the correct way to achieve this goal?
PS: How would it be possible to add a new token pair (to every authenticated output) automatically with cakephp so the tokens are 'rolling'?
The whole api output is json encoded if that matters
also cakephp still sets a cookie sometimes even though i disabled this (AuthComponent::$sessionKey = false;). How to stop this?
EDIT: So I've added an beforeRender() function to my userscontroller and now the tokens are rolling (Y)
//renew tokens
public function beforeRender() {
//only add tokens if logged in
if ($this->Auth->loggedIn()) {
//find old token
$oldToken = $this->Token->findByUser_id($this->Auth->user('id'));
//delete old token
$this->Token->delete($oldToken['Token']['id']);
//create new token pair
$this->Token->create();
$this->Token->save(array(
'user_id' => $this->Auth->user('id'),
'public' => Security::hash(substr(str_shuffle('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!##$') , 0 , 15 )),
'private' => Security::hash(substr(str_shuffle('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!##$') , 0 , 15 ))
));
//set it for the view
$this->set(array('token' => $this->Token->read()));
}
}
Is this the right way to implement something like this? I always want to do things the right and 'perfect' way so any criticsm is welcome :)
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',
],
],