Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement business logic permissions in PostgreSQL?

Let's assume I have a table of items:

CREATE TABLE items
(
    item serial PRIMARY KEY,
    ...
);

Now I want to introduce the concept of "permissions" for each item (note, I'm not talking about database access permissions here, but business logic permissions for that item). Each item has default permissions and also per-user permissions that may override default permissions.

I tried to think of ways to implement this and came up with the following solutions:

1) The Boolean Solution

Use a boolean column for each permission:

CREATE TABLE items
(
    item serial PRIMARY KEY,

    can_change_description boolean NOT NULL,
    can_change_price boolean NOT NULL,
    can_delete_item_from_store boolean NOT NULL,
    ...
);

CREATE TABLE item_per_user_permissions
(
    item int NOT NULL REFERENCES items(item),
    user int NOT NULL REFERENCES users(user),

    PRIMARY KEY(item, user),

    can_change_description boolean NOT NULL,
    can_change_price boolean NOT NULL,
    can_delete_item_from_store boolean NOT NULL,
    ...
);

Advantages: Each permission is named.

Disadvantages: There are dozens of permissions which increases the number of columns significantly and you have to define them twice (once in each table).

2) The Integer Solution

Use an integer and treat it as a bitfield (i.e. bit 0 is for can_change_description, bit 1 is for can_change_price, and so on, and use bitwise operations to set or read permissions).

CREATE DOMAIN permissions AS integer;

Advantages: very fast.

Disadvantages: You have to keep track of which bit stands for which permission in both the database and the front-end interface.

3) The Bitfield Solution

Same as 2), but use bit(n). Most likely the same advantages and disadvantages, maybe slightly slower.

4) The Enum Solution

Use an enum type for the permissions:

CREATE TYPE permission AS ENUM ('can_change_description', 'can_change_price', .....);

and then create an extra table for default permissions:

CREATE TABLE item_default_permissions
(
    item int NOT NULL REFERENCES items(item),
    perm permission NOT NULL,

    PRIMARY KEY(item, perm)
);

and change the per-user definition table to:

CREATE TABLE item_per_user_permissions
(
    item int NOT NULL REFERENCES items(item),
    user int NOT NULL REFERENCES users(user),
    perm permission NOT NULL,

    PRIMARY KEY(item, user, perm)    
);

Advantages: Easy to name individual permissions (you don't have to handle bit positions).

Disadvantages: Even when just retrieving the default permissions, it requires accessing two additional tables: first, the default permissions table, and second, the system catalog storing the enum values.

Especially because the default permissions must be retrieved for every single page view of that item, the performance impact of the last alternative might be significant.

Can you think of other alternatives?

Which approach should be taken?

Please note: this question has been reposted on DBA.

like image 893
JohnCand Avatar asked Nov 10 '22 10:11

JohnCand


1 Answers

It really will depend on what level you need to go to for permissions

Role Based - Not User

User Based - (harder to maintain)

or Combination of the two

User and Roles...

The following on our systems User Table -> contains UserNames etc

Roles => list of Roles such as Office Support, Mechanic

Security Level -> List of Levels e.g. Read Only, Administrator

Application Function Table -> contains functions that can have permissions applied e.g. Can_Edit_Users

Application Security Table -> Username + Application Function Id , Role and Either bit for yes/no permission - or an integer for a more complex Role Level using the Security Level.

This gives the most flexibility but can be difficult to build the UI for.

In the application we get the users permissions once, and do a function to check if they have permissions

e.g. CurrentUser.HasPermissions("Can_Edit_Users")

public bool HasPermissions(string Permission)
{
return this.Permissions.Any(p=>p.FunctionName==Permission && IsAllowed==true);
}
like image 170
Traci Avatar answered Nov 14 '22 22:11

Traci