Hack 61. Set Security Based on Rows

You can use a VIEW to set user-based access rights on a column-by-column basis. You can also set access rights on a per-row basis.

The standard SQL permissions system lets you control access on a per-user, per-table basis. If you want scott to be able to see the t table, for instance, you issue this command:

GRANT SELECT ON t TO scott

You can get even finer control by setting up a view that shows only part of the table, and granting access to that instead. The following creates a view, v, which excludes rows with the words Top Secret in the f column. You then revoke Scott's access to t and grant it on v:

CREATE VIEW v AS SELECT * FROM t WHERE f != 'Top Secret'; REVOKE SELECT ON t FROM scott; GRANT SELECT ON v TO scott;

You can do the same kind of thing with INSERT, UPDATE, and DELETE, in place of the SELECT command. You can also refer to the PUBLIC role to mean any named user.

SQL Server, Oracle, and PostgreSQL support the PUBLIC role. MySQL does not support PUBLIC.

Updating or inserting via a view in Postgre SQL 8.2 or below is not supported. Hopefully this will be remedied in a later version of PostgreSQL.

 

8.4.1. Make Use of Usernames

Suppose you've got an employee table where every staff member has a department, a boss, and a salary (see Table 8-3). You can set up the system so that each user has permission to see only the details of their minions.

Table 8-3. The employee table

UserName Name Department Salary Boss
Hall Col. Hall HQ 8000

NULL

Bilko Sgt. Bilko Motor pool 5000 Hall
Doberman Pvt. Doberman Motor pool 3000 Bilko
Barbella Cpl. Barbella Motor pool 4000 Bilko

In the employee table it is important that the actual SQL username is included in the table details because this hack uses the CURRENT_USER [Hack #59] to control permissions.

You can create a single VIEW, minions, that looks different to each user:

CREATE VIEW minions AS SELECT * FROM employee WHERE boss=CURRENT_USER

Some database systems vary as to how you can access the CURRENT_USER information. See "Define Personalized Parameters" [Hack #59].

Now, when user bilko logs into the system and queries this view, he sees the details for Doberman and Barbella. If user hall tries the same query he will see only Bilko's record. You can allow users doberman and barbella full access to the view, but when they run the query it will return zero rows.

To enforce permissions you need to revoke access to the underlying table from everyone (or simply not give any access in the first place), and allow everyone SELECT and, possibly, UPDATE and DELETE permission to the VIEW:

GRANT SELECT, UPDATE, DELETE TO PUBLIC ON minions

Now Bilko can see the salary for his employees:

mysql> SELECT * FROM minions; +--------------------+---------------+------------+--------+---------------+ | userName | name | department | salary | boss | +--------------------+---------------+------------+--------+---------------+ | barbella@localhost | Cpl. Barbella | Motor pool | 4000 |bilko@localhost| | doberman@localhost | Pvt. Doberman | Motor pool | 3000 |bilko@localhost| +--------------------+---------------+------------+--------+---------------+ 2 rows in set (0.00 sec)

He can change any of their details. Here, Bilko gives Doberman a pay raise:

mysql> UPDATE minions -> SET salary = 3100 -> WHERE name='Pvt. Doberman'; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0

He has permission to try the same thing on Col. Hall (or indeed himself), and no error is generated when he tries it. However, when he attempts the change, zero rows match, and Col. Hall's salary remains unchanged:

mysql> UPDATE minions -> SET salary = 3100 -> WHERE name='Col. Hall'; Query OK, 0 rows affected (0.00 sec) Rows matched: 0 Changed: 0 Warnings: 0

 

8.4.2. Hacking the Hack

You have considerable flexibility with a schema such as this, and you will need it to make it work because there are some potential problems.

8.4.2.1. One-way trap

For instance, there is the potential for users to make mistakes that cannot be recovered. Bilko can set the boss value for one of his men, but after he has done that he cannot change it back again. If he sets the boss to an employee who does not exist or who cannot be contacted, there is no way to fix the error without the DBA intervening.

8.4.2.2. Supervisor mode

It is useful to have a "supervisor" user who is trusted and who can see and change anything. The supervisor can fix problems such as the one-way trap and set up and maintain the hierarchy. You still have the underlying employee table and you could give appropriate permissions on that, but you can do better.

You could change the minions view so that anyone in the personnel office can see all rows, for example. The code to do that would be:

CREATE VIEW minions AS SELECT * FROM employee WHERE boss=CURRENT_USER OR CURRENT_USER IN (SELECT userName FROM employee WHERE dept='Personnel')

Of course, if you do something like that you are going to have to be a little less generous with your permissions. Even if you can't see how this scheme could be circumvented you can be sure that Sgt. Bilko and Cpl. Barbella will figure out how to do it. The scam goes like this: Bilko sets Barbella's department to Personnel, Barbella can now run any trick the two of them cook up, and Barbella then sets his own department back to Motor pool and covers his tracks.

Категории