This definition replaces the standard default, do-nothing, implementation that is shipped with Veil (see veil_init(bool)).
function veil_init(bool) returns bool as ' declare doing_reset alias for $1; 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'');
The first task of veil_init() is to declare a set of Veil shared variables. This is done by calling veil_share(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 vdemo_owner.roles; select into dummyint veil_init_range(''privs_range'', min(privilege_id), max(privilege_id)) from vdemo_owner.privileges; -- Load range for detail_types select into dummyint veil_init_range(''det_types_range'', min(detail_type_id), max(detail_type_id)) from vdemo_owner.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 vdemo_owner.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 vdemo_owner.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 veil_init_range(text, int4, 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 veil_init_int4array(text, text), and are populated by calling veil_int4array_set(text, int4, 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 veil_init_bitmap_array(text, text, text) and veil_bitmap_array_setbit(text, int4, 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;
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(int4) returns bool as ' declare _person_id alias for $1; 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 vdemo_owner.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 vdemo_owner.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 vdemo_owner.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;
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.
veil_int4_get()
, and then checks whether the specified privilege is present in the global_context
bitmap.
function i_have_global_priv(int4) returns bool as ' declare priv_id alias for $1; 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 stable 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 vdemo_owner.privileges where i_have_global_priv(10001); create rule ii_privileges as on insert to privileges do instead insert into vdemo_owner.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 vdemo_owner.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 vdemo_owner.privileges where privilege_id = old.privilege_id and i_have_global_priv(10004);
The privileges used above are select_privileges
(10001), insert_privileges
(10002), update_privileges
(10003), and delete_privileges
(10004).
i_have_global_context()
. If the user does not have access in the global context, and the connected user is the same user as the owner of the data we are looking at, then we test whether the specified privilege exists in the role_privs
bitmap array for the Personal Context
role.
function i_have_personal_priv(int4, int4) returns bool as ' declare priv_id alias for $1; person_id alias for $2; 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;
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 vdemo_owner.persons where i_have_personal_priv(10013, person_id);
function i_have_project_priv(int4, int4) returns bool as ' declare priv_id alias for $1; project_id alias for $2; 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 stable 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 vdemo_owner.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(int4, int4, int4) returns bool as ' declare priv_id alias for $1; project_id alias for $2; person_id alias for $3; 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 stable 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 vdemo_owner.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 vdemo_owner.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(int4, int4) returns bool as ' declare result bool; detail_id alias for $1; person_id alias for $2; begin select into result i_have_personal_priv(veil_int4array_get(''det_types_privs'', detail_id), person_id); return result; end; ' language plpgsql stable 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 vdemo_owner.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);