actor Functions
This page documents all 51 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 |
||
|
sql |
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 |
||
|
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;
generate_barcode
Signature: actor.generate_barcode(prefix text, numchars integer, seqname text)
Returns: text
Language |
sql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
Generate a barcode starting with 'prefix' and followed by 'numchars' numbers. The auto portion numbers are generated from the provided sequence, guaranteeing uniquness across all barcodes generated with the same sequence. The number is left-padded with zeros to meet the numchars size requirement. Returns NULL if the sequnce value is higher than numchars can accommodate.
SELECT NEXTVAL($3); -- bump the sequence up 1
SELECT CASE
WHEN LENGTH(CURRVAL($3)::TEXT) > $2 THEN NULL
ELSE $1 || LPAD(CURRVAL($3)::TEXT, $2, '0')
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
record_usr_delta
Signature: actor.record_usr_delta()
Returns: trigger
Language |
plpgsql |
Volatility |
VOLATILE |
Strict |
No |
Security Definer |
No |
BEGIN
INSERT INTO actor.usr_delta_history (eg_user, eg_ws, usr_id, delta, keylist)
SELECT a.eg_user,
a.eg_ws,
OLD.id,
evergreen.json_delta(to_json(OLD.*), to_json(NEW.*), TG_ARGV),
TG_ARGV
FROM auditor.get_audit_info() a;
RETURN NEW;
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, '') || ' ' ||
COALESCE(NEW.guardian, '') || ' ' ||
COALESCE(evergreen.unaccent_and_squash(NEW.guardian), '')
);
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,
guardian_email = 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;
-- LP#885270: Addresses owned by src_usr that are referenced by other
-- users (as billing_address or mailing_address) cannot be deleted.
-- Reassign ownership of those addresses to one of the referencing
-- users so the address is preserved for them.
UPDATE actor.usr_address addr SET usr = sub.new_owner
FROM (
SELECT a.id, (
SELECT u.id FROM actor.usr u
WHERE (u.billing_address = a.id OR u.mailing_address = a.id)
AND u.id != src_usr
LIMIT 1
) AS new_owner
FROM actor.usr_address a
WHERE a.usr = src_usr
AND EXISTS (
SELECT 1 FROM actor.usr u
WHERE (u.billing_address = a.id OR u.mailing_address = a.id)
AND u.id != src_usr
)
) sub
WHERE addr.id = sub.id;
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;