]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/sql/Pg/006.schema.permissions.sql
Add hold_priority column to permission.grp_tree table.
[Evergreen.git] / Open-ILS / src / sql / Pg / 006.schema.permissions.sql
1 /*
2  * Copyright (C) 2004-2008  Georgia Public Library Service
3  * Copyright (C) 2008  Equinox Software, Inc.
4  * Mike Rylander <miker@esilibrary.com> 
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  */
17
18
19 DROP SCHEMA IF EXISTS permission CASCADE;
20
21 BEGIN;
22 CREATE SCHEMA permission;
23
24 CREATE TABLE permission.perm_list (
25         id              SERIAL  PRIMARY KEY,
26         code            TEXT    NOT NULL UNIQUE,
27         description     TEXT
28 );
29 CREATE INDEX perm_list_code_idx ON permission.perm_list (code);
30 CREATE TRIGGER maintain_perm_i18n_tgr
31     AFTER UPDATE ON permission.perm_list
32     FOR EACH ROW EXECUTE PROCEDURE oils_i18n_id_tracking('ppl');
33
34 CREATE TABLE permission.grp_tree (
35         id                      SERIAL  PRIMARY KEY,
36         name                    TEXT    NOT NULL UNIQUE,
37         parent                  INT     REFERENCES permission.grp_tree (id) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED,
38         usergroup               BOOL    NOT NULL DEFAULT TRUE,
39         perm_interval           INTERVAL DEFAULT '3 years'::interval NOT NULL,
40         description             TEXT,
41         application_perm        TEXT,
42         hold_priority       INT   NOT NULL DEFAULT 0
43 );
44 CREATE INDEX grp_tree_parent_idx ON permission.grp_tree (parent);
45
46 CREATE TABLE permission.grp_penalty_threshold (
47     id          SERIAL          PRIMARY KEY,
48     grp         INT             NOT NULL REFERENCES permission.grp_tree (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
49     org_unit    INT             NOT NULL REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
50     penalty     INT             NOT NULL REFERENCES config.standing_penalty (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
51     threshold   NUMERIC(8,2)    NOT NULL,
52     CONSTRAINT penalty_grp_once UNIQUE (grp,penalty,org_unit)
53 );
54
55 CREATE TABLE permission.grp_perm_map (
56         id              SERIAL  PRIMARY KEY,
57         grp             INT     NOT NULL REFERENCES permission.grp_tree (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
58         perm            INT     NOT NULL REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED,
59         depth           INT     NOT NULL,
60         grantable       BOOL    NOT NULL DEFAULT FALSE,
61                 CONSTRAINT perm_grp_once UNIQUE (grp,perm)
62 );
63
64 CREATE TABLE permission.usr_perm_map (
65         id              SERIAL  PRIMARY KEY,
66         usr             INT     NOT NULL REFERENCES actor.usr (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
67         perm            INT     NOT NULL REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED,
68         depth           INT     NOT NULL,
69         grantable       BOOL    NOT NULL DEFAULT FALSE,
70                 CONSTRAINT perm_usr_once UNIQUE (usr,perm)
71 );
72
73 CREATE TABLE permission.usr_object_perm_map (
74         id              SERIAL  PRIMARY KEY,
75         usr             INT     NOT NULL REFERENCES actor.usr (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
76         perm            INT     NOT NULL REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED,
77     object_type TEXT NOT NULL,
78     object_id   TEXT NOT NULL,
79         grantable       BOOL    NOT NULL DEFAULT FALSE,
80                 CONSTRAINT perm_usr_obj_once UNIQUE (usr,perm,object_type,object_id)
81 );
82
83 CREATE INDEX uopm_usr_idx ON permission.usr_object_perm_map (usr);
84
85 CREATE TABLE permission.usr_grp_map (
86         id      SERIAL  PRIMARY KEY,
87         usr     INT     NOT NULL REFERENCES actor.usr (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
88         grp     INT     NOT NULL REFERENCES permission.grp_tree (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
89                 CONSTRAINT usr_grp_once UNIQUE (usr,grp)
90 );
91
92 CREATE OR REPLACE FUNCTION permission.grp_ancestors ( INT ) RETURNS SETOF permission.grp_tree AS $$
93         SELECT  a.*
94         FROM    connectby('permission.grp_tree'::text,'parent'::text,'id'::text,'name'::text,$1::text,100,'.'::text)
95                         AS t(keyid text, parent_keyid text, level int, branch text,pos int)
96                 JOIN permission.grp_tree a ON a.id::text = t.keyid::text
97         ORDER BY
98                 CASE WHEN a.parent IS NULL
99                         THEN 0
100                         ELSE 1
101                 END, a.name;
102 $$ LANGUAGE SQL STABLE;
103
104 CREATE OR REPLACE FUNCTION permission.usr_perms ( INT ) RETURNS SETOF permission.usr_perm_map AS $$
105         SELECT  DISTINCT ON (usr,perm) *
106           FROM  (
107                         (SELECT * FROM permission.usr_perm_map WHERE usr = $1)
108                                         UNION ALL
109                         (SELECT -p.id, $1 AS usr, p.perm, p.depth, p.grantable
110                           FROM  permission.grp_perm_map p
111                           WHERE p.grp IN (
112                                 SELECT  (permission.grp_ancestors(
113                                                 (SELECT profile FROM actor.usr WHERE id = $1)
114                                         )).id
115                                 )
116                         )
117                                         UNION ALL
118                         (SELECT -p.id, $1 AS usr, p.perm, p.depth, p.grantable
119                           FROM  permission.grp_perm_map p 
120                           WHERE p.grp IN (SELECT (permission.grp_ancestors(m.grp)).id FROM permission.usr_grp_map m WHERE usr = $1))
121                 ) AS x
122           ORDER BY 2, 3, 1 DESC, 5 DESC ;
123 $$ LANGUAGE SQL STABLE;
124
125 CREATE TABLE permission.usr_work_ou_map (
126         id      SERIAL  PRIMARY KEY,
127         usr     INT     NOT NULL REFERENCES actor.usr (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
128         work_ou INT     NOT NULL REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
129                 CONSTRAINT usr_work_ou_once UNIQUE (usr,work_ou)
130 );
131
132 CREATE OR REPLACE FUNCTION permission.usr_can_grant_perm ( iuser INT, tperm TEXT, target_ou INT ) RETURNS BOOL AS $$
133 DECLARE
134         r_usr   actor.usr%ROWTYPE;
135         r_perm  permission.usr_perm_map%ROWTYPE;
136 BEGIN
137
138         SELECT * INTO r_usr FROM actor.usr WHERE id = iuser;
139
140         IF r_usr.active = FALSE THEN
141                 RETURN FALSE;
142         END IF;
143
144         IF r_usr.super_user = TRUE THEN
145                 RETURN TRUE;
146         END IF;
147
148         FOR r_perm IN   SELECT  *
149                           FROM  permission.usr_perms(iuser) p
150                                 JOIN permission.perm_list l
151                                         ON (l.id = p.perm)
152                           WHERE (l.code = tperm AND p.grantable IS TRUE)
153                 LOOP
154
155                 PERFORM *
156                   FROM  actor.org_unit_descendants(target_ou,r_perm.depth)
157                   WHERE id = r_usr.home_ou;
158
159                 IF FOUND THEN
160                         RETURN TRUE;
161                 ELSE
162                         RETURN FALSE;
163                 END IF;
164         END LOOP;
165
166         RETURN FALSE;
167 END;
168 $$ LANGUAGE PLPGSQL;
169
170 CREATE OR REPLACE FUNCTION permission.usr_has_home_perm ( iuser INT, tperm TEXT, target_ou INT ) RETURNS BOOL AS $$
171 DECLARE
172         r_usr   actor.usr%ROWTYPE;
173         r_perm  permission.usr_perm_map%ROWTYPE;
174 BEGIN
175
176         SELECT * INTO r_usr FROM actor.usr WHERE id = iuser;
177
178         IF r_usr.active = FALSE THEN
179                 RETURN FALSE;
180         END IF;
181
182         IF r_usr.super_user = TRUE THEN
183                 RETURN TRUE;
184         END IF;
185
186         FOR r_perm IN   SELECT  *
187                           FROM  permission.usr_perms(iuser) p
188                                 JOIN permission.perm_list l
189                                         ON (l.id = p.perm)
190                           WHERE l.code = tperm
191                                 OR p.perm = -1 LOOP
192
193                 PERFORM *
194                   FROM  actor.org_unit_descendants(target_ou,r_perm.depth)
195                   WHERE id = r_usr.home_ou;
196
197                 IF FOUND THEN
198                         RETURN TRUE;
199                 ELSE
200                         RETURN FALSE;
201                 END IF;
202         END LOOP;
203
204         RETURN FALSE;
205 END;
206 $$ LANGUAGE PLPGSQL;
207
208 CREATE OR REPLACE FUNCTION permission.usr_has_work_perm ( iuser INT, tperm TEXT, target_ou INT ) RETURNS BOOL AS $$
209 DECLARE
210         r_woum  permission.usr_work_ou_map%ROWTYPE;
211         r_usr   actor.usr%ROWTYPE;
212         r_perm  permission.usr_perm_map%ROWTYPE;
213 BEGIN
214
215         SELECT * INTO r_usr FROM actor.usr WHERE id = iuser;
216
217         IF r_usr.active = FALSE THEN
218                 RETURN FALSE;
219         END IF;
220
221         IF r_usr.super_user = TRUE THEN
222                 RETURN TRUE;
223         END IF;
224
225         FOR r_perm IN   SELECT  *
226                           FROM  permission.usr_perms(iuser) p
227                                 JOIN permission.perm_list l
228                                         ON (l.id = p.perm)
229                           WHERE l.code = tperm
230                                 OR p.perm = -1
231                 LOOP
232
233                 FOR r_woum IN   SELECT  *
234                                   FROM  permission.usr_work_ou_map
235                                   WHERE usr = iuser
236                         LOOP
237
238                         PERFORM *
239                           FROM  actor.org_unit_descendants(target_ou,r_perm.depth)
240                           WHERE id = r_woum.work_ou;
241
242                         IF FOUND THEN
243                                 RETURN TRUE;
244                         END IF;
245
246                 END LOOP;
247
248         END LOOP;
249
250         RETURN FALSE;
251 END;
252 $$ LANGUAGE PLPGSQL;
253
254 CREATE OR REPLACE FUNCTION permission.usr_has_object_perm ( iuser INT, tperm TEXT, obj_type TEXT, obj_id TEXT, target_ou INT ) RETURNS BOOL AS $$
255 DECLARE
256         r_usr   actor.usr%ROWTYPE;
257         res     BOOL;
258 BEGIN
259
260         SELECT * INTO r_usr FROM actor.usr WHERE id = iuser;
261
262         IF r_usr.active = FALSE THEN
263                 RETURN FALSE;
264         END IF;
265
266         IF r_usr.super_user = TRUE THEN
267                 RETURN TRUE;
268         END IF;
269
270         SELECT TRUE INTO res FROM permission.usr_object_perm_map WHERE usr = r_usr.id AND object_type = obj_type AND object_id = obj_id;
271
272         IF FOUND THEN
273                 RETURN TRUE;
274         END IF;
275
276         IF target_ou > -1 THEN
277                 RETURN permission.usr_has_perm( iuser, tperm, target_ou);
278         END IF;
279
280         RETURN FALSE;
281
282 END;
283 $$ LANGUAGE PLPGSQL;
284
285 CREATE OR REPLACE FUNCTION permission.usr_has_object_perm ( INT, TEXT, TEXT, TEXT ) RETURNS BOOL AS $$
286     SELECT permission.usr_has_object_perm( $1, $2, $3, $4, -1 );
287 $$ LANGUAGE SQL;
288
289 CREATE OR REPLACE FUNCTION permission.usr_has_perm ( INT, TEXT, INT ) RETURNS BOOL AS $$
290         SELECT  CASE
291                         WHEN permission.usr_has_home_perm( $1, $2, $3 ) THEN TRUE
292                         WHEN permission.usr_has_work_perm( $1, $2, $3 ) THEN TRUE
293                         ELSE FALSE
294                 END;
295 $$ LANGUAGE SQL;
296
297
298 CREATE OR REPLACE FUNCTION permission.usr_has_perm_at_nd(
299         user_id    IN INTEGER,
300         perm_code  IN TEXT
301 )
302 RETURNS SETOF INTEGER AS $$
303 --
304 -- Return a set of all the org units for which a given user has a given
305 -- permission, granted directly (not through inheritance from a parent
306 -- org unit).
307 --
308 -- The permissions apply to a minimum depth of the org unit hierarchy,
309 -- for the org unit(s) to which the user is assigned.  (They also apply
310 -- to the subordinates of those org units, but we don't report the
311 -- subordinates here.)
312 --
313 -- For purposes of this function, the permission.usr_work_ou_map table
314 -- defines which users belong to which org units.  I.e. we ignore the
315 -- home_ou column of actor.usr.
316 --
317 -- The result set may contain duplicates, which should be eliminated
318 -- by a DISTINCT clause.
319 --
320 DECLARE
321         b_super       BOOLEAN;
322         n_perm        INTEGER;
323         n_min_depth   INTEGER; 
324         n_work_ou     INTEGER;
325         n_curr_ou     INTEGER;
326         n_depth       INTEGER;
327         n_curr_depth  INTEGER;
328 BEGIN
329         --
330         -- Check for superuser
331         --
332         SELECT INTO b_super
333                 super_user
334         FROM
335                 actor.usr
336         WHERE
337                 id = user_id;
338         --
339         IF NOT FOUND THEN
340                 return;                         -- No user?  No permissions.
341         ELSIF b_super THEN
342                 --
343                 -- Super user has all permissions everywhere
344                 --
345                 FOR n_work_ou IN
346                         SELECT
347                                 id
348                         FROM
349                                 actor.org_unit
350                         WHERE
351                                 parent_ou IS NULL
352                 LOOP
353                         RETURN NEXT n_work_ou; 
354                 END LOOP;
355                 RETURN;
356         END IF;
357         --
358         -- Translate the permission name
359         -- to a numeric permission id
360         --
361         SELECT INTO n_perm
362                 id
363         FROM
364                 permission.perm_list
365         WHERE
366                 code = perm_code;
367         --
368         IF NOT FOUND THEN
369                 RETURN;               -- No such permission
370         END IF;
371         --
372         -- Find the highest-level org unit (i.e. the minimum depth)
373         -- to which the permission is applied for this user
374         --
375         -- This query is modified from the one in permission.usr_perms().
376         --
377         SELECT INTO n_min_depth
378                 min( depth )
379         FROM    (
380                 SELECT depth 
381                   FROM permission.usr_perm_map upm
382                  WHERE upm.usr = user_id 
383                    AND (upm.perm = n_perm OR upm.perm = -1)
384                                 UNION
385                 SELECT  gpm.depth
386                   FROM  permission.grp_perm_map gpm
387                   WHERE (gpm.perm = n_perm OR gpm.perm = -1)
388                 AND gpm.grp IN (
389                            SELECT       (permission.grp_ancestors(
390                                         (SELECT profile FROM actor.usr WHERE id = user_id)
391                                 )).id
392                         )
393                                 UNION
394                 SELECT  p.depth
395                   FROM  permission.grp_perm_map p 
396                   WHERE (p.perm = n_perm OR p.perm = -1)
397                     AND p.grp IN (
398                                 SELECT (permission.grp_ancestors(m.grp)).id 
399                                 FROM   permission.usr_grp_map m
400                                 WHERE  m.usr = user_id
401                         )
402         ) AS x;
403         --
404         IF NOT FOUND THEN
405                 RETURN;                -- No such permission for this user
406         END IF;
407         --
408         -- Identify the org units to which the user is assigned.  Note that
409         -- we pay no attention to the home_ou column in actor.usr.
410         --
411         FOR n_work_ou IN
412                 SELECT
413                         work_ou
414                 FROM
415                         permission.usr_work_ou_map
416                 WHERE
417                         usr = user_id
418         LOOP            -- For each org unit to which the user is assigned
419                 --
420                 -- Determine the level of the org unit by a lookup in actor.org_unit_type.
421                 -- We take it on faith that this depth agrees with the actual hierarchy
422                 -- defined in actor.org_unit.
423                 --
424                 SELECT INTO n_depth
425                     type.depth
426                 FROM
427                     actor.org_unit_type type
428                         INNER JOIN actor.org_unit ou
429                             ON ( ou.ou_type = type.id )
430                 WHERE
431                     ou.id = n_work_ou;
432                 --
433                 IF NOT FOUND THEN
434                         CONTINUE;        -- Maybe raise exception?
435                 END IF;
436                 --
437                 -- Compare the depth of the work org unit to the
438                 -- minimum depth, and branch accordingly
439                 --
440                 IF n_depth = n_min_depth THEN
441                         --
442                         -- The org unit is at the right depth, so return it.
443                         --
444                         RETURN NEXT n_work_ou;
445                 ELSIF n_depth > n_min_depth THEN
446                         --
447                         -- Traverse the org unit tree toward the root,
448                         -- until you reach the minimum depth determined above
449                         --
450                         n_curr_depth := n_depth;
451                         n_curr_ou := n_work_ou;
452                         WHILE n_curr_depth > n_min_depth LOOP
453                                 SELECT INTO n_curr_ou
454                                         parent_ou
455                                 FROM
456                                         actor.org_unit
457                                 WHERE
458                                         id = n_curr_ou;
459                                 --
460                                 IF FOUND THEN
461                                         n_curr_depth := n_curr_depth - 1;
462                                 ELSE
463                                         --
464                                         -- This can happen only if the hierarchy defined in
465                                         -- actor.org_unit is corrupted, or out of sync with
466                                         -- the depths defined in actor.org_unit_type.
467                                         -- Maybe we should raise an exception here, instead
468                                         -- of silently ignoring the problem.
469                                         --
470                                         n_curr_ou = NULL;
471                                         EXIT;
472                                 END IF;
473                         END LOOP;
474                         --
475                         IF n_curr_ou IS NOT NULL THEN
476                                 RETURN NEXT n_curr_ou;
477                         END IF;
478                 ELSE
479                         --
480                         -- The permission applies only at a depth greater than the work org unit.
481                         -- Use connectby() to find all dependent org units at the specified depth.
482                         --
483                         FOR n_curr_ou IN
484                                 SELECT ou::INTEGER
485                                 FROM connectby( 
486                                                 'actor.org_unit',         -- table name
487                                                 'id',                     -- key column
488                                                 'parent_ou',              -- recursive foreign key
489                                                 n_work_ou::TEXT,          -- id of starting point
490                                                 (n_min_depth - n_depth)   -- max depth to search, relative
491                                         )                             --   to starting point
492                                         AS t(
493                                                 ou text,            -- dependent org unit
494                                                 parent_ou text,     -- (ignore)
495                                                 level int           -- depth relative to starting point
496                                         )
497                                 WHERE
498                                         level = n_min_depth - n_depth
499                         LOOP
500                                 RETURN NEXT n_curr_ou;
501                         END LOOP;
502                 END IF;
503                 --
504         END LOOP;
505         --
506         RETURN;
507         --
508 END;
509 $$ LANGUAGE 'plpgsql';
510
511
512 CREATE OR REPLACE FUNCTION permission.usr_has_perm_at_all_nd(
513         user_id    IN INTEGER,
514         perm_code  IN TEXT
515 )
516 RETURNS SETOF INTEGER AS $$
517 --
518 -- Return a set of all the org units for which a given user has a given
519 -- permission, granted either directly or through inheritance from a parent
520 -- org unit.
521 --
522 -- The permissions apply to a minimum depth of the org unit hierarchy, and
523 -- to the subordinates of those org units, for the org unit(s) to which the
524 -- user is assigned.
525 --
526 -- For purposes of this function, the permission.usr_work_ou_map table
527 -- assigns users to org units.  I.e. we ignore the home_ou column of actor.usr.
528 --
529 -- The result set may contain duplicates, which should be eliminated
530 -- by a DISTINCT clause.
531 --
532 DECLARE
533         n_head_ou     INTEGER;
534         n_child_ou    INTEGER;
535 BEGIN
536         FOR n_head_ou IN
537                 SELECT DISTINCT * FROM permission.usr_has_perm_at_nd( user_id, perm_code )
538         LOOP
539                 --
540                 -- The permission applies only at a depth greater than the work org unit.
541                 -- Use connectby() to find all dependent org units at the specified depth.
542                 --
543                 FOR n_child_ou IN
544                         SELECT ou::INTEGER
545                         FROM connectby( 
546                                         'actor.org_unit',   -- table name
547                                         'id',               -- key column
548                                         'parent_ou',        -- recursive foreign key
549                                         n_head_ou::TEXT,    -- id of starting point
550                                         0                   -- no limit on search depth
551                                 )
552                                 AS t(
553                                         ou text,            -- dependent org unit
554                                         parent_ou text,     -- (ignore)
555                                         level int           -- (ignore)
556                                 )
557                 LOOP
558                         RETURN NEXT n_child_ou;
559                 END LOOP;
560         END LOOP;
561         --
562         RETURN;
563         --
564 END;
565 $$ LANGUAGE 'plpgsql';
566
567
568 CREATE OR REPLACE FUNCTION permission.usr_has_perm_at(
569         user_id    IN INTEGER,
570         perm_code  IN TEXT
571 )
572 RETURNS SETOF INTEGER AS $$
573 SELECT DISTINCT * FROM permission.usr_has_perm_at_nd( $1, $2 );
574 $$ LANGUAGE 'sql';
575
576
577 CREATE OR REPLACE FUNCTION permission.usr_has_perm_at_all(
578         user_id    IN INTEGER,
579         perm_code  IN TEXT
580 )
581 RETURNS SETOF INTEGER AS $$
582 SELECT DISTINCT * FROM permission.usr_has_perm_at_all_nd( $1, $2 );
583 $$ LANGUAGE 'sql';
584
585
586 COMMIT;
587