--liquibase formatted sql


-- ============================================================================
--changeset michael.hoennig:context-DEFINE endDelimiter:--//
-- ----------------------------------------------------------------------------

/*
    Callback which is called after the context has been (re-) defined.
    This function will be overwritten by later changesets.
 */
create procedure base.contextDefined(
    currentTask varchar(127),
    currentRequest text,
    currentSubject varchar(63),
    assumedRoles varchar(1023)
)
    language plpgsql as $$
begin
end; $$;

/*
    Defines the transaction context.
 */
create or replace procedure base.defineContext(
    currentTask varchar(127),
    currentRequest text = null,
    currentSubject varchar(63) = null,
    assumedRoles varchar(1023) = null
)
    language plpgsql as $$
begin
    currentTask := coalesce(currentTask, '');
    assert length(currentTask) <= 127, FORMAT('currentTask must not be longer than 127 characters: "%s"', currentTask);
    assert length(currentTask) >= 12, FORMAT('currentTask must be at least 12 characters long: "%s""', currentTask);
    execute format('set local hsadminng.currentTask to %L', currentTask);

    currentRequest := coalesce(currentRequest, '');
    execute format('set local hsadminng.currentRequest to %L', currentRequest);

    currentSubject := coalesce(currentSubject, '');
    assert length(currentSubject) <= 63, FORMAT('currentSubject must not be longer than 63 characters: "%s"', currentSubject);
    execute format('set local hsadminng.currentSubject to %L', currentSubject);

    assumedRoles := coalesce(assumedRoles, '');
    assert length(assumedRoles) <= 1023, FORMAT('assumedRoles must not be longer than 1023 characters: "%s"', assumedRoles);
    execute format('set local hsadminng.assumedRoles to %L', assumedRoles);

    call base.contextDefined(currentTask, currentRequest, currentSubject, assumedRoles);
end; $$;
--//


-- ============================================================================
--changeset michael.hoennig:context-CURRENT-TASK endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
    Returns the current task as set by `hsadminng.currentTask`.
    Raises exception if not set.
 */
create or replace function base.currentTask()
    returns varchar(127)
    stable -- leakproof
    language plpgsql as $$
declare
    currentTask varchar(127);
begin
    begin
        currentTask := current_setting('hsadminng.currentTask');
    exception
        when others then
            currentTask := null;
    end;
    if (currentTask is null or currentTask = '') then
        raise exception '[401] currentTask must be defined, please call `base.defineContext(...)`';
    end if;
    return currentTask;
end; $$;
--//


-- ============================================================================
--changeset michael.hoennig:context-CURRENT-REQUEST endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
    Returns the current http request as set via `base.defineContext(...)`.
    Raises exception if not set.
 */
create or replace function base.currentRequest()
    returns text
    stable -- leakproof
    language plpgsql as $$
declare
    currentRequest text;
begin
    begin
        currentRequest := current_setting('hsadminng.currentRequest');
    exception
        when others then
            currentRequest := null;
    end;
    return currentRequest;
end; $$;
--//


-- ============================================================================
--changeset michael.hoennig:context-current-subject endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
    Returns the current user as defined by `base.defineContext(...)`.
 */
create or replace function base.currentSubject()
    returns varchar(63)
    stable -- leakproof
    language plpgsql as $$
declare
    currentSubject varchar(63);
begin
    begin
        currentSubject := current_setting('hsadminng.currentSubject');
    exception
        when others then
            currentSubject := null;
    end;
    return currentSubject;
end; $$;
--//


-- ============================================================================
--changeset michael.hoennig:context-base.ASSUMED-ROLES endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
    Returns assumed role names as set in `hsadminng.assumedRoles`
    or empty array, if not set.
 */
create or replace function base.assumedRoles()
    returns varchar(1023)[]
    stable -- leakproof
    language plpgsql as $$
begin
    return string_to_array(current_setting('hsadminng.assumedRoles', true), ';');
end; $$;

create or replace function base.cleanIdentifier(rawIdentifier varchar)
    returns varchar
    returns null on null input
    language plpgsql as $$
declare
    cleanIdentifier varchar;
begin
    cleanIdentifier := regexp_replace(rawIdentifier, '[^A-Za-z0-9\-._|]+', '', 'g');
    return cleanIdentifier;
end; $$;

create or replace function base.pureIdentifier(rawIdentifier varchar)
    returns varchar
    returns null on null input
    language plpgsql as $$
declare
    cleanIdentifier varchar;
begin
    cleanIdentifier := base.cleanIdentifier(rawIdentifier);
    if cleanIdentifier != rawIdentifier then
        raise exception 'identifier "%" contains invalid characters, maybe use "%"', rawIdentifier, cleanIdentifier;
    end if;
    return cleanIdentifier;
end; $$;

create or replace function base.findObjectUuidByIdName(objectTable varchar, objectIdName varchar)
    returns uuid
    returns null on null input
    language plpgsql as $$
declare
    sql  varchar;
    uuid uuid;
begin
    objectTable := base.pureIdentifier(objectTable);
    objectIdName := base.pureIdentifier(objectIdName);
    sql := format('select * from %sUuidByIdName(%L);', objectTable, objectIdName);
    begin
        execute sql into uuid;
    exception
        when others then
            raise exception 'function %UuidByIdName(...) not found, add identity view support for table %', objectTable, objectTable;
    end;
    return uuid;
end ; $$;

create or replace function base.findIdNameByObjectUuid(objectTable varchar, objectUuid uuid)
    returns varchar
    returns null on null input
    language plpgsql as $$
declare
    sql    varchar;
    idName varchar;
begin
    objectTable := base.pureIdentifier(objectTable);
    sql := format('select * from %sIdNameByUuid(%L::uuid);', objectTable, objectUuid);
    begin
        execute sql into idName;
    exception
        when others then
            raise exception 'function %IdNameByUuid(...) not found, add identity view support for table %', objectTable, objectTable;
    end;
    return idName;
end ; $$;

create or replace function base.currentSubjects()
    returns varchar(1023)[]
    stable -- leakproof
    language plpgsql as $$
declare
    assumedRoles varchar(1023)[];
begin
    assumedRoles := base.assumedRoles();
    if array_length(assumedRoles, 1) > 0 then
        return assumedRoles;
    else
        return array [base.currentSubject()]::varchar(1023)[];
    end if;
end; $$;

create or replace function base.hasAssumedRole()
    returns boolean
    stable -- leakproof
    language plpgsql as $$
begin
    return array_length(base.assumedRoles(), 1) > 0;
end; $$;
--//

