RbacRbacSystemRebuildGenerator.java

package net.hostsharing.hsadminng.rbac.generator;

import net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacGrantDefinition;

import java.util.HashSet;
import java.util.Set;
import static net.hostsharing.hsadminng.rbac.generator.StringWriter.with;

class RbacRbacSystemRebuildGenerator {

    private final RbacSpec rbacDef;
    private final Set<RbacGrantDefinition> rbacGrants = new HashSet<>();
    private final String liquibaseTagPrefix;
    private final String rawTableName;

    RbacRbacSystemRebuildGenerator(final RbacSpec rbacDef, final String liquibaseTagPrefix) {
        this.rbacDef = rbacDef;
        this.liquibaseTagPrefix = liquibaseTagPrefix;
        this.rawTableName = rbacDef.getRootEntityAlias().getRawTableNameWithSchema();
    }

    void generateTo(final StringWriter plPgSql) {
        plPgSql.writeLn("""
                -- ============================================================================
                --changeset RbacRbacSystemRebuildGenerator:${liquibaseTagPrefix}-rbac-rebuild runOnChange:true endDelimiter:--//
                --validCheckSum: ANY
                -- ----------------------------------------------------------------------------

                -- HOWTO: Rebuild RBAC-system for table ${rawTableName} after changing its RBAC specification.
                --
                -- begin transaction;
                --  call base.defineContext('re-creating RBAC for table ${rawTableName}', null, <<insert executing global admin user here>>);
                --  call ${rawTableName}_rebuild_rbac_system();
                -- commit;
                --
                -- How it works:
                -- 1. All grants previously created from the RBAC specification of this table will be deleted.
                --    These grants are identified by `${rawTableName}.grantedByTriggerOf IS NOT NULL`.
                --    User-induced grants (`${rawTableName}.grantedByTriggerOf IS NULL`) are NOT deleted.
                -- 2. New role types will be created, but existing role types which are not specified anymore,
                --    will NOT be deleted!
                -- 3. All newly specified grants will be created.
                --
                -- IMPORTANT:
                -- Make sure not to skip any previously defined role-types or you might break indirect grants!
                -- E.g. If, in an updated version of the RBAC system for a table, you remove the AGENT role type
                -- and now directly grant the TENANT role to the ADMIN role, all external grants to the AGENT role
                -- of this table would be in a dead end.
    
                create or replace procedure ${rawTableName}_rebuild_rbac_system()
                    language plpgsql as $$
                DECLARE
                    DECLARE
                    row ${rawTableName};
                    grantsAfter numeric;
                    grantsBefore numeric;
                BEGIN
                    SELECT count(*) INTO grantsBefore FROM rbac.grant;
    
                    FOR row IN SELECT * FROM ${rawTableName} LOOP
                            -- first delete all generated grants for this row from the previously defined RBAC system
                            DELETE FROM rbac.grant g
                                   WHERE g.grantedbytriggerof = row.uuid;
    
                            -- then build the grants according to the currently defined RBAC rules
                            CALL ${rawTableName}_build_rbac_system(row);
                        END LOOP;
    
                    select count(*) into grantsAfter from rbac.grant;
    
                    -- print how the total count of grants has changed
                    raise notice 'total grant count before -> after: % -> %', grantsBefore, grantsAfter;
                END;
                $$;
                --//

                """,
                with("liquibaseTagPrefix", liquibaseTagPrefix),
                with("rawTableName", rawTableName));
    }
}