So I have an application that has several modules (think of modules as different pages), each module has a set of permissions; view, add, edit, delete
I want each user role to have privileges for each module, for example
Role A Permissions
Module 1 -> view
Module 2 -> add, edit
Module 3 -> view, add, edit, delete
etc.
How can I design the database to support that and how would I go about implementing it using bitwise operators (or would there be a more efficient way for this particular case?)
I already have the user, user_role and role tables but I'm unsure on how to design the Module table.
Here I am showing how we can implement it with Mysql.
Below is a sample tables with some sample data:
Table 1 : Permission table to store permission name along with it bit like 1,2,4,8..etc (multiple of 2)
CREATE TABLE IF NOT EXISTS `permission` (
`bit` int(11) NOT NULL,
`name` varchar(50) NOT NULL,
PRIMARY KEY (`bit`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Insert some sample data into the table.
INSERT INTO `permission` (`bit`, `name`) VALUES
(1, 'User-Add'),
(2, 'User-Edit'),
(4, 'User-Delete'),
(8, 'User-View'),
(16, 'Blog-Add'),
(32, 'Blog-Edit'),
(64, 'Blog-Delete'),
(128, 'Blog-View');
Table 2: User table to store user id,name and role. Role will be calculated as sum of permissions.
Example :
If user 'Ketan' having permission of 'User-Add' (bit=1) and 'Blog-Delete' (bit-64) so role will be 65 (1+64).
If user 'Mehata' having permission of 'Blog-View' (bit=128) and 'User-Delete' (bit-4) so role will be 132 (128+4).
CREATE TABLE IF NOT EXISTS `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`role` int(11) NOT NULL,
`created_date` datetime NOT NULL
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Sample data-
INSERT INTO `user` (`id`, `name`, `role`, `created_date`)
VALUES (NULL, 'Ketan', '65', '2013-01-09 00:00:00'),
(NULL, 'Mehata', '132', '2013-01-09 00:00:00');
Loding permission of user
After login if we want to load user permission than we can query below to get the permissions:
SELECT permission.bit,permission.name
FROM user LEFT JOIN permission ON user.role & permission.bit
WHERE user.id = 1
Here user.role "&" permission.bit is a Bitwise operator which will give output as -
User-Add - 1
Blog-Delete - 64
If we want to check whether a particular user have user-edit permission or not-
SELECT * FROM `user`
WHERE role & (select bit from permission where name='user-edit')
Output = No rows.
Click here for more information.
If you decide to use a bitmask, remember that the number of permissions you can keep track of is limited (you can track 31 permissions in a signed 4-byte integer database column). Each permission would then be assigned a value that is a power of two (1, 2, 4, 8, etc), and you could perform bitwise operations to check for permission matches.
From what you're looking to accomplish, I would suggest creating a role_has_module_privs table instead. This approach is much more scalable, and more efficient from a querying perspective. But if you have a finite number of combinations, bitmasks may be more efficient.
Related
I am trying to understand the best way to work with permissions and as far as I'm aware there are two major options.
The first option is to use bitwise operations, and for this I would use the following database structure:
users
user_id | user_permission
---------------------
1 | 15
2 | 1
permissions
permission_id | permission_name
-----------------------
1 | Read
2 | Write
4 | Execute
8 | Delete
And then to check that the user has permission I would use the operation:
$user_permission & $permission_id
The main benefits I see to this are:
Trivial to set, get, and validate permissions
Less storage (no child database; no additional rows per user permission)
The main drawbacks I see to this are:
Listing users' permissions slightly more complicated
Cannot use foreign key constraints
Limited permissions (64 if using BIGINT)
The second option is to use a many-to-many child table, and for this I would use the following database structure:
users
user_id
-------
1
2
permissions
permission_id | permission_name
-----------------------
1 | Read
2 | Write
3 | Execute
4 | Delete
user_permissions
user_id | permission_id
-----------------------
1 | 1
1 | 2
1 | 3
1 | 4
2 | 1
And then to check that the user has permission I would use the operation (where $user_permission is an array of permission_ids):
in_array($permission_id, $user_permission);
The main benefits I see to this are:
Can use foreign key constraints
Trivial to list users' permissions
Allows for a far greater number of permissions
The main drawbacks I see to this are:
Greater storage (child database; additional rows per user permission)
Setting and getting permissions slightly more complicated
Question
Which of these would be the better option? I see benefits and drawbacks to each and am unsure which would be more suitable. Although I am aware that context probably plays a role; so in which situations would bitwise operations be better and in which would a many-to-many child table be better? Or is there a third option of which I'm unaware?
I'm currently more inclined to use a many-to-many table for the benefits of foreign key constraints and a greater number of permission possibilities, but I wonder if I'm missing something else; bitwise operation permissions seem to be quite prevalent so I'd assume there is a good reason for using them.
I Think bitwise operator are the best way to implement user permission.
Here I am showing how we can implement it with Mysql.
Below is a sample tables with some sample data:
Table 1 : Permission table to store permission name along with it bit like 1,2,4,8..etc (multiple of 2)
CREATE TABLE IF NOT EXISTS `permission` (
`bit` int(11) NOT NULL,
`name` varchar(50) NOT NULL,
PRIMARY KEY (`bit`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Insert some sample data into the table.
INSERT INTO `permission` (`bit`, `name`) VALUES
(1, 'User-Add'),
(2, 'User-Edit'),
(4, 'User-Delete'),
(8, 'User-View'),
(16, 'Blog-Add'),
(32, 'Blog-Edit'),
(64, 'Blog-Delete'),
(128, 'Blog-View');
Table 2: User table to store user id,name and role. Role will be calculated as sum of permissions.
Example :
If user 'Ketan' having permission of 'User-Add' (bit=1) and 'Blog-Delete' (bit-64) so role will be 65 (1+64).
If user 'Mehata' having permission of 'Blog-View' (bit=128) and 'User-Delete' (bit-4) so role will be 132 (128+4).
CREATE TABLE IF NOT EXISTS `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`role` int(11) NOT NULL,
`created_date` datetime NOT NULL
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Sample data-
INSERT INTO `user` (`id`, `name`, `role`, `created_date`)
VALUES (NULL, 'Ketan', '65', '2013-01-09 00:00:00'),
(NULL, 'Mehata', '132', '2013-01-09 00:00:00');
Loding permission of user
After login if we want to load user permission than we can query below to get the permissions:
SELECT permission.bit,permission.name
FROM user LEFT JOIN permission ON user.role & permission.bit
WHERE user.id = 1
Here user.role "&" permission.bit is a Bitwise operator which will give output as -
User-Add - 1
Blog-Delete - 64
If we want to check weather a particular user have user-edit permission or not-
SELECT * FROM `user`
WHERE role & (select bit from permission where name='user-edit')
Output = No rows.
You can see also : http://goo.gl/ATnj6j
I would not go with the bitwise operations solution. Unless you are really really cramped for space, breaking this out into its own table and mapping table won't cost that much disk. It would be easier for people who aren't you to understand, and you can more easily enforce FK relationships this way. Also, as you mentioned, the number of permissions can grow virtually without limit. Depending on how you index the table, queries like "show me all users with Read permission" seems like it would be quicker to execute and easier to understand (that's subjective, I realize).
I'm working on a search engine using CakePHP 2.0 and am having difficulty finding the most efficient way to get the result I'm wanting.
Say I'm querying people and I get a set of 20 results with 5 age 20, 10 age 30 and 5 age 40. In addition, 15 of these people have brown eyes, 3 have blue and 2 have green. I want to find the most efficient way to get those specific counts. I'll then display these results on the page so that users can see what's in the results with those parameters. They will then be able to click on one of them to add that search parameter to the current query.
This isn't something that I can store in a database or cache at all because each search could be different and could/will return different results.
If I'm not explaining what I'm trying to do (this may be likely) there are several websites that do this. Cars.com uses this method when searching for cars. You search a generic search and then links on the side allow you to narrow your results. These links include counts of the current result set that fall within the specific parameter.
An idea has been to get the full result set and then parse through it generating the counts and this would work, but in my specific project I'm dealing with thousands of records and it seems like this could add additional load time to the page and/or strain on the server.
Here's a visual example:
Cars.com is likely using associated tags for each feature that is counted. With associated tags a table record has many and belongs to many feature tags.
So that they don't have to create a tag for each car price. They create price range tags.
For every car record there are associated tag records that hold all features of that car. You can then cache a count in the tag record of how many cars have that feature.
The SQL table structure might be something like this.
CREATE TABLE `cars` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`make` varchar(45) DEFAULT NULL,
`model` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `features` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(45) DEFAULT NULL,
`count` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `cars_features` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`car_id` int(11) NOT NULL,
`feature_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
For every Car record there can be multiple Feature records. These are associated to each Car via the cars_features table. When someone searches and finds Car XXXX you can then look up the Features of that car, and also display a cached count of how many cars have that feature.
EDIT:
To narrow the counts so that they are limited to only the cars that were discovered in the search. You'll need to first get a list of all the Car IDs and then perform a COUNT using a JOIN between the cars_features table and features.
Here is some sample data.
INSERT INTO `cars` (`make`, `model`) VALUES ('Ford', 'Explorer');
INSERT INTO `cars` (`make`, `model`) VALUES ('Hond', 'Civic');
INSERT INTO `cars` (`make`, `model`) VALUES ('Hond', 'Civic');
INSERT INTO `features` (`name`, `count`) VALUES ('Red', 2);
INSERT INTO `features` (`name`, `count`) VALUES ('Green', 1);
INSERT INTO `cars_features` (`car_id`, `feature_id`) VALUES (1, 1);
INSERT INTO `cars_features` (`car_id`, `feature_id`) VALUES (2, 1);
INSERT INTO `cars_features` (`car_id`, `feature_id`) VALUES (3, 2);
Assuming we searched for that returned two items so that our Car IDs were (1,2). We could find a feature count using the following SQL query.
SELECT `features`.`id`,COUNT(`features`.`id`)
FROM `cars_features`
JOIN `features` ON (`cars_features`.`feature_id`=`features`.`id`)
WHERE `cars_features`.`car_id` IN (1,2)
GROUP BY `features`.`id`
This will report that count for each Feature limited to just the Car records found.
I'll try to write the above in CakePHP model format.
$this->CarFeature->find('all',array(
'conditions'=>array('CarFeature.car_id'=>$ids),
'fields'=>array('Feature.id','COUNT(Feature.id)'),
'group'=>array('Feature.id'),
'contain'=>'Feature'
));
The question… Is it possible to add MySQL permissions to only allow to select fields based on permissions?
Example:
User user1 can only select/insert/delete from the users table where the column instance is equal to 1 (1 being passed via PHP).
User user2 can only select/insert/delete from the users table where the column instance is equal to 2 (1 being passed via PHP).
Here's the background info:
I'm creating an application with the same code base being used for multiple sites. Conditions within the app load different layouts. The multiple sites are using the same database because most information can be shared between sites. A user that registers on one site must also register on another site (this is how we want it because the sites are "by invitation only")
What I'm thinking of doing is to have users table: id, email, password, instance. The instance column would have the id of the site.
On the application layer every time I need to select/insert/delete from this table I append instance = [site_id] to the query... example: SELECT * FROM users WHERE email = '' AND instance = [site_id];
no, the permission is per table,db,server,etc but not for rows, however there is a solution, you can use view tables and set permission to user, for example
mysql> CREATE VIEW test.v AS SELECT * FROM t where email = '' AND instance = [site_id];
just create 2 view tables and grant access to those users
here is the Mysql documentation
It is not possible from what I know, MySQL doesn't allow conditional users.
Use one user for both sites and modify your all queries accordingly to your 'instance'. So every time you query something site-specific you add WHERE instance = $site_id.
MySQL does not facilitate using permissions to lock down certain rows based on the MySQL user that is connected to the database.
If you have users that you want to limit in such a way, it is probably best to not give them direct database access, but have them connecting through another layer.
Using a view is not a good idea - every user would have to use different queries (referencing their own personal view) to accomplish the same things. If you were just limiting the columns that a user could see (instead of the rows), a view would be a good solution.
It is possible to use stored procedures to accomplish something like what you're looking for:
# This table already exists in your schema
CREATE TABLE `user` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`email` VARCHAR(50) NOT NULL,
`instance` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB;
INSERT INTO `user`(`id`,`email`,`instance`)
VALUES ( NULL,'wat#lol.com','1');
INSERT INTO `user`(`id`,`email`,`instance`)
VALUES ( NULL,'huh#bwu.com','2');
INSERT INTO `user`(`id`,`email`,`instance`)
VALUES ( NULL,'no#yes.lol','1');
# This would be a new table you would have to create
CREATE TABLE `user_instance` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`user` VARCHAR(20) NOT NULL,
`instance` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `user_instance` (`user`,`instance`)
) ENGINE=INNODB;
INSERT INTO `user_instance`(`user`,`instance`) VALUES ('user1','1');
INSERT INTO `user_instance`(`user`,`instance`) VALUES ('user2','2');
# This is a stored procedure that might accomplish what you want
DELIMITER $$
CREATE PROCEDURE `p_get_users`()
BEGIN
SELECT `user`.`id`, `user`.`email`
FROM `user`
WHERE `user`.`instance` IN(
SELECT `instance`
FROM `user_instance`
WHERE `user` = USER());
END$$
DELIMITER ;
Below is my friend table,
I included 2 entries to show how it works, When a user adds a person as a friend it inserts 2 entries into the DB with this code;
<?PHP
//status 0=approved 1=declined approval 3=pending approval
$sql = "insert into friend_friend (userid,friendid,status,submit_date)
values
('$_SESSION[auto_id]','$friendid','0',now()),
('$friendid','$_SESSION[auto_id]','3',now())"; //Line above is my user ID, the other users ID, status 0 for approved on my side, date
//next entry is the receiving users entry, there ID, my ID, 3 for not approved yet, date
executeQuery($sql);
//So that code above is my php that adds a friend
//Below is my table scheme for the friends table
CREATE TABLE IF NOT EXISTS `friend_friend` (
`autoid` int(11) NOT NULL AUTO_INCREMENT,
`userid` int(10) DEFAULT NULL,
`friendid` int(10) DEFAULT NULL,
`status` enum('1','0','3') NOT NULL DEFAULT '0',
`submit_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`alert_message` enum('yes','no') NOT NULL DEFAULT 'yes',
PRIMARY KEY (`autoid`),
KEY `userid` (`userid`),
KEY `friendid` (`friendid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1756421 ;
--
-- Dumping data for table `friend_friend`
--
INSERT INTO `friend_friend` (`autoid`, `userid`, `friendid`, `status`, `submit_date`, `alert_message`) VALUES
(637229, 2, 1, '1', '2007-10-18 01:02:00', 'no');
INSERT INTO `friend_friend` (`autoid`, `userid`, `friendid`, `status`, `submit_date`, `alert_message`) VALUES
(637230, 1, 2, '1', '2007-10-18 01:02:00', 'no');
INSERT INTO `friend_friend` (`autoid`, `userid`, `friendid`, `status`, `submit_date`, `alert_message`) VALUES
(637231, 22901, 1, '1', '2007-10-18 02:24:05', 'no');
INSERT INTO `friend_friend` (`autoid`, `userid`, `friendid`, `status`, `submit_date`, `alert_message`) VALUES
(637232, 1, 22901, '1', '2007-10-18 02:24:05', 'no');
?>
What I am wanting to do is split the friend_friend table up into multiple tables based on user ID number
Like all user ID's between 1-20,000 go to one table, all userIDs 20,001-40,000, 40,001-60,000 all go to a different table
I am not sure how to do this best, I would need to detect which table a user should query when adding a new friend and well as when retrieving friend list of users
I assume in my code at the top, the 2 entries to add a user would have to be broken into 2 queries and update different tables probably?
Assuming you are using MySQL 5.1 or above, then you can use partitioning to do what you want. See the following links:
http://dev.mysql.com/doc/refman/5.1/en/partitioning.html
http://dev.mysql.com/tech-resources/articles/performance-partitioning.html
The term of art is "sharding" (to help you in literature searches, web searches, etc) -- or at least, one popular term of art (vocabulary is unfortunately not fully settled in this area). Once you do research it you'll learn that the concomitant problem is having to query all shards (and UNION ALL them, typically -- or sometimes aggregate them back in different ways) when you don't know where (part or all of) the answer(s) may be.
So, sharding (in particular "horizontal sharding", which is what you're doing here) should be done advisedly in application-specific ways, to try and group entries that are "together" so that as often as feasible checking a single shard will suffice. Vertical sharding (putting different columns, rather than rows, in different tables) is easier to design, as you need only examine the most frequent queries to ensure each of them can be entirely satisfied by very few (ideally only one) shard.
Oh, and, of course, this huge amount of delicate, advanced work is NOT really worth doing until it does get proven to be needed -- and then, it will be to ensure the database backend's work is split among many servers, because a single server just can't cut it any longer. You appear to be just trying to learn the very fundamentals of sharding (my apologies if I'm reading this wrong!-) and part of the problem -- like for other hard and important parts of system architecture -- is that there's no real motivation until the system size goes WELL above what's reasonable to present in a "toy application"...!-)
I'm creating a blog, and am storing user permissions (to post/edit/delete blog posts) in a mysql table.
Should I create one column per permission, or combine all percussions into a string in one column such as 101 would mean that a user could post and delete but not edit.
The reason I ask is that I am worried about having too many column in my table.
First of all, I would rule out combining all permissions into a single field. It seems economical at first, but it can turn into a bit of a problem if you will ever need to expand or modify your permissions structure.
Creating a column for each permission in the user table is a good design for a simple system, but may limit your future expandability.
I recommend implementing a many-to-many relationship between users and permissions. This allows you to add as many types of permissions you want without changing the schema. It is very easy to query with a simple join, and is portable to other databases.
You accomplish this by creating two new tables. Assuming the following schema:
CREATE TABLE `users` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`username` VARCHAR(100),
-- other user fields --
);
We can add the m2m permissions schema like this:
CREATE TABLE `permissions` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(50) NOT NULL UNIQUE,
);
CREATE TABLE `users_permissions` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY_KEY,
`user_id` INT NOT NULL,
`permission_id` INT NOT NULL
);
You might then add some sample users and permissions:
INSERT INTO `users` (DEFAULT, 'joe');
INSERT INTO `users` (DEFAULT, 'beth');
INSERT INTO `users` (DEFAULT, 'frank');
INSERT INTO `permissions` (DEFAULT, 'Administrator');
INSERT INTO `permissions` (DEFAULT, 'Write Blog');
INSERT INTO `permissions` (DEFAULT, 'Edit Blog');
INSERT INTO `permissions` (DEFAULT, 'Delete Blog');
And finally you can associate users with permissions like so:
-- joe gets all permissions
INSERT INTO `permissions` (DEFAULT, 1, 1);
INSERT INTO `permissions` (DEFAULT, 1, 2);
INSERT INTO `permissions` (DEFAULT, 1, 3);
INSERT INTO `permissions` (DEFAULT, 1, 4);
-- beth can write and edit
INSERT INTO `permissions` (DEFAULT, 2, 2);
INSERT INTO `permissions` (DEFAULT, 2, 3);
-- frank can only write
INSERT INTO `permissions` (DEFAULT, 3, 2);
For a smaller blog, you may not need a flexible schema like this, but it is a proven design. If you like, you can also take this one step further and create a role system. This works by giving each user a role (one-to-many), and each role has a number of permissions (many-to-many). This way permissions don't need to be set on a per-user basis, and you can simply assign them a role like "Administrator", or "Editor" or "Contributor", along with the associated permissions for that role.
My choice would be separate columns. Makes it easier to query later on if you are looking for specific permissions.
You might want to check out some standard designs on permissions, no need to invent the wheel for the 4th time :)
Consider mysql's (nonstandard) SET type. More experienced coders may favor a bit field (which is really what's underneath mysql's SET type).
Don't use a string because:
A string is a very inefficient way to store bit values -- you're using a byte per flag, where you only need a single bit
Querying against that field would require heinous string manipulations, and would never be efficient
I'd say it would be fine to put a Post, Edit and Delete Column.
But, if you take Wordpress's take on permissions, they simply serialize it into an array, and then store that array in a table of Settings (4 Columns: UserID, Settings Key, Setting Value). I think Wordpress's method only really works if you aren't going to give permissions their own table.
Another method is to do a User_ID - Permission Relationship Table. In one column put the User_ID, and in the other the permission. But, make each row a permissions. IE, if you wanted to give User ID 1 all permissions it would be:
Permissions: Add: 1, Edit: 2, Delete: 3
Table
Row 1: UserID: 1 Permission: 1
Row 2: UserID: 1 Permission: 2
Row 3: UserID: 1 Permission: 3
You could use bitwise combinations (bit fields) within one column like
const READ = 1;
const WRITE = 2;
const DELETE = 4;
...
so resulting permission would be
read-only: 1
read-write: 3
read & delete, but not write: 5
and so on...
To check the permission in a bit field, your query has to look like
SELECT * FROM table t WHERE t.permission & required_permission
with required_permission being the bitwise or of the required permission flags.
But I also would recommend to check out some resources to find out about standard designs...