veil.veil_demo_init_fns
table which is created as an inherited table of veil.veil_init_fns
. By registering the initialisation functions using a veil_demo-specific inherited table, when the veil_demo extension is dropped, so is the registration data for veil.veil_demo_init(performing_reset bool).
function veil.veil_demo_init(doing_reset bool) returns bool as ' declare exists_privs_range bool; exists_roles_range bool; exists_role_privs bool; exists_det_types_range bool; exists_det_types_privs bool; init_reqd bool; dummy bool; dummy2 bool; dummyint int4; _count int4; begin -- Declare all shared variables. select into exists_privs_range, exists_roles_range, exists_role_privs, exists_det_types_range, exists_det_types_privs veil.share(''privs_range''), veil.share(''roles_range''), veil.share(''role_privs''), veil.share(''det_types_range''), veil.share(''det_types_privs''); init_reqd = not (exists_privs_range and exists_role_privs and
The first task of veil_init() is to declare a set of Veil shared variables. This is done by calling share(name text). This function returns true if the variable already exists, and creates the variable and returns false, if not.
These variables are defined as shared because they will be identical for each session. Making them shared means that only one session has to deal with the overhead of their initialisation.
init_reqd = not (exists_privs_range and exists_role_privs and exists_role_privs and exists_det_types_range and exists_det_types_privs); if init_reqd or doing_reset then -- Load ranges for privs and roles. select into dummyint veil.init_range(''roles_range'', min(role_id), max(role_id)) from hidden.roles; select into dummyint veil.init_range(''privs_range'', min(privilege_id), max(privilege_id)) from hidden.privileges; -- Load range for detail_types select into dummyint veil.init_range(''det_types_range'', min(detail_type_id), max(detail_type_id)) from hidden.detail_types; -- Initialise array of required privs for detail_types select into dummy veil.init_int4array(''det_types_privs'', ''det_types_range''); select into _count count(veil.int4array_set(''det_types_privs'', detail_type_id, required_privilege_id)) from hidden.detail_types; -- Initialise role_privs bitmap_array select into dummy veil.init_bitmap_array(''role_privs'', ''roles_range'', ''privs_range''); -- Populate role_privs bitmap_array select into _count count(veil.bitmap_array_setbit(''role_privs'', role_id, privilege_id)) from hidden.role_privileges; end if;
We then check whether the shared variables must be initialised. We will initialise them if they have not already been initialised by another session, or if we are performing a reset (see veil_perform_reset()).
Each variable is initialised in its own way.
Ranges are set by a single call to init_range(name text, min int4, max int4). Ranges are used to create bitmap and array types of a suitable size.
Int4Arrays are used to record mappings of one integer to another. In the demo, they are used to record the mapping of detail_type_id to required_privilege_id. We use this variable so that we can look-up the privilege required to access a given project_detail or person_detail without having to explicitly fetch from attribute_detail_types.
Int4Arrays are initialised by a call to init_int4array(arrayname text, range text), and are populated by calling int4array_set(arrayname text, idx int4, value int4) for each value to be recorded. Note that rather than using a cursor to loop through each detail_type record, we use select count(). This requires less code and has the same effect.
We use a BitmapArray to record the set of privileges for each role. Its initialisation and population is handled in much the same way as described above for Int4Arrays, using the functions init_bitmap_array(bmarray text, array_range text, bitmap_range text) and bitmap_array_setbit(bmarray text, arr_idx int4, bitno int4).
-- Declare important session variables, so that we do not -- get odd, undefined variable, error messages. select into dummyint, dummy, dummy2 veil.int4_set(''person_id'', null), veil.init_bitmap(''global_context'', ''privs_range''), veil.init_bitmap_hash(''project_context'', ''privs_range''); return true; end; ' language plpgsql volatile security definer;
The final section of code defines and initialises a set of session variables. These are defined here to avoid getting undefined variable errors from any access function that may be called before an authenticated connection has been established.
Note that this and all Veil related functions are defined with security definer
attributes. This means that the function will be executed with the privileges of the function's owner, rather than those of the invoker. This is absolutely critical as the invoker must have no privileges on the base objects, or on the raw Veil functions themselves. The only access to objects protected by Veil must be through user-defined functions and views.
function connect_person(_person_id int4) returns bool as ' declare dummy int4; _connect bool; proj_roles record; last_proj int4; first_rec bool; begin -- In reality this function would require some authentication token such -- as a password. This is just a dumb demo version. select into _connect disconnect_person(); -- Test whether provided person exists. This is where we would, in a -- real version, do proper authentication. select into dummy 1 from hidden.persons where person_id = _person_id; if found then -- The person exists and passes authentication -- From the persons roles set the global_context bitmap. select into dummy count(veil.union_from_bitmap_array(''global_context'', ''role_privs'', role_id)) from hidden.person_roles where person_id = _person_id; -- Check that user has can_connect privilege select into _connect veil.bitmap_testbit(''global_context'', 10100); if not _connect then select into _connect disconnect_person(); return false; end if; -- From the persons assignments set the project_context bitmap hash. select into dummy count(veil.union_into_bitmap_hash(''project_context'', project_id::text, veil.bitmap_from_array(''scratch_bitmap'', ''role_privs'', role_id))) from hidden.assignments where person_id = _person_id; -- Finally, record the person_id for the connection. select into dummy veil.int4_set(''person_id'', _person_id); return true; else return false; end if; end; ' language plpgsql volatile security definer;
This function identifies the user, ensures that they have can_connect privilege. It initialises the global_context bitmap to contain the union of all privileges for each role the person is assigned through person_roles. It also sets up a bitmap hash containing a bitmap of privileges for each project to which the person is assigned.
global_context
bitmap.
function i_have_global_priv(priv_id int4) returns bool as ' declare connection_id int4; result bool; begin select into connection_id, result veil.int4_get(''person_id''), veil.bitmap_testbit(''global_context'', priv_id); if connection_id is null then return false; else return result; end if; end; ' language plpgsql volatile security definer;
The following example shows this function in use by the secured view, privileges
:
create view privileges( privilege_id, privilege_name) as select privilege_id, privilege_name from hidden.privileges where i_have_global_priv(10001); create rule ii_privileges as on insert to privileges do instead insert into hidden.privileges (privilege_id, privilege_name) select new.privilege_id, new.privilege_name where i_have_global_priv(10002); create rule iu_privileges as on update to privileges do instead update hidden.privileges set privilege_name = new.privilege_name, privilege_id = new.privilege_id where privilege_id = old.privilege_id and i_have_global_priv(10003); create rule id_privileges as on delete to privileges do instead delete from hidden.privileges where privilege_id = old.privilege_id and i_have_global_priv(10004); grant select, insert, update, delete on privileges to public;
The privileges used above are select_privileges
(10001), insert_privileges
(10002), update_privileges
(10003), and delete_privileges
(10004).
role_privs
bitmap array for the Personal Context
role.
function i_have_personal_priv(priv_id int4, person_id int4) returns bool as ' declare connection_id int4; result bool; begin select into connection_id, result veil.int4_get(''person_id''), veil.bitmap_testbit(''global_context'', priv_id); if connection_id is null then -- No-one is connected return false; else if result then -- We have the required privilege in global context. No need -- to check any further return true; else if person_id = connection_id then -- We are connected as the owner of this record. Check -- whether we have the required privilege in personal -- context. select into result veil.bitmap_array_testbit(''role_privs'', 11002, priv_id); return result; else -- We have no personal context rights to this record return false; end if; end if; end if; end; ' language plpgsql volatile security definer;
Here is an example of this function in use from the persons secured view:
create view persons( person_id, person_name) as select person_id, person_name from hidden.persons where i_have_personal_priv(10013, person_id); create rule ii_persons as on insert to persons do instead insert into hidden.persons (person_id, person_name) select new.person_id, new.person_name where i_have_personal_priv(10014, new.person_id); create rule iu_persons as on update to persons do instead update hidden.persons set person_id = new.person_id, person_name = new.person_name where person_id = old.person_id and i_have_personal_priv(10015, old.person_id); create rule id_persons as on delete to persons do instead delete from hidden.persons where person_id = old.person_id and i_have_personal_priv(10016, old.person_id); grant select, insert, update, delete on persons to public;
function i_have_project_priv(priv_id int4, project_id int4) returns bool as ' declare connection_id int4; result bool; begin select into connection_id, result veil.int4_get(''person_id''), veil.bitmap_testbit(''global_context'', priv_id); if connection_id is null then -- No-one is connected return false; else if result then -- We have the required privilege in global context. No need -- to check any further return true; else select into result veil.bitmap_hash_testbit(''project_context'', project_id::text, priv_id); return result; end if; end if; end; ' language plpgsql volatile security definer;
Here is an example of this function in use from the instead-of insert trigger for the projects secured view:
create rule ii_projects as on insert to projects do instead insert into hidden.projects (project_id, project_name) select new.project_id, new.project_name where i_have_project_priv(10018, new.project_id);
function i_have_proj_or_pers_priv( priv_id int4, project_id int4, person_id int4) returns bool as ' declare connection_id int4; result bool; begin select into connection_id, result veil.int4_get(''person_id''), veil.bitmap_testbit(''global_context'', priv_id); if connection_id is null then -- No-one is connected return false; else if result then -- We have the required privilege in global context. No need -- to check any further return true; else if person_id = connection_id then -- We are connected as the owner of this record. Check -- whether we have the required privilege in personal -- context. select into result veil.bitmap_array_testbit(''role_privs'', 11002, priv_id); return result; end if; select into result veil.bitmap_hash_testbit(''project_context'', project_id::text, priv_id); return result; -- We have no personal context rights to this record -- so check project context return false; end if; end if; end; ' language plpgsql volatile security definer;
Here is an example of this function in use from the instead-of update trigger for the assignments secured view:
create rule ii_assignments as on insert to assignments do instead insert into hidden.assignments (project_id, person_id, role_id) select new.project_id, new.person_id, new.role_id where i_have_proj_or_pers_priv(10026, new.project_id, new.person_id); create rule iu_assignments as on update to assignments do instead update hidden.assignments set project_id = new.project_id, person_id = new.person_id, role_id = new.person_id where project_id = old.project_id and person_id = old.person_id and i_have_proj_or_pers_priv(10027, old.project_id, old.person_id);
function i_have_person_detail_priv(detail_id int4, person_id int4) returns bool as ' declare result bool; begin select into result i_have_personal_priv(veil.int4array_get(''det_types_privs'', detail_id), person_id); return result; end; ' language plpgsql volatile security definer;
The function is shown in use, below, in the instead-of delete trigger for person_details. Note that two distinct access functions are being used here.
create rule id_person_details as on delete to person_details do instead delete from hidden.person_details where person_id = old.person_id and detail_type_id = old.detail_type_id and i_have_personal_priv(10040, old.person_id) and i_have_person_detail_priv(old.detail_type_id, old.person_id);