Reputation: 2321
I am designing a database that restricts access to certain objects. My colleague and I have discussed different ways to approach this, with there being to main candidates: 1) Implicit access, and 2) Explicit access
For illustration, let's assume there are the following tables:
User (Id)
ObjectA (Id, ParentId) -- Where ParentId is an ObjectA
ObjectB (Id, ObjectAId)
UserObjectA (UserId, ObjectAId) -- Grants access to an ObjectA
UserObjectB (UserId, ObjectBId) -- Grants access to an ObjectB
Implicit approach:
Explicit approach:
The implicit approach has two benefits: 1) Save space when a user has access to many objects implicitly, and 2) a user who implicitly has access to all objects will have access to all objects added in the future without having to trigger inserts or handle the inserts in sprocs when objects are created.
The explicit approach has the benefit that queries are much simpler, maintanable, and more performant.
Initially, we ran with the implicit approach. However, after getting into the implementation of various sprocs, the logic to handle the access is becoming a beast and there are various subtleties that we've run into that make this approach more error-prone than the explicit approach. (Note that the real scenario is somewhat more complicated than the simplified example.) I'm finding myself constantly implementing recursive CTEs to determine access, which doesn't allow me to (when considering performance) abstract away certain parts of the logic in views or inline TVFs. So I have to repeat and tweak the error-prone logic in lots of different sprocs. If anything ever changed, we'd have a big maintenance task on our hands.
So, have we made a mistake going with this implicit access approach? I'm definitely having second thoughts and would appreciate advice form anyone who has experience with similar design decisions.
Upvotes: 2
Views: 71
Reputation: 48297
If you can wait a month Postgres 9.5 will be out and has Row Security. Oracle has it now if you have ten million bucks kicking around.
For now, or in other dbs, you can mimic row security:
Each protected table gets an "owner" column. By default only the owner can select, update, or delete that row.
Each "child" table also has an owner column, with a cascading foreign key to the parent table. So if change parent.owner, then this changes all children.owners as well
Use updateable CHECK OPTION views to enforce security.
You need to set current_user from your application. Here's how for pg + spring
In Postgres:
create schema protected;
create table protected.foo (
foo_id int primary key,
bar text,
owner name not null default_current user
);
create table protected.foo_children (
foo_child_id int primary key,
foo_id int not null references foo(food_id),
owner name not null default current_user references foo(owner) on update cascade
);
Now some CHECK OPTION views - use security_barrier if postgres:
create view public.foo with (security_barrier) as
select
*
from protected.foo
where
owner = current_user
WITH CHECK OPTION;
create view public.foo_children with (security_barrier) as
select
*
from protected.foo_children
where
owner = current_user
WITH CHECK OPTION;
grant delete, insert, select, update on public.foo to some_users;
grant delete, insert, select, update on public.foo_children to some_users;
For sharing, you need to add some more tables. The important thing is that you can index the right columns so that you don't kill performance:
create schema acl;
create table acl.foo (
foo_id int primary key references protected.foo(foo_id),
grantee name not null,
privilege char(1) not null
);
Update your views:
create or update view public.foo with (security_barrier) as
select
*
from protected.foo
where
owner = current_user
or exists ( select 1 from acl.foo where privilege in ('s','u','d') and grantee = current_user) );
--add update trigger that checks for update privilege
--add delete trigger that checks for delete privilege
Upvotes: 1