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).
Related
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/)
I'm looking to create a database for users with multi-level user rights and I don't know how to go about doing this. What I mean is that I want a manager of a business to be able to purchase my product; that person would be given Owner rights, but would also be able to grand additional users under that license--those people would be given Manager or User rights. Each level (as well as my level: Admin, and my staff: SuperUser) would obviously have individual rights/privileges).
What I'm asking, more specifically, is how to set up the database. For example, if my business is a corporate calendar/organizer, the Owner would set up departments, each with a Manager and many Users. What's the best and most efficient way to structure the database? Like, would each user (and each calendar entry) have to be associated with an ID that belongs to that specific Owner account? I'm just a little lost as to what the best way to organize the database to keep everything together, as I will have multiple different Owners with their own company structure under them.
I want to use MySQL and PHP.
I tried to make this as logical as possible. I think I'm making it too hard, but I am sure there is a standard that makes it easier....Thanks in advance.
At the very least every product/object whatsoever needs a foreign_key in its table, as for example the user's id. This is necessary and sets the relation from the product/object with the user.
And then it depends on how complex you want your system to become. An easy way would be to just use boolean columns in the user table, like an admin, an editor column and so on, with only true and false as values. In your code you could then use if and case to check if a user is an admin and show him parts of your app or not. Like a delete link for example. But you could also restrict updating and deleting to people whose user has a true value in the sufficient column.
The more complex route would include other id-fields in the tables which set a relation of something to something else. Like say you want the user to be a seller or a buyer, then you would add seller_id and buyer_id columns to the products table and check if the ids correspond with the user_id. But not "the" user_id, but a different user_id which you saved when the user created the product listing for example. This way you could guarantee, that besides your staff the user who created this thing has rights to edit it, too, because of the product's user_id being the same as his user_id (current user) when he is logged in to your system.
You could do even more complex relations but then you'd have to create another table and save other ids in it which relate certain users with say other users. In this table you save let's say a maintainer_id and a maintained_id, both have values of certain user_ids but this way you could make a relation between objects one user could change, though they belong to others. Or if you're talking of customers so the mainter_id would be allowed to write messages to those people with maintained_id, like if someone is a seller and the others are potential buyers.
I'm having a little trouble understanding exactly what you're looking for. From what I've gathered, it seems you want a database that holds permissions, users, and departments. In this very basic example I've created 3 tables. (assuming one user can only belong to one department)
You could set a foreign key in the users table which links to the primary key in the permissions table. The departments table would have the foreign key of the user_id.
You could base all of the logic on what each permission can do with your queries and application side logic.
(I can't embed images due to not having 'enough rep')
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.
I might be approaching the problem from the wrong point of view so please correct me if my technique is wrong.
I'm creating guest users on a website with php. When someone visits the website, a guest user is automatically created. But if that person logs into a "normal" account (non guest) then whatever that person did in the guest account is transferred to the new account.
Example, someone visits a website without authenticating, add products from the shop in his cart and then logs in. I will need to transfer the cart items from the old user (guest) to the new user.
But it would be too time consuming (and possibly too resource hungry) to find every item in every table that is linked to the guest user account and then change them all. Right?
But I've got constraints! Yay! If I update the guest user id, all the constraints change. Fabulous! But what if I need to assign them to another user id? How would I do that?
Any help is greatly appreciated.
I am using session array to manage it, previously i was doing through database table but it was too tedious.
When user add any product to cart if he is logged in then insert in order tables and if not then add in session array. Now when guest login into system then check session array and if it is not null then add all entries in user order tables.
You can't do exactly what you're suggesting because the user row (for the logged in user) presumably already exists. The only option would be to go through each table and update the UserID there, like you suggest. The feasability of this depends on what they can do as a guest user. If it's only one or two tables, just do it this way.
But, every problem can always be solved by another layer of abstraction, right? Put another single table (I'll call it "pseudousers") in between the real users table and everything else. Link everything else to this new table, and use the "psuedousers" to link up to the users table. When the guest user logs in, just change the UserID in the pseudousers table and you're done.
What I mean is:
+---------+ +-----------+ +-------+
| Users |----|Pseudousers|----| Carts |
+---------+ +-----------+ +-------+
|
| +-------+
|------------| ... |
+-------+
When you create a guest user, you create a new row in Users and Pseudousers. When they log in, update the UserID in Pseudousers to point to that user, and then you can safely delete the guest user row in Users (it's no longer in use).
Hopefully someone can come up with a better name than "Pseudousers" though.
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.