I am trying to implement RBAC in my project by following the tutorial* on the Yii website. However I am confused when trying to implement the permissions by group.
For this example I have added a group field into the user table and have defined two groups, user (2) and admin (1).
I then created a console command which looks like this:
class RbacController extends Controller
{
public function actionInit()
{
$auth = \Yii::$app->authManager;
$rule = new \app\rbac\UserGroupRule;
$auth->add($rule);
$search = $auth->createPermission('search');
$search->description = 'Search';
$search->ruleName = $rule->name;
$auth->add($search);
$user = $auth->createRole('user');
$user->ruleName = $rule->name;
$auth->add($user);
$admin = $auth->createRole('admin');
$admin->ruleName = $rule->name;
$auth->add($admin);
$auth->addChild($admin, $user);
}
}
And I have this file: rbac/UserGroupRule.php
class UserGroupRule extends Rule
{
public $name = 'userGroup';
public function execute($user, $item, $params)
{
// return true; // force return to true for test
if(!Yii::$app->user->isGuest) {
$group = Yii::$app->user->identity->group;
if($item->name === 'search') {
return $group == 1;
}
return false;
}
}
I'm trying to test the permission with if(\Yii::$app->user->can('search')).
Firstly, I wonder why the console command is required here as I can't see where it's being used.
The $item parameter in the execute method has the value of search, but the tutorial shows that it expects this value to be role type.
Regardless of what I return in the execute method, it seems to return false.
Can anyone answer these questions?
http://www.yiiframework.com/doc-2.0/guide-security-authorization.html
I guess you have an authManager with DbManager ?
'authManager' => [
'class' => 'yii\rbac\DbManager',
],
to init the rbac from the console just use yii rbac/init in a console (in correct project dir) then the database entries were done (before that the rbac tables should be empty)
if you haven't done yet create the tables with
yii migrate --migrationPath=#yii/rbac/migrations
$item is just the auth permission or role entry. The rule is called for every entry, if you have added a rule. In your case for permission "search" and roles "user" and "admin" the rules is executed.
your have added entries with rule checking. So if you e.g. check if the user can "search" by e.g.
if (\Yii::$app->user->can('search')) {
// can search
}
then the rule is checked or executed (which is your UserGroupRule). And in your case it would return true for admins and false for user given by the group field.
edit:
I hope you have added this to your components in your config file.
return [
// ...
'components' => [
'authManager' => [
'class' => 'yii\rbac\DbManager',
],
// ...
],
];
You have created 2 roles in your rbac (user/admin) and as far as i understand your are using a group column in the User table to allocate those roles to the user. And in your code you will need to have to check the permissions or roles. So from the DB the correct Entry is selected and if a Rule is attached this rule is then executed. And this checks the current user group and returns true or false. So in your case no assignments to those roles or permissions are done. It uses the Rule to return true or false depending on the user group. But here are other extensions search for yii2admin or yii2rbac, where you can also assign user to roles/permissions etc by database entries.
I would say you should get more help where you can "chat" e.g. the yii chat which is linked on the yii homepage.
Related
How to display records in gridview in yii2 using rule created for roles, in RBAC ?
Suppose, there is two roles "admin" and "agent".
Now the requirement is;
In grid for agent, display only client which is assigned to that
agent.
For admin, grid will show all client list.
Here the example I am using this in my code
// User.php -> Model
public function getUserRolesAsArray($userId)
{
$roles = Yii::$app->authManager->getRolesByUser($userId);
if (!empty($roles)) {
foreach ($roles as $role) {
$userRole[] = $role->name;
}
return $userRole;
}
}
// view.php -> view file
[
'label' => 'Role',
'value' => $model->getUserRoles($model->id) ?? null,
],
Kindly try this i think this may be help you
It is done,
I have to create a permissions that will given to role, and based on that permission, DataProvider query is modified
I have a regular User model. The system works fine when I use it. But now I am trying to create unit tests in the PHPUnit that integrated with Laravel.
I have a factory that creates a user:
$factory->define(App\User::class, function (Faker\Generator $faker) {
return [
'id' => $faker->randomNumber(9),
'email' => $faker->safeEmail,
'first_name' => $faker->firstNameMale,
'last_name' => $faker->lastName,
'password' => bcrypt(str_random(10)),
'remember_token' => str_random(10),
];
});
I changed the User to have integer ID as the primary key but it not defined as auto-increment.
So the factory create random number for the ID.
Also I have created the simple test:
public function test_id_is_visible() {
$user = factory(App\User::class)->create();
$this->actingAs($user);
$this->visit('/userprofile');
$this->see($user->id);
}
That test always fails, but it seems to be OK when I navigate to the page manually.
I have noticed that in the test the $user->id is always 0. Even it can't be 0 in the factory. I checked and Laravel insert the user correctly to the database and it have correct ID, but in the code I always get 0.
What can I do to get the correct value of the ID?
EDIT
Now I see that if I changes $user = factory(App\User::class)->create(); to $user = factory(App\User::class)->make(); the user instance holds a correct ID. But why create clears the ID?
Update for Laravel 5.8+ Make sure to set
public $incrementing = false;
on your model.
The problem happened because the ID is not defined as auto-increment.
More information you can found in:
https://stackoverflow.com/a/31350800/1725836
Even the question is for Laravel 4 it is still relevant for Laravel 5.2.
You can write your tests like this:
$users = factory(User::class, 5)->make();
$users->each(function ($item, $key) {
$item->id = ++$key;
});
Now each user will have an id, without the need to persist the models to database (using create() instead of make()).
public function test_id_is_visible() {
$user = factory(App\User::class)->make();
$user->id = 7;
$this->actingAs($user);
$this->visit('/userprofile');
$this->see($user->id);
}
I am doing two different types of auth for 'student' and 'consultant', thus two tables.
So far everything works fine. A user can choose usertype and then login, but when I'm printing {{ Auth::user()['username'] }} in the page error occurs: only 'student' name can be displayed, since I specified model and table in config/auth.php to be 'Student' and 'students'. As for consultant users, they can successfully login, but username can't be displayed.
I tried to set model and table with config during runtime but doesn't work, here's part of my AuthController:
if( $request->only('usertype')['usertype'] == 'student' ) {
Config::set('auth.model','Student');
Config::set('auth.table','students');
$userprovider = new \Illuminate\Auth\EloquentUserProvider(app('hash'), 'App\Student');
Auth::setProvider($userprovider);
$this->redirectPath = '/student/home';
}
else if( $request->only('usertype')['usertype'] == 'consultant' ) {
Config::set('auth.model','Consultant');
Config::set('auth.table','consultants');
$userprovider = new \Illuminate\Auth\EloquentUserProvider(app('hash'), 'App\Consultant');
Auth::setProvider($userprovider);
$this->redirectPath = '/consultant/home';
} $credentials = $this->getCredentials($request);
if (Auth::attempt($credentials, $request->has('remember'))) {
return $this->handleUserWasAuthenticated($request, $throttles);
}
Seems the Config::set doesn't work at all. What could be the problem?
I'm using Laravel 5.1.
Let me try answering my question, based on jedrzej's solution in the comment.
Config::set did work, but only for once and needs to be modified for every request for authentication. In this case, doing a middleware is an ideal solution - store user type in session on successful login and then in the middleware read that value from session and set config variables - laravel.com/docs/master/middleware.
Another easier solution is using some package for multi-auth. I used Sarav\Multiauth (https://packagist.org/packages/sarav/laravel-multiauth) and it works perfectly.
I want want to restrict the records based on the user logged in viewing the records.
For example I have following two models:
Assignments
Users
So if a student (user) is viewing the summary of assignments he should be able to see only his assignments and able to perform view/delete/edit only his assignment.
But if a teacher (user) is viewing summary of assignments then he should see all assignments and can perform add/edit/delete on all assignments.
I am already aware I can do this by putting group conditions in find and then appropriate in code in view/edit/delete actions also.
My Question is - what is the best ways to handle scenarios like this in cakephp? Putting conditions everywhere don't seems good way to me.
Consider as two separate problems to solve
Access control
The first is how to deny students who e.g. just manipulate the url to attempt to view/edit/delete things they don't own. For that use isAuthorized, there's an example in the book, adapted to the info in the question that'd be:
// app/Controller/AppController.php
public $components = array(
'Session',
'Auth' => array(
'authorize' => array('Controller') // Added this line
)
);
public function isAuthorized($user) {
// Teachers can access/do everything - adapt to whatever identifies a teacher
if ($user['is_teacher'])) {
return true;
}
// Anyone logged in can access the index
if ($this->action === 'index') {
return true;
}
// The owner of a whatever can view, edit and delete it
$id = $this->request->params['pass'][0];
$owner = $this->{$this->modelClass}->field('user_id', array('id' => $id));
if ($owner === $user['id'])) {
return true;
}
// Default deny
return false;
}
Restrict student data
The event system, available since 2.1, is an easy way to enforce the data restriction mentioned. Again there's a relevant example in the book, adapted to the information in the question, that'd be:
// app/Controller/AssignmentsController.php
public function beforeFilter() {
if (!$this->Auth->user('is_teacher')) {
$currentUser = $this->Auth->user('id');
$this->Assignment->getEventManager()->attach(
function(CakeEvent $e) use ($currentUser) {
$e->data[0]['conditions']['user_id'] = $currentUser;
return $e->data[0];
},
'Model.beforeFind'
);
}
}
This will add a user_id condition to all finds, therefore the index listing will show only their own assignments, whereas for a teacher it will show all assignments.
I would create 2 separate models - StudentAssignments and TeacherAssignments. both models can extend Assignments model. you can then filter your conditions in the beforeFind of each model.
This way, you have decoupled models and you can manipulate them appropriately.
Example:
App::uses('Assignment', 'Model');
class StudentAssignments extends Assignment
{
function beforeFind( $queryData ) {
$queryData['conditions'] = array_merge( (array)$queryData['conditions'],
array( $this->alias.'.user_id' => CakeSession::read('User.id') ) );
return parent::beforeFind($queryData);
}
}
Then you can call $this->StudentAssignments->find('all'); to pull all assignments for a single user
I've a standard Gii created admin view, which use a CGridView, and it's showing my user table data.
the problem is that user with name 'root' must NOT BE VISIBLE.
Is there a way to add a static where condition " ... and username !='root' " ?
admin.php [view]
'columns'=>array(
'id',
'username',
'password',
'realname',
'email',
.....
user.php [model]
public function search()
{
// Warning: Please modify the following code to remove attributes that
// should not be searched.
$criteria=new CDbCriteria;
$criteria->compare('id',$this->id);
$criteria->compare('username',$this->username,true);
$criteria->compare('password',$this->password,true);
$criteria->compare('realname',$this->realname,true);
$criteria->compare('email',$this->email,true);
......
return new CActiveDataProvider($this, array(
'criteria'=>$criteria,
));
}
You can use CDbCriteria's addCondition like this:
$criteria->addCondition("username !='root'");
Your best option would be to use Yii scopes which are essentially a saved where clause (or other modification of your existing criteria) that you can apply all over your app and only need to change in one place if your criteria ends up changing later.
What makes them even cooler is that you can string them together with other scopes / criteria changes (from users in grids for instance) without having to keep track of what criteria clause is getting changed by what.
A few examples that might apply to your situation. In your controller you probably have something like this:
$users = User::model()->search()->findAll();
Asgaroth's answer answers what you were asking on the surface. But there is so much more you can do (and do easily) using scopes.
If you add the below to your user model:
class User extends CActiveRecord
{
......
public function scopes()
{
return array(
'active'=>array(
'condition'=>'active=1',
),
'isAdmin'=>array(
'condition'=>'isAdmin=1',
),
);
}
}
then you can retrieve active users (with your users' filters still applied) like this in your controller:
$users = User::model()->active()->search()->findAll();
Or you can retrieve all active admin users (without being filtered by your gridview criteria) like this:
$users = User::model()->active()->isAdmin()->findAll();
Default scopes are just an extension of the same idea:
class User extends CActiveRecord
{
public function defaultScope()
{
return array(
'condition'=>"username != 'root'",
);
}
}
If before your isAdmin scope would return the root user, applying the default scope will eliminate the root user from the models returned, as it applies to every User::model() query you make.