JavaDoc for RbacView

This commit is contained in:
Michael Hoennig 2024-03-23 15:00:07 +01:00
parent 87c23d2aa9
commit fbe2204d72
8 changed files with 264 additions and 21 deletions

View File

@ -19,6 +19,7 @@ import java.util.Optional;
import java.util.UUID;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NULLABLE;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.fetchedBySql;
@ -149,8 +150,8 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
SELECT *
FROM hs_office_relation AS r
WHERE r.type = 'DEBITOR' AND r.holderUuid = ${REF}.debitorRelUuid
""")
)
"""),
NULLABLE)
.toRole("refundBankAccount", ADMIN).grantRole("debitorRel", AGENT)
.toRole("debitorRel", AGENT).grantRole("refundBankAccount", REFERRER)
@ -159,8 +160,8 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
SELECT *
FROM hs_office_relation AS partnerRel
WHERE ${debitorRel}.anchorUuid = partnerRel.holderUuid
""")
)
"""),
NULLABLE)
.toRole("partnerRel", ADMIN).grantRole("debitorRel", ADMIN)
.toRole("partnerRel", AGENT).grantRole("debitorRel", AGENT)
.toRole("debitorRel", AGENT).grantRole("partnerRel", TENANT)

View File

@ -16,6 +16,7 @@ import java.util.UUID;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.GLOBAL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NULLABLE;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
@ -90,16 +91,16 @@ public class HsOfficeRelationEntity implements HasUuid, Stringifyable {
.withUpdatableColumns("contactUuid")
.importEntityAlias("anchorPerson", HsOfficePersonEntity.class,
dependsOnColumn("anchorUuid"),
fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.anchorUuid")
)
fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.anchorUuid"),
NULLABLE)
.importEntityAlias("holderPerson", HsOfficePersonEntity.class,
dependsOnColumn("holderUuid"),
fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.holderUuid")
)
fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.holderUuid"),
NULLABLE)
.importEntityAlias("contact", HsOfficeContactEntity.class,
dependsOnColumn("contactUuid"),
fetchedBySql("select * from hs_office_contact as c where c.uuid = ${REF}.contactUuid")
)
fetchedBySql("select * from hs_office_contact as c where c.uuid = ${REF}.contactUuid"),
NULLABLE)
.createRole(OWNER, (with) -> {
with.owningUser(CREATOR);
with.incomingSuperRole(GLOBAL, ADMIN);

View File

@ -66,6 +66,21 @@ public class RbacView {
private EntityAlias rootEntityAliasProxy;
private RbacRoleDefinition previousRoleDef;
/** Crates an RBAC definition template for the given entity class and defining the given alias.
*
* @param alias
* an alias name for this entity/table, which can be used in further grants
*
* @param entityClass
* the Java class for which this RBAC definition is to be defined
* (the class to which the calling method belongs)
*
* @return
* the newly created RBAC definition template
*
* @param <E>
* a JPA entity class extending RbacObject
*/
public static <E extends RbacObject> RbacView rbacViewFor(final String alias, final Class<E> entityClass) {
return new RbacView(alias, entityClass);
}
@ -77,22 +92,71 @@ public class RbacView {
entityAliases.put("global", new EntityAlias("global"));
}
/**
* Specifies, which columns of the restricted view are updatable at all.
*
* @param columnNames
* A list of the updatable columns.
*
* @return
* the `this` instance itself to allow chained calls.
*/
public RbacView withUpdatableColumns(final String... columnNames) {
Collections.addAll(updatableColumns, columnNames);
verifyVersionColumnExists();
return this;
}
/** Specifies the SQL query which creates the identity view for this entity.
*
* <p>An identity view is a view which maps an objectUuid to an idName.
* The idName should be a human-readable representation of the row, but as short as possible.
* The idName must only consist of letters (A-Z, a-z), digits (0-9), dash (-), dot (.) and unserscore '_'.
* It's used to create the object-specific-role-names like test_customer#abc.admin - here 'abc' is the idName.
* The idName not necessarily unique in a table, but it should be avoided.
* </p>
*
* @param sqlExpression
* Either specify an SQL projection (the part between SELECT and FROM), e.g. `SQL.projection("columnName")
* or the whole SELECT query returning the uuid and idName columns,
* e.g. `SQL.query("SELECT ... AS uuid, ... AS idName FROM ... JOIN ...").
* Only add really important columns, just enough to create a short human-readable representation.
*
* @return
* the `this` instance itself to allow chained calls.
*/
public RbacView withIdentityView(final SQL sqlExpression) {
this.identityViewSqlQuery = sqlExpression;
return this;
}
/**
* Specifies a ORDER BY clause for the generated restricted view.
*
* <p>A restricted view is generated, no matter if the order was specified or not.</p>
*
* @param orderBySqlExpression
* That's the part behind `ORDER BY`, e.g. `SQL.expression("prefix").
*
* @return
* the `this` instance itself to allow chained calls.
*/
public RbacView withRestrictedViewOrderBy(final SQL orderBySqlExpression) {
this.orderBySqlExpression = orderBySqlExpression;
return this;
}
/**
* Specifies that the given role (OWNER, ADMIN, ...) is to be created for new/updated roles in this table.
*
* @param role
* OWNER, ADMIN, AGENT etc.
* @param with
* a lambda which receives the created role to create grants and permissions to and from the newly created role,
* e.g. the owning user, incoming superroles, outgoing subroles
* @return
* the `this` instance itself to allow chained calls.
*/
public RbacView createRole(final Role role, final Consumer<RbacRoleDefinition> with) {
final RbacRoleDefinition newRoleDef = findRbacRole(rootEntityAlias, role).toCreate();
with.accept(newRoleDef);
@ -100,6 +164,15 @@ public class RbacView {
return this;
}
/**
* Specifies that the given role (OWNER, ADMIN, ...) is to be created for new/updated roles in this table,
* which is becomes sub-role of the previously created role.
*
* @param role
* OWNER, ADMIN, AGENT etc.
* @return
* the `this` instance itself to allow chained calls.
*/
public RbacView createSubRole(final Role role) {
final RbacRoleDefinition newRoleDef = findRbacRole(rootEntityAlias, role).toCreate();
findOrCreateGrantDef(newRoleDef, previousRoleDef).toCreate();
@ -107,6 +180,19 @@ public class RbacView {
return this;
}
/**
* Specifies that the given role (OWNER, ADMIN, ...) is to be created for new/updated roles in this table,
* which is becomes sub-role of the previously created role.
*
* @param role
* OWNER, ADMIN, AGENT etc.
* @param with
* a lambda which receives the created role to create grants and permissions to and from the newly created role,
* e.g. the owning user, incoming superroles, outgoing subroles
* @return
* the `this` instance itself to allow chained calls.
*/
public RbacView createSubRole(final Role role, final Consumer<RbacRoleDefinition> with) {
final RbacRoleDefinition newRoleDef = findRbacRole(rootEntityAlias, role).toCreate();
findOrCreateGrantDef(newRoleDef, previousRoleDef).toCreate();
@ -115,10 +201,38 @@ public class RbacView {
return this;
}
/**
* Specifies that the given permission is to be created for each new row in the target table.
*
* <p>Grants to permissions created by this method have to be specified separately,
* often it's easier to read to use createRole/createSubRole and use with.permission(...).</p>
*
* @param permission
* e.g. INSERT, SELECT, UPDATE, DELETE
*
* @return
* the newly created permission definition
*/
public RbacPermissionDefinition createPermission(final Permission permission) {
return createPermission(rootEntityAlias, permission);
}
/**
* Specifies that the given permission is to be created for each new row in the target table,
* but for another table, e.g. a table with details data with different access rights.
*
* <p>Grants to permissions created by this method have to be specified separately,
* often it's easier to read to use createRole/createSubRole and use with.permission(...).</p>
*
* @param entityAliasName
* A previously defined entity alias name.
*
* @param permission
* e.g. INSERT, SELECT, UPDATE, DELETE
*
* @return
* the newly created permission definition
*/
public RbacPermissionDefinition createPermission(final String entityAliasName, final Permission permission) {
return createPermission(findEntityAlias(entityAliasName), permission);
}
@ -134,6 +248,32 @@ public class RbacView {
return this;
}
/**
* Imports the RBAC template from the given entity class and defines an alias name for it.
* This method is especially for proxy-entities, if the root entity does not have its own
* roles, a proxy-entity can be specified and its roles can be used instead.
*
* @param aliasName
* An alias name for the entity class. The same entity class can be imported multiple times,
* if multiple references to its table exist, then distinct alias names habe to be defined.
*
* @param entityClass
* A JPA entity class extending RbacObject which also implements an `rbac` method returning
* its RBAC specification.
*
* @param fetchSql
* An SQL SELECT statement which fetches the referenced row. Use `${REF}` to speficiy the
* newly created or updated row (will be replaced by NEW/OLD from the trigger method).
*
* @param dependsOnColum
* The column, usually containing an uuid, on which this other table depends.
*
* @return
* the newly created permission definition
*
* @param <EC>
* a JPA entity class extending RbacObject
*/
public <EC extends RbacObject> RbacView importRootEntityAliasProxy(
final String aliasName,
final Class<? extends HasUuid> entityClass,
@ -146,6 +286,18 @@ public class RbacView {
return this;
}
/**
* Imports the RBAC template from the given entity class and defines an alias name for it.
* This method is especially to declare sub-entities, e.g. details to a main object.
*
* @see {@link}
*
* @return
* the newly created permission definition
*
* @param <EC>
* a JPA entity class extending RbacObject
*/
public RbacView importSubEntityAlias(
final String aliasName, final Class<? extends HasUuid> entityClass,
final SQL fetchSql, final Column dependsOnColum) {
@ -153,6 +305,33 @@ public class RbacView {
return this;
}
/**
* Imports the RBAC template from the given entity class and defines an anlias name for it.
*
* @param aliasName
* An alias name for the entity class. The same entity class can be imported multiple times,
* if multiple references to its table exist, then distinct alias names habe to be defined.
*
* @param entityClass
* A JPA entity class extending RbacObject which also implements an `rbac` method returning
* its RBAC specification.
*
* @param fetchSql
* An SQL SELECT statement which fetches the referenced row. Use `${REF}` to speficiy the
* newly created or updated row (will be replaced by NEW/OLD from the trigger method).
*
* @param dependsOnColum
* The column, usually containing an uuid, on which this other table depends.
*
* @param nullable
* Specifies whether the dependsOnColum is nullable or not.
*
* @return
* the newly created permission definition
*
* @param <EC>
* a JPA entity class extending RbacObject
*/
public RbacView importEntityAlias(
final String aliasName, final Class<? extends HasUuid> entityClass,
final Column dependsOnColum, final SQL fetchSql, final Nullable nullable) {
@ -160,13 +339,7 @@ public class RbacView {
return this;
}
public RbacView importEntityAlias(
final String aliasName, final Class<? extends HasUuid> entityClass,
final Column dependsOnColum, final SQL fetchSql) {
importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, false, NOT_NULL);
return this;
}
// TODO: remove once it's not used in HsOffice...Entity anymore
public RbacView importEntityAlias(
final String aliasName, final Class<? extends HasUuid> entityClass,
final Column dependsOnColum) {
@ -232,6 +405,16 @@ public class RbacView {
}
}
/**
* Starts declaring a grant to a given role.
*
* @param entityAlias
* A previously speciried entity alias name.
* @param role
* OWNER, ADMIN, AGENT, ...
* @return
* a grant builder
*/
public RbacGrantBuilder toRole(final String entityAlias, final Role role) {
return new RbacGrantBuilder(entityAlias, role);
}
@ -430,6 +613,16 @@ public class RbacView {
permDefs.add(this);
}
/**
* Grants the permission under definition to the given role.
*
* @param entityAlias
* A previously declared entity alias name.
* @param role
* OWNER, ADMIN, ...
* @return
* The RbacView specification to which this permission definition belongs.
*/
public RbacView grantedTo(final String entityAlias, final Role role) {
findOrCreateGrantDef(this, findRbacRole(entityAlias, role)).toCreate();
return RbacView.this;
@ -460,19 +653,61 @@ public class RbacView {
return this;
}
/**
* Specifies which user becomes the owner of newly created objects.
* @param userRole
* GLOBAL_ADMIN, CREATOR, ...
* @return
* The grant definition for further chained calls.
*/
public RbacGrantDefinition owningUser(final RbacUserReference.UserRole userRole) {
return grantRoleToUser(this, findUserRef(userRole));
}
/**
* Specifies which permission is to be created for newly created objects.
* @param permission
* INSERT, SELECT, ...
* @return
* The grant definition for further chained calls.
*/
public RbacGrantDefinition permission(final Permission permission) {
return grantPermissionToRole(createPermission(entityAlias, permission), this);
}
/**
* Specifies in incoming super role which gets granted the role under definition.
*
* <p>Incoming means an incoming grant arrow in our grant-diagrams.
* Super-role means that it's the role to which another role is granted.
* Both means actually the same, just in different aspects.</p>
*
* @param entityAlias
* A previously declared entity alias name.
* @param role
* OWNER, ADMIN, ...
* @return
* The grant definition for further chained calls.
*/
public RbacGrantDefinition incomingSuperRole(final String entityAlias, final Role role) {
final var incomingSuperRole = findRbacRole(entityAlias, role);
return grantSubRoleToSuperRole(this, incomingSuperRole);
}
/**
* Specifies in outgoing sub role which gets granted the role under definition.
*
* <p>Outgoing means an outgoing grant arrow in our grant-diagrams.
* Sub-role means which is granted to another role.
* Both means actually the same, just in different aspects.</p>
*
* @param entityAlias
* A previously declared entity alias name.
* @param role
* OWNER, ADMIN, ...
* @return
* The grant definition for further chained calls.
*/
public RbacGrantDefinition outgoingSubRole(final String entityAlias, final Role role) {
final var outgoingSubRole = findRbacRole(entityAlias, role);
return grantSubRoleToSuperRole(outgoingSubRole, this);
@ -802,6 +1037,9 @@ public class RbacView {
}
}
/**
* This main method generates the RbacViews (PostgreSQL+diagram) for all given entity classes.
*/
public static void main(String[] args) {
Stream.of(
TestCustomerEntity.class,

View File

@ -4,7 +4,6 @@ import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import java.nio.file.*;
import java.time.LocalDateTime;
import static java.util.stream.Collectors.joining;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacGrantDefinition.GrantType.*;

View File

@ -5,7 +5,6 @@ import lombok.SneakyThrows;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.time.LocalDateTime;
import static net.hostsharing.hsadminng.rbac.rbacdef.PostgresTriggerReference.NEW;
import static net.hostsharing.hsadminng.rbac.rbacdef.StringWriter.with;

View File

@ -14,6 +14,7 @@ import java.io.IOException;
import java.util.UUID;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NULLABLE;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.fetchedBySql;
@ -52,7 +53,8 @@ public class TestDomainEntity implements HasUuid {
fetchedBySql("""
SELECT * FROM test_package p
WHERE p.uuid= ${ref}.packageUuid
"""))
"""),
NULLABLE)
.toRole("package", ADMIN).grantPermission(INSERT)
.createRole(OWNER, (with) -> {

View File

@ -14,6 +14,7 @@ import java.io.IOException;
import java.util.UUID;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.*;
@ -53,7 +54,8 @@ public class TestPackageEntity implements HasUuid {
fetchedBySql("""
SELECT * FROM test_customer c
WHERE c.uuid= ${ref}.customerUuid
"""))
"""),
NOT_NULL)
.toRole("customer", ADMIN).grantPermission(INSERT)
.createRole(OWNER, (with) -> {

View File

@ -160,6 +160,7 @@ create or replace function cleanIdentifier(rawIdentifier varchar)
declare
cleanIdentifier varchar;
begin
-- TODO: remove the ':' from the list of allowed characters as soon as it's not used anymore
cleanIdentifier := regexp_replace(rawIdentifier, '[^A-Za-z0-9\-._:]+', '', 'g');
return cleanIdentifier;
end; $$;