From ece36209a56965829f19222ce67cc52f74b2329e Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Wed, 3 Aug 2022 06:12:16 +0200 Subject: [PATCH] add RbacRole... --- .../rbac/rbacrole/RbacRoleController.java | 34 ++++++++++ .../rbac/rbacrole/RbacRoleEntity.java | 36 +++++++++++ .../rbac/rbacrole/RbacRoleRepository.java | 58 +++++++++++++++++ .../hsadminng/rbac/rbacrole/RbacRoleType.java | 5 ++ .../db/changelog/2022-07-28-005-rbac-base.sql | 62 +++++++++++++++++-- .../db/changelog/2022-07-29-050-hs-base.sql | 12 +++- .../2022-07-29-061-hs-customer-rbac.sql | 12 +++- .../2022-07-29-070-hs-package-rbac.sql | 10 +++ 8 files changed, 222 insertions(+), 7 deletions(-) create mode 100644 src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleController.java create mode 100644 src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleEntity.java create mode 100644 src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleRepository.java create mode 100644 src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleType.java diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleController.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleController.java new file mode 100644 index 00000000..7a0c3864 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleController.java @@ -0,0 +1,34 @@ +package net.hostsharing.hsadminng.rbac.rbacrole; + +import net.hostsharing.hsadminng.context.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RestController; + +import javax.transaction.Transactional; + +@RestController + +public class RbacRoleController { + + @Autowired + private Context context; + + @Autowired + private RbacRoleRepository rbacRoleRepository; + + @GetMapping(value = "/api/rbacroles") + @Transactional + public Iterable listCustomers( + @RequestHeader(value = "current-user") String userName, + @RequestHeader(value = "assumed-roles", required = false) String assumedRoles + ) { + context.setCurrentUser(userName); + if (assumedRoles != null && !assumedRoles.isBlank()) { + context.assumeRoles(assumedRoles); + } + return rbacRoleRepository.findAll(); + } + +} diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleEntity.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleEntity.java new file mode 100644 index 00000000..0b343d8c --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleEntity.java @@ -0,0 +1,36 @@ +package net.hostsharing.hsadminng.rbac.rbacrole; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.annotation.Immutable; + +import javax.persistence.*; +import java.util.UUID; + +@Entity +@Table(name = "rbacrole_rv") +@Getter +@Setter +@Immutable +@NoArgsConstructor +@AllArgsConstructor +public class RbacRoleEntity { + + @Id + private UUID uuid; + + @Column(name="objectuuid") + private UUID objectUuid; + + @Column(name="roletype") + @Enumerated(EnumType.STRING) + private RbacRoleType roleType; + + @Column(name="objecttable") + private String objectTable; + + @Column(name="objectidname") + private String objectIdName; +} diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleRepository.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleRepository.java new file mode 100644 index 00000000..1df718fb --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleRepository.java @@ -0,0 +1,58 @@ +package net.hostsharing.hsadminng.rbac.rbacrole; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.repository.Repository; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface RbacRoleRepository extends Repository { + + /** + * Retrieves an entity by its id. + * + * @param id must not be {@literal null}. + * @return the entity with the given id or {@literal Optional#empty()} if none found. + * @throws IllegalArgumentException if {@literal id} is {@literal null}. + */ + Optional findByUuid(UUID id); + + /** + * Returns whether an entity with the given id exists. + * + * @param id must not be {@literal null}. + * @return {@literal true} if an entity with the given id exists, {@literal false} otherwise. + * @throws IllegalArgumentException if {@literal id} is {@literal null}. + */ + boolean existsByUuid(RbacRoleEntity id); + + /** + * Returns all instances of the type. + * + * @return all entities + */ + Iterable findAll(); + + /** + * Returns all entities sorted by the given options. + * + * @param sort the {@link Sort} specification to sort the results by, can be {@link Sort#unsorted()}, must not be + * {@literal null}. + * @return all entities sorted by the given options + */ + List findAll(Sort sort); + + /** + * Returns a {@link Page} of entities meeting the paging restriction provided in the {@link Pageable} object. + * + * @param pageable the pageable to request a paged result, can be {@link Pageable#unpaged()}, must not be + * {@literal null}. + * @return a page of entities + */ + Page findAll(Pageable pageable); + +} + diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleType.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleType.java new file mode 100644 index 00000000..51d58bc6 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleType.java @@ -0,0 +1,5 @@ +package net.hostsharing.hsadminng.rbac.rbacrole; + +public enum RbacRoleType { + owner, admin, tenant +} diff --git a/src/main/resources/db/changelog/2022-07-28-005-rbac-base.sql b/src/main/resources/db/changelog/2022-07-28-005-rbac-base.sql index 2929adec..3666b4b6 100644 --- a/src/main/resources/db/changelog/2022-07-28-005-rbac-base.sql +++ b/src/main/resources/db/changelog/2022-07-28-005-rbac-base.sql @@ -151,13 +151,11 @@ create type RbacRoleDescriptor as create or replace function roleDescriptor(objectTable varchar(63), objectUuid uuid, roleType RbacRoleType) returns RbacRoleDescriptor returns null on null input - -- STABLE LEAKPROOF + stable leakproof language sql as $$ select objectTable, objectUuid, roleType::RbacRoleType; $$; - - create or replace function createRole(roleDescriptor RbacRoleDescriptor) returns uuid returns null on null input @@ -347,6 +345,22 @@ select granteeId = grantedId or granteeId in (with recursive grants as (select d from grants); $$; +create or replace function isGranted(granteeIds uuid[], grantedId uuid) + returns bool + returns null on null input + language plpgsql as $$ +declare + granteeId uuid; +begin + -- TODO: 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 @@ -607,6 +621,7 @@ begin return regexp_replace(rawIdentifier, '\W+', ''); end; $$; +-- TODO: rename to findObjectUuidByIdName create or replace function findUuidByIdName(objectTable varchar, objectIdName varchar) returns uuid returns null on null input @@ -628,18 +643,38 @@ begin return uuid; end ; $$; +create or replace function findIdNameByObjectUuid(objectTable varchar, objectUuid uuid) + returns varchar + returns null on null input + language plpgsql as $$ +declare + sql varchar; + idName varchar; +begin + objectTable := pureIdentifier(objectTable); + sql := format('select * from %sIdNameByUuid(%L::uuid);', objectTable, objectUuid); + begin + raise notice 'sql: %', sql; + 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 currentSubjects() returns varchar(63)[] stable leakproof language plpgsql as $$ declare - assumedRoles varchar(63)[]; + assumedRoles varchar(63)[]; begin assumedRoles := assumedRoles(); if array_length(assumedRoles(), 1) > 0 then return assumedRoles(); else - return array[currentUser()]::varchar(63)[]; + return array [currentUser()]::varchar(63)[]; end if; end; $$; @@ -708,3 +743,20 @@ grant all privileges on all tables in schema public to restricted; --// + +-- ============================================================================ +--changeset rbac-base-ROLE-RESTRICTED-VIEW:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +/* + Creates a view to the role table with row-level limitation + based on the grants of the current user or assumed roles. + */ +drop view if exists rbacrole_rv; +create or replace view rbacrole_rv as +select r.*, o.objectTable, + findIdNameByObjectUuid(o.objectTable, o.uuid) as objectIdName + from rbacrole as r + join rbacobject as o on o.uuid=r.objectuuid + where isGranted(currentSubjectIds(), r.uuid); +grant all privileges on rbacrole_rv to restricted; +--// diff --git a/src/main/resources/db/changelog/2022-07-29-050-hs-base.sql b/src/main/resources/db/changelog/2022-07-29-050-hs-base.sql index f8edf9a5..82cd5fe4 100644 --- a/src/main/resources/db/changelog/2022-07-29-050-hs-base.sql +++ b/src/main/resources/db/changelog/2022-07-29-050-hs-base.sql @@ -58,7 +58,7 @@ select target.uuid, target.name as idName grant all privileges on global_iv to restricted; /* - Returns the objectUuid for a given identifying name (in this case the prefix). + Returns the objectUuid for a given identifying name (in this case the idName). */ create or replace function globalUuidByIdName(idName varchar) returns uuid @@ -66,6 +66,16 @@ create or replace function globalUuidByIdName(idName varchar) strict as $$ select uuid from global_iv iv where iv.idName = globalUuidByIdName.idName; $$; + +/* + Returns the identifying name for a given objectUuid (in this case the idName). + */ +create or replace function globalIdNameByUuid(uuid uuid) + returns varchar + language sql + strict as $$ +select idName from global_iv iv where iv.uuid = globalIdNameByUuid.uuid; +$$; --// -- ============================================================================ diff --git a/src/main/resources/db/changelog/2022-07-29-061-hs-customer-rbac.sql b/src/main/resources/db/changelog/2022-07-29-061-hs-customer-rbac.sql index 8524edab..5afa8dfb 100644 --- a/src/main/resources/db/changelog/2022-07-29-061-hs-customer-rbac.sql +++ b/src/main/resources/db/changelog/2022-07-29-061-hs-customer-rbac.sql @@ -163,6 +163,16 @@ create or replace function customerUuidByIdName(idName varchar) strict as $$ select uuid from customer_iv iv where iv.idName = customerUuidByIdName.idName; $$; + +/* + Returns the identifying name for a given objectUuid (in this case the prefix). + */ +create or replace function customerIdNameByUuid(uuid uuid) + returns varchar + language sql + strict as $$ +select idName from customer_iv iv where iv.uuid = customerIdNameByUuid.uuid; +$$; --// @@ -170,7 +180,7 @@ $$; --changeset hs-customer-rbac-RESTRICTED-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- /* - Creates a view to the customer main table with row-level limitatation + Creates a view to the customer main table with row-level limitation based on the 'view' permission of the current user or assumed roles. */ set session session authorization default; diff --git a/src/main/resources/db/changelog/2022-07-29-070-hs-package-rbac.sql b/src/main/resources/db/changelog/2022-07-29-070-hs-package-rbac.sql index ea54d12a..f5b8263d 100644 --- a/src/main/resources/db/changelog/2022-07-29-070-hs-package-rbac.sql +++ b/src/main/resources/db/changelog/2022-07-29-070-hs-package-rbac.sql @@ -162,6 +162,16 @@ create or replace function packageUuidByIdName(idName varchar) strict as $$ select uuid from package_iv iv where iv.idName = packageUuidByIdName.idName; $$; + +/* + Returns the identifying name for a given objectUuid (in this case the name). + */ +create or replace function packageIdNameByUuid(uuid uuid) + returns varchar + language sql + strict as $$ +select idName from package_iv iv where iv.uuid = packageIdNameByUuid.uuid; +$$; --//