1
0

rbac-optimization (#80)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/80
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
Michael Hoennig
2024-07-27 10:18:07 +02:00
parent 4d27a98c9a
commit e1fda412ae
43 changed files with 639 additions and 186 deletions

View File

@@ -70,7 +70,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class HsBookingItemEntity implements Stringifyable, RbacObject, PropertiesProvider {
public class HsBookingItemEntity implements Stringifyable, RbacObject<HsBookingItemEntity>, PropertiesProvider {
private static Stringify<HsBookingItemEntity> stringify = stringify(HsBookingItemEntity.class)
.withProp(HsBookingItemEntity::getProject)

View File

@@ -34,7 +34,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class HsBookingProjectEntity implements Stringifyable, RbacObject {
public class HsBookingProjectEntity implements Stringifyable, RbacObject<HsBookingProjectEntity> {
private static Stringify<HsBookingProjectEntity> stringify = stringify(HsBookingProjectEntity.class)
.withProp(HsBookingProjectEntity::getDebitor)

View File

@@ -70,7 +70,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class HsHostingAssetEntity implements Stringifyable, RbacObject, PropertiesProvider {
public class HsHostingAssetEntity implements Stringifyable, RbacObject<HsHostingAssetEntity>, PropertiesProvider {
private static Stringify<HsHostingAssetEntity> stringify = stringify(HsHostingAssetEntity.class)
.withProp(HsHostingAssetEntity::getType)

View File

@@ -27,7 +27,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@AllArgsConstructor
@FieldNameConstants
@DisplayName("BankAccount")
public class HsOfficeBankAccountEntity implements RbacObject, Stringifyable {
public class HsOfficeBankAccountEntity implements RbacObject<HsOfficeBankAccountEntity>, Stringifyable {
private static Stringify<HsOfficeBankAccountEntity> toString = stringify(HsOfficeBankAccountEntity.class, "bankAccount")
.withIdProp(HsOfficeBankAccountEntity::getIban)

View File

@@ -35,7 +35,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@AllArgsConstructor
@FieldNameConstants
@DisplayName("Contact")
public class HsOfficeContactEntity implements Stringifyable, RbacObject {
public class HsOfficeContactEntity implements Stringifyable, RbacObject<HsOfficeContactEntity> {
private static Stringify<HsOfficeContactEntity> toString = stringify(HsOfficeContactEntity.class, "contact")
.withProp(Fields.caption, HsOfficeContactEntity::getCaption)

View File

@@ -41,7 +41,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@NoArgsConstructor
@AllArgsConstructor
@DisplayName("CoopAssetsTransaction")
public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, RbacObject {
public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, RbacObject<HsOfficeCoopAssetsTransactionEntity> {
private static Stringify<HsOfficeCoopAssetsTransactionEntity> stringify = stringify(HsOfficeCoopAssetsTransactionEntity.class)
.withIdProp(HsOfficeCoopAssetsTransactionEntity::getTaggedMemberNumber)
@@ -105,6 +105,13 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, RbacO
@OneToOne(mappedBy = "adjustedAssetTx")
private HsOfficeCoopAssetsTransactionEntity adjustmentAssetTx;
@Override
public HsOfficeCoopAssetsTransactionEntity load() {
RbacObject.super.load();
membership.load();
return this;
}
public String getTaggedMemberNumber() {
return ofNullable(membership).map(HsOfficeMembershipEntity::toShortString).orElse("M-???????");
}

View File

@@ -39,7 +39,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@NoArgsConstructor
@AllArgsConstructor
@DisplayName("CoopShareTransaction")
public class HsOfficeCoopSharesTransactionEntity implements Stringifyable, RbacObject {
public class HsOfficeCoopSharesTransactionEntity implements Stringifyable, RbacObject<HsOfficeCoopSharesTransactionEntity> {
private static Stringify<HsOfficeCoopSharesTransactionEntity> stringify = stringify(HsOfficeCoopSharesTransactionEntity.class)
.withIdProp(HsOfficeCoopSharesTransactionEntity::getMemberNumberTagged)
@@ -102,6 +102,13 @@ public class HsOfficeCoopSharesTransactionEntity implements Stringifyable, RbacO
@OneToOne(mappedBy = "adjustedShareTx")
private HsOfficeCoopSharesTransactionEntity adjustmentShareTx;
@Override
public HsOfficeCoopSharesTransactionEntity load() {
RbacObject.super.load();
membership.load();
return this;
}
@Override
public String toString() {
return stringify.apply(this);

View File

@@ -21,6 +21,7 @@ import org.hibernate.annotations.NotFoundAction;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
@@ -57,7 +58,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@NoArgsConstructor
@AllArgsConstructor
@DisplayName("Debitor")
public class HsOfficeDebitorEntity implements RbacObject, Stringifyable {
public class HsOfficeDebitorEntity implements RbacObject<HsOfficeDebitorEntity>, Stringifyable {
public static final String DEBITOR_NUMBER_TAG = "D-";
public static final String TWO_DECIMAL_DIGITS = "^([0-9]{2})$";
@@ -77,7 +78,7 @@ public class HsOfficeDebitorEntity implements RbacObject, Stringifyable {
@Version
private int version;
@ManyToOne
@ManyToOne(fetch = FetchType.LAZY)
@JoinFormula(
referencedColumnName = "uuid",
value = """
@@ -91,14 +92,14 @@ public class HsOfficeDebitorEntity implements RbacObject, Stringifyable {
WHERE pRel.holderUuid = dRel.anchorUuid
)
""")
@NotFound(action = NotFoundAction.IGNORE)
@NotFound(action = NotFoundAction.IGNORE) // TODO.impl: map a simplified raw-PartnerEntity, just for the partner-number
private HsOfficePartnerEntity partner;
@Column(name = "debitornumbersuffix", length = 2)
@Pattern(regexp = TWO_DECIMAL_DIGITS)
private String debitorNumberSuffix;
@ManyToOne(cascade = { PERSIST, MERGE, REFRESH, DETACH }, optional = false)
@ManyToOne(cascade = { PERSIST, MERGE, REFRESH, DETACH }, optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "debitorreluuid", nullable = false)
private HsOfficeRelationEntity debitorRel;
@@ -117,13 +118,27 @@ public class HsOfficeDebitorEntity implements RbacObject, Stringifyable {
@Column(name = "vatreversecharge")
private boolean vatReverseCharge;
@ManyToOne
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "refundbankaccountuuid")
@NotFound(action = NotFoundAction.IGNORE)
private HsOfficeBankAccountEntity refundBankAccount;
@Column(name = "defaultprefix", columnDefinition = "char(3) not null")
private String defaultPrefix;
@Override
public HsOfficeDebitorEntity load() {
RbacObject.super.load();
if (partner != null) {
partner.load();
}
debitorRel.load();
if (refundBankAccount != null) {
refundBankAccount.load();
}
return this;
}
private String getDebitorNumberString() {
return ofNullable(partner)
.filter(partner -> debitorNumberSuffix != null)

View File

@@ -21,6 +21,7 @@ import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
@@ -61,7 +62,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@NoArgsConstructor
@AllArgsConstructor
@DisplayName("Membership")
public class HsOfficeMembershipEntity implements RbacObject, Stringifyable {
public class HsOfficeMembershipEntity implements RbacObject<HsOfficeMembershipEntity>, Stringifyable {
public static final String MEMBER_NUMBER_TAG = "M-";
public static final String TWO_DECIMAL_DIGITS = "^([0-9]{2})$";
@@ -80,7 +81,7 @@ public class HsOfficeMembershipEntity implements RbacObject, Stringifyable {
@Version
private int version;
@ManyToOne
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "partneruuid")
private HsOfficePartnerEntity partner;
@@ -99,6 +100,13 @@ public class HsOfficeMembershipEntity implements RbacObject, Stringifyable {
@Enumerated(EnumType.STRING)
private HsOfficeMembershipStatus status;
@Override
public HsOfficeMembershipEntity load() {
RbacObject.super.load();
partner.load();
return this;
}
public void setValidFrom(final LocalDate validFrom) {
setValidity(toPostgresDateRange(validFrom, getValidTo()));
}

View File

@@ -26,7 +26,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@NoArgsConstructor
@AllArgsConstructor
@DisplayName("PartnerDetails")
public class HsOfficePartnerDetailsEntity implements RbacObject, Stringifyable {
public class HsOfficePartnerDetailsEntity implements RbacObject<HsOfficePartnerDetailsEntity>, Stringifyable {
private static Stringify<HsOfficePartnerDetailsEntity> stringify = stringify(
HsOfficePartnerDetailsEntity.class,

View File

@@ -40,7 +40,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@NoArgsConstructor
@AllArgsConstructor
@DisplayName("Partner")
public class HsOfficePartnerEntity implements Stringifyable, RbacObject {
public class HsOfficePartnerEntity implements Stringifyable, RbacObject<HsOfficePartnerEntity> {
public static final String PARTNER_NUMBER_TAG = "P-";
@@ -66,15 +66,23 @@ public class HsOfficePartnerEntity implements Stringifyable, RbacObject {
@Column(name = "partnernumber", columnDefinition = "numeric(5) not null")
private Integer partnerNumber;
@ManyToOne(cascade = { PERSIST, MERGE, REFRESH, DETACH }, optional = false)
@ManyToOne(cascade = { PERSIST, MERGE, REFRESH, DETACH }, optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "partnerreluuid", nullable = false)
private HsOfficeRelationEntity partnerRel;
@ManyToOne(cascade = { PERSIST, MERGE, REFRESH, DETACH }, optional = true)
@ManyToOne(cascade = { PERSIST, MERGE, REFRESH, DETACH }, optional = true, fetch = FetchType.LAZY)
@JoinColumn(name = "detailsuuid")
@NotFound(action = NotFoundAction.IGNORE)
private HsOfficePartnerDetailsEntity details;
@Override
public HsOfficePartnerEntity load() {
RbacObject.super.load();
partnerRel.load();
details.load();
return this;
}
public String getTaggedPartnerNumber() {
return PARTNER_NUMBER_TAG + partnerNumber;
}

View File

@@ -30,7 +30,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@AllArgsConstructor
@FieldNameConstants
@DisplayName("Person")
public class HsOfficePersonEntity implements RbacObject, Stringifyable {
public class HsOfficePersonEntity implements RbacObject<HsOfficePersonEntity>, Stringifyable {
private static Stringify<HsOfficePersonEntity> toString = stringify(HsOfficePersonEntity.class, "person")
.withProp(Fields.personType, HsOfficePersonEntity::getPersonType)

View File

@@ -56,15 +56,15 @@ public class HsOfficeRelationEntity implements RbacObject, Stringifyable {
@Version
private int version;
@ManyToOne
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "anchoruuid")
private HsOfficePersonEntity anchor;
@ManyToOne
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "holderuuid")
private HsOfficePersonEntity holder;
@ManyToOne
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "contactuuid")
private HsOfficeContactEntity contact;
@@ -75,6 +75,15 @@ public class HsOfficeRelationEntity implements RbacObject, Stringifyable {
@Column(name = "mark")
private String mark;
@Override
public HsOfficeRelationEntity load() {
RbacObject.super.load();
anchor.load();
holder.load();
contact.load();
return this;
}
@Override
public String toString() {
return toString.apply(this);

View File

@@ -40,7 +40,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@NoArgsConstructor
@AllArgsConstructor
@DisplayName("SEPA-Mandate")
public class HsOfficeSepaMandateEntity implements Stringifyable, RbacObject {
public class HsOfficeSepaMandateEntity implements Stringifyable, RbacObject<HsOfficeSepaMandateEntity> {
private static Stringify<HsOfficeSepaMandateEntity> stringify = stringify(HsOfficeSepaMandateEntity.class)
.withProp(e -> e.getBankAccount().getIban())

View File

@@ -1,10 +1,18 @@
package net.hostsharing.hsadminng.rbac.rbacobject;
import org.hibernate.Hibernate;
import java.util.UUID;
public interface RbacObject {
public interface RbacObject<T extends RbacObject<?>> {
UUID getUuid();
int getVersion();
default T load() {
Hibernate.initialize(this);
//noinspection unchecked
return (T) this;
};
}

View File

@@ -24,7 +24,7 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class TestCustomerEntity implements RbacObject {
public class TestCustomerEntity implements RbacObject<TestCustomerEntity> {
@Id
@GeneratedValue

View File

@@ -27,7 +27,7 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class TestDomainEntity implements RbacObject {
public class TestDomainEntity implements RbacObject<TestDomainEntity> {
@Id
@GeneratedValue

View File

@@ -27,7 +27,7 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class TestPackageEntity implements RbacObject {
public class TestPackageEntity implements RbacObject<TestPackageEntity> {
@Id
@GeneratedValue

View File

@@ -0,0 +1,13 @@
--liquibase formatted sql
-- ============================================================================
-- PG-STAT-STATEMENTS-EXTENSION
--changeset pg-stat-statements-extension:1 context:pg_stat_statements endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Makes improved uuid generation available.
*/
create extension if not exists "pg_stat_statements";
--//

View File

@@ -372,6 +372,9 @@ create table RbacPermission
op RbacOp not null,
opTableName varchar(60)
);
-- TODO.perf: check if these indexes are really useful
create index on RbacPermission (objectUuid, op);
create index on RbacPermission (opTableName, op);
ALTER TABLE RbacPermission
ADD CONSTRAINT RbacPermission_uc UNIQUE NULLS NOT DISTINCT (objectUuid, op, opTableName);
@@ -495,78 +498,68 @@ create index on RbacGrants (ascendantUuid);
create index on RbacGrants (descendantUuid);
call create_journal('RbacGrants');
create or replace function findGrantees(grantedId uuid)
returns setof RbacReference
returns null on null input
language sql as $$
select reference.*
from (with recursive grants as (select descendantUuid,
ascendantUuid
from RbacGrants
where descendantUuid = grantedId
union all
select "grant".descendantUuid,
"grant".ascendantUuid
from RbacGrants "grant"
inner join grants recur on recur.ascendantUuid = "grant".descendantUuid)
select ascendantUuid
from grants) as grantee
join RbacReference reference on reference.uuid = grantee.ascendantUuid;
with recursive grants as (
select descendantUuid, ascendantUuid
from RbacGrants
where descendantUuid = grantedId
union all
select g.descendantUuid, g.ascendantUuid
from RbacGrants g
inner join grants on grants.ascendantUuid = g.descendantUuid
)
select ref.*
from grants
join RbacReference ref on ref.uuid = grants.ascendantUuid;
$$;
create or replace function isGranted(granteeIds uuid[], grantedId uuid)
returns bool
returns null on null input
language sql as $$
with recursive grants as (
select descendantUuid, ascendantUuid
from RbacGrants
where descendantUuid = grantedId
union all
select "grant".descendantUuid, "grant".ascendantUuid
from RbacGrants "grant"
inner join grants recur on recur.ascendantUuid = "grant".descendantUuid
)
select exists (
select true
from grants
where ascendantUuid = any(granteeIds)
) or grantedId = any(granteeIds);
$$;
create or replace function isGranted(granteeId uuid, grantedId uuid)
returns bool
returns null on null input
language sql as $$
select granteeId = grantedId or granteeId in (with recursive grants as (select descendantUuid, ascendantUuid
from RbacGrants
where descendantUuid = grantedId
union all
select "grant".descendantUuid, "grant".ascendantUuid
from RbacGrants "grant"
inner join grants recur on recur.ascendantUuid = "grant".descendantUuid)
select ascendantUuid
from grants);
select * from isGranted(array[granteeId], grantedId);
$$;
create or replace function isGranted(granteeIds uuid[], grantedId uuid)
returns bool
returns null on null input
language plpgsql as $$
declare
granteeId uuid;
begin
-- TODO.perf: needs optimization
foreach granteeId in array granteeIds
loop
if isGranted(granteeId, grantedId) then
return true;
end if;
end loop;
return false;
end; $$;
create or replace function isPermissionGrantedToSubject(permissionId uuid, subjectId uuid)
returns BOOL
stable -- leakproof
language sql as $$
with recursive grants as (
select descendantUuid, ascendantUuid
from RbacGrants
where descendantUuid = permissionId
union all
select g.descendantUuid, g.ascendantUuid
from RbacGrants g
inner join grants on grants.ascendantUuid = g.descendantUuid
)
select exists(
select *
from RbacUser
where uuid in (with recursive grants as (select descendantUuid,
ascendantUuid
from RbacGrants g
where g.descendantUuid = permissionId
union all
select g.descendantUuid,
g.ascendantUuid
from RbacGrants g
inner join grants recur on recur.ascendantUuid = g.descendantUuid)
select ascendantUuid
from grants
where ascendantUuid = subjectId)
);
select true
from grants
where ascendantUuid = subjectId
);
$$;
create or replace function hasInsertPermission(objectUuid uuid, tableName text )
@@ -708,14 +701,14 @@ begin
end; $$;
-- ============================================================================
--changeset rbac-base-QUERY-ACCESSIBLE-OBJECT-UUIDS:1 endDelimiter:--//
--changeset rbac-base-QUERY-ACCESSIBLE-OBJECT-UUIDS:1 runOnChange=true endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
*/
create or replace function queryAccessibleObjectUuidsOfSubjectIds(
requiredOp RbacOp,
forObjectTable varchar, -- reduces the result set, but is not really faster when used in restricted view
forObjectTable varchar,
subjectIds uuid[],
maxObjects integer = 8000)
returns setof uuid
@@ -724,23 +717,29 @@ create or replace function queryAccessibleObjectUuidsOfSubjectIds(
declare
foundRows bigint;
begin
return query select distinct perm.objectUuid
from (with recursive grants as (select descendantUuid, ascendantUuid, 1 as level
from RbacGrants
where assumed
and ascendantUuid = any (subjectIds)
union
distinct
select "grant".descendantUuid, "grant".ascendantUuid, level + 1 as level
from RbacGrants "grant"
inner join grants recur on recur.descendantUuid = "grant".ascendantUuid
where assumed)
select descendantUuid
from grants) as granted
join RbacPermission perm
on granted.descendantUuid = perm.uuid and (requiredOp = 'SELECT' or perm.op = requiredOp)
join RbacObject obj on obj.uuid = perm.objectUuid and obj.objectTable = forObjectTable
limit maxObjects + 1;
return query
WITH RECURSIVE grants AS (
SELECT descendantUuid, ascendantUuid, 1 AS level
FROM RbacGrants
WHERE assumed
AND ascendantUuid = any(subjectIds)
UNION ALL
SELECT g.descendantUuid, g.ascendantUuid, grants.level + 1 AS level
FROM RbacGrants g
INNER JOIN grants ON grants.descendantUuid = g.ascendantUuid
WHERE g.assumed
),
granted AS (
SELECT DISTINCT descendantUuid
FROM grants
)
SELECT DISTINCT perm.objectUuid
FROM granted
JOIN RbacPermission perm ON granted.descendantUuid = perm.uuid
JOIN RbacObject obj ON obj.uuid = perm.objectUuid
WHERE (requiredOp = 'SELECT' OR perm.op = requiredOp)
AND obj.objectTable = forObjectTable
LIMIT maxObjects+1;
foundRows = lastRowCount();
if foundRows > maxObjects then
@@ -751,7 +750,6 @@ begin
end if;
end;
$$;
--//
-- ============================================================================
@@ -764,24 +762,23 @@ create or replace function queryPermissionsGrantedToSubjectId(subjectId uuid)
returns setof RbacPermission
strict
language sql as $$
-- @formatter:off
select *
from RbacPermission
where uuid in (
with recursive grants as (
select distinct descendantUuid, ascendantUuid
from RbacGrants
where ascendantUuid = subjectId
union all
select "grant".descendantUuid, "grant".ascendantUuid
from RbacGrants "grant"
inner join grants recur on recur.descendantUuid = "grant".ascendantUuid
)
select descendantUuid
from grants
);
-- @formatter:on
with recursive grants as (
select descendantUuid, ascendantUuid
from RbacGrants
where ascendantUuid = subjectId
union all
select g.descendantUuid, g.ascendantUuid
from RbacGrants g
inner join grants on grants.descendantUuid = g.ascendantUuid
)
select perm.*
from RbacPermission perm
where perm.uuid in (
select descendantUuid
from grants
);
$$;
--//
-- ============================================================================

View File

@@ -175,16 +175,38 @@ begin
Creates a restricted view based on the 'SELECT' permission of the current subject.
*/
sql := format($sql$
set session session authorization default;
create view %1$s_rv as
with accessibleObjects as (
select queryAccessibleObjectUuidsOfSubjectIds('SELECT', '%1$s', currentSubjectsUuids())
create or replace view %1$s_rv as
with accessible_%1$s_uuids as (
with recursive grants as (
select descendantUuid, ascendantUuid, 1 as level
from RbacGrants
where assumed
and ascendantUuid = any (currentSubjectsuUids())
union all
select g.descendantUuid, g.ascendantUuid, level + 1 as level
from RbacGrants g
inner join grants on grants.descendantUuid = g.ascendantUuid
where g.assumed
),
granted as (
select distinct descendantUuid
from grants
)
select distinct perm.objectUuid as objectUuid
from granted
join RbacPermission perm on granted.descendantUuid = perm.uuid
join RbacObject obj on obj.uuid = perm.objectUuid
where perm.op = 'SELECT'
and obj.objectTable = '%1$s'
limit 8001
)
select target.*
from %1$s as target
where target.uuid in (select * from accessibleObjects)
where target.uuid in (select * from accessible_%1$s_uuids)
order by %2$s;
grant all privileges on %1$s_rv to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME};
grant all privileges on %1$s_rv to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME};
$sql$, targetTable, orderBy);
execute sql;

View File

@@ -21,6 +21,8 @@ databaseChangeLog:
file: db/changelog/0-basis/010-context.sql
- include:
file: db/changelog/0-basis/020-audit-log.sql
- include:
file: db/changelog/0-basis/090-log-slow-queries-extensions.sql
- include:
file: db/changelog/1-rbac/1050-rbac-base.sql
- include: