How can I determine permissions in a hierarchical

2019-07-19 16:11发布

问题:

I’m trying to create performant logic for determining permissions within a hierarchical organization.

Employees are assigned to one or more units. Units are hierarchical with (theoretically) infinite depth (in reality it’s no more than 6 layers).

For example, employee Jane may be the Supervisor of the Accounts Receivable unit (a child of the Accounting unit), and also Member of the Ethics Committee (a child of Committees, which is itself a child of Office of the CEO).

As the Supervisor of Accounts Receivable, Jane should have permission to view personnel files of everyone else in Accounts Receivable, but not in the Ethics Committee since she’s just a Member. Similarly, regular employees of the Accounts Receivable unit should not be able to view one another’s profiles, though they’d all need permission to, say, view the accounting records of the company.

I imagine the database architecture for this will look something like:

| **employees** | **units**   | **positions** | **assignments** | **permissions** |
| ------------- | ----------- | ------------- | --------------- | --------------- |
| id            | id          | id            | employee_id     | unit_id         |
| name          | name        | title         | unit_id         | is_management   |
|               | parent_path | is_management | position_id     | ability         |

With that in mind, how can I write a performant query to determine which permissions Jane has over Sam, an Accountant in Accounts Receivable, versus over Bill, a Receptionist in Office of the CEO?

The closest I have is something like:

create function permissions(actor employees, subject employees) returns setof permissions as $$
begin
  for unit_id in select unit_id from assignments where employee_id = subject.id loop
    select permissions.name
    from assignments
    left join units on (unit.id = assignments.unit_id)
    left join positions on (positions.id = assignments.position_id)
    left join permissions on (
      permissions.unit_id = units.id
      and permissions.is_management = positions.is_management
    )
    where assignments.user_id = actor.id
      and (units.parent_path = unit_id or units.parent_path @> unit_id)
  end loop;
end;
$$ language plpgsql stable;