I'm trying to implement RBAC, but a User can have multiple roles, each under a particular context.
The context is based on 3 factors, Company, Product and Region.
So User A could be an Admin of Company 1, and also be a Viewer of Company 2 > Product 3 > Region 4
What is the best approach for this kind of RBAC set up?
I'm hand rolling this at the moment - so looking for the best way to structure the DB tables that would allow for this level of granular access.
Unfortunately this is on a legacy application with runs PHP CodeIgniter / mySQL - so any modern existing libs are probably not compatible.
UPDATE
I have this so far. Permissions table maps to Roles, Roles are then assigned to users, and given context.
account_id | role_id | company_id | product_id | region_id |
------------------------------------------------------------
1 | 1 | | | |
2 | 2 | 1 | | |
2 | 3 | 2 | 1 | |
3 | 4 | 3 | 1 | 2 |
3 | 4 | 3 | 1 | 3 |
3 | 4 | 3 | 1 | 4 |
The SQL for this looks a bit like this...
SELECT DISTINCT account_id
FROM user_roles ur, role_permissions rp
JOIN permissions p ON rp.permission_id=p.id
WHERE (
#correct user that has a role with the permission we want
ur.account_id = 1
AND rp.permission_id IN (1,2,3)
AND ur.role_id = rp.role_id
#if the user is sys admin they can see everything
AND (
ur.role_id = 1
#if the user is company admin
OR ( ur.role_id = 2 AND ur.company_id=#cid )
#if the user is product admin
OR ( ur.role_id = 3 AND ur.company_id=#cid AND ur.product_id=#pid )
#if the user is any other role then the need explicit access
OR ( ur.role_id > 3 AND ur.company_id=#cid AND ur.product_id=#pid AND ur.country_code=#cc )
)
);
This works, but I'm sure there must be a better way to do this.
The next issue is how to then apply a hierarchy to the roles?
So a 'Admin' of a company > product > region can only create users, of their role or lower?
I would have to look up the role they have assigned at the context we are in, and then return every role who has a lower rank than the users one?
Could this context be better placed as a string? '1.3.gb' / '1.2' ? And then when looking up the role, I form the context string first?
User table can have a cross reference to a roll table, each roll has a specific role identification number with a can_write, can_read column (ect...)
Your heircachy could look something like this:
Users:
uid(int) auto_increment primary
role_id(int)
Roles:
rid(int) auto_increment primary
description(varchar)
// any access values ie: can_read(int)
Then you could set-up a sub roles table which allows viewings on regions.
Some example code for the accessing of these tables:
$stmp = (new PDO(...))->Prepare("SELECT role_id FROM Users WHERE uid = :userid");
$stmp->bindValue(":userid", $id); // I'd suggest storing a login key using sessions
$stmp->execute();
$user_permissions = $stmp->Fetch()['role_id'];
// Reference the Roles table for permission lists
$stmp = (new PDO(...))->Prepare("SELECT * FROM Roles WHERE rid = :roleid");
$stmp->bindValue(":roleid", $user_permissions);
[...]
Hope this helped.
Related
I'm not sure why I'm struggling with this it seems like a very simple concept. So my struggling makes me think that perhaps my data modeling needs another component...
I'm using Laravel 5 and am trying to define some model relationships. BelongsTo,HasA, etc. Before I can write the code, I need to at least conceptually understand what type of relationship I'm creating.
I have an application to where users can send people referral links, if a person clicks on the link and signs up, their user record makes note of the code that referred them. This way I can trace back and see who referred a particular user. But a referral is NOT necessary to sign up
Tables:
USERS
+----+-------------+
| id | referral_id |
+----+-------------+
| 1 | 1 |
| 2 | null |
| 3 | 2 |
+----+-------------+
REFERRALS
+----+---------------+---------+
| id | referral_code | user_id |
+----+---------------+---------+
| 1 | 12345 | 2 |
| 2 | 54321 | 2 |
| 3 | 99999 | 2 |
+----+---------------+---------+
USERS.REFERRAL_ID references REFERRALS.ID
and
REFERRALS.USER_ID references USERS.ID
But what kind of relationships are these?
The only one that seems obvious to me is that REFERRALS.USER_ID belongs to USERS.
But what about USERS.REFERRAL_ID, saying it belongsTo Referrals doesn't feel right, as that record isn't required and I don't feel like it 'owns' the user by any means. Saying it hasA referral doesn't feel correct either, as again the user doesn't own or even require the referral.
I guess what is confusing me is that REFERRALS is an optional entity.
How should I conceptualize the relationship between USERS.REFERRAL_ID and REFERRALS.ID?
Is it bad to have this sort of "circular reference"? Would I be better off creating a pivot table?
No need to add any reference to the Referrals table in the User table, you already have that relation defined in the referral table ( user_id column )
Further reading: https://en.wikipedia.org/wiki/Database_normalization
The Relationship is
USER has many REFERRALS
REFERRAL belongs to USER ( inviter )
REFERRAL belongs to USER ( invitee )
Modify your REFERRALS table
+----+---------------+---------+------------+
| id | referral_code | user_id | invitee_id |
+----+---------------+---------+------------+
| 1 | 12345 | 2 | 1 |
| 2 | 54321 | 1 | null |
| 3 | 99999 | 3 | 1 |
+----+---------------+---------+------------+
user_id is the id of the user that sends the invitation
invitee_id is the id of the user that accepts and registers
invitee_id column is nullable() and will contain the id of the invitee from users table when they join.
Think of it as a JOIN table between inviter and invitee.
Here is my pivot table project_group:
+-----+----------+------------+----------+---------+
| ids | group_id | project_id | admin_id | user_id |
+-----+----------+------------+----------+---------+
| 4 | 115 | 1 | 1 | [3,4,5] |
| 5 | 115 | 2 | 1 | [5,2,1] |
| 6 | 115 | 3 | 1 | [1,3,6] |
This table represent group linked to the projects....user_id is which users can see projects/group... Is there any way to display correct projects/group only to the users defined in user_id?
Also content in user_id field can be changed....
The best way to handle this would be to first normalize your database. Storing comma separated lists in a cell is allowed, but generally bad practice, as explained in this question.
If you can have multiple users per project, you should have a linking table with a column for project and a column for user, like this:
project_users:
| project_id | user_id |
and you can make (project_id, user_id) a composite primary key.
That way, you can select the users for a project (say, project 1) like this:
SELECT user_id
FROM project_users
WHERE project_id = 1;
Once you have these, you can display the project data only to users whose id is returned in the above list.
I have built an SQL Fiddle that helps demonstrate this visually, if it helps.
It is good to note that this proper normalization gives the opportunity to a lot of useful data as well, as it becomes easier to search for users by project, but also you can search for project information based on a user.
I'm designing a very simple RBAC (Role Based Access Control) system in my PHP project and after giving it some thought I've came up with a solution, but without knowing much about building business systems I'm not sure if there are, or would be, any major design flaws with my solution.
So basically, I want to give a user a set of 'roles'. I will use these roles to allow or deny access to certain functionality on the application.
Here is the roles table:
# Roles
- id [auto-increment, unsigned]
- role [string, length:50]
# User_Roles
- user_id [FK:user_id, unsigned]
- role_id [FK:roles_id, unsigned]
Note: user_id and role_id to be unique index
The problem that concerned me was there was no information in the database about what the role actually does. But then I started to think if that was important. Because if the roles had meaningful names, and a structure, then you could query the user_roles table to get all the users roles and then within code something like:
# Fetch user with ID 1 from database
$user = User::find(1);
# Fetch the roles the user has from the database
# #returns : Array of roles
$userRoles = $user->roles()
# $userRoles = ['newmember', 'member.post', 'member.chat']
# Can the user send a message?
if(in_array('member.message', $userRoles)
{
# User can send a message
}
else
{
# User can not send a message
}
The roles can be managed, and can have whatever meaning they like within the organisation. I'm just concerned that the roles in the database have no meaning, I can't help but think there's probably a better way to achieve this.
Would my solution be feasible long term?
Thanks
Here's my approach to such RBAC systems, first I'd split the application into modules, at least logically.
Think of the modules as entities/resources in your application on which certain actions can be performed on. A resource could be a User, a Membership, a Product, ..
Let's assume we're building a site like Stackoverflow and we have the following modules:
Questions (Q&A section)
Chat
Each of these modules register themselves in the applications database table called modules which could look like:
# Modules
- id [an application-wide unique module identifier, provided by the module]
- name [string, human readable]
Each of these modules come with a set of actions which are pre-defined by the module, e.g actions to create a question, kick users from chat rooms etc. These actions get installed along with the modules into the applications database table called 'actions', which could look like:
# Actions
- module_id [reference to the modules table, is the namespace for the token]
- action [string / int, action identifier provided by the module, unique in the scope of the module]
- name [string, human readable name]
- Primary Key: [module_id, action]
Let's define the modules and actions for our Stackoverflow clone now:
Modules
+------------------------------------------+
| ID | Name |
+------------------------------------------+
| questions | Question and Answer Module |
| chat | Chat Module |
+------------------------------------------+
Along with the modules, the actions will be installed:
Actions
+-----------------------------------------------+
| Module | Action | Name |
+-----------------------------------------------+
| questions | read | Read Questions |
| questions | create | Create Questions |
| questions | edit | Edit Questions |
| questions | delete | Delete Questions |
| questions | vote | Vote on Questions |
| | | |
| chat | join | Join the Chat |
| chat | kick | Kick users |
| chat | create | Create Chatrooms |
+-----------------------------------------------+
The important thing here is that you can not modify the entries in these tables directly as admin of the systems so there's no GUI
to add/remove actions etc.
The next step is to create some roles for our system, this is done over the admin's user interface, roles can be defined arbitrarly.
Let's start off with some basic roles:
Q&A User:
- can read questions
- can create questions
Q&A Moderator:
- can read questions
- can create questions
- can vote on questiosn
- can edit questions
Q&A Admin:
- can read questions
- can create questions
- can vote on questiosn
- can edit questions
- can delete questions
Chat User:
- can join the chat
Chat Moderator:
- can join the chat
- can kick users from the chat
Chat Admin:
- can join the chat
- can kick users from the chat
- can create chat rooms
First, the roles are created in the roles table:
# Roles
- id [auto-increment, unsigned]
- name [string, length:50]
Populated with our custom definitions:
Roles
+-----------------------+
| ID | Name |
+-----------------------+
| 1 | Q&A User |
| 2 | Q&A Moderator |
| 3 | Q&A Admin |
| 4 | Chat User |
| 5 | Chat Moderator |
| 6 | Chat Admin |
+-----------------------+
In our super fancy admin UI we now have a sidebar with a list of all installed modules and their associated actions. Since
our typical admin is super lazy and doesn't know anything about programming he can now conveniently assign actions to each
role with drag&drop, i.e assigning permissions to roles.
These assigned permissions are stored in our mapping table Roles_Actions:
# Roles_Actions
- role_id
- module_id
- action
PK: [role_id, module_id, action]
The populated table:
Roles_Actions
+--------------------------------+
| Role ID | Module ID | Action |
+--------------------------------+
| 1 | questions | read |
| 1 | questions | create |
| 2 | questions | read |
| 2 | questions | create |
| 2 | questions | vote |
| 2 | questions | edit |
...
| 6 | chat | join |
| 6 | chat | kick |
| 6 | chat | create |
+--------------------------------+
Now we have to assign the roles to the users in the systems, let's say we have four users initially:
- Chuck Norris which is a Q&A Admin and also a Chat Admin (UID = 1)
- Bruce Willis which is a Q&A Moderator and a Chat User (UID = 2)
- Dilbert which is a Q&A Moderator and a Chat Moderator (UID = 3)
# User_Roles
- user_id [FK:user_id, unsigned]
- role_id [FK:roles_id, unsigned]
The populated Table:
Users_Roles
+---------------------------------+
| User ID | Role ID |
+---------------------------------+
| 1 | 3 (= Q&A Admin) |
| 1 | 6 (= Chat Admin) |
| 2 | 2 (= Q&A Moderator) |
| 2 | 4 (= Chat User) |
| 3 | 2 (= Q&A Moderator) |
| 3 | 5 (= Chat Moderator) |
+---------------------------------+
So one user can have multiple roles, and the permissions (module/action pairs) are then merged together in the application layer.
If you want to go one step further you could also provide some sort of inheritance in the role model, e.g the Q&A Moderator
could be defined as child of Q&A User, inheriting all permissions from the Q&A User and extending it with moderator rights.
So how could the authorization look like on the application layer?
Let's assume Dilbert which is a Q&A Moderator and Chat Moderator logs into the system, then you collect all his roles and all
permissions assigned to those roles. Next you start to build a permission tree and remove all duplicate permissions, the permission
tree could be represented as an associative array like:
Dilbert's permission tree:
$dilberts_permissions = array(
"questions" => array("read", "create", "vote", "edit")
"chat" => array("join", "kick")
)
For convenience we create a simple helper function like:
function has_permission($path, $tree) {
list($module, $action) = explode('.', $path);
return (array_key_exists($module, $tree) && in_array($action, $tree[$module]));
}
In our code we can now check if Dilbert is allowed to do certain things with a syntax like:
has_permission("questions.create", $dilberts_permissions); // => TRUE
has_permission("questions.delete", $dilberts_permissions); // => FALSE
has_permission("chat.join", $dilberts_permissions); // => TRUE
has_permission("chat.create", $dilberts_permissions); // => FALSE
I've got a project, to build a model agency cms.
The back end and other parts are okay for me, what I am inexperienced with is the different users, and I am stuck with the logic.
My logic would be this
Create a groups table with the following names and levels
id | group_id | level
1 | Admin | 20
2 | Moderator | 10
3 | Model | 1
4 | Photographer | 1
5 | Stylist | 1
6 | Agency | 1
the group id and user id would be saved in an users groups table like this
group_id | user_id
1 | 1
1 | 5
1 | 6
3 | 10
And here comes what I am stuck with it, so since these users have different data, I was thinking to create multiple forms for them with some fields hidden what is not needed for the actual user type, and when someone browses the profile, a switch chase would be made for the group check, and show different profiles
example
switch ($user->groupId) {
case 3:
// model profile
break;
case 4:
// photographer profile
break;
// and others
}
Is it a good logic in a way? Could somebody show me some examples or give me a hint?
Thank you
Edit
I am not using framework, i have made my own basic cms based on propel
IMO the best way to achieve this is to try using Single Table Inheritance or Concrete Table Inheritance in Propel. You can read about it here http://propelorm.org/Propel/documentation/09-inheritance.html
Hey all,
I am setting up a PHP web app that will make use of subdomains for accounts. I am storing subdomains in a MySQL table with the following fields:
subdomain_id | owner_id | name | date_created
owner_id maps back to user_id in the user table The user table has the following fields:
user_id | email_address | etc...
Now I am trying to figure out what is the best way to store which users have access to which subdomain. Is the best to set up another table with the following fields?
id | subdomain_id | user_id
That would contain data such as the following (showing user #6 has access to subdomains 4 & 7):
id | sudomain_id | user_id
1 | 4 | 6
2 | 4 | 23
3 | 7 | 6
Is there a more efficient way?
That is the correct way to model a many-to-many relationship, but the id column is entirely unnecessary. You don't need to give every table an artificial identifier. The primary key of that table is simply (subdomain_id, user_id).