User permissions fine-grain control PHP MySQL - php

I am building a web app which users use to assign tasks to each other.
The users are all on a LAN.
There is a basic permissions system already, where user roles are either Admin or normal user.
What I have to implement is something really fine-grained...and I cant think of a good way to do this
Heres a list of things I need to have control over:
If a user can change other users info
If the user can access the LAN network via VPN(this is another challenge all together, i think)
Users can assign tasks to only a set of other users(maybe all, maybe few)
If user has the ability to change/delete an already assigned task from another user
If the user can delete other users
If a user can change other users permissions
What I want is an example of something similar so that I dont have to re-invent the wheel. Or maybe an idea on how to implement this with as little headache as possible :P. Also I dont know how I would structure a MySQL table/s to store all these permissions.
I thought of assigning users roles but I need more flexibility than just those roles.
Any ideas?

One simple approach is to only have one extra column in the user table, called Access, Permission or something like that. Then for each of the permissions you want you define a constant with a integer value that's dividable by 2, or the number 1, e.g:
class PermissionChecker {
const CAN_CHANGE_OTHER_USERS_INFO = 1; // in bit form: 000001
const CAN_ACCESS_LAN_VIA_VPN = 2; // 000010
const CAN_ASSIGN_TASKS = 4; // 000100
const CAN_CHANGE_OTHER_USERS_TASKS = 8;// 001000
const CAN_DELETE_USERS = 16; // 010000
const CAN_CHANGE_USER_PERMISSIONS = 32;// 100000
public static function hasAccess($userAccess, $accessFlag) {
return $userAccess & $accessFlag; // The bitwise operation is the key....
}
}
$userAccess = 33; // read from database, in bit form: 100001
// check permissions like:
if (PermissionChecker::hasAccess($userAccess, PermissionChecker::CAN_CHANGE_OTHER_USERS_INFO)) {
// this will be true, because 100001 & 000001 returns 1
}
if (PermissionChecker::hasAccess($userAccess, PermissionChecker::CAN_CHANGE_USER_PERMISSIONS)) {
// this will be true as well, because 100001 & 100000 returns 1
}
if (PermissionChecker::hasAccess($userAccess, PermissionChecker::CAN_CHANGE_OTHER_USERS_TASKS)) {
// this will not happen... because 100001 & 001000 returns 0
}
Then when another permission level is needed you only have to add another number that's dividable by 2, e.g. 64, 128, etc. Maybe not the prettiest solution, but a simple one.
And when you want to store permissions for a user you use the bitwise-or operator, like:
$userAccess = PermissionChecker::CAN_CHANGE_OTHER_USERS_TASKS |
PermissionChecker::CAN_CHANGE_OTHER_USERS_INFO; // will give $userAccess = 9;
// then store $userAccess in the database for current user...

One option is to use a database table to store the permissions where a row is a single permission. If a permission is not present in the table for a given user_id then that user does not have the permission.
user_id | permission
-----------------------------
1 | CHANGE_USER_INFO
1 | DELETE_USERS
1 | CHANGE_USER_PERMISSIONS
2 | VPN_ACCESS
As you can see, user 1 would be able to change info, delete and change permissions. User 2 can only access the VPN. This would be easy to add and remove permissions, you'd just need to insert or delete. It would also be easy to create new permission types in future.
To address your point number 3. That one would be more complicated. For that you could have another table with columns user_id and target_user_id where you would insert the user_id of which users can assign tasks to. Alternatively you could add another column to the table above like a generic ID, then insert ASSIGN_TASK and populate the ID column with the target user.

Allow the administrator to define roles, as well as define which powers to give each role. That can be done with a bitwise comparison (good), or with a many-to-many relationship database tables (better).
This system gives you most flexibility, as it easily allows you to add new abilities without reconfiguring the whole system of roles.

Related

Protecting against record ID manipulation

How do you prevent a malicious user from changing URL or form data, specifically a record ID. For example:
http://example.com/deleteproduct.php?id=34
The user could change the ID value from 34 to say 69 and in doing so delete a record belonging to another customer. I guess the obvious protection is to validate the ID before performing the delete to make sure the user has access to that record but is there perhaps another approach that is consider better practice? The downside of validating the ID requires more database queries which would be great to avoid.
I guess the obvious protection is to validate the ID before performing the delete to make sure the user has access to that record.
This is the only way to ensure that your user has access to delete these rows.
The downside of validating the ID requires more database queries which would be great to avoid.
Not necessarily. You can simply check when you're deleting to only delete rows that belong to your user.
For example, assuming your table structure looks similar to:
users
-----
id | username
1 | Dave
2 | John
products
-----
id | name | user_owner
1 | Milk | 1
2 | Cake | 2
So if Dave visited deleteproduct.php?id=2, the following query would execute:
DELETE FROM products WHERE id = 2 AND user_owner = 1;
It wouldn't delete anything, and $mysqli->affected_rows would return zero.
When affected rows is zero it means that the product ID was invalid or the product didn't belong to the user, either way: you would display a message telling the user that the product id is invalid.
Do they have authorization to delete items? If so, does it matter?
If it's per-record authorization... just check if they have authorization for the requested id. Long answer short: check if they're authorized and never trust user input.
What you said is very dangerous.
Security
In the first place you need to protect data and make it only available for the selected users. Please create or finetune your access list with roles and permissions.
Show only links/buttons for elements that may be accessed by the (logged in) user
Check before access the elements by the (logged in) user the permissions (and role). Not only the action, but also the data
Authentication (user logged in or not?), authorization (has the user the right permissions to access the (data) element?)
In the second place, it's not a right approach to use internal id's public.
It's always good to hide your internal id's, because it makes your application more safe.
Data Access
You have to access your data. The most simple idea is checking it in your query, like so:
DELETE FROM table WHERE id = 34 AND user_id = 1 // Delete only if id = 34 and user is 1
Do you understand the idea?
Internal Id's
You can encode your id's by using existing algorithms. Decoding is only possible with your secret key. There are many solutions and packages.
Hashids is a small open-source library that generates short, unique, non-sequential ids from numbers. (http://hashids.org/php/)

Validating users upon login and giving them limited rights to database

I'm new to PHP and MYSQL, trying to create a website which users can use to input data into a database. An example of what i'm trying to do would be a database for various banks and the various services they provide.For example, a user from Citibank creates an account on my website, he will enter his LoginID,Password,Email & the name of his bank(which would be Citibank in this case).
Upon successfully creating an account and logging in, he would be the "Admin" account for Citibank with the rights to Create,Delete,Insert & View all data from Citibank ONLY. He would also be able to further create & delete Outlets, and create/delete a SubUser account for that outlet.The SubUser account would have all the rights the Admin account would have minus the right to create further SubUsers, BUT restricted to only the Outlet it is in charge of.Both Admin and Sub accounts would be logging in through the website.
I've listed down the rights which i think the accounts would need:
Rights to database
SELECT,INSERT,UPDATE,DELETE,(JOIN?)
I am currently thinking of implementing the following table for the Admin account:
Admin
+----------+-----------+------------+------------+
| BankID | BankName | UserName | Password |
+----------+-----------+------------+------------+
| 1 | Citibank | CitiAdmin | PassCiti |
| 2 | StanChart | StanAdmin | PassStan |
| 3 | HSBC | HSBCAdmin | PassHSBC |
+----------+-----------+------------+------------+
Where the BankID would be of type SERIAL, while the BankName,UserName and Password would be entered by the user upon creation of his account.The reason why i do not split the above table into 2 tables with one containing the BankID and BankName and the other containing Username & Password would be for ease of use as i feel that splitting it up would be needless, and be over-normalising it.
While the following table would be for the Subuser accounts:
SubUsers
+------+------------+--------------+-------------+
| ID | OutletID | Name | Password |
+------+------------+--------------+-------------+
| 1 | 1 | CitiSub1 | PassSub1 |
| 2 | 1 | CitiSub2 | PassSub2 |
| 3 | 2 | StanSub1 | PassSub1 |
| 4 | 2 | StanSub2 | PassSub2 |
| 5 | 3 | HSBCSub1 | PassSub1 |
| 6 | 4 | HSBCSub2 | PassSub2 |
+------+------------+--------------+-------------+
By doing this, upon user login, i would get the userentry from $_POST[User] and $POST[Pass] and match if against the data drawn from the query
$query="SELECT Username AND Password FROM Admin AND SubUsers";
and if there is a match, the user will be logged in.By doing this i am able to achieve a first level of verification where only registered users are able to access the database.
However how would i restrict access to both the Admin account, AND the SubUser account.The Admin account would only be able to access data pertaining to his Bank, and the SubUser account would only be able to access data pertaining to his Outlet.
I've considered using PHP sessions to perhaps record data about the user when logging in by changing the login query from
$query="SELECT Username AND Password FROM Admin AND SubUsers";
to a query that first selects Username and Password from Admin, and runs the $_POST[User] and $_POST[Pass] through it, and if there isnt a match it would draw Username and Password from SubUser and repeat the process, and would log a result into the session depending if the match happened in the Admin table or SubUser table.
However,doing this would only change the webpages available to the user upon login and not their actual access to the database itself.The closest solution i can think of using this method would be to create a brand new set of webpages for the user depending on whether the user is an Admin or SubUser, which i would rather NOT do as i am still new to programming, and increasing the number of webpages would only increase the number of bugs that will ineveitably show up.
Are there any other methods to restrict user access to the database, and or other solutions to optimise what i'm trying to do?
I've looked at How to configure phpMyAdmin for multiple users - each with access to their database only but it's a little too technical for me and seems to be dealing with user access to databases instead of tables.
Any advise/help/guidance will be MUCH appreciated.
What an interesting and thorough question. It is rather of the type that requires a book to answer thoroughly though. I admire your ambition.
First design it properly.
Ask yourself what actions users might need to do and give them a name. Once you store the privelege names in a table, you can assign them to roles or users as required. You authenticate the ability to do each thing at PHP level, either by checking before each action that the appropriate privelege is applied or by writing each action as a function that includes authentication of priveleges.
Put the bank id and branch id as Foreign Keys in each table. That way you simply include the bankid and branchid as 'AND' additions to your WHERE clause. This wa you need only one database but you control who gets to see what using intelligently written SQL.
If you need the users to be able to run SQL on their data, ensure that all queries are run through a function that adds the requisite AND (bankid='%s' AND branchid='%s') clause. This effectively separates the data. You can add a check of your returned data if you need to and also consider using encryption (different key for each bank) though that is going a bit far.
This pretty much is what is meant by application-layer control. The PHP application selects what data you have access to based on stored priveleges. I cannot re-inforce how important it is to plan your priveleges, given them meaningful names and verbose descriptions. It seems a lot of work when you start but it makes the difference. it certainly beats having to create a new database for each user. Don't worry about filling up your SERIAL ids - a BIGINT can handle a million transactions per second for over 200,000 years.
Once designed, authentication is the next hurdle. I reckon you should do this before you write anything fancy as it's really quite hard to get right.
What I would do is:
Collect bank,branch and username (allow these to autocomplete in your HTML) and then password.
Store the password as an SHA1 or MD5 hash.
Once authenticated, you pop the usernumber, bank and branch numbers into your $_SESSION They can then easily be retrieved for SQL later.
For added security, though increased complexity, you can also pick these numbers out of the database as required. Some recommend storing them in a separate session table.
There is so much more to say about how to design this sort of project and much of it can be found elsewhere on this site so I will not prattle on further. Please feel free to ask if anything is unclear.
I hope this helps.
EDIT:
Handling the priveleges.
There is no simple way to handle priveleges. I use a single header file for all my pages that automatically extracts privelege information:
a. Identify the user, usually picking the usernumber from $_SESSION.
b. Identify the user's priveleges from the DB table users_priveleges.
c. Create an array containing the privelege names.
d. For Each through the array to compare whenever a privelege-required operation is required.
This method needs a lot of tables and is perhaps a bit advanced for your needs but if you have the following tables (skeleton details provided here only) it is pretty much infinitely expandable:
roles (role_id,rolename,role_detailed_description)
priveleges (privelege_id,privelegename,privelege_detailed_description)
users (user_id,user_details)
users_roles (user_id,role_id) (optional but a good idea)
users_priveleges (user_id,privelege_id) - priveleges granted to each user
roles_priveleges (role_id,privelege_id) - the priveleges each role has.
What you do is enter a line in the roles_priveleges table linking a role to a privelege. Repeat for all priveleges required by the role. Could be a lot. Not a problem.
When a user is added, you grant them a role. I then read the roles_priveleges table and present the super-user with a list of possible roles as checkboxes, ticked if the privelege would usually be granted, not if otherwise. The super-user deselects or selects from the list as required then saves the list.
On saving the list, I mark all entries for that user in the users_priveleges table as inactive and insert a new line for each privelege. This allows you to track the changes and, importantly, the date the priveleges were reviewed, even if they were not changed. It does not end up using much data as each line in users_priveleges consists of three Bigints,a bool and 2 dates.
If you never want to grant one user a privelege that their role would not normally posess then you can simply use roles_priveleges and users_roles. This is minimally less data-hungry but is notably less flexible.
I will concede the method I have described is a little inelegant but it provides very good role based and user based privelege management whilst keeping the DB in the 4th Normal Form or higher. IMHO it is worth going the extra mile because your application will one day be bigger and it is far easier to add this stuff now rather than later.
Also, from a beginner's point of view, it is very easy to create dummy data and ensure your SQL joins are working before you embark on something a bit harder.

PHP displaying links based on user permissions?

I'm trying to show or not show links based on a users access level. The links will be different depending on the section of the site the user might be in. The links also may not all be in one menu. They will more than likely be in various places on the page.
Currently I have a database table that contains Users, Groups and Sections. The main menu is built from the Sections database table. I'm thinking I should create an Actions table and add a link that I'd like to show for each section in the action menu. So, my tables so far are like.
Users
user_id
Groups
group_id
group_title
Sections
section_id
section_title
Table I'm thinking of adding.
Actions
action_id
action_title
action_group_id
action_section_id
The part I'm not sure on is should I add the same link multiple times to the Actions table for each group that is allowed access. Or, just add it once and do a if group id is greater than, then show link.
Example for entering the same link multiple times.
action_id action_title action_group_id action_section_id
1 View all 1 1
2 View all 2 1
3 View all 3 1
I was hoping to not flood the page with a bunch of if/then statements. Plus, this doesn't seem like the best way to handle because it requires human interpretation as to what the access levels stand for.
Any help on this is appreciated. I could be going in the complete wrong direction here?
Create a many to many relationship with an additional table where you insert an entry for each permission the group has access to. Am I correct in assuming section is what you're creating permission to?
Table: Group_Section (Or whatever you'd like to name it)
Group_id | Section_ID
---------+-----------
1 | 1
1 | 2
1 | 3
2 | 1
2 | 3
|
You can see that the Group with ID = 1 can access sections 1,2,3 while Group with ID = 2 can access only 1,3. You can then add whatever permissions to the table you want and manage them through the use of foreign keys.
Does that make sense?
Here is a good article but the things are discussed in general http://en.wikipedia.org/wiki/Access_controlenter link description here
In your case, use what TheCapn wrote and I'll just add, that its 'best to start session for every user and just check his access level when he's trying to reach a restricted part.
Personnaly, to do this kind of thing, i set a user level in the user table and a section level in the section table.
Then you simply have to filter the section according to your user level.
You can do this by adding a statemtn to you sql like
AND section_level >= "user_level";
or then again, get all the section and filter tham with php.
foreach($section as $s){
if ($s->level >= user_level) echo $s->title
}
Of course, you'll need to adjust the <. = and > according to the hierachy of your system.
I personnaly use a lowering hierachy, meaning, the lower the level you are the more right you have. This way you can make a 'banned' user by setting his level to 99 or something.
THis would be only for your menus, make sure you control the user_level on each page as well so if someone get to the page directly it get kicked..
Hope it points your in the right direction. ;)

Permissions to view Entries in MySQL Database Front-End

I've got a PHP/HTML/Javascript driven front-end for a MySQL database which archives different files/papers for our office (kind of an electronic index for physical paperwork).
The users want to be able to have permissions on each of these entries; for instance HR complaints need to be indexed, but should not be viewable by all users of the database.
The user heirarchy is two-tier. Each user is a member of one OR MORE distribution lists (similar to an Email list). When a file is indexed, the user choses the permissions for others: for instance he/she can select the following for a sample HR complaint:
List | Permission
`````````````````````````````````
HR Dept | Read/Write
Board Members | Read
John Smith | Read/Write
Mary Smith | Read
and should be invisible to anybody else. Now I've tried several things to implement this, the most recent being a relations table which relates the following:
User 1->Many DistributionList // Assoc the user to some lists
Permission 1->Many DistributionList //Indicates level of permission that the list has
Permission 1->Many User //Indicates level of permission for each user
However the permission table contains a row for each file for each permission, which, given a few thousand files and ~50-60 lists/users, means a few hundred thousand entries. Since this index will not be flushed often (maybe flush files older than 50 years) that number could skyrocket. Not to mention that the queries are somewhat complicated, and take a decent amount of time (~1 second for the SQL request) for only a couple hundred files.
Is there a more efficient way to create this sort of User based stuff? Is it possible instead to make users in SQL itself with these permissions and let the connection handle these things?
tl;dr: What is the best way to put read/write/invisible permissions on entries in MySQL using PHP, Javascript, HTML and PHPMyAdmin?
If there are not many groups (64 max) you could use a SET of permissions for each file (1 extra column).
http://dev.mysql.com/doc/refman/5.0/en/set.html
So each file could have one column denoting which groups it belongs to, which can be expanded as the systems grows.
You could also add a second column if you want to go beyond 64, but that would require more complex queries (keeping departments separate would help here, HRgroups, Boardgroups etc..)
You could also add exceptions to specific users or create another group. But as you say one associating entry per file/user is the best your going to get.

Best practices for website permissions system

Current project I am working on is a web application, that has to be delivered to multiple customers on their own servers. The website needs a permission control system, that will manage areas and features users can or can not use.
As for know, I'm designing a database-driven permissions system, with permissions stored in database. Every user has a role. The role defines the list permissions available. The schema looks like this:
users table
1. user_id
2. name
3. role_id
roles table
1. role_id
2. name
permissions table
1. permission_id
2. name
roles_permissions table:
1. role_id
2. permission_id
In the code I would fetch logged users role and permissions, and check if the user is able to perform action or see area like so:
if($user->hasPermission('Edit HR')) {
// let user see the editing HR section
}
The hasPermission would check if user has a permission with a name 'Edit HR' and will return the needed result. The problem I see is the database table has to have the permission record with a name being exactly 'Edit HR', not 'Edit_hr' or 'HR Editing'. So I have to make sure the data for the permissions system is the same for every database the applications are using. Which kind of makes me think this is a flawed design and needs to be re-designed. Creating new sections and features would also require to update all the databases, which also makes me a sad panda.
So, basically, the question is: what is the best way to design the database driven permission system and keep the database integrity on multiple databases?
The scheme you've come up with looks fine. The only thing I would add to that is on the permissions table I would add a field called tag or similar.
The permission tag would be something like EDIT_HR, and you would use this as the reference in your code instead of its name. Use the name just for display purposes for example HR Editing. This way the name can vary as required, and it won't affect your code.
The solution I'm using is to have a global $current_user object that reads the permissions table on creation and stores all permission actions that are valid for it. This array is then searched whenever you need to check an action. It saves on DB queries, although if there are security implications for storing this kind of data in a global object, I haven't found it.
There's only 1 db table required (sample):
user_id | user_role | user_action
---------------------------------
0 | 10 | view_dashboard
0 | 1 | view_users
User role corresponds to the minimum user type (Admin, editor, visitor, etc.) so all actions with a user_role >= $current_user role are available. The user_id column allows you to override certain levels for a specific user.
With this kind of setup, it's also easy to have a page that lists all permissions and allows a user to modify the value with a simple dropdown (but make sure not every user can do that).

Categories