245 lines
8.2 KiB
PL/PgSQL
245 lines
8.2 KiB
PL/PgSQL
-- Advance one explicit invokeWithLayer observation without paying one client
|
|
-- round trip for every lock, comparison and projection update. The caller
|
|
-- still owns the surrounding transaction/savepoint so identity_changed can
|
|
-- roll the complete attempt back and retry with a fresh READ COMMITTED view.
|
|
|
|
CREATE OR REPLACE FUNCTION public.telesrv_advance_auth_session_layer(
|
|
p_raw_auth_key_id bigint,
|
|
p_session_id bigint,
|
|
p_layer integer,
|
|
p_msg_id bigint,
|
|
p_expires_at timestamptz
|
|
)
|
|
RETURNS TABLE (
|
|
advance_status text,
|
|
current_layer integer,
|
|
current_msg_id bigint,
|
|
current_observation_id bigint,
|
|
current_expires_at timestamptz,
|
|
shared_default boolean,
|
|
applied boolean
|
|
)
|
|
LANGUAGE plpgsql
|
|
AS $$
|
|
DECLARE
|
|
v_now timestamptz := now();
|
|
v_hint_expiry integer;
|
|
v_hint_perm_id bigint;
|
|
v_hint_bound boolean := false;
|
|
v_hint_has_identity boolean := false;
|
|
v_hint_identity_id bigint;
|
|
v_raw_expiry integer;
|
|
v_actual_perm_id bigint;
|
|
v_actual_bound boolean := false;
|
|
v_actual_has_identity boolean := false;
|
|
v_actual_identity_id bigint;
|
|
v_perm_expiry integer;
|
|
v_current_found boolean := false;
|
|
v_current_layer integer := 0;
|
|
v_current_msg_id bigint := 0;
|
|
v_current_observation_id bigint := 0;
|
|
v_current_expires_at timestamptz := p_expires_at;
|
|
v_shared_default boolean := false;
|
|
v_observation_id bigint;
|
|
v_key_ids bigint[];
|
|
v_updated integer;
|
|
BEGIN
|
|
IF p_layer <= 0
|
|
OR p_msg_id <= 0
|
|
OR p_msg_id % 4 <> 0
|
|
OR (p_msg_id & 4294967295) = 0
|
|
OR p_expires_at IS NULL
|
|
OR NOT v_now < p_expires_at
|
|
OR p_expires_at - interval '301 seconds' > v_now + interval '30 seconds'
|
|
THEN
|
|
RETURN QUERY SELECT
|
|
'evidence_invalid'::text, 0, 0::bigint, 0::bigint,
|
|
COALESCE(p_expires_at, v_now), false, false;
|
|
RETURN;
|
|
END IF;
|
|
|
|
-- Lock-free identity hint. It is deliberately revalidated after the raw
|
|
-- row lock; a newly committed first bind returns identity_changed rather
|
|
-- than taking a permanent identity lock in raw->identity order.
|
|
SELECT key.expires_at, binding.perm_auth_key_id
|
|
INTO v_hint_expiry, v_hint_perm_id
|
|
FROM public.auth_keys AS key
|
|
LEFT JOIN public.temp_auth_key_bindings AS binding
|
|
ON binding.temp_auth_key_id = key.auth_key_id
|
|
WHERE key.auth_key_id = p_raw_auth_key_id;
|
|
IF NOT FOUND THEN
|
|
RETURN QUERY SELECT
|
|
'auth_key_not_found'::text, 0, 0::bigint, 0::bigint,
|
|
p_expires_at, false, false;
|
|
RETURN;
|
|
END IF;
|
|
|
|
v_hint_bound := v_hint_perm_id IS NOT NULL;
|
|
v_hint_has_identity := v_hint_bound OR v_hint_expiry = 0;
|
|
IF v_hint_has_identity THEN
|
|
v_hint_identity_id := CASE
|
|
WHEN v_hint_bound THEN v_hint_perm_id
|
|
ELSE p_raw_auth_key_id
|
|
END;
|
|
PERFORM pg_advisory_xact_lock(
|
|
1096111176::integer,
|
|
hashint8(v_hint_identity_id)::integer
|
|
);
|
|
END IF;
|
|
|
|
SELECT key.expires_at
|
|
INTO v_raw_expiry
|
|
FROM public.auth_keys AS key
|
|
WHERE key.auth_key_id = p_raw_auth_key_id
|
|
FOR UPDATE;
|
|
IF NOT FOUND THEN
|
|
RETURN QUERY SELECT
|
|
'auth_key_not_found'::text, 0, 0::bigint, 0::bigint,
|
|
p_expires_at, false, false;
|
|
RETURN;
|
|
END IF;
|
|
|
|
v_actual_perm_id := NULL;
|
|
SELECT binding.perm_auth_key_id
|
|
INTO v_actual_perm_id
|
|
FROM public.temp_auth_key_bindings AS binding
|
|
WHERE binding.temp_auth_key_id = p_raw_auth_key_id;
|
|
v_actual_bound := FOUND;
|
|
IF NOT v_actual_bound THEN
|
|
v_actual_perm_id := p_raw_auth_key_id;
|
|
END IF;
|
|
v_actual_has_identity := v_actual_bound OR v_raw_expiry = 0;
|
|
v_actual_identity_id := v_actual_perm_id;
|
|
|
|
IF v_actual_has_identity <> v_hint_has_identity
|
|
OR (v_actual_has_identity AND v_actual_identity_id <> v_hint_identity_id)
|
|
OR v_actual_bound <> v_hint_bound
|
|
OR v_raw_expiry <> v_hint_expiry
|
|
THEN
|
|
RETURN QUERY SELECT
|
|
'identity_changed'::text, 0, 0::bigint, 0::bigint,
|
|
p_expires_at, false, false;
|
|
RETURN;
|
|
END IF;
|
|
|
|
IF v_actual_bound THEN
|
|
SELECT key.expires_at
|
|
INTO v_perm_expiry
|
|
FROM public.auth_keys AS key
|
|
WHERE key.auth_key_id = v_actual_perm_id
|
|
FOR UPDATE;
|
|
IF NOT FOUND OR v_raw_expiry <= 0 OR v_perm_expiry <> 0 THEN
|
|
RETURN QUERY SELECT
|
|
'binding_invalid'::text, 0, 0::bigint, 0::bigint,
|
|
p_expires_at, false, false;
|
|
RETURN;
|
|
END IF;
|
|
END IF;
|
|
|
|
SELECT evidence.layer,
|
|
evidence.msg_id,
|
|
evidence.observation_id,
|
|
evidence.expires_at
|
|
INTO v_current_layer,
|
|
v_current_msg_id,
|
|
v_current_observation_id,
|
|
v_current_expires_at
|
|
FROM public.auth_key_session_layers AS evidence
|
|
WHERE evidence.raw_auth_key_id = p_raw_auth_key_id
|
|
AND evidence.session_id = p_session_id
|
|
FOR UPDATE;
|
|
v_current_found := FOUND;
|
|
|
|
IF v_current_found AND v_now < v_current_expires_at THEN
|
|
IF p_msg_id < v_current_msg_id THEN
|
|
SELECT key.layer = v_current_layer
|
|
AND key.layer_observation_id = v_current_observation_id
|
|
INTO v_shared_default
|
|
FROM public.auth_keys AS key
|
|
WHERE key.auth_key_id = v_actual_perm_id;
|
|
RETURN QUERY SELECT
|
|
'ok'::text, v_current_layer, v_current_msg_id,
|
|
v_current_observation_id, v_current_expires_at,
|
|
v_shared_default, false;
|
|
RETURN;
|
|
END IF;
|
|
IF p_msg_id = v_current_msg_id THEN
|
|
IF p_layer <> v_current_layer THEN
|
|
RETURN QUERY SELECT
|
|
'conflict'::text, v_current_layer, v_current_msg_id,
|
|
v_current_observation_id, v_current_expires_at,
|
|
false, false;
|
|
RETURN;
|
|
END IF;
|
|
SELECT key.layer = v_current_layer
|
|
AND key.layer_observation_id = v_current_observation_id
|
|
INTO v_shared_default
|
|
FROM public.auth_keys AS key
|
|
WHERE key.auth_key_id = v_actual_perm_id;
|
|
RETURN QUERY SELECT
|
|
'ok'::text, v_current_layer, v_current_msg_id,
|
|
v_current_observation_id, v_current_expires_at,
|
|
v_shared_default, false;
|
|
RETURN;
|
|
END IF;
|
|
END IF;
|
|
|
|
v_observation_id := nextval('public.auth_key_layer_observation_seq');
|
|
INSERT INTO public.auth_key_session_layers (
|
|
raw_auth_key_id,
|
|
session_id,
|
|
layer,
|
|
msg_id,
|
|
observation_id,
|
|
expires_at
|
|
) VALUES (
|
|
p_raw_auth_key_id,
|
|
p_session_id,
|
|
p_layer,
|
|
p_msg_id,
|
|
v_observation_id,
|
|
p_expires_at
|
|
)
|
|
ON CONFLICT (raw_auth_key_id, session_id) DO UPDATE SET
|
|
layer = EXCLUDED.layer,
|
|
msg_id = EXCLUDED.msg_id,
|
|
observation_id = EXCLUDED.observation_id,
|
|
expires_at = EXCLUDED.expires_at
|
|
RETURNING layer, msg_id, observation_id, expires_at
|
|
INTO v_current_layer,
|
|
v_current_msg_id,
|
|
v_current_observation_id,
|
|
v_current_expires_at;
|
|
|
|
v_key_ids := ARRAY[p_raw_auth_key_id];
|
|
IF v_actual_perm_id <> p_raw_auth_key_id THEN
|
|
v_key_ids := array_append(v_key_ids, v_actual_perm_id);
|
|
END IF;
|
|
UPDATE public.auth_keys AS key
|
|
SET layer = p_layer,
|
|
layer_observation_id = v_observation_id
|
|
WHERE key.auth_key_id = ANY(v_key_ids)
|
|
AND key.layer_observation_id < v_observation_id;
|
|
GET DIAGNOSTICS v_updated = ROW_COUNT;
|
|
IF v_updated <> cardinality(v_key_ids) THEN
|
|
RAISE EXCEPTION
|
|
'publish auth session Layer defaults updated % of % locked keys',
|
|
v_updated,
|
|
cardinality(v_key_ids)
|
|
USING ERRCODE = '23000';
|
|
END IF;
|
|
|
|
UPDATE public.authorizations AS authz
|
|
SET layer = p_layer
|
|
WHERE authz.auth_key_id = ANY(v_key_ids);
|
|
|
|
RETURN QUERY SELECT
|
|
'ok'::text, v_current_layer, v_current_msg_id,
|
|
v_current_observation_id, v_current_expires_at,
|
|
true, true;
|
|
END;
|
|
$$;
|
|
|
|
COMMENT ON FUNCTION public.telesrv_advance_auth_session_layer(
|
|
bigint, bigint, integer, bigint, timestamptz
|
|
) IS 'Atomically advances durable invokeWithLayer evidence while preserving auth identity lock order';
|