yii rbac: check autorizations on groups instead of users - yii

I have a question about the rbac system. I think I've pretty well understood it but I need more informations about a special case.
I would like to do the autorisations on groups instead of users. I mean for instance the group "HR" has permission to create a person. Then any person who join this group would have it as well.
Let me give you more informations.
A part of my database:
And this a part of what my group hierarchy could be:
So what I'm looking for, this would be a must, is a system where each group has some autorizations. People get the autorizations of their group and of their parents group (for instance people in "Forsys" has the autorizations of "Forsys", "R&D" and "Administration").
The solution I see at the moment is using bizrule. But I'm not sure write php code in database is a good idea and then if I update the group hierarchy (R&D inherits of RH instead of Administration) I would have to modify bizrule in database. I tried it and it works well but as you can see it require a lot of code.
$user = User::model()->with("people","people.groups")->findByPk(Yii::app()->user->id);
foreach($user->people[0]->groups as $group)
if($group->id == 2)
return true;
return false;
It's just for see if a user is in a group (without checking parent groups and hierarchy)
Another possibility could be create a new table "group_auth" where we would say for instance:
-Group_2 has role "managePerson"
-Group_3 has operation "deleteUser"
...
And then everytime a user is added in or removed of a group we would update his autorizations in the auth_assigment table.
I'd like to hear other opinions on this subject.
All comments will be appreciated :)
Thank you for reading and sorry for my English if you had difficulties to understand me.
Michaël S.

Do users ever get their own authorization items? If not, seems like you could in essence swap out the userid column in auth_assignment and name it / treat it as groupID instead. That way you wouldn't need to worry about keeping user auth assignments in sync with your group roles.
A couple of places you'd probably need to make some changes:
- by default CWebUser passes in the logged in userid for use in bizrules. Might be good to change that our with your own override that passes in groupId/groupIds instead.
- you'd need to override CDbAuthManager and rework some of how things work there
We've done something similar on a project I've worked on (we were handling multi-tenant RBAC custom permissions), which required custom CDbAuthManager overrides. It gets a bit tricky if you do it, but there is an awful lot of power available to you.
Edit:
Understood about your users sometimes needing to have additional authorizations. What if your group has a 'roles' field with different roles serialized in it (or some other method of having multiple roles stored for that group, could also be a relationship).
Then, on user login (for efficiency), you'd store those roles in session. Probably the easiest way to handle things would be to write a custom checkAccess for your WebUser override:
https://github.com/yiisoft/yii/blob/1.1.13/framework/web/auth/CWebUser.php#L801
as that will make things simpler to do your custom checking. Then I'd probably do something like:
if(Yii::app()->user->hasGroupAccess() || Yii::app()->user->checkAccess('operation/task/role')) {
....
}
In your WebUser hasGroupAccess method, you could loop over all group roles and send those to checkAccess as well.
Think that will work?

What I use to check access for groups when it's in another table, or somewhere else in the application I give the user the role per default. By using this:
return array(
'components'=>array(
'authManager'=>array(
'class'=>'CDbAuthManager',
'defaultRoles'=>array('authenticated', 'R&D', 'Administration'),
),
),
);
Under: Using Default Roles
By using this, every user gets these assignments. Now, I create a business rule to make sure that the checkAccess('group') will return the correct value.
For example in your case the business rule for R&D would be:
return (
count(
Person::model()->findByPk(Yii::app()->user->id)->groups(array('name'=>'R&D'))
) > 0
) ? true : false;
So what this does is:
find the logged-in person by primary key
look into groups (from the user) for the group with name R&D
if there is a group: return true (else return false)

Related

Eloquent: Get pages based on user role

I have a User, Role & Page setup, all with many-to-many relationships and the pivot tables setup in the usual fashion (role_user, page_role), along with the eloquent methods to attach a model to the pivot tables.
My idea is to allow a user to have many roles, and a page can be accessed by many roles.
However now I'd like to return a collection where I have my users details and then the pages they're allowed to access.
The closest I have got is:
return User::find( Auth::user()->id )->with('roles.pages')->first()->roles;
Now this returns each role the user has, and each page that the role can access. Which is correct, however I have duplication on the pages part.
How would I go about getting only a list of pages the user is able to access with no duplication?
Cheers
Read that answer to get you on the track: HasManyThrough with one-to-many relationship
Only for your setup you need to adjust the query - join 2 pivot tables (and make sure they represent real data, ie no rows referencing non-existing models):
// User model
// accessor so you can access it like any relation: $user->pages;
public function getPagesAttribute()
{
if ( ! array_key_exists('pages', $this->relations)) $this->loadPages();
return $this->getRelation('pages');
}
// here you load the pages and set the collection as a relation
protected function loadPages()
{
$pages = Page::join('page_role as pr', 'pr.page_id', '=', 'pages.id')
->join('role_user as ru', 'ru.role_id', '=', 'pr.role_id')
->where('ru.user_id', $this->id)
->distinct()
->get(['pages.*', 'user_id']);
$hasMany = new Illuminate\Database\Eloquent\Relations\HasMany(Page::query(), $this, 'user_id', 'id');
$hasMany->matchMany(array($this), $pages, 'pages');
return $this;
}
One more thing: I hardcoded tables and columns names for sake of simplicity, but in real life I suggest you rely on the relationships and their getters, like: $relation->getTable(), $relation->getForeignKey() etc.
Now suggestion about your code:
return User::find( // 2. query to get the same user
Auth::user()->id // 1. query to get the user and his id
)->with('roles.pages')
->first() // 3. query to get ANOTHER user (or the same, luckily..)
->roles;
Use Auth::id() instead of Auth::user()->id (for Laravel ver 4.1.25+) to avoid redundant query
find() and first() are methods that execute the query, so you just returned the user with id = Auth::user()->id and moment later you fetch another one, who comes first() from the users table..
You don't need to use User::whatever for authenticated user, use Auth::user() instead.
So the code with suggested solution would look like this:
Auth::user()->pages; // collection of Page models with unique entries

Eloquent many-to-many with filters on both sides

I've been reading the docs but I"m not sure how to do this. http://doc.laravelbook.com/eloquent/#many-to-many
Say I have a users, roles, and a pivot table.
I have belongsToMany set up for both Role and User
In a controller, I want to get a user_id and return what roles they have of a specific type only.
(There is also a role type table, but I can work with the IDs directly).
I start something like this
$specific_type_role = Role::where('role_type_id', 3)::where(?$user_id?)
//need to involve
$circle_users = RoleUser::where('user_id', $user_id)->get();
but I think it should be able to be done automatically. don't know how to include the filter right in the query.
Not sure if it's that what you need but, you probably will be able to do something like that:
public function getAdminRoles()
{
$user = User::find(1);
return $user->roles()->where('role_type_id', 1)->get();
}

How to check the user's CRUD permissions for an object in Salesforce?

According to a requirement, i have to change the owner of an account if the user does not have read access to a third object.
I need a functionality similar to the isAccessible() method of Describe Field Result, but it is only available for the current logged in user.
Is there any other way to check the user's CRUD permissions for an object in Apex code?
I wrote an article about this on my blog. There is a feature that was just released in version 24.0 of the API (Spring Release) that will let you do just this on a record by record basis for the current user.
Here is the link to that blog entry that goes into details: How to tell if a user has access to a record
Don't confuse record level access with CRUD - the latter is the ability for a user to Create, Read, Update or Delete an object in general, regardless of sharing rules etc. that might affect the user's access to a particular record.
To check whether a user can create (e.g. Contacts) in general, just use
Schema.sObjectType.Contact.isCreateable()
(returns true or false)
From the documentation. it sounds like you want to use execute anonymously.
Apex generally runs in system context; that is, the current user's permissions, field-level security, and sharing rules aren’t taken into account during code execution.​ The only exceptions to this rule are Apex code that is executed with the executeAnonymous call. executeAnonymous always executes using the full permissions of the current user. For more information on executeAnonymous, see Anonymous Blocks.
Although Apex doesn't enforce object-level and field-level permissions by default, you can enforce these permissions in your code by explicitly calling the sObject describe result methods (of Schema.DescribeSObjectResult) and the field describe result methods (of Schema.DescribeFieldResult) that check the current user's access permission levels. In this way, you can verify if the current user has the necessary permissions, and only if he or she has sufficient permissions, you can then perform a specific DML operation or a query.
For example, you can call the isAccessible, isCreateable, or isUpdateable methods of Schema.DescribeSObjectResult to verify whether the current user has read, create, or update access to an sObject, respectively. Similarly, Schema.DescribeFieldResult exposes these access control methods that you can call to check the current user's read, create, or update access for a field. In addition, you can call the isDeletable method provided by Schema.DescribeSObjectResult to check if the current user has permission to delete a specific sObject.
http://www.salesforce.com/us/developer/docs/apexcode/index_Left.htm#StartTopic=Content/apex_classes_perms_enforcing.htm#kanchor431
Have you tried the runAs() method?
Something like (not verified):
User u = [SELECT Id FROM User WHERE Name='John Doe'];
System.runAs(u) {
if (Schema.sObjectType.Contact.fields.Email.isAccessible()) {
// do something
}
}
The DescribeSObjectResult class has methods for checking CRUD.
E.g. this allows you to test whether or not the current user can update the account object in general.
Schema.DescribeSObjectResult drSObj = Schema.sObjectType.Account;
Boolean thisUserMayUpdate = drSObj.isUpdateable();
#John De Santiago: your article covers record level access rather than object CRUD (= object level access)
Very old post. Since then SF add option to query object permission:
Select SobjectType ,ParentId, PermissionsEdit, PermissionsRead
From ObjectPermissions
Order by ParentID, SobjectType ASC
Basically you will need to get the profile and permissionset of the user that you want to check and the relevant object. So it will be something like:
Select SobjectType ,ParentId, PermissionsEdit, PermissionsRead
From ObjectPermissions
where parentId IN :UserProfileIdAndPermission
AND sObjectType=:objectType
Order by ParentID, SobjectType ASC

Can anyone explain how CDbCriteria->scopes works?

I've just checked the man page of CDbCriteria, but there is not enough info about it.
This property is available since v1.1.7 and I couldn't find any help for it.
Is it for dynamically changing Model->scopes "on-the-fly"?
Scopes are an easy way to create simple filters by default. With a scope you can sort your results by specific columns automatically, limit the results, apply conditions, etc. In the links provided by #ldg there's a big example of how cool they are:
$posts=Post::model()->published()->recently()->findAll();
Somebody is retrieving all the recently published posts in one single line. They are easier to maintain than inline conditions (for example Post::model()->findAll('status=1')) and are encapsulated inside each model, which means big transparency and ease of use.
Plus, you can create your own parameter based scopes like this:
public function last($amount)
{
$this->getDbCriteria()->mergeWith(array(
'order' => 't.create_time DESC',
'limit' => $amount,
));
return $this;
}
Adding something like this into a Model will let you choose the amount of objects you want to retrieve from the database (sorted by its create time).
By returning the object itself you allow method chaining.
Here's an example:
$last3posts=Post::model()->last(3)->findAll();
Gets the last 3 items. Of course you can expand the example to almost any property in the database. Cheers
Yes, scopes can be used to change the attributes of CDbCriteria with pre-built conditions and can also be passed parameters. Before 1.1.7 you could use them in a model() query and can be chained together. See:
http://www.yiiframework.com/doc/guide/1.1/en/database.ar#named-scopes
Since 1.1.7, you can also use scopes as a CDbCriteria property.
See: http://www.yiiframework.com/doc/guide/1.1/en/database.arr#relational-query-with-named-scopes

Tricky class design issue

I'm working on implementing a class for managing user permissions on my website.
For example: employees can view customer records but nothing else, employers can view customers as well as manage employees, and admins can do both those things as well as manage employers.
So far, what I've got is this:
I've stored a list of permissions, e.g addCustomer, delCustomer, etc. Each permission is linked to a list of the user roles which are allowed to do that action.
I've got a simple permissions class built. I'm using it something like this:
if ($permissions->can('addCustomer'))
echo " Add Customer ";
else
echo 'Not allowed to add customers';
However the tricky part is that in some places, I need to be more specific. For example: a customer has got the permission: readMsgs which allows him to read the messages between himself and an employee. However, if he has that permission, then he can simply change the url from:
site.com/messages/read/100
to
site.com/messages/read/101
And read message # 101 as well, which might be between another customer and employee. A customer shouldn't be able to read anyone's messages except himself.
Similarly, a customer has got the editCustomer permission, which allows him to edit his own profile by going to:
site.com/customers/99
(where 99 is his customer id)
But if he goes to
site.com/customers/100
He should not be allowed to access that page.
How can I solve this problem? Ideally I'd like to be able to pass on an id to the permissions class. E.g:
if (! $permissions->can('readMsg', $msgId))
echo 'not allowed';
if (! $permissions->can('editCustomer', $requestedCustomerId))
echo 'not allowed';
Any ideas how I'd have to restructure my class structure to allow the above kind of thing?
I would be more granular in my taxonomy of permissions (e.g., "readOwnMsgs" vs. "readAnyMsg"). This would elaborate your permission-checking code (e.g., site.com/messages/read/### goes something along the lines of "proceed if canReadAnyMsg or if canReadOwnMsg and message author is current user"), suggesting that this logic should be encapsulated in separate classes broken down by resource type or whatever other circumstances might have an effect on contextual information required to make such decisions.
I would have a message class with a canRead(User) function. This would check the user's permissions and say "Oh, I'm a message from a manager to an employee. Unless the user is the reciepient of the message, they can't read it." or just as easily "I'm a message from a manager to an employee. The user is a manager, so he can read it."
I'm typing it out in English because I suck a php (which appears to be the language of choice.)