actor Functions
This page documents all 49 function(s) in the actor schema.
Function Index
| Function | Return Type | Language | Volatility | Security |
|---|---|---|---|---|
address_alert_matches(org_unit integer, street1 text, stree…) |
|
sql |
VOLATILE |
|
|
plpgsql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
calculate_system_penalties(match_user integer, context_org integer) |
|
plpgsql |
VOLATILE |
|
|
plpgsql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
get_cascade_setting(setting_name text, org_id integer, us…) |
|
plpgsql |
VOLATILE |
|
get_cascade_setting_batch(setting_names text[], org_id integer,…) |
|
plpgsql |
VOLATILE |
|
|
plpgsql |
VOLATILE |
||
insert_usr_activity(usr integer, ewho text, ewhat text, e…) |
|
plpgsql |
VOLATILE |
|
|
plpgsql |
VOLATILE |
||
|
sql |
STABLE |
||
org_unit_ancestor_setting(setting_name text, org_id integer) |
|
plpgsql |
STABLE |
|
org_unit_ancestor_setting_batch(org_id integer, setting_names text[]) |
|
plpgsql |
STABLE |
|
org_unit_ancestor_setting_batch_by_org(setting_name text, org_ids integer[]) |
|
plpgsql |
STABLE |
|
|
sql |
VOLATILE |
||
|
sql |
STABLE |
||
|
sql |
STABLE |
||
|
sql |
STABLE |
||
|
sql |
VOLATILE |
||
|
sql |
VOLATILE |
||
|
sql |
STABLE |
||
|
sql |
STABLE |
||
|
sql |
STABLE |
||
|
plpgsql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
|
sql |
STABLE |
||
|
sql |
STABLE |
||
|
plpgsql |
VOLATILE |
||
otpauth_uri_get_proof(otp_uri text, fuzziness integer DEFAU…) |
|
plperlu |
VOLATILE |
|
otpauth_uri_get_proof(usr_id integer, otype text, purpose t…) |
|
plpgsql |
VOLATILE |
|
|
plpgsql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
|
plperlu |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
|
sql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
||
|
plpgsql |
VOLATILE |
address_alert_matches
Signature: actor.address_alert_matches(org_unit integer, street1 text, street2 text, city text, county text, state text, country text, post_code text, mailing_address boolean DEFAULT false, billing_address boolean DEFAULT false)
Returns: SETOF actor.address_alert
Language |
sql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
SELECT *
FROM actor.address_alert
WHERE
active
AND owner IN (SELECT id FROM actor.org_unit_ancestors($1))
AND (
(NOT mailing_address AND NOT billing_address)
OR (mailing_address AND $9)
OR (billing_address AND $10)
)
AND (
(
match_all
AND COALESCE($2, '') ~* COALESCE(street1, '.*')
AND COALESCE($3, '') ~* COALESCE(street2, '.*')
AND COALESCE($4, '') ~* COALESCE(city, '.*')
AND COALESCE($5, '') ~* COALESCE(county, '.*')
AND COALESCE($6, '') ~* COALESCE(state, '.*')
AND COALESCE($7, '') ~* COALESCE(country, '.*')
AND COALESCE($8, '') ~* COALESCE(post_code, '.*')
) OR (
NOT match_all
AND (
$2 ~* street1
OR $3 ~* street2
OR $4 ~* city
OR $5 ~* county
OR $6 ~* state
OR $7 ~* country
OR $8 ~* post_code
)
)
)
ORDER BY actor.org_unit_proximity(owner, $1)
approve_pending_address
Signature: actor.approve_pending_address(pending_id integer)
Returns: bigint
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
Replaces an address with a pending address. This is done by giving the pending address the ID of the old address. The replaced address is retained with -id.
DECLARE
old_id INT;
BEGIN
SELECT INTO old_id replaces FROM actor.usr_address where id = pending_id;
IF old_id IS NULL THEN
UPDATE actor.usr_address SET pending = 'f' WHERE id = pending_id;
RETURN pending_id;
END IF;
-- address replaces an existing address
DELETE FROM actor.usr_address WHERE id = -old_id;
UPDATE actor.usr_address SET id = -id WHERE id = old_id;
UPDATE actor.usr_address SET replaces = NULL, id = old_id, pending = 'f' WHERE id = pending_id;
RETURN old_id;
END
au_updated
Signature: actor.au_updated()
Returns: trigger
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
BEGIN
NEW.last_update_time := now();
RETURN NEW;
END;
calculate_system_penalties
Signature: actor.calculate_system_penalties(match_user integer, context_org integer)
Returns: SETOF actor.usr_standing_penalty
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
user_object actor.usr%ROWTYPE;
new_sp_row actor.usr_standing_penalty%ROWTYPE;
existing_sp_row actor.usr_standing_penalty%ROWTYPE;
collections_fines permission.grp_penalty_threshold%ROWTYPE;
max_fines permission.grp_penalty_threshold%ROWTYPE;
max_overdue permission.grp_penalty_threshold%ROWTYPE;
max_items_out permission.grp_penalty_threshold%ROWTYPE;
max_lost permission.grp_penalty_threshold%ROWTYPE;
max_longoverdue permission.grp_penalty_threshold%ROWTYPE;
penalty_id INT;
tmp_grp INT;
items_overdue INT;
items_out INT;
items_lost INT;
items_longoverdue INT;
context_org_list INT[];
current_fines NUMERIC(8,2) := 0.0;
tmp_fines NUMERIC(8,2);
tmp_groc RECORD;
tmp_circ RECORD;
tmp_org actor.org_unit%ROWTYPE;
tmp_penalty config.standing_penalty%ROWTYPE;
tmp_depth INTEGER;
BEGIN
SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
-- Max fines
SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
SELECT BTRIM(value,'"')::INT INTO penalty_id FROM actor.org_unit_ancestor_setting('circ.custom_penalty_override.PATRON_EXCEEDS_FINES', context_org);
IF NOT FOUND THEN penalty_id := 1; END IF;
-- Fail if the user has a high fine balance
LOOP
tmp_grp := user_object.profile;
LOOP
SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = penalty_id AND org_unit = tmp_org.id;
IF max_fines.threshold IS NULL THEN
SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
ELSE
EXIT;
END IF;
IF tmp_grp IS NULL THEN
EXIT;
END IF;
END LOOP;
IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
EXIT;
END IF;
SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
END LOOP;
IF max_fines.threshold IS NOT NULL THEN
-- The IN clause in all of the RETURN QUERY calls is used to surface now-stale non-custom penalties
-- so that the calling code can clear them at the boundary where custom penalties are configured.
-- Otherwise we would see orphaned "stock" system penalties that would never go away on their own.
RETURN QUERY
SELECT *
FROM actor.usr_standing_penalty
WHERE usr = match_user
AND org_unit = max_fines.org_unit
AND (stop_date IS NULL or stop_date > NOW())
AND standing_penalty IN (1, penalty_id);
SELECT INTO context_org_list ARRAY_AGG(id) FROM actor.org_unit_full_path( max_fines.org_unit );
SELECT SUM(f.balance_owed) INTO current_fines
FROM money.materialized_billable_xact_summary f
JOIN (
SELECT r.id
FROM booking.reservation r
WHERE r.usr = match_user
AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
AND xact_finish IS NULL
UNION ALL
SELECT g.id
FROM money.grocery g
WHERE g.usr = match_user
AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
AND xact_finish IS NULL
UNION ALL
SELECT circ.id
FROM action.circulation circ
WHERE circ.usr = match_user
AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
AND xact_finish IS NULL ) l USING (id);
IF current_fines >= max_fines.threshold THEN
new_sp_row.usr := match_user;
new_sp_row.org_unit := max_fines.org_unit;
new_sp_row.standing_penalty := penalty_id;
RETURN NEXT new_sp_row;
END IF;
END IF;
-- Start over for max overdue
SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
SELECT BTRIM(value,'"')::INT INTO penalty_id FROM actor.org_unit_ancestor_setting('circ.custom_penalty_override.PATRON_EXCEEDS_OVERDUE_COUNT', context_org);
IF NOT FOUND THEN penalty_id := 2; END IF;
-- Fail if the user has too many overdue items
LOOP
tmp_grp := user_object.profile;
LOOP
SELECT * INTO max_overdue FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = penalty_id AND org_unit = tmp_org.id;
IF max_overdue.threshold IS NULL THEN
SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
ELSE
EXIT;
END IF;
IF tmp_grp IS NULL THEN
EXIT;
END IF;
END LOOP;
IF max_overdue.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
EXIT;
END IF;
SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
END LOOP;
IF max_overdue.threshold IS NOT NULL THEN
RETURN QUERY
SELECT *
FROM actor.usr_standing_penalty
WHERE usr = match_user
AND org_unit = max_overdue.org_unit
AND (stop_date IS NULL or stop_date > NOW())
AND standing_penalty IN (2, penalty_id);
SELECT INTO items_overdue COUNT(*)
FROM action.circulation circ
JOIN actor.org_unit_full_path( max_overdue.org_unit ) fp ON (circ.circ_lib = fp.id)
WHERE circ.usr = match_user
AND circ.checkin_time IS NULL
AND circ.due_date < NOW()
AND (circ.stop_fines = 'MAXFINES' OR circ.stop_fines IS NULL);
IF items_overdue >= max_overdue.threshold::INT THEN
new_sp_row.usr := match_user;
new_sp_row.org_unit := max_overdue.org_unit;
new_sp_row.standing_penalty := penalty_id;
RETURN NEXT new_sp_row;
END IF;
END IF;
-- Start over for max out
SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
SELECT BTRIM(value,'"')::INT INTO penalty_id FROM actor.org_unit_ancestor_setting('circ.custom_penalty_override.PATRON_EXCEEDS_CHECKOUT_COUNT', context_org);
IF NOT FOUND THEN penalty_id := 3; END IF;
-- Fail if the user has too many checked out items
LOOP
tmp_grp := user_object.profile;
LOOP
SELECT * INTO max_items_out FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = penalty_id AND org_unit = tmp_org.id;
IF max_items_out.threshold IS NULL THEN
SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
ELSE
EXIT;
END IF;
IF tmp_grp IS NULL THEN
EXIT;
END IF;
END LOOP;
IF max_items_out.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
EXIT;
END IF;
SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
END LOOP;
-- Fail if the user has too many items checked out
IF max_items_out.threshold IS NOT NULL THEN
RETURN QUERY
SELECT *
FROM actor.usr_standing_penalty
WHERE usr = match_user
AND org_unit = max_items_out.org_unit
AND (stop_date IS NULL or stop_date > NOW())
AND standing_penalty IN (3, penalty_id);
SELECT INTO items_out COUNT(*)
FROM action.circulation circ
JOIN actor.org_unit_full_path( max_items_out.org_unit ) fp ON (circ.circ_lib = fp.id)
WHERE circ.usr = match_user
AND circ.checkin_time IS NULL
AND (circ.stop_fines IN (
SELECT 'MAXFINES'::TEXT
UNION ALL
SELECT 'LONGOVERDUE'::TEXT
UNION ALL
SELECT 'LOST'::TEXT
WHERE 'true' ILIKE
(
SELECT CASE
WHEN (SELECT value FROM actor.org_unit_ancestor_setting('circ.tally_lost', circ.circ_lib)) ILIKE 'true' THEN 'true'
ELSE 'false'
END
)
UNION ALL
SELECT 'CLAIMSRETURNED'::TEXT
WHERE 'false' ILIKE
(
SELECT CASE
WHEN (SELECT value FROM actor.org_unit_ancestor_setting('circ.do_not_tally_claims_returned', circ.circ_lib)) ILIKE 'true' THEN 'true'
ELSE 'false'
END
)
) OR circ.stop_fines IS NULL)
AND xact_finish IS NULL;
IF items_out >= max_items_out.threshold::INT THEN
new_sp_row.usr := match_user;
new_sp_row.org_unit := max_items_out.org_unit;
new_sp_row.standing_penalty := penalty_id;
RETURN NEXT new_sp_row;
END IF;
END IF;
-- Start over for max lost
SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
SELECT BTRIM(value,'"')::INT INTO penalty_id FROM actor.org_unit_ancestor_setting('circ.custom_penalty_override.PATRON_EXCEEDS_LOST_COUNT', context_org);
IF NOT FOUND THEN penalty_id := 5; END IF;
-- Fail if the user has too many lost items
LOOP
tmp_grp := user_object.profile;
LOOP
SELECT * INTO max_lost FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = penalty_id AND org_unit = tmp_org.id;
IF max_lost.threshold IS NULL THEN
SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
ELSE
EXIT;
END IF;
IF tmp_grp IS NULL THEN
EXIT;
END IF;
END LOOP;
IF max_lost.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
EXIT;
END IF;
SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
END LOOP;
IF max_lost.threshold IS NOT NULL THEN
RETURN QUERY
SELECT *
FROM actor.usr_standing_penalty
WHERE usr = match_user
AND org_unit = max_lost.org_unit
AND (stop_date IS NULL or stop_date > NOW())
AND standing_penalty IN (5, penalty_id);
SELECT INTO items_lost COUNT(*)
FROM action.circulation circ
JOIN actor.org_unit_full_path( max_lost.org_unit ) fp ON (circ.circ_lib = fp.id)
WHERE circ.usr = match_user
AND circ.checkin_time IS NULL
AND (circ.stop_fines = 'LOST')
AND xact_finish IS NULL;
IF items_lost >= max_lost.threshold::INT AND 0 < max_lost.threshold::INT THEN
new_sp_row.usr := match_user;
new_sp_row.org_unit := max_lost.org_unit;
new_sp_row.standing_penalty := penalty_id;
RETURN NEXT new_sp_row;
END IF;
END IF;
-- Start over for max longoverdue
SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
SELECT BTRIM(value,'"')::INT INTO penalty_id FROM actor.org_unit_ancestor_setting('circ.custom_penalty_override.PATRON_EXCEEDS_LONGOVERDUE_COUNT', context_org);
IF NOT FOUND THEN penalty_id := 35; END IF;
-- Fail if the user has too many longoverdue items
LOOP
tmp_grp := user_object.profile;
LOOP
SELECT * INTO max_longoverdue
FROM permission.grp_penalty_threshold
WHERE grp = tmp_grp AND
penalty = penalty_id AND
org_unit = tmp_org.id;
IF max_longoverdue.threshold IS NULL THEN
SELECT parent INTO tmp_grp
FROM permission.grp_tree WHERE id = tmp_grp;
ELSE
EXIT;
END IF;
IF tmp_grp IS NULL THEN
EXIT;
END IF;
END LOOP;
IF max_longoverdue.threshold IS NOT NULL
OR tmp_org.parent_ou IS NULL THEN
EXIT;
END IF;
SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
END LOOP;
IF max_longoverdue.threshold IS NOT NULL THEN
RETURN QUERY
SELECT *
FROM actor.usr_standing_penalty
WHERE usr = match_user
AND org_unit = max_longoverdue.org_unit
AND (stop_date IS NULL or stop_date > NOW())
AND standing_penalty IN (35, penalty_id);
SELECT INTO items_longoverdue COUNT(*)
FROM action.circulation circ
JOIN actor.org_unit_full_path( max_longoverdue.org_unit ) fp
ON (circ.circ_lib = fp.id)
WHERE circ.usr = match_user
AND circ.checkin_time IS NULL
AND (circ.stop_fines = 'LONGOVERDUE')
AND xact_finish IS NULL;
IF items_longoverdue >= max_longoverdue.threshold::INT
AND 0 < max_longoverdue.threshold::INT THEN
new_sp_row.usr := match_user;
new_sp_row.org_unit := max_longoverdue.org_unit;
new_sp_row.standing_penalty := penalty_id;
RETURN NEXT new_sp_row;
END IF;
END IF;
-- Start over for collections warning
SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
SELECT BTRIM(value,'"')::INT INTO penalty_id FROM actor.org_unit_ancestor_setting('circ.custom_penalty_override.PATRON_EXCEEDS_COLLECTIONS_WARNING', context_org);
IF NOT FOUND THEN penalty_id := 4; END IF;
-- Fail if the user has a collections-level fine balance
LOOP
tmp_grp := user_object.profile;
LOOP
SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = penalty_id AND org_unit = tmp_org.id;
IF max_fines.threshold IS NULL THEN
SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
ELSE
EXIT;
END IF;
IF tmp_grp IS NULL THEN
EXIT;
END IF;
END LOOP;
IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
EXIT;
END IF;
SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
END LOOP;
IF max_fines.threshold IS NOT NULL THEN
RETURN QUERY
SELECT *
FROM actor.usr_standing_penalty
WHERE usr = match_user
AND org_unit = max_fines.org_unit
AND (stop_date IS NULL or stop_date > NOW())
AND standing_penalty IN (4, penalty_id);
SELECT INTO context_org_list ARRAY_AGG(id) FROM actor.org_unit_full_path( max_fines.org_unit );
SELECT SUM(f.balance_owed) INTO current_fines
FROM money.materialized_billable_xact_summary f
JOIN (
SELECT r.id
FROM booking.reservation r
WHERE r.usr = match_user
AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
AND r.xact_finish IS NULL
UNION ALL
SELECT g.id
FROM money.grocery g
WHERE g.usr = match_user
AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
AND g.xact_finish IS NULL
UNION ALL
SELECT circ.id
FROM action.circulation circ
WHERE circ.usr = match_user
AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
AND circ.xact_finish IS NULL ) l USING (id);
IF current_fines >= max_fines.threshold THEN
new_sp_row.usr := match_user;
new_sp_row.org_unit := max_fines.org_unit;
new_sp_row.standing_penalty := penalty_id;
RETURN NEXT new_sp_row;
END IF;
END IF;
-- Start over for in collections
SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
SELECT BTRIM(value,'"')::INT INTO penalty_id FROM actor.org_unit_ancestor_setting('circ.custom_penalty_override.PATRON_IN_COLLECTIONS', context_org);
IF NOT FOUND THEN penalty_id := 30; END IF;
-- Remove the in-collections penalty if the user has paid down enough
-- This penalty is different, because this code is not responsible for creating
-- new in-collections penalties, only for removing them
LOOP
tmp_grp := user_object.profile;
LOOP
SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = penalty_id AND org_unit = tmp_org.id;
IF max_fines.threshold IS NULL THEN
SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
ELSE
EXIT;
END IF;
IF tmp_grp IS NULL THEN
EXIT;
END IF;
END LOOP;
IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
EXIT;
END IF;
SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
END LOOP;
IF max_fines.threshold IS NOT NULL THEN
SELECT INTO context_org_list ARRAY_AGG(id) FROM actor.org_unit_full_path( max_fines.org_unit );
-- first, see if the user had paid down to the threshold
SELECT SUM(f.balance_owed) INTO current_fines
FROM money.materialized_billable_xact_summary f
JOIN (
SELECT r.id
FROM booking.reservation r
WHERE r.usr = match_user
AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
AND r.xact_finish IS NULL
UNION ALL
SELECT g.id
FROM money.grocery g
WHERE g.usr = match_user
AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
AND g.xact_finish IS NULL
UNION ALL
SELECT circ.id
FROM action.circulation circ
WHERE circ.usr = match_user
AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
AND circ.xact_finish IS NULL ) l USING (id);
IF current_fines IS NULL OR current_fines <= max_fines.threshold THEN
-- patron has paid down enough
SELECT INTO tmp_penalty * FROM config.standing_penalty WHERE id = penalty_id;
IF tmp_penalty.org_depth IS NOT NULL THEN
-- since this code is not responsible for applying the penalty, it can't
-- guarantee the current context org will match the org at which the penalty
--- was applied. search up the org tree until we hit the configured penalty depth
SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
WHILE tmp_depth >= tmp_penalty.org_depth LOOP
RETURN QUERY
SELECT *
FROM actor.usr_standing_penalty
WHERE usr = match_user
AND org_unit = tmp_org.id
AND (stop_date IS NULL or stop_date > NOW())
AND standing_penalty IN (30, penalty_id);
IF tmp_org.parent_ou IS NULL THEN
EXIT;
END IF;
SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
END LOOP;
ELSE
-- no penalty depth is defined, look for exact matches
RETURN QUERY
SELECT *
FROM actor.usr_standing_penalty
WHERE usr = match_user
AND org_unit = max_fines.org_unit
AND (stop_date IS NULL or stop_date > NOW())
AND standing_penalty IN (30, penalty_id);
END IF;
END IF;
END IF;
RETURN;
END;
change_password
Signature: actor.change_password(user_id integer, new_pw text, pw_type text DEFAULT 'main'::text)
Returns: void
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
Allows setting a salted password for a user by passing actor.usr id and the text of the password.
DECLARE
new_salt TEXT;
BEGIN
SELECT actor.create_salt(pw_type) INTO new_salt;
IF pw_type = 'main' THEN
-- Only 'main' passwords are required to have
-- the extra layer of MD5 hashing.
PERFORM actor.set_passwd(
user_id, pw_type, md5(new_salt || md5(new_pw)), new_salt
);
ELSE
PERFORM actor.set_passwd(user_id, pw_type, new_pw, new_salt);
END IF;
END;
create_salt
Signature: actor.create_salt(pw_type text)
Returns: text
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
type_row actor.passwd_type%ROWTYPE;
BEGIN
/* Returns a new salt based on the passwd_type encryption settings.
* Returns NULL If the password type is not crypt()'ed.
*/
SELECT INTO type_row * FROM actor.passwd_type WHERE code = pw_type;
IF NOT FOUND THEN
RAISE EXCEPTION 'No such password type: %', pw_type;
END IF;
IF type_row.iter_count IS NULL THEN
-- This password type is unsalted. That's OK.
RETURN NULL;
END IF;
RETURN gen_salt(type_row.crypt_algo, type_row.iter_count);
END;
crypt_pw_insert
Signature: actor.crypt_pw_insert()
Returns: trigger
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
BEGIN
NEW.passwd = MD5( NEW.passwd );
RETURN NEW;
END;
crypt_pw_update
Signature: actor.crypt_pw_update()
Returns: trigger
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
BEGIN
IF NEW.passwd <> OLD.passwd THEN
NEW.passwd = MD5( NEW.passwd );
END IF;
RETURN NEW;
END;
get_cascade_setting
Signature: actor.get_cascade_setting(setting_name text, org_id integer, user_id integer, workstation_id integer)
Returns: actor.cascade_setting_summary
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
setting_value JSON;
summary actor.cascade_setting_summary;
org_setting_type config.org_unit_setting_type%ROWTYPE;
BEGIN
summary.name := setting_name;
-- Collect the org setting type status first in case we exit early.
-- The existance of an org setting type is not considered
-- privileged information.
SELECT INTO org_setting_type *
FROM config.org_unit_setting_type WHERE name = setting_name;
IF FOUND THEN
summary.has_org_setting := TRUE;
ELSE
summary.has_org_setting := FALSE;
END IF;
-- User and workstation settings have the same priority.
-- Start with user settings since that's the simplest code path.
-- The workstation_id is ignored if no user_id is provided.
IF user_id IS NOT NULL THEN
SELECT INTO summary.value value FROM actor.usr_setting
WHERE usr = user_id AND name = setting_name;
IF FOUND THEN
-- if we have a value, we have a setting type
summary.has_user_setting := TRUE;
IF workstation_id IS NOT NULL THEN
-- Only inform the caller about the workstation
-- setting type disposition when a workstation id is
-- provided. Otherwise, it's NULL to indicate UNKNOWN.
summary.has_workstation_setting := FALSE;
END IF;
RETURN summary;
END IF;
-- no user setting value, but a setting type may exist
SELECT INTO summary.has_user_setting EXISTS (
SELECT TRUE FROM config.usr_setting_type
WHERE name = setting_name
);
IF workstation_id IS NOT NULL THEN
IF NOT summary.has_user_setting THEN
-- A workstation setting type may only exist when a user
-- setting type does not.
SELECT INTO summary.value value
FROM actor.workstation_setting
WHERE workstation = workstation_id AND name = setting_name;
IF FOUND THEN
-- if we have a value, we have a setting type
summary.has_workstation_setting := TRUE;
RETURN summary;
END IF;
-- no value, but a setting type may exist
SELECT INTO summary.has_workstation_setting EXISTS (
SELECT TRUE FROM config.workstation_setting_type
WHERE name = setting_name
);
END IF;
-- Finally make use of the workstation to determine the org
-- unit if none is provided.
IF org_id IS NULL AND summary.has_org_setting THEN
SELECT INTO org_id owning_lib
FROM actor.workstation WHERE id = workstation_id;
END IF;
END IF;
END IF;
-- Some org unit settings are protected by a view permission.
-- First see if we have any data that needs protecting, then
-- check the permission if needed.
IF NOT summary.has_org_setting THEN
RETURN summary;
END IF;
-- avoid putting the value into the summary until we confirm
-- the value should be visible to the caller.
SELECT INTO setting_value value
FROM actor.org_unit_ancestor_setting(setting_name, org_id);
IF NOT FOUND THEN
-- No value found -- perm check is irrelevant.
RETURN summary;
END IF;
IF org_setting_type.view_perm IS NOT NULL THEN
IF user_id IS NULL THEN
RAISE NOTICE 'Perm check required but no user_id provided';
RETURN summary;
END IF;
IF NOT permission.usr_has_perm(
user_id, (SELECT code FROM permission.perm_list
WHERE id = org_setting_type.view_perm), org_id)
THEN
RAISE NOTICE 'Perm check failed for user % on %',
user_id, org_setting_type.view_perm;
RETURN summary;
END IF;
END IF;
-- Perm check succeeded or was not necessary.
summary.value := setting_value;
RETURN summary;
END;
get_cascade_setting_batch
Signature: actor.get_cascade_setting_batch(setting_names text[], org_id integer, user_id integer, workstation_id integer)
Returns: SETOF actor.cascade_setting_summary
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
-- Returns a row per setting matching the setting name order. If no
-- value is applied, NULL is returned to retain name-response ordering.
DECLARE
setting_name TEXT;
summary actor.cascade_setting_summary;
BEGIN
FOREACH setting_name IN ARRAY setting_names LOOP
SELECT INTO summary * FROM actor.get_cascade_setting(
setting_Name, org_id, user_id, workstation_id);
RETURN NEXT summary;
END LOOP;
END;
get_salt
Signature: actor.get_salt(pw_usr integer, pw_type text)
Returns: text
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
pw_salt TEXT;
type_row actor.passwd_type%ROWTYPE;
BEGIN
/* Returns the salt for the requested user + type. If the password
* type of "main" is requested and no password exists in actor.passwd,
* the user's existing password is migrated and the new salt is returned.
* Returns NULL if the password type is not crypt'ed (iter_count is NULL).
*/
SELECT INTO pw_salt salt FROM actor.passwd
WHERE usr = pw_usr AND passwd_type = pw_type;
IF FOUND THEN
RETURN pw_salt;
END IF;
IF pw_type = 'main' THEN
-- Main password has not yet been migrated.
-- Do it now and return the newly created salt.
RETURN actor.migrate_passwd(pw_usr);
END IF;
-- We have no salt to return. actor.create_salt() needed.
RETURN NULL;
END;
insert_usr_activity
Signature: actor.insert_usr_activity(usr integer, ewho text, ewhat text, ehow text, edata text DEFAULT NULL::text)
Returns: SETOF actor.usr_activity
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
new_row actor.usr_activity%ROWTYPE;
BEGIN
SELECT id INTO new_row.etype FROM actor.usr_activity_get_type(ewho, ewhat, ehow);
IF FOUND THEN
new_row.usr := usr;
new_row.event_data := edata;
INSERT INTO actor.usr_activity (usr, etype, event_data)
VALUES (usr, new_row.etype, new_row.event_data)
RETURNING * INTO new_row;
RETURN NEXT new_row;
END IF;
END;
migrate_passwd
Signature: actor.migrate_passwd(pw_usr integer)
Returns: text
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
pw_salt TEXT;
usr_row actor.usr%ROWTYPE;
BEGIN
/* Migrates legacy actor.usr.passwd value to actor.passwd with
* a password type 'main' and returns the new salt. For backwards
* compatibility with existing CHAP-style API's, we perform a
* layer of intermediate MD5(MD5()) hashing. This is intermediate
* hashing is not required of other passwords.
*/
-- Avoid calling get_salt() here, because it may result in a
-- migrate_passwd() call, creating a loop.
SELECT INTO pw_salt salt FROM actor.passwd
WHERE usr = pw_usr AND passwd_type = 'main';
-- Only migrate passwords that have not already been migrated.
IF FOUND THEN
RETURN pw_salt;
END IF;
SELECT INTO usr_row * FROM actor.usr WHERE id = pw_usr;
pw_salt := actor.create_salt('main');
PERFORM actor.set_passwd(
pw_usr, 'main', MD5(pw_salt || usr_row.passwd), pw_salt);
-- clear the existing password
UPDATE actor.usr SET passwd = '' WHERE id = usr_row.id;
RETURN pw_salt;
END;
org_unit_ancestor_at_depth
Signature: actor.org_unit_ancestor_at_depth(integer, integer)
Returns: actor.org_unit
Language |
sql |
Volatility |
STABLE |
Strict |
No |
Security Definer |
No |
SELECT a.*
FROM actor.org_unit a
WHERE id = ( SELECT FIRST(x.id)
FROM actor.org_unit_ancestors($1) x
JOIN actor.org_unit_type y
ON x.ou_type = y.id AND y.depth = $2);
org_unit_ancestor_setting
Signature: actor.org_unit_ancestor_setting(setting_name text, org_id integer)
Returns: SETOF actor.org_unit_setting
Language |
plpgsql |
Volatility |
STABLE |
Strict |
No |
Security Definer |
No |
Search "up" the org_unit tree until we find the first occurrence of an org_unit_setting with the given name.
DECLARE
setting RECORD;
cur_org INT;
BEGIN
cur_org := org_id;
LOOP
SELECT INTO setting * FROM actor.org_unit_setting WHERE org_unit = cur_org AND name = setting_name;
IF FOUND THEN
RETURN NEXT setting;
EXIT;
END IF;
SELECT INTO cur_org parent_ou FROM actor.org_unit WHERE id = cur_org;
EXIT WHEN cur_org IS NULL;
END LOOP;
RETURN;
END;
org_unit_ancestor_setting_batch
Signature: actor.org_unit_ancestor_setting_batch(org_id integer, setting_names text[])
Returns: SETOF actor.org_unit_setting
Language |
plpgsql |
Volatility |
STABLE |
Strict |
No |
Security Definer |
No |
For each setting name passed, search "up" the org_unit tree until we find the first occurrence of an org_unit_setting with the given name.
DECLARE
setting RECORD;
setting_name TEXT;
cur_org INT;
BEGIN
FOREACH setting_name IN ARRAY setting_names
LOOP
cur_org := org_id;
LOOP
SELECT INTO setting * FROM actor.org_unit_setting WHERE org_unit = cur_org AND name = setting_name;
IF FOUND THEN
RETURN NEXT setting;
EXIT;
END IF;
SELECT INTO cur_org parent_ou FROM actor.org_unit WHERE id = cur_org;
EXIT WHEN cur_org IS NULL;
END LOOP;
END LOOP;
RETURN;
END;
org_unit_ancestor_setting_batch_by_org
Signature: actor.org_unit_ancestor_setting_batch_by_org(setting_name text, org_ids integer[])
Returns: SETOF actor.org_unit_setting
Language |
plpgsql |
Volatility |
STABLE |
Strict |
No |
Security Definer |
No |
DECLARE
setting RECORD;
org_id INTEGER;
BEGIN
/* Returns one actor.org_unit_setting row per org unit ID provided.
When no setting exists for a given org unit, the setting row
will contain all empty values. */
FOREACH org_id IN ARRAY org_ids LOOP
SELECT INTO setting * FROM
actor.org_unit_ancestor_setting(setting_name, org_id);
RETURN NEXT setting;
END LOOP;
RETURN;
END;
org_unit_ancestors
Signature: actor.org_unit_ancestors(integer)
Returns: SETOF actor.org_unit
Language |
sql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
WITH RECURSIVE org_unit_ancestors_distance(id, distance) AS (
SELECT $1, 0
UNION
SELECT ou.parent_ou, ouad.distance+1
FROM actor.org_unit ou JOIN org_unit_ancestors_distance ouad ON (ou.id = ouad.id)
WHERE ou.parent_ou IS NOT NULL
)
SELECT ou.* FROM actor.org_unit ou JOIN org_unit_ancestors_distance ouad USING (id) ORDER BY ouad.distance DESC;
org_unit_ancestors_distance
Signature: actor.org_unit_ancestors_distance(integer)
Returns: TABLE(id integer, distance integer)
Language |
sql |
Volatility |
STABLE |
Strict |
No |
Security Definer |
No |
WITH RECURSIVE org_unit_ancestors_distance(id, distance) AS (
SELECT $1, 0
UNION
SELECT ou.parent_ou, ouad.distance+1
FROM actor.org_unit ou JOIN org_unit_ancestors_distance ouad ON (ou.id = ouad.id)
WHERE ou.parent_ou IS NOT NULL
)
SELECT * FROM org_unit_ancestors_distance;
org_unit_combined_ancestors
Signature: actor.org_unit_combined_ancestors(integer, integer)
Returns: SETOF actor.org_unit
Language |
sql |
Volatility |
STABLE |
Strict |
No |
Security Definer |
No |
SELECT *
FROM actor.org_unit_ancestors($1)
UNION
SELECT *
FROM actor.org_unit_ancestors($2);
org_unit_common_ancestors
Signature: actor.org_unit_common_ancestors(integer, integer)
Returns: SETOF actor.org_unit
Language |
sql |
Volatility |
STABLE |
Strict |
No |
Security Definer |
No |
SELECT *
FROM actor.org_unit_ancestors($1)
INTERSECT
SELECT *
FROM actor.org_unit_ancestors($2);
org_unit_descendants
Signature: actor.org_unit_descendants(integer)
Returns: SETOF actor.org_unit
Language |
sql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
WITH RECURSIVE descendant_depth AS (
SELECT ou.id,
ou.parent_ou,
out.depth
FROM actor.org_unit ou
JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
WHERE ou.id = $1
UNION ALL
SELECT ou.id,
ou.parent_ou,
out.depth
FROM actor.org_unit ou
JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
JOIN descendant_depth ot ON (ot.id = ou.parent_ou)
) SELECT ou.* FROM actor.org_unit ou JOIN descendant_depth USING (id);
org_unit_descendants
Signature: actor.org_unit_descendants(integer, integer)
Returns: SETOF actor.org_unit
Language |
sql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
WITH RECURSIVE descendant_depth AS (
SELECT ou.id,
ou.parent_ou,
out.depth
FROM actor.org_unit ou
JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
JOIN anscestor_depth ad ON (ad.id = ou.id)
WHERE ad.depth = $2
UNION ALL
SELECT ou.id,
ou.parent_ou,
out.depth
FROM actor.org_unit ou
JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
JOIN descendant_depth ot ON (ot.id = ou.parent_ou)
), anscestor_depth AS (
SELECT ou.id,
ou.parent_ou,
out.depth
FROM actor.org_unit ou
JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
WHERE ou.id = $1
UNION ALL
SELECT ou.id,
ou.parent_ou,
out.depth
FROM actor.org_unit ou
JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
JOIN anscestor_depth ot ON (ot.parent_ou = ou.id)
) SELECT ou.* FROM actor.org_unit ou JOIN descendant_depth USING (id);
org_unit_descendants_distance
Signature: actor.org_unit_descendants_distance(integer)
Returns: TABLE(id integer, distance integer)
Language |
sql |
Volatility |
STABLE |
Strict |
No |
Security Definer |
No |
WITH RECURSIVE org_unit_descendants_distance(id, distance) AS (
SELECT $1, 0
UNION
SELECT ou.id, oudd.distance+1
FROM actor.org_unit ou JOIN org_unit_descendants_distance oudd ON (ou.parent_ou = oudd.id)
)
SELECT * FROM org_unit_descendants_distance;
org_unit_full_path
Signature: actor.org_unit_full_path(integer)
Returns: SETOF actor.org_unit
Language |
sql |
Volatility |
STABLE |
Strict |
No |
Security Definer |
No |
SELECT aou.*
FROM actor.org_unit AS aou
JOIN (
(SELECT au.id, t.depth FROM actor.org_unit_ancestors($1) AS au JOIN actor.org_unit_type t ON (au.ou_type = t.id))
UNION
(SELECT au.id, t.depth FROM actor.org_unit_descendants($1) AS au JOIN actor.org_unit_type t ON (au.ou_type = t.id))
) AS ad ON (aou.id=ad.id)
ORDER BY ad.depth;
org_unit_full_path
Signature: actor.org_unit_full_path(integer, integer)
Returns: SETOF actor.org_unit
Language |
sql |
Volatility |
STABLE |
Strict |
No |
Security Definer |
No |
SELECT * FROM actor.org_unit_full_path((actor.org_unit_ancestor_at_depth($1, $2)).id)
org_unit_parent_protect
Signature: actor.org_unit_parent_protect()
Returns: trigger
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
current_aou actor.org_unit%ROWTYPE;
seen_ous INT[];
depth_count INT;
BEGIN
current_aou := NEW;
depth_count := 0;
seen_ous := ARRAY[NEW.id];
IF (TG_OP = 'UPDATE') THEN
IF (NEW.parent_ou IS NOT DISTINCT FROM OLD.parent_ou) THEN
RETURN NEW; -- Doing an UPDATE with no change, just return it
END IF;
END IF;
LOOP
IF current_aou.parent_ou IS NULL THEN -- Top of the org tree?
RETURN NEW; -- No loop. Carry on.
END IF;
IF current_aou.parent_ou = ANY(seen_ous) THEN -- Parent is one we have seen?
RAISE 'OU LOOP: Saw % twice', current_aou.parent_ou; -- LOOP! ABORT!
END IF;
-- Get the next one!
SELECT INTO current_aou * FROM actor.org_unit WHERE id = current_aou.parent_ou;
seen_ous := seen_ous || current_aou.id;
depth_count := depth_count + 1;
IF depth_count = 100 THEN
RAISE 'OU CHECK TOO DEEP';
END IF;
END LOOP;
RETURN NEW;
END;
org_unit_prox_update
Signature: actor.org_unit_prox_update()
Returns: trigger
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
BEGIN
IF TG_OP = 'DELETE' THEN
DELETE FROM actor.org_unit_proximity WHERE (from_org = OLD.id or to_org= OLD.id);
END IF;
IF TG_OP = 'UPDATE' THEN
IF NEW.parent_ou <> OLD.parent_ou THEN
DELETE FROM actor.org_unit_proximity WHERE (from_org = OLD.id or to_org= OLD.id);
INSERT INTO actor.org_unit_proximity (from_org, to_org, prox)
SELECT l.id, r.id, actor.org_unit_proximity(l.id,r.id)
FROM actor.org_unit l, actor.org_unit r
WHERE (l.id = NEW.id or r.id = NEW.id);
END IF;
END IF;
IF TG_OP = 'INSERT' THEN
INSERT INTO actor.org_unit_proximity (from_org, to_org, prox)
SELECT l.id, r.id, actor.org_unit_proximity(l.id,r.id)
FROM actor.org_unit l, actor.org_unit r
WHERE (l.id = NEW.id or r.id = NEW.id);
END IF;
RETURN null;
END;
org_unit_proximity
Signature: actor.org_unit_proximity(integer, integer)
Returns: integer
Language |
sql |
Volatility |
STABLE |
Strict |
No |
Security Definer |
No |
SELECT COUNT(id)::INT FROM (
SELECT id FROM actor.org_unit_combined_ancestors($1, $2)
EXCEPT
SELECT id FROM actor.org_unit_common_ancestors($1, $2)
) z;
org_unit_simple_path
Signature: actor.org_unit_simple_path(integer, integer)
Returns: integer[]
Language |
sql |
Volatility |
STABLE |
Strict |
No |
Security Definer |
No |
WITH RECURSIVE descendant_depth(id, path) AS (
SELECT aou.id,
ARRAY[aou.id]
FROM actor.org_unit aou
JOIN actor.org_unit_type aout ON (aout.id = aou.ou_type)
WHERE aou.id = $2
UNION ALL
SELECT aou.id,
dd.path || ARRAY[aou.id]
FROM actor.org_unit aou
JOIN actor.org_unit_type aout ON (aout.id = aou.ou_type)
JOIN descendant_depth dd ON (dd.id = aou.parent_ou)
) SELECT dd.path
FROM actor.org_unit aou
JOIN descendant_depth dd USING (id)
WHERE aou.id = $1 ORDER BY dd.path;
otpauth_uri
Signature: actor.otpauth_uri(usr_id integer, otype text DEFAULT 'totp'::text, purpose text DEFAULT 'mfa'::text, additional_params hstore DEFAULT ''::hstore)
Returns: text
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
issuer TEXT := 'Evergreen';
algorithm TEXT := 'SHA1';
digits TEXT := '6';
period TEXT := '30';
counter TEXT := '0';
otp_secret TEXT;
otp_params HSTORE;
uri TEXT;
param_name TEXT;
uri_otype TEXT;
BEGIN
IF additional_params IS NULL THEN additional_params = ''::HSTORE; END IF;
-- we're going to be a bit strict here, for now
IF otype NOT IN ('webauthn','email','sms','totp','hotp') THEN RETURN NULL; END IF;
IF purpose NOT IN ('mfa','login') THEN RETURN NULL; END IF;
uri_otype := otype;
IF otype NOT IN ('totp','hotp') THEN
uri_otype := 'totp'; -- others are time-based, but with different settings
END IF;
-- protect "our" keys
additional_params := additional_params - ARRAY['issuer','algorithm','digits','period'];
SELECT passwd, salt::HSTORE INTO otp_secret, otp_params FROM actor.passwd WHERE usr = usr_id AND passwd_type = otype || '-' || purpose;
IF NOT FOUND THEN
issuer := COALESCE(
(SELECT value FROM config.internal_flag WHERE name = otype||'.'||purpose||'.issuer' AND enabled),
issuer
);
algorithm := COALESCE(
(SELECT value FROM config.internal_flag WHERE name = otype||'.'||purpose||'.algorithm' AND enabled),
algorithm
);
digits := COALESCE(
(SELECT value FROM config.internal_flag WHERE name = otype||'.'||purpose||'.digits' AND enabled),
digits
);
period := COALESCE(
(SELECT value FROM config.internal_flag WHERE name = otype||'.'||purpose||'.period' AND enabled),
period
);
otp_params := HSTORE('counter', counter)
|| HSTORE('issuer', issuer)
|| HSTORE('algorithm', UPPER(algorithm))
|| HSTORE('digits', digits)
|| HSTORE('period', period);
IF additional_params ? 'counter' THEN
otp_params := otp_params - 'counter';
END IF;
otp_params := additional_params || otp_params;
WITH new_secret AS (
INSERT INTO actor.passwd (usr, salt, passwd, passwd_type)
VALUES (usr_id, otp_params::TEXT, gen_random_uuid()::TEXT, otype || '-' || purpose)
RETURNING passwd, salt
) SELECT passwd, salt::HSTORE INTO otp_secret, otp_params FROM new_secret;
ELSE
otp_params := otp_params - akeys(additional_params); -- remove what we're receiving
otp_params := additional_params || otp_params;
IF additional_params != ''::HSTORE THEN -- new additional params were passed, let's save the salt again
UPDATE actor.passwd SET salt = otp_params::TEXT WHERE usr = usr_id AND passwd_type = otype || '-' || purpose;
END IF;
END IF;
uri := 'otpauth://' || uri_otype || '/' || evergreen.uri_escape(otp_params -> 'issuer') || ':' || usr_id::TEXT
||'?secret=' || evergreen.encode_base32(otp_secret);
FOREACH param_name IN ARRAY akeys(otp_params) LOOP
uri := uri || '&' || evergreen.uri_escape(param_name) || '=' || evergreen.uri_escape(otp_params -> param_name);
END LOOP;
RETURN uri;
END;
otpauth_uri_get_proof
Signature: actor.otpauth_uri_get_proof(otp_uri text, fuzziness integer DEFAULT 0)
Returns: TABLE(period_step integer, proof text)
Language |
plperlu |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
use Pass::OTP;
use Pass::OTP::URI;
my $otp_uri = shift;
my $fuzziness_width = shift // 0;
return undef unless $otp_uri;
my %otp_config = Pass::OTP::URI::parse($otp_uri);
return undef unless $otp_config{type};
for my $fuzziness ( -$fuzziness_width .. $fuzziness_width ) {
$otp_config{'start-time'} = $otp_config{period} * $fuzziness;
return_next({period_step => $fuzziness, proof => Pass::OTP::otp(%otp_config)});
}
return undef;
otpauth_uri_get_proof
Signature: actor.otpauth_uri_get_proof(usr_id integer, otype text, purpose text, fuzziness integer DEFAULT 0)
Returns: TABLE(period_step integer, proof text)
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
BEGIN
RETURN QUERY
SELECT * FROM actor.otpauth_uri_get_proof( actor.otpauth_uri($1, $2, $3), $4 );
RETURN;
END;
permit_remoteauth
Signature: actor.permit_remoteauth(profile_name text, userid bigint)
Returns: text
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
usr actor.usr%ROWTYPE;
profile config.remoteauth_profile%ROWTYPE;
perm TEXT;
context_org_list INT[];
home_prox INT;
block TEXT;
penalty_count INT;
BEGIN
SELECT INTO usr * FROM actor.usr WHERE id = userid AND NOT deleted;
IF usr IS NULL THEN
RETURN 'not_found';
END IF;
IF usr.barred IS TRUE THEN
RETURN 'blocked';
END IF;
SELECT INTO profile * FROM config.remoteauth_profile WHERE name = profile_name;
SELECT INTO context_org_list ARRAY_AGG(id) FROM actor.org_unit_full_path( profile.context_org );
-- user's home library must be within the context org
IF profile.restrict_to_org IS TRUE AND usr.home_ou NOT IN (SELECT * FROM UNNEST(context_org_list)) THEN
RETURN 'not_found';
END IF;
SELECT INTO perm code FROM permission.perm_list WHERE id = profile.perm;
IF permission.usr_has_perm(usr.id, perm, profile.context_org) IS FALSE THEN
RETURN 'not_found';
END IF;
IF usr.expire_date < NOW() AND profile.allow_expired IS FALSE THEN
RETURN 'expired';
END IF;
IF usr.active IS FALSE AND profile.allow_inactive IS FALSE THEN
RETURN 'blocked';
END IF;
-- Proximity of user's home_ou to context_org to see if penalties should be ignored.
SELECT INTO home_prox prox FROM actor.org_unit_proximity WHERE from_org = usr.home_ou AND to_org = profile.context_org;
-- Loop through the block list to see if the user has any matching penalties.
IF profile.block_list IS NOT NULL THEN
FOR block IN SELECT UNNEST(STRING_TO_ARRAY(profile.block_list, '|')) LOOP
SELECT INTO penalty_count COUNT(DISTINCT csp.*)
FROM actor.usr_standing_penalty usp
JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
WHERE usp.usr = usr.id
AND usp.org_unit IN ( SELECT * FROM UNNEST(context_org_list) )
AND ( usp.stop_date IS NULL or usp.stop_date > NOW() )
AND ( csp.ignore_proximity IS NULL OR csp.ignore_proximity < home_prox )
AND csp.block_list ~ block;
IF penalty_count > 0 THEN
-- User has penalties that match this block, so auth is not permitted.
-- Don't bother testing the rest of the block list.
RETURN 'blocked';
END IF;
END LOOP;
END IF;
-- User has passed all tests.
RETURN 'success';
END;
purge_usr_activity_by_type
Signature: actor.purge_usr_activity_by_type(act_type integer)
Returns: void
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
cur_usr INTEGER;
BEGIN
FOR cur_usr IN SELECT DISTINCT(usr)
FROM actor.usr_activity WHERE etype = act_type LOOP
DELETE FROM actor.usr_activity WHERE id IN (
SELECT id
FROM actor.usr_activity
WHERE usr = cur_usr AND etype = act_type
ORDER BY event_time DESC OFFSET 1
);
END LOOP;
END
remove_otpauth_uri
Signature: actor.remove_otpauth_uri(usr_id integer, otype text, purpose text, proof text, fuzziness integer DEFAULT 1)
Returns: boolean
Language |
plperlu |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
use Pass::OTP;
use Pass::OTP::URI;
my $usr_id = shift;
my $otype = shift;
my $purpose = shift;
my $proof = shift;
my $fuzziness_width = shift // 1;
if ($otype eq 'webauthn') { # nothing to prove
my $waq = spi_prepare('DELETE FROM actor.passwd WHERE usr = $1 AND passwd_type = $2 || $$-$$ || $3;', 'INTEGER', 'TEXT', 'TEXT');
my $res = spi_exec_prepared($waq, $usr_id, $otype, $purpose);
spi_freeplan($waq);
return 1;
}
# Normalize the proof value
$proof =~ s/\D//g;
return 0 unless $proof; # all-0s is not valid
my $q = spi_prepare('SELECT actor.otpauth_uri($1, $2, $3) AS uri;', 'INTEGER', 'TEXT', 'TEXT');
my $otp_uri = spi_exec_prepared($q, {limit => 1}, $usr_id, $otype, $purpose)->{rows}[0]{uri};
spi_freeplan($q);
return 0 unless $otp_uri;
my %otp_config = Pass::OTP::URI::parse($otp_uri);
for my $fuzziness ( -$fuzziness_width .. $fuzziness_width ) {
$otp_config{'start-time'} = $otp_config{period} * $fuzziness;
my $otp_code = Pass::OTP::otp(%otp_config);
if ($otp_code eq $proof) {
$q = spi_prepare('DELETE FROM actor.passwd WHERE usr = $1 AND passwd_type = $2 || $$-$$ || $3;', 'INTEGER', 'TEXT', 'TEXT');
my $res = spi_exec_prepared($q, $usr_id, $otype, $purpose);
spi_freeplan($q);
return 1;
}
}
return 0;
restrict_usr_message_limited
Signature: actor.restrict_usr_message_limited()
Returns: trigger
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
BEGIN
IF TG_OP = 'UPDATE' THEN
UPDATE actor.usr_message
SET read_date = NEW.read_date,
deleted = NEW.deleted
WHERE id = NEW.id;
RETURN NEW;
END IF;
RETURN NULL;
END;
set_passwd
Signature: actor.set_passwd(pw_usr integer, pw_type text, new_pass text, new_salt text DEFAULT NULL::text)
Returns: boolean
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
pw_salt TEXT;
pw_text TEXT;
BEGIN
/* Sets the password value, creating a new actor.passwd row if needed.
* If the password type supports it, the new_pass value is crypt()'ed.
* For crypt'ed passwords, the salt comes from one of 3 places in order:
* new_salt (if present), existing salt (if present), newly created
* salt.
*/
IF new_salt IS NOT NULL THEN
pw_salt := new_salt;
ELSE
pw_salt := actor.get_salt(pw_usr, pw_type);
IF pw_salt IS NULL THEN
/* We have no salt for this user + type. Assume they want a
* new salt. If this type is unsalted, create_salt() will
* return NULL. */
pw_salt := actor.create_salt(pw_type);
END IF;
END IF;
IF pw_salt IS NULL THEN
pw_text := new_pass; -- unsalted, use as-is.
ELSE
pw_text := CRYPT(new_pass, pw_salt);
END IF;
UPDATE actor.passwd
SET passwd = pw_text, salt = pw_salt, edit_date = NOW()
WHERE usr = pw_usr AND passwd_type = pw_type;
IF NOT FOUND THEN
-- no password row exists for this user + type. Create one.
INSERT INTO actor.passwd (usr, passwd_type, salt, passwd)
VALUES (pw_usr, pw_type, pw_salt, pw_text);
END IF;
RETURN TRUE;
END;
stat_cat_check
Signature: actor.stat_cat_check()
Returns: trigger
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
DECLARE
sipfield actor.stat_cat_sip_fields%ROWTYPE;
use_count INT;
BEGIN
IF NEW.sip_field IS NOT NULL THEN
SELECT INTO sipfield * FROM actor.stat_cat_sip_fields WHERE field = NEW.sip_field;
IF sipfield.one_only THEN
SELECT INTO use_count count(id) FROM actor.stat_cat WHERE sip_field = NEW.sip_field AND id != NEW.id;
IF use_count > 0 THEN
RAISE EXCEPTION 'Sip field cannot be used twice';
END IF;
END IF;
END IF;
RETURN NEW;
END;
stat_cat_entry_usr_map_cascade_delete
Signature: actor.stat_cat_entry_usr_map_cascade_delete()
Returns: trigger
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
BEGIN
DELETE FROM actor.stat_cat_entry_usr_map
WHERE stat_cat_entry = OLD.value
AND stat_cat = OLD.stat_cat;
RETURN NEW;
END;
stat_cat_entry_usr_map_cascade_update
Signature: actor.stat_cat_entry_usr_map_cascade_update()
Returns: trigger
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
BEGIN
UPDATE actor.stat_cat_entry_usr_map
SET stat_cat_entry = NEW.value
WHERE stat_cat_entry = OLD.value
AND stat_cat = OLD.stat_cat;
RETURN NEW;
END;
user_ingest_name_keywords
Signature: actor.user_ingest_name_keywords()
Returns: trigger
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
BEGIN
NEW.name_kw_tsvector := TO_TSVECTOR(
COALESCE(NEW.prefix, '') || ' ' ||
COALESCE(NEW.first_given_name, '') || ' ' ||
COALESCE(evergreen.unaccent_and_squash(NEW.first_given_name), '') || ' ' ||
COALESCE(NEW.second_given_name, '') || ' ' ||
COALESCE(evergreen.unaccent_and_squash(NEW.second_given_name), '') || ' ' ||
COALESCE(NEW.family_name, '') || ' ' ||
COALESCE(evergreen.unaccent_and_squash(NEW.family_name), '') || ' ' ||
COALESCE(NEW.suffix, '') || ' ' ||
COALESCE(NEW.pref_prefix, '') || ' ' ||
COALESCE(NEW.pref_first_given_name, '') || ' ' ||
COALESCE(evergreen.unaccent_and_squash(NEW.pref_first_given_name), '') || ' ' ||
COALESCE(NEW.pref_second_given_name, '') || ' ' ||
COALESCE(evergreen.unaccent_and_squash(NEW.pref_second_given_name), '') || ' ' ||
COALESCE(NEW.pref_family_name, '') || ' ' ||
COALESCE(evergreen.unaccent_and_squash(NEW.pref_family_name), '') || ' ' ||
COALESCE(NEW.pref_suffix, '') || ' ' ||
COALESCE(NEW.name_keywords, '')
);
RETURN NEW;
END;
usr_activity_get_type
Signature: actor.usr_activity_get_type(ewho text, ewhat text, ehow text)
Returns: SETOF config.usr_activity_type
Language |
sql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
SELECT * FROM config.usr_activity_type
WHERE
enabled AND
(ewho IS NULL OR ewho = $1) AND
(ewhat IS NULL OR ewhat = $2) AND
(ehow IS NULL OR ehow = $3)
ORDER BY
-- BOOL comparisons sort false to true
COALESCE(ewho, '') != COALESCE($1, ''),
COALESCE(ewhat,'') != COALESCE($2, ''),
COALESCE(ehow, '') != COALESCE($3, '')
LIMIT 1;
usr_activity_transient_trg
Signature: actor.usr_activity_transient_trg()
Returns: trigger
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
BEGIN
DELETE FROM actor.usr_activity act USING config.usr_activity_type atype
WHERE atype.transient AND
NEW.etype = atype.id AND
act.etype = atype.id AND
act.usr = NEW.usr;
RETURN NEW;
END;
usr_delete
Signature: actor.usr_delete(src_usr integer, dest_usr integer)
Returns: void
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
Logically deletes a user. Removes personally identifiable information, and purges associated data in other tables.
DECLARE
old_profile actor.usr.profile%type;
old_home_ou actor.usr.home_ou%type;
new_profile actor.usr.profile%type;
new_home_ou actor.usr.home_ou%type;
new_name text;
new_dob actor.usr.dob%type;
BEGIN
SELECT
id || '-PURGED-' || now(),
profile,
home_ou,
dob
INTO
new_name,
old_profile,
old_home_ou,
new_dob
FROM
actor.usr
WHERE
id = src_usr;
--
-- Quit if no such user
--
IF old_profile IS NULL THEN
RETURN;
END IF;
--
perform actor.usr_purge_data( src_usr, dest_usr );
--
-- Find the root grp_tree and the root org_unit. This would be simpler if we
-- could assume that there is only one root. Theoretically, someday, maybe,
-- there could be multiple roots, so we take extra trouble to get the right ones.
--
SELECT
id
INTO
new_profile
FROM
permission.grp_ancestors( old_profile )
WHERE
parent is null;
--
SELECT
id
INTO
new_home_ou
FROM
actor.org_unit_ancestors( old_home_ou )
WHERE
parent_ou is null;
--
-- Truncate date of birth
--
IF new_dob IS NOT NULL THEN
new_dob := date_trunc( 'year', new_dob );
END IF;
--
UPDATE
actor.usr
SET
card = NULL,
profile = new_profile,
usrname = new_name,
email = NULL,
passwd = random()::text,
standing = DEFAULT,
ident_type =
(
SELECT MIN( id )
FROM config.identification_type
),
ident_value = NULL,
ident_type2 = NULL,
ident_value2 = NULL,
net_access_level = DEFAULT,
photo_url = NULL,
prefix = NULL,
first_given_name = new_name,
second_given_name = NULL,
family_name = new_name,
suffix = NULL,
alias = NULL,
guardian = NULL,
day_phone = NULL,
evening_phone = NULL,
other_phone = NULL,
mailing_address = NULL,
billing_address = NULL,
home_ou = new_home_ou,
dob = new_dob,
active = FALSE,
master_account = DEFAULT,
super_user = DEFAULT,
barred = FALSE,
deleted = TRUE,
juvenile = DEFAULT,
usrgroup = 0,
claims_returned_count = DEFAULT,
credit_forward_balance = DEFAULT,
last_xact_id = DEFAULT,
pref_prefix = NULL,
pref_first_given_name = NULL,
pref_second_given_name = NULL,
pref_family_name = NULL,
pref_suffix = NULL,
name_keywords = NULL,
create_date = now(),
expire_date = now()
WHERE
id = src_usr;
END;
usr_merge
Signature: actor.usr_merge(src_usr integer, dest_usr integer, del_addrs boolean, del_cards boolean, deactivate_cards boolean)
Returns: void
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
Merges all user date from src_usr to dest_usr. When collisions occur, keep dest_usr’s data and delete src_usr’s data.
DECLARE
suffix TEXT;
bucket_row RECORD;
picklist_row RECORD;
queue_row RECORD;
folder_row RECORD;
BEGIN
-- Bail if src_usr equals dest_usr because the result of merging a
-- user with itself is not what you want.
IF src_usr = dest_usr THEN
RETURN;
END IF;
-- do some initial cleanup
UPDATE actor.usr SET card = NULL WHERE id = src_usr;
UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr;
UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr;
-- actor.*
IF del_cards THEN
DELETE FROM actor.card where usr = src_usr;
ELSE
IF deactivate_cards THEN
UPDATE actor.card SET active = 'f' WHERE usr = src_usr;
END IF;
UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr;
END IF;
IF del_addrs THEN
DELETE FROM actor.usr_address WHERE usr = src_usr;
ELSE
UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr;
END IF;
UPDATE actor.usr_message SET usr = dest_usr WHERE usr = src_usr;
-- dupes are technically OK in actor.usr_standing_penalty, should manually delete them...
UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr;
PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr);
PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr);
-- permission.*
PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr);
PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr);
PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr);
PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr);
-- container.*
-- For each *_bucket table: transfer every bucket belonging to src_usr
-- into the custody of dest_usr.
--
-- In order to avoid colliding with an existing bucket owned by
-- the destination user, append the source user's id (in parenthesese)
-- to the name. If you still get a collision, add successive
-- spaces to the name and keep trying until you succeed.
--
FOR bucket_row in
SELECT id, name
FROM container.biblio_record_entry_bucket
WHERE owner = src_usr
LOOP
suffix := ' (' || src_usr || ')';
LOOP
BEGIN
UPDATE container.biblio_record_entry_bucket
SET owner = dest_usr, name = name || suffix
WHERE id = bucket_row.id;
EXCEPTION WHEN unique_violation THEN
suffix := suffix || ' ';
CONTINUE;
END;
EXIT;
END LOOP;
END LOOP;
FOR bucket_row in
SELECT id, name
FROM container.call_number_bucket
WHERE owner = src_usr
LOOP
suffix := ' (' || src_usr || ')';
LOOP
BEGIN
UPDATE container.call_number_bucket
SET owner = dest_usr, name = name || suffix
WHERE id = bucket_row.id;
EXCEPTION WHEN unique_violation THEN
suffix := suffix || ' ';
CONTINUE;
END;
EXIT;
END LOOP;
END LOOP;
FOR bucket_row in
SELECT id, name
FROM container.copy_bucket
WHERE owner = src_usr
LOOP
suffix := ' (' || src_usr || ')';
LOOP
BEGIN
UPDATE container.copy_bucket
SET owner = dest_usr, name = name || suffix
WHERE id = bucket_row.id;
EXCEPTION WHEN unique_violation THEN
suffix := suffix || ' ';
CONTINUE;
END;
EXIT;
END LOOP;
END LOOP;
FOR bucket_row in
SELECT id, name
FROM container.user_bucket
WHERE owner = src_usr
LOOP
suffix := ' (' || src_usr || ')';
LOOP
BEGIN
UPDATE container.user_bucket
SET owner = dest_usr, name = name || suffix
WHERE id = bucket_row.id;
EXCEPTION WHEN unique_violation THEN
suffix := suffix || ' ';
CONTINUE;
END;
EXIT;
END LOOP;
END LOOP;
UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr;
-- vandelay.*
-- transfer queues the same way we transfer buckets (see above)
FOR queue_row in
SELECT id, name
FROM vandelay.queue
WHERE owner = src_usr
LOOP
suffix := ' (' || src_usr || ')';
LOOP
BEGIN
UPDATE vandelay.queue
SET owner = dest_usr, name = name || suffix
WHERE id = queue_row.id;
EXCEPTION WHEN unique_violation THEN
suffix := suffix || ' ';
CONTINUE;
END;
EXIT;
END LOOP;
END LOOP;
UPDATE vandelay.session_tracker SET usr = dest_usr WHERE usr = src_usr;
-- money.*
PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr);
PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr);
UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr;
UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr;
UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr;
-- action.*
UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr;
UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
UPDATE action.usr_circ_history SET usr = dest_usr WHERE usr = src_usr;
UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr;
UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
UPDATE action.hold_request SET canceled_by = dest_usr WHERE canceled_by = src_usr;
UPDATE action.hold_request_reset_reason_entry SET requestor = dest_usr WHERE requestor = src_usr;
UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr;
UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr;
-- acq.*
UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
UPDATE acq.invoice SET closed_by = dest_usr WHERE closed_by = src_usr;
-- transfer picklists the same way we transfer buckets (see above)
FOR picklist_row in
SELECT id, name
FROM acq.picklist
WHERE owner = src_usr
LOOP
suffix := ' (' || src_usr || ')';
LOOP
BEGIN
UPDATE acq.picklist
SET owner = dest_usr, name = name || suffix
WHERE id = picklist_row.id;
EXCEPTION WHEN unique_violation THEN
suffix := suffix || ' ';
CONTINUE;
END;
EXIT;
END LOOP;
END LOOP;
UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;
-- asset.*
UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
-- serial.*
UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr;
UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr;
-- reporter.*
-- It's not uncommon to define the reporter schema in a replica
-- DB only, so don't assume these tables exist in the write DB.
BEGIN
UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
EXCEPTION WHEN undefined_table THEN
-- do nothing
END;
BEGIN
UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
EXCEPTION WHEN undefined_table THEN
-- do nothing
END;
BEGIN
UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
EXCEPTION WHEN undefined_table THEN
-- do nothing
END;
BEGIN
-- transfer folders the same way we transfer buckets (see above)
FOR folder_row in
SELECT id, name
FROM reporter.template_folder
WHERE owner = src_usr
LOOP
suffix := ' (' || src_usr || ')';
LOOP
BEGIN
UPDATE reporter.template_folder
SET owner = dest_usr, name = name || suffix
WHERE id = folder_row.id;
EXCEPTION WHEN unique_violation THEN
suffix := suffix || ' ';
CONTINUE;
END;
EXIT;
END LOOP;
END LOOP;
EXCEPTION WHEN undefined_table THEN
-- do nothing
END;
BEGIN
-- transfer folders the same way we transfer buckets (see above)
FOR folder_row in
SELECT id, name
FROM reporter.report_folder
WHERE owner = src_usr
LOOP
suffix := ' (' || src_usr || ')';
LOOP
BEGIN
UPDATE reporter.report_folder
SET owner = dest_usr, name = name || suffix
WHERE id = folder_row.id;
EXCEPTION WHEN unique_violation THEN
suffix := suffix || ' ';
CONTINUE;
END;
EXIT;
END LOOP;
END LOOP;
EXCEPTION WHEN undefined_table THEN
-- do nothing
END;
BEGIN
-- transfer folders the same way we transfer buckets (see above)
FOR folder_row in
SELECT id, name
FROM reporter.output_folder
WHERE owner = src_usr
LOOP
suffix := ' (' || src_usr || ')';
LOOP
BEGIN
UPDATE reporter.output_folder
SET owner = dest_usr, name = name || suffix
WHERE id = folder_row.id;
EXCEPTION WHEN unique_violation THEN
suffix := suffix || ' ';
CONTINUE;
END;
EXIT;
END LOOP;
END LOOP;
EXCEPTION WHEN undefined_table THEN
-- do nothing
END;
-- propagate preferred name values from the source user to the
-- destination user, but only when values are not being replaced.
WITH susr AS (SELECT * FROM actor.usr WHERE id = src_usr)
UPDATE actor.usr SET
pref_prefix =
COALESCE(pref_prefix, (SELECT pref_prefix FROM susr)),
pref_first_given_name =
COALESCE(pref_first_given_name, (SELECT pref_first_given_name FROM susr)),
pref_second_given_name =
COALESCE(pref_second_given_name, (SELECT pref_second_given_name FROM susr)),
pref_family_name =
COALESCE(pref_family_name, (SELECT pref_family_name FROM susr)),
pref_suffix =
COALESCE(pref_suffix, (SELECT pref_suffix FROM susr))
WHERE id = dest_usr;
-- Copy and deduplicate name keywords
-- String -> array -> rows -> DISTINCT -> array -> string
WITH susr AS (SELECT * FROM actor.usr WHERE id = src_usr),
dusr AS (SELECT * FROM actor.usr WHERE id = dest_usr)
UPDATE actor.usr SET name_keywords = (
WITH keywords AS (
SELECT DISTINCT UNNEST(
REGEXP_SPLIT_TO_ARRAY(
COALESCE((SELECT name_keywords FROM susr), '') || ' ' ||
COALESCE((SELECT name_keywords FROM dusr), ''), E'\\s+'
)
) AS parts
) SELECT STRING_AGG(kw.parts, ' ') FROM keywords kw
) WHERE id = dest_usr;
-- Finally, delete the source user
PERFORM actor.usr_delete(src_usr,dest_usr);
END;
usr_merge_rows
Signature: actor.usr_merge_rows(table_name text, col_name text, src_usr integer, dest_usr integer)
Returns: void
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
Attempts to move each row of the specified table from src_user to dest_user. Where conflicts exist, the conflicting "source" row is deleted.
DECLARE
sel TEXT;
upd TEXT;
del TEXT;
cur_row RECORD;
BEGIN
sel := 'SELECT id::BIGINT FROM ' || table_name || ' WHERE ' || quote_ident(col_name) || ' = ' || quote_literal(src_usr);
upd := 'UPDATE ' || table_name || ' SET ' || quote_ident(col_name) || ' = ' || quote_literal(dest_usr) || ' WHERE id = ';
del := 'DELETE FROM ' || table_name || ' WHERE id = ';
FOR cur_row IN EXECUTE sel LOOP
BEGIN
--RAISE NOTICE 'Attempting to merge % %', table_name, cur_row.id;
EXECUTE upd || cur_row.id;
EXCEPTION WHEN unique_violation THEN
--RAISE NOTICE 'Deleting conflicting % %', table_name, cur_row.id;
EXECUTE del || cur_row.id;
END;
END LOOP;
END;
usr_purge_data
Signature: actor.usr_purge_data(src_usr integer, specified_dest_usr integer)
Returns: void
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
Finds rows dependent on a given row in actor.usr and either deletes them or reassigns them to a different user.
DECLARE
suffix TEXT;
renamable_row RECORD;
dest_usr INTEGER;
BEGIN
IF specified_dest_usr IS NULL THEN
dest_usr := 1; -- Admin user on stock installs
ELSE
dest_usr := specified_dest_usr;
END IF;
-- action_trigger.event (even doing this, event_output may--and probably does--contain PII and should have a retention/removal policy)
UPDATE action_trigger.event SET context_user = dest_usr WHERE context_user = src_usr;
-- acq.*
UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
UPDATE acq.lineitem SET creator = dest_usr WHERE creator = src_usr;
UPDATE acq.lineitem SET editor = dest_usr WHERE editor = src_usr;
UPDATE acq.lineitem SET selector = dest_usr WHERE selector = src_usr;
UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
UPDATE acq.invoice SET closed_by = dest_usr WHERE closed_by = src_usr;
DELETE FROM acq.lineitem_usr_attr_definition WHERE usr = src_usr;
-- Update with a rename to avoid collisions
FOR renamable_row in
SELECT id, name
FROM acq.picklist
WHERE owner = src_usr
LOOP
suffix := ' (' || src_usr || ')';
LOOP
BEGIN
UPDATE acq.picklist
SET owner = dest_usr, name = name || suffix
WHERE id = renamable_row.id;
EXCEPTION WHEN unique_violation THEN
suffix := suffix || ' ';
CONTINUE;
END;
EXIT;
END LOOP;
END LOOP;
UPDATE acq.picklist SET creator = dest_usr WHERE creator = src_usr;
UPDATE acq.picklist SET editor = dest_usr WHERE editor = src_usr;
UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
UPDATE acq.purchase_order SET creator = dest_usr WHERE creator = src_usr;
UPDATE acq.purchase_order SET editor = dest_usr WHERE editor = src_usr;
UPDATE acq.claim_event SET creator = dest_usr WHERE creator = src_usr;
-- action.*
DELETE FROM action.circulation WHERE usr = src_usr;
UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
UPDATE action.hold_request SET canceled_by = dest_usr WHERE canceled_by = src_usr;
UPDATE action.hold_request_reset_reason_entry SET requestor = dest_usr WHERE requestor = src_usr;
DELETE FROM action.hold_request WHERE usr = src_usr;
UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
DELETE FROM action.non_cataloged_circulation WHERE patron = src_usr;
UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
DELETE FROM action.survey_response WHERE usr = src_usr;
UPDATE action.fieldset SET owner = dest_usr WHERE owner = src_usr;
DELETE FROM action.usr_circ_history WHERE usr = src_usr;
UPDATE action.curbside SET notes = NULL WHERE patron = src_usr;
-- actor.*
DELETE FROM actor.card WHERE usr = src_usr;
DELETE FROM actor.stat_cat_entry_usr_map WHERE target_usr = src_usr;
DELETE FROM actor.usr_privacy_waiver WHERE usr = src_usr;
DELETE FROM actor.usr_message WHERE usr = src_usr;
-- The following update is intended to avoid transient violations of a foreign
-- key constraint, whereby actor.usr_address references itself. It may not be
-- necessary, but it does no harm.
UPDATE actor.usr_address SET replaces = NULL
WHERE usr = src_usr AND replaces IS NOT NULL;
DELETE FROM actor.usr_address WHERE usr = src_usr;
DELETE FROM actor.usr_org_unit_opt_in WHERE usr = src_usr;
UPDATE actor.usr_org_unit_opt_in SET staff = dest_usr WHERE staff = src_usr;
DELETE FROM actor.usr_setting WHERE usr = src_usr;
DELETE FROM actor.usr_standing_penalty WHERE usr = src_usr;
UPDATE actor.usr_message SET title = 'purged', message = 'purged', read_date = NOW() WHERE usr = src_usr;
DELETE FROM actor.usr_message WHERE usr = src_usr;
UPDATE actor.usr_standing_penalty SET staff = dest_usr WHERE staff = src_usr;
UPDATE actor.usr_message SET editor = dest_usr WHERE editor = src_usr;
-- asset.*
UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
-- auditor.*
DELETE FROM auditor.actor_usr_address_history WHERE id = src_usr;
DELETE FROM auditor.actor_usr_history WHERE id = src_usr;
UPDATE auditor.asset_call_number_history SET creator = dest_usr WHERE creator = src_usr;
UPDATE auditor.asset_call_number_history SET editor = dest_usr WHERE editor = src_usr;
UPDATE auditor.asset_copy_history SET creator = dest_usr WHERE creator = src_usr;
UPDATE auditor.asset_copy_history SET editor = dest_usr WHERE editor = src_usr;
UPDATE auditor.biblio_record_entry_history SET creator = dest_usr WHERE creator = src_usr;
UPDATE auditor.biblio_record_entry_history SET editor = dest_usr WHERE editor = src_usr;
-- biblio.*
UPDATE biblio.record_entry SET creator = dest_usr WHERE creator = src_usr;
UPDATE biblio.record_entry SET editor = dest_usr WHERE editor = src_usr;
UPDATE biblio.record_note SET creator = dest_usr WHERE creator = src_usr;
UPDATE biblio.record_note SET editor = dest_usr WHERE editor = src_usr;
-- container.*
-- Update buckets with a rename to avoid collisions
FOR renamable_row in
SELECT id, name
FROM container.biblio_record_entry_bucket
WHERE owner = src_usr
LOOP
suffix := ' (' || src_usr || ')';
LOOP
BEGIN
UPDATE container.biblio_record_entry_bucket
SET owner = dest_usr, name = name || suffix
WHERE id = renamable_row.id;
EXCEPTION WHEN unique_violation THEN
suffix := suffix || ' ';
CONTINUE;
END;
EXIT;
END LOOP;
END LOOP;
FOR renamable_row in
SELECT id, name
FROM container.call_number_bucket
WHERE owner = src_usr
LOOP
suffix := ' (' || src_usr || ')';
LOOP
BEGIN
UPDATE container.call_number_bucket
SET owner = dest_usr, name = name || suffix
WHERE id = renamable_row.id;
EXCEPTION WHEN unique_violation THEN
suffix := suffix || ' ';
CONTINUE;
END;
EXIT;
END LOOP;
END LOOP;
FOR renamable_row in
SELECT id, name
FROM container.copy_bucket
WHERE owner = src_usr
LOOP
suffix := ' (' || src_usr || ')';
LOOP
BEGIN
UPDATE container.copy_bucket
SET owner = dest_usr, name = name || suffix
WHERE id = renamable_row.id;
EXCEPTION WHEN unique_violation THEN
suffix := suffix || ' ';
CONTINUE;
END;
EXIT;
END LOOP;
END LOOP;
FOR renamable_row in
SELECT id, name
FROM container.user_bucket
WHERE owner = src_usr
LOOP
suffix := ' (' || src_usr || ')';
LOOP
BEGIN
UPDATE container.user_bucket
SET owner = dest_usr, name = name || suffix
WHERE id = renamable_row.id;
EXCEPTION WHEN unique_violation THEN
suffix := suffix || ' ';
CONTINUE;
END;
EXIT;
END LOOP;
END LOOP;
DELETE FROM container.user_bucket_item WHERE target_user = src_usr;
-- money.*
DELETE FROM money.billable_xact WHERE usr = src_usr;
DELETE FROM money.collections_tracker WHERE usr = src_usr;
UPDATE money.collections_tracker SET collector = dest_usr WHERE collector = src_usr;
-- permission.*
DELETE FROM permission.usr_grp_map WHERE usr = src_usr;
DELETE FROM permission.usr_object_perm_map WHERE usr = src_usr;
DELETE FROM permission.usr_perm_map WHERE usr = src_usr;
DELETE FROM permission.usr_work_ou_map WHERE usr = src_usr;
-- reporter.*
-- Update with a rename to avoid collisions
BEGIN
FOR renamable_row in
SELECT id, name
FROM reporter.output_folder
WHERE owner = src_usr
LOOP
suffix := ' (' || src_usr || ')';
LOOP
BEGIN
UPDATE reporter.output_folder
SET owner = dest_usr, name = name || suffix
WHERE id = renamable_row.id;
EXCEPTION WHEN unique_violation THEN
suffix := suffix || ' ';
CONTINUE;
END;
EXIT;
END LOOP;
END LOOP;
EXCEPTION WHEN undefined_table THEN
-- do nothing
END;
BEGIN
UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
EXCEPTION WHEN undefined_table THEN
-- do nothing
END;
-- Update with a rename to avoid collisions
BEGIN
FOR renamable_row in
SELECT id, name
FROM reporter.report_folder
WHERE owner = src_usr
LOOP
suffix := ' (' || src_usr || ')';
LOOP
BEGIN
UPDATE reporter.report_folder
SET owner = dest_usr, name = name || suffix
WHERE id = renamable_row.id;
EXCEPTION WHEN unique_violation THEN
suffix := suffix || ' ';
CONTINUE;
END;
EXIT;
END LOOP;
END LOOP;
EXCEPTION WHEN undefined_table THEN
-- do nothing
END;
BEGIN
UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
EXCEPTION WHEN undefined_table THEN
-- do nothing
END;
BEGIN
UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
EXCEPTION WHEN undefined_table THEN
-- do nothing
END;
-- Update with a rename to avoid collisions
BEGIN
FOR renamable_row in
SELECT id, name
FROM reporter.template_folder
WHERE owner = src_usr
LOOP
suffix := ' (' || src_usr || ')';
LOOP
BEGIN
UPDATE reporter.template_folder
SET owner = dest_usr, name = name || suffix
WHERE id = renamable_row.id;
EXCEPTION WHEN unique_violation THEN
suffix := suffix || ' ';
CONTINUE;
END;
EXIT;
END LOOP;
END LOOP;
EXCEPTION WHEN undefined_table THEN
-- do nothing
END;
-- vandelay.*
-- Update with a rename to avoid collisions
FOR renamable_row in
SELECT id, name
FROM vandelay.queue
WHERE owner = src_usr
LOOP
suffix := ' (' || src_usr || ')';
LOOP
BEGIN
UPDATE vandelay.queue
SET owner = dest_usr, name = name || suffix
WHERE id = renamable_row.id;
EXCEPTION WHEN unique_violation THEN
suffix := suffix || ' ';
CONTINUE;
END;
EXIT;
END LOOP;
END LOOP;
UPDATE vandelay.session_tracker SET usr = dest_usr WHERE usr = src_usr;
-- NULL-ify addresses last so other cleanup (e.g. circ anonymization)
-- can access the information before deletion.
UPDATE actor.usr SET
active = FALSE,
card = NULL,
mailing_address = NULL,
billing_address = NULL
WHERE id = src_usr;
END;
verify_passwd
Signature: actor.verify_passwd(pw_usr integer, pw_type text, test_passwd text)
Returns: boolean
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
Yes (returns NULL on NULL input) |
Security Definer |
No |
DECLARE
pw_salt TEXT;
api_enabled BOOL;
BEGIN
/* Returns TRUE if the password provided matches the in-db password.
* If the password type is salted, we compare the output of CRYPT().
* NOTE: test_passwd is MD5(salt || MD5(password)) for legacy
* 'main' passwords.
*
* Password type 'api' requires that the user be enabled as an
* integrator in the openapi.integrator table.
*/
IF pw_type = 'api' THEN
SELECT enabled INTO api_enabled
FROM openapi.integrator
WHERE id = pw_usr;
IF NOT FOUND OR api_enabled IS FALSE THEN
-- API integrator account not registered
RETURN FALSE;
END IF;
END IF;
SELECT INTO pw_salt salt FROM actor.passwd
WHERE usr = pw_usr AND passwd_type = pw_type;
IF NOT FOUND THEN
-- no such password
RETURN FALSE;
END IF;
IF pw_salt IS NULL THEN
-- Password is unsalted, compare the un-CRYPT'ed values.
RETURN EXISTS (
SELECT TRUE FROM actor.passwd WHERE
usr = pw_usr AND
passwd_type = pw_type AND
passwd = test_passwd
);
END IF;
RETURN EXISTS (
SELECT TRUE FROM actor.passwd WHERE
usr = pw_usr AND
passwd_type = pw_type AND
passwd = CRYPT(test_passwd, pw_salt)
);
END;