implements user granting roles to other users
This commit is contained in:
		@@ -56,7 +56,7 @@ public class RbacGrantController implements RbacgrantsApi {
 | 
				
			|||||||
        final var uri =
 | 
					        final var uri =
 | 
				
			||||||
            MvcUriComponentsBuilder.fromController(getClass())
 | 
					            MvcUriComponentsBuilder.fromController(getClass())
 | 
				
			||||||
                .path("/api/rbac-grants/{roleUuid}")
 | 
					                .path("/api/rbac-grants/{roleUuid}")
 | 
				
			||||||
                .buildAndExpand(body.getRoleUuid())
 | 
					                .buildAndExpand(body.getGrantedRoleUuid())
 | 
				
			||||||
                .toUri();
 | 
					                .toUri();
 | 
				
			||||||
        return ResponseEntity.created(uri).build();
 | 
					        return ResponseEntity.created(uri).build();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,24 +18,27 @@ import java.util.UUID;
 | 
				
			|||||||
@NoArgsConstructor
 | 
					@NoArgsConstructor
 | 
				
			||||||
@AllArgsConstructor
 | 
					@AllArgsConstructor
 | 
				
			||||||
public class RbacGrantEntity {
 | 
					public class RbacGrantEntity {
 | 
				
			||||||
 | 
					    @Column(name = "grantedbyroleidname", updatable = false, insertable = false)
 | 
				
			||||||
 | 
					    private String grantedByRoleIdName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Column(name = "grantedroleidname", updatable = false, insertable = false)
 | 
				
			||||||
 | 
					    private String grantedRoleIdName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Column(name = "username", updatable = false, insertable = false)
 | 
					    @Column(name = "username", updatable = false, insertable = false)
 | 
				
			||||||
    private String userName;
 | 
					    private String granteeUserName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Column(name = "roleidname", updatable = false, insertable = false)
 | 
					 | 
				
			||||||
    private String roleIdName;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private boolean managed;
 | 
					 | 
				
			||||||
    private boolean assumed;
 | 
					    private boolean assumed;
 | 
				
			||||||
    private boolean empowered;
 | 
					
 | 
				
			||||||
 | 
					    @Column(name = "grantedbyroleuuid", updatable = false, insertable = false)
 | 
				
			||||||
 | 
					    private UUID grantedByRoleUuid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Id
 | 
				
			||||||
 | 
					    @Column(name = "grantedroleuuid")
 | 
				
			||||||
 | 
					    private UUID grantedRoleUuid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Id
 | 
					    @Id
 | 
				
			||||||
    @Column(name = "useruuid")
 | 
					    @Column(name = "useruuid")
 | 
				
			||||||
    private UUID userUuid;
 | 
					    private UUID granteeUserUuid;
 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Id
 | 
					 | 
				
			||||||
    @Column(name = "roleuuid")
 | 
					 | 
				
			||||||
    private UUID roleUuid;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Column(name = "objecttable", updatable = false, insertable = false)
 | 
					    @Column(name = "objecttable", updatable = false, insertable = false)
 | 
				
			||||||
    private String objectTable;
 | 
					    private String objectTable;
 | 
				
			||||||
@@ -46,15 +49,12 @@ public class RbacGrantEntity {
 | 
				
			|||||||
    @Column(name = "objectidname", updatable = false, insertable = false)
 | 
					    @Column(name = "objectidname", updatable = false, insertable = false)
 | 
				
			||||||
    private String objectIdName;
 | 
					    private String objectIdName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Column(name = "roletype", updatable = false, insertable = false)
 | 
					    @Column(name = "grantedroletype", updatable = false, insertable = false)
 | 
				
			||||||
    @Enumerated(EnumType.STRING)
 | 
					    @Enumerated(EnumType.STRING)
 | 
				
			||||||
    private RbacRoleType roleType;
 | 
					    private RbacRoleType grantedRoleType;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public String toDisplay() {
 | 
					    public String toDisplay() {
 | 
				
			||||||
        return "grant( " + userName + " -> " + roleIdName + ": " +
 | 
					        return "{ grant " + (assumed ? "assumed " : "") +
 | 
				
			||||||
            (managed ? "managed " : "") +
 | 
					            "role " + grantedRoleIdName + " to user " + granteeUserName + " by role " + grantedByRoleIdName + " }";
 | 
				
			||||||
            (assumed ? "assumed " : "") +
 | 
					 | 
				
			||||||
            (empowered ? "empowered " : "") +
 | 
					 | 
				
			||||||
            ")";
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,6 @@ import java.util.UUID;
 | 
				
			|||||||
@NoArgsConstructor
 | 
					@NoArgsConstructor
 | 
				
			||||||
public class RbacGrantId implements Serializable {
 | 
					public class RbacGrantId implements Serializable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private UUID userUuid;
 | 
					    private UUID granteeUserUuid;
 | 
				
			||||||
    private UUID roleUuid;
 | 
					    private UUID grantedRoleUuid;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,16 +6,14 @@ components:
 | 
				
			|||||||
        RbacGrant:
 | 
					        RbacGrant:
 | 
				
			||||||
            type: object
 | 
					            type: object
 | 
				
			||||||
            properties:
 | 
					            properties:
 | 
				
			||||||
                userUuid:
 | 
					 | 
				
			||||||
                    type: string
 | 
					 | 
				
			||||||
                    format: uuid
 | 
					 | 
				
			||||||
                roleUuid:
 | 
					 | 
				
			||||||
                    type: string
 | 
					 | 
				
			||||||
                    format: uuid
 | 
					 | 
				
			||||||
                assumed:
 | 
					                assumed:
 | 
				
			||||||
                    type: boolean
 | 
					                    type: boolean
 | 
				
			||||||
                empowered:
 | 
					                grantedRoleUuid:
 | 
				
			||||||
                    type: boolean
 | 
					                    type: string
 | 
				
			||||||
 | 
					                    format: uuid
 | 
				
			||||||
 | 
					                granteeUserUuid:
 | 
				
			||||||
 | 
					                    type: string
 | 
				
			||||||
 | 
					                    format: uuid
 | 
				
			||||||
            required:
 | 
					            required:
 | 
				
			||||||
                - userUuid
 | 
					                - grantedRoleUuid
 | 
				
			||||||
                - roleUuid
 | 
					                - granteeUserUuid
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -353,11 +353,10 @@ $$;
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
create table RbacGrants
 | 
					create table RbacGrants
 | 
				
			||||||
(
 | 
					(
 | 
				
			||||||
 | 
					    grantedByRoleUuid   uuid references RbacRole (uuid) on delete cascade,
 | 
				
			||||||
    ascendantUuid       uuid references RbacReference (uuid) on delete cascade,
 | 
					    ascendantUuid       uuid references RbacReference (uuid) on delete cascade,
 | 
				
			||||||
    descendantUuid      uuid references RbacReference (uuid) on delete cascade,
 | 
					    descendantUuid      uuid references RbacReference (uuid) on delete cascade,
 | 
				
			||||||
    managed        boolean not null default false, -- created by system (true) vs. user (false)
 | 
					 | 
				
			||||||
    assumed             boolean not null default true,  -- auto assumed (true) vs. needs assumeRoles (false)
 | 
					    assumed             boolean not null default true,  -- auto assumed (true) vs. needs assumeRoles (false)
 | 
				
			||||||
    empowered      boolean not null default false, -- true: allows grant+revoke for descendant role
 | 
					 | 
				
			||||||
    primary key (ascendantUuid, descendantUuid)
 | 
					    primary key (ascendantUuid, descendantUuid)
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
create index on RbacGrants (ascendantUuid);
 | 
					create index on RbacGrants (ascendantUuid);
 | 
				
			||||||
@@ -463,8 +462,8 @@ begin
 | 
				
			|||||||
            perform assertReferenceType('permissionId (descendant)', permissionIds[i], 'RbacPermission');
 | 
					            perform assertReferenceType('permissionId (descendant)', permissionIds[i], 'RbacPermission');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            insert
 | 
					            insert
 | 
				
			||||||
                into RbacGrants (ascendantUuid, descendantUuid, managed, assumed, empowered)
 | 
					                into RbacGrants (ascendantUuid, descendantUuid, assumed)
 | 
				
			||||||
                values (roleUuid, permissionIds[i], true, true, false)
 | 
					                values (roleUuid, permissionIds[i], true)
 | 
				
			||||||
            on conflict do nothing; -- allow granting multiple times
 | 
					            on conflict do nothing; -- allow granting multiple times
 | 
				
			||||||
        end loop;
 | 
					        end loop;
 | 
				
			||||||
end;
 | 
					end;
 | 
				
			||||||
@@ -476,13 +475,13 @@ begin
 | 
				
			|||||||
    perform assertReferenceType('superRoleId (ascendant)', superRoleId, 'RbacRole');
 | 
					    perform assertReferenceType('superRoleId (ascendant)', superRoleId, 'RbacRole');
 | 
				
			||||||
    perform assertReferenceType('subRoleId (descendant)', subRoleId, 'RbacRole');
 | 
					    perform assertReferenceType('subRoleId (descendant)', subRoleId, 'RbacRole');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (isGranted(subRoleId, superRoleId)) then
 | 
					    if isGranted(subRoleId, superRoleId) then
 | 
				
			||||||
        raise exception '[400] Cyclic role grant detected between % and %', subRoleId, superRoleId;
 | 
					        raise exception '[400] Cyclic role grant detected between % and %', subRoleId, superRoleId;
 | 
				
			||||||
    end if;
 | 
					    end if;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    insert
 | 
					    insert
 | 
				
			||||||
        into RbacGrants (ascendantUuid, descendantUuid, managed, assumed, empowered)
 | 
					        into RbacGrants (ascendantuuid, descendantUuid, assumed)
 | 
				
			||||||
        values (superRoleId, subRoleId, true, doAssume, false)
 | 
					        values (superRoleId, subRoleId, doAssume)
 | 
				
			||||||
    on conflict do nothing; -- allow granting multiple times
 | 
					    on conflict do nothing; -- allow granting multiple times
 | 
				
			||||||
end; $$;
 | 
					end; $$;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -497,48 +496,6 @@ begin
 | 
				
			|||||||
    end if;
 | 
					    end if;
 | 
				
			||||||
end; $$;
 | 
					end; $$;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
create or replace procedure grantRoleToUser(roleUuid uuid, userUuid uuid)
 | 
					 | 
				
			||||||
    language plpgsql as $$
 | 
					 | 
				
			||||||
begin
 | 
					 | 
				
			||||||
    perform assertReferenceType('roleId (descendant)', roleUuid, 'RbacRole');
 | 
					 | 
				
			||||||
    perform assertReferenceType('userId (ascendant)', userUuid, 'RbacUser');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    insert
 | 
					 | 
				
			||||||
        into RbacGrants (ascendantUuid, descendantUuid, managed, assumed, empowered)
 | 
					 | 
				
			||||||
        values (userUuid, roleUuid, true, true, true);
 | 
					 | 
				
			||||||
    -- TODO: What should happen on mupltiple grants? What if options are not the same?
 | 
					 | 
				
			||||||
    -- on conflict do nothing; -- allow granting multiple times
 | 
					 | 
				
			||||||
end; $$;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
    Attributes of a grant assignment.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
create type RbacGrantOptions as
 | 
					 | 
				
			||||||
(
 | 
					 | 
				
			||||||
    managed   boolean, -- created by system (true) vs. user (false)
 | 
					 | 
				
			||||||
    assumed   boolean, -- auto assumed (true) vs. needs assumeRoles (false)
 | 
					 | 
				
			||||||
    empowered boolean  -- true: allows grant+revoke for descendant role
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
create or replace procedure grantRoleToUser(roleUuid uuid, userUuid uuid, grantOptions RbacGrantOptions)
 | 
					 | 
				
			||||||
    language plpgsql as $$
 | 
					 | 
				
			||||||
begin
 | 
					 | 
				
			||||||
    perform assertReferenceType('roleId (descendant)', roleUuid, 'RbacRole');
 | 
					 | 
				
			||||||
    perform assertReferenceType('userId (ascendant)', userUuid, 'RbacUser');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if not isGranted(currentSubjectIds(), roleUuid) then
 | 
					 | 
				
			||||||
        raise exception '[403] Access to role uuid % forbidden for %', roleUuid, currentSubjects();
 | 
					 | 
				
			||||||
    end if;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    insert
 | 
					 | 
				
			||||||
        into RbacGrants (ascendantUuid, descendantUuid, managed, assumed, empowered)
 | 
					 | 
				
			||||||
        values (userUuid, roleUuid, grantOptions.managed, grantOptions.assumed, grantOptions.empowered);
 | 
					 | 
				
			||||||
    -- TODO: What should happen on mupltiple grants? What if options are not the same?
 | 
					 | 
				
			||||||
    --      Most powerful or latest grant wins? What about managed?
 | 
					 | 
				
			||||||
    -- on conflict do nothing; -- allow granting multiple times
 | 
					 | 
				
			||||||
end; $$;
 | 
					 | 
				
			||||||
--//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-- ============================================================================
 | 
					-- ============================================================================
 | 
				
			||||||
--changeset rbac-base-QUERY-ACCESSIBLE-OBJECT-UUIDS:1 endDelimiter:--//
 | 
					--changeset rbac-base-QUERY-ACCESSIBLE-OBJECT-UUIDS:1 endDelimiter:--//
 | 
				
			||||||
-- ----------------------------------------------------------------------------
 | 
					-- ----------------------------------------------------------------------------
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,6 +23,7 @@ begin
 | 
				
			|||||||
    if (currentUser is null or currentUser = '') then
 | 
					    if (currentUser is null or currentUser = '') then
 | 
				
			||||||
        raise exception '[401] hsadminng.currentUser must be defined, please use "SET LOCAL ...;"';
 | 
					        raise exception '[401] hsadminng.currentUser must be defined, please use "SET LOCAL ...;"';
 | 
				
			||||||
    end if;
 | 
					    end if;
 | 
				
			||||||
 | 
					    raise debug 'currentUser: %', currentUser;
 | 
				
			||||||
    return currentUser;
 | 
					    return currentUser;
 | 
				
			||||||
end; $$;
 | 
					end; $$;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,101 @@
 | 
				
			|||||||
 | 
					--liquibase formatted sql
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- ============================================================================
 | 
				
			||||||
 | 
					--changeset rbac-user-grant-GRANT-ROLE-TO-USER:1 endDelimiter:--//
 | 
				
			||||||
 | 
					-- ----------------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					create or replace function assumedRoleUuid()
 | 
				
			||||||
 | 
					    returns uuid
 | 
				
			||||||
 | 
					    stable leakproof
 | 
				
			||||||
 | 
					    language plpgsql as $$
 | 
				
			||||||
 | 
					declare
 | 
				
			||||||
 | 
					    currentSubjectUuids uuid[];
 | 
				
			||||||
 | 
					begin
 | 
				
			||||||
 | 
					    -- exactly one role must be assumed, not none not more than one
 | 
				
			||||||
 | 
					    if cardinality(assumedRoles()) <> 1 then
 | 
				
			||||||
 | 
					        raise exception '[400] Granting roles to user is only possible if exactly one role is assumed, given: %', assumedRoles();
 | 
				
			||||||
 | 
					    end if;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    currentSubjectUuids := currentSubjectIds();
 | 
				
			||||||
 | 
					    return currentSubjectUuids[1];
 | 
				
			||||||
 | 
					end; $$;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					create or replace procedure grantRoleToUserUnchecked(grantedByRoleUuid uuid, roleUuid uuid, userUuid uuid, doAssume boolean = true)
 | 
				
			||||||
 | 
					    language plpgsql as $$
 | 
				
			||||||
 | 
					begin
 | 
				
			||||||
 | 
					    perform assertReferenceType('grantingRoleUuid', grantedByRoleUuid, 'RbacRole');
 | 
				
			||||||
 | 
					    perform assertReferenceType('roleId (descendant)', roleUuid, 'RbacRole');
 | 
				
			||||||
 | 
					    perform assertReferenceType('userId (ascendant)', userUuid, 'RbacUser');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    insert
 | 
				
			||||||
 | 
					        into RbacGrants (grantedByRoleUuid, ascendantUuid, descendantUuid, assumed)
 | 
				
			||||||
 | 
					        values (grantedByRoleUuid, userUuid, roleUuid, doAssume);
 | 
				
			||||||
 | 
					    -- TODO: What should happen on mupltiple grants? What if options are not the same?
 | 
				
			||||||
 | 
					    --      Most powerful or latest grant wins? What about managed?
 | 
				
			||||||
 | 
					    -- on conflict do nothing; -- allow granting multiple times
 | 
				
			||||||
 | 
					end; $$;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					create or replace procedure grantRoleToUser(grantedByRoleUuid uuid, grantedRoleUuid uuid, userUuid uuid, doAssume boolean = true)
 | 
				
			||||||
 | 
					    language plpgsql as $$
 | 
				
			||||||
 | 
					begin
 | 
				
			||||||
 | 
					    perform assertReferenceType('grantingRoleUuid', grantedByRoleUuid, 'RbacRole');
 | 
				
			||||||
 | 
					    perform assertReferenceType('grantedRoleUuid (descendant)', grantedRoleUuid, 'RbacRole');
 | 
				
			||||||
 | 
					    perform assertReferenceType('userUuid (ascendant)', userUuid, 'RbacUser');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if NOT isGranted(currentSubjectIds(), grantedByRoleUuid) then
 | 
				
			||||||
 | 
					        raise exception '[403] Access to granted-by-role % forbidden for %', grantedByRoleUuid, currentSubjects();
 | 
				
			||||||
 | 
					    end if;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if NOT isGranted(grantedByRoleUuid, grantedRoleUuid) then
 | 
				
			||||||
 | 
					        raise exception '[403] Access to granted role % forbidden for %', grantedRoleUuid, currentSubjects();
 | 
				
			||||||
 | 
					    end if;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    insert
 | 
				
			||||||
 | 
					        into RbacGrants (grantedByRoleUuid, ascendantUuid, descendantUuid, assumed)
 | 
				
			||||||
 | 
					        values (grantedByRoleUuid, userUuid, grantedRoleUuid, doAssume);
 | 
				
			||||||
 | 
					    -- TODO: What should happen on mupltiple grants? What if options are not the same?
 | 
				
			||||||
 | 
					    --      Most powerful or latest grant wins? What about managed?
 | 
				
			||||||
 | 
					    -- on conflict do nothing; -- allow granting multiple times
 | 
				
			||||||
 | 
					end; $$;
 | 
				
			||||||
 | 
					--//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- ============================================================================
 | 
				
			||||||
 | 
					--changeset rbac-user-grant-REVOKE-ROLE-FROM-USER:1 endDelimiter:--//
 | 
				
			||||||
 | 
					-- ----------------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					create or replace procedure checkRevokeRoleFromUserPreconditions(grantedByRoleUuid uuid, grantedRoleUuid uuid, userUuid uuid)
 | 
				
			||||||
 | 
					    language plpgsql as $$
 | 
				
			||||||
 | 
					begin
 | 
				
			||||||
 | 
					    perform assertReferenceType('grantedByRoleUuid', grantedByRoleUuid, 'RbacRole');
 | 
				
			||||||
 | 
					    perform assertReferenceType('grantedRoleUuid (descendant)', grantedRoleUuid, 'RbacRole');
 | 
				
			||||||
 | 
					    perform assertReferenceType('userUuid (ascendant)', userUuid, 'RbacUser');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if NOT isGranted(currentSubjectIds(), grantedByRoleUuid) then
 | 
				
			||||||
 | 
					        raise exception '[403] Revoking role created by % is forbidden for %.', grantedByRoleUuid, currentSubjects();
 | 
				
			||||||
 | 
					    end if;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if NOT isGranted(grantedByRoleUuid, grantedRoleUuid) then
 | 
				
			||||||
 | 
					        raise exception '[403] Revoking role % is forbidden for %.', grantedRoleUuid, currentSubjects();
 | 
				
			||||||
 | 
					    end if;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if NOT isGranted(currentSubjectIds(), grantedByRoleUuid) then
 | 
				
			||||||
 | 
					        raise exception '[403] Revoking role granted by % is forbidden for %.', grantedByRoleUuid, currentSubjects();
 | 
				
			||||||
 | 
					    end if;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if NOT isGranted(userUuid, grantedRoleUuid) then
 | 
				
			||||||
 | 
					        raise exception '[404] No such grant found granted by % for user % to role %.', grantedByRoleUuid, userUuid, grantedRoleUuid;
 | 
				
			||||||
 | 
					    end if;
 | 
				
			||||||
 | 
					end; $$;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					create or replace procedure revokeRoleFromUser(grantedByRoleUuid uuid, grantedRoleUuid uuid, userUuid uuid)
 | 
				
			||||||
 | 
					    language plpgsql as $$
 | 
				
			||||||
 | 
					begin
 | 
				
			||||||
 | 
					    call checkRevokeRoleFromUserPreconditions(grantedByRoleUuid, grantedRoleUuid, userUuid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    raise INFO 'delete from RbacGrants where ascendantUuid = % and descendantUuid = %', userUuid, grantedRoleUuid;
 | 
				
			||||||
 | 
					    delete from RbacGrants as g
 | 
				
			||||||
 | 
					       where g.ascendantUuid = userUuid and g.descendantUuid = grantedRoleUuid
 | 
				
			||||||
 | 
					         and g.grantedByRoleUuid = revokeRoleFromUser.grantedByRoleUuid;
 | 
				
			||||||
 | 
					end; $$;
 | 
				
			||||||
 | 
					--/
 | 
				
			||||||
@@ -33,23 +33,25 @@ grant all privileges on rbacrole_rv to restricted;
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
drop view if exists rbacgrants_rv;
 | 
					drop view if exists rbacgrants_rv;
 | 
				
			||||||
create or replace view rbacgrants_rv as
 | 
					create or replace view rbacgrants_rv as
 | 
				
			||||||
select userName, objectTable||'#'||objectIdName||'.'||roletype as roleIdName,
 | 
					 | 
				
			||||||
       managed, assumed, empowered,
 | 
					 | 
				
			||||||
       ascendantUuid as userUuid,
 | 
					 | 
				
			||||||
       descendantUuid as roleUuid,
 | 
					 | 
				
			||||||
       objectTable, objectUuid, objectIdName, roleType
 | 
					 | 
				
			||||||
    -- @formatter:off
 | 
					    -- @formatter:off
 | 
				
			||||||
 | 
					    select o.objectTable || '#' || findIdNameByObjectUuid(o.objectTable, o.uuid) || '.' || r.roletype as grantedByRoleIdName,
 | 
				
			||||||
 | 
					           g.objectTable || '#' || g.objectIdName || '.' || g.roletype as grantedRoleIdName, g.userName, g.assumed,
 | 
				
			||||||
 | 
					           g.grantedByRoleUuid, g.descendantUuid as grantedRoleUuid, g.ascendantUuid as userUuid,
 | 
				
			||||||
 | 
					           g.objectTable, g.objectUuid, g.objectIdName, g.roleType as grantedRoleType
 | 
				
			||||||
        from (
 | 
					        from (
 | 
				
			||||||
             select g.*, u.name as userName, o.objecttable, r.objectuuid, r.roletype,
 | 
					             select g.grantedbyroleuuid, g.ascendantuuid, g.descendantuuid, g.assumed,
 | 
				
			||||||
 | 
					                    u.name as userName, o.objecttable, r.objectuuid, r.roletype,
 | 
				
			||||||
                    findIdNameByObjectUuid(o.objectTable, o.uuid) as objectIdName
 | 
					                    findIdNameByObjectUuid(o.objectTable, o.uuid) as objectIdName
 | 
				
			||||||
                 from rbacgrants as g
 | 
					                 from rbacgrants as g
 | 
				
			||||||
                 join rbacrole as r on r.uuid = g.descendantUuid
 | 
					                 join rbacrole as r on r.uuid = g.descendantUuid
 | 
				
			||||||
                 join rbacobject o on o.uuid = r.objectuuid
 | 
					                 join rbacobject o on o.uuid = r.objectuuid
 | 
				
			||||||
                 join rbacuser u on u.uuid = g.ascendantuuid
 | 
					                 join rbacuser u on u.uuid = g.ascendantuuid
 | 
				
			||||||
                 where isGranted(currentSubjectIds(), r.uuid)
 | 
					                 where isGranted(currentSubjectIds(), r.uuid)
 | 
				
			||||||
         ) as unordered
 | 
					         ) as g
 | 
				
			||||||
 | 
					        join RbacRole as r on r.uuid = grantedByRoleUuid
 | 
				
			||||||
 | 
					        join RbacObject as o on o.uuid = r.objectUuid
 | 
				
			||||||
 | 
					    order by grantedRoleIdName;
 | 
				
			||||||
    -- @formatter:on
 | 
					    -- @formatter:on
 | 
				
			||||||
    order by roleIdName;
 | 
					 | 
				
			||||||
grant all privileges on rbacrole_rv to restricted;
 | 
					grant all privileges on rbacrole_rv to restricted;
 | 
				
			||||||
--//
 | 
					--//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -67,15 +69,10 @@ create or replace function insertRbacGrant()
 | 
				
			|||||||
declare
 | 
					declare
 | 
				
			||||||
    newGrant RbacGrants_RV;
 | 
					    newGrant RbacGrants_RV;
 | 
				
			||||||
begin
 | 
					begin
 | 
				
			||||||
    if new.managed then
 | 
					    call grantRoleToUser(assumedRoleUuid(), new.grantedRoleUuid, new.userUuid, new.assumed);
 | 
				
			||||||
        raise exception '[400] Managed grants cannot be inserted via RBacGrants_RV.';
 | 
					 | 
				
			||||||
    end if;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    call grantRoleToUser(new.roleUuid, new.userUuid,
 | 
					 | 
				
			||||||
        ROW(false, new.assumed, new.empowered));
 | 
					 | 
				
			||||||
    select grv.*
 | 
					    select grv.*
 | 
				
			||||||
        from RbacGrants_RV grv
 | 
					        from RbacGrants_RV grv
 | 
				
			||||||
        where grv.userUuid=new.userUuid and grv.roleUuid=new.roleUuid
 | 
					        where grv.userUuid=new.userUuid and grv.grantedRoleUuid=new.grantedRoleUuid
 | 
				
			||||||
        into newGrant;
 | 
					        into newGrant;
 | 
				
			||||||
    return newGrant;
 | 
					    return newGrant;
 | 
				
			||||||
end; $$;
 | 
					end; $$;
 | 
				
			||||||
@@ -88,6 +85,33 @@ create trigger insertRbacGrant_Trigger
 | 
				
			|||||||
    on RbacGrants_rv
 | 
					    on RbacGrants_rv
 | 
				
			||||||
    for each row
 | 
					    for each row
 | 
				
			||||||
execute function insertRbacGrant();
 | 
					execute function insertRbacGrant();
 | 
				
			||||||
 | 
					--/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- ============================================================================
 | 
				
			||||||
 | 
					--changeset rbac-views-GRANTS-RV-DELETE-TRIGGER:1 endDelimiter:--//
 | 
				
			||||||
 | 
					-- ----------------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					    Instead of delete trigger function for RbacGrants_RV.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					create or replace function deleteRbacGrant()
 | 
				
			||||||
 | 
					    returns trigger
 | 
				
			||||||
 | 
					    language plpgsql as $$
 | 
				
			||||||
 | 
					begin
 | 
				
			||||||
 | 
					    call revokeRoleFromUser(assumedRoleUuid(), old.grantedRoleUuid, old.userUuid);
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					end; $$;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					    Creates an instead of delete trigger for the RbacGrants_rv view.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					create trigger deleteRbacGrant_Trigger
 | 
				
			||||||
 | 
					    instead of delete
 | 
				
			||||||
 | 
					    on RbacGrants_rv
 | 
				
			||||||
 | 
					    for each row
 | 
				
			||||||
 | 
					execute function deleteRbacGrant();
 | 
				
			||||||
 | 
					--/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-- ============================================================================
 | 
					-- ============================================================================
 | 
				
			||||||
@@ -220,3 +244,4 @@ begin
 | 
				
			|||||||
             ) xp;
 | 
					             ) xp;
 | 
				
			||||||
    -- @formatter:on
 | 
					    -- @formatter:on
 | 
				
			||||||
end; $$;
 | 
					end; $$;
 | 
				
			||||||
 | 
					--//
 | 
				
			||||||
@@ -26,7 +26,7 @@ create or replace function withoutPermissions()
 | 
				
			|||||||
    language plpgsql
 | 
					    language plpgsql
 | 
				
			||||||
    strict as $$
 | 
					    strict as $$
 | 
				
			||||||
begin
 | 
					begin
 | 
				
			||||||
    return row (array[]::uuid[]);
 | 
					    return row (array []::uuid[]);
 | 
				
			||||||
end; $$;
 | 
					end; $$;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
--//
 | 
					--//
 | 
				
			||||||
@@ -167,7 +167,8 @@ create or replace function createRole(
 | 
				
			|||||||
    permissions RbacPermissions,
 | 
					    permissions RbacPermissions,
 | 
				
			||||||
    superRoles RbacSuperRoles,
 | 
					    superRoles RbacSuperRoles,
 | 
				
			||||||
    subRoles RbacSubRoles = null,
 | 
					    subRoles RbacSubRoles = null,
 | 
				
			||||||
    users RbacUsers = null
 | 
					    users RbacUsers = null,
 | 
				
			||||||
 | 
					    grantingRoleUuid uuid = null
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
    returns uuid
 | 
					    returns uuid
 | 
				
			||||||
    called on null input
 | 
					    called on null input
 | 
				
			||||||
@@ -200,7 +201,7 @@ begin
 | 
				
			|||||||
    if users is not null then
 | 
					    if users is not null then
 | 
				
			||||||
        foreach userUuid in array users.useruUids
 | 
					        foreach userUuid in array users.useruUids
 | 
				
			||||||
            loop
 | 
					            loop
 | 
				
			||||||
                call grantRoleToUser(roleUuid, userUuid);
 | 
					                call grantRoleToUserUnchecked(grantingRoleUuid, roleUuid, userUuid);
 | 
				
			||||||
            end loop;
 | 
					            end loop;
 | 
				
			||||||
    end if;
 | 
					    end if;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -210,26 +211,47 @@ end; $$;
 | 
				
			|||||||
create or replace function createRole(
 | 
					create or replace function createRole(
 | 
				
			||||||
    roleDescriptor RbacRoleDescriptor,
 | 
					    roleDescriptor RbacRoleDescriptor,
 | 
				
			||||||
    permissions RbacPermissions,
 | 
					    permissions RbacPermissions,
 | 
				
			||||||
    users RbacUsers = null
 | 
					    users RbacUsers = null,
 | 
				
			||||||
 | 
					    grantingRoleUuid uuid = null
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
    returns uuid
 | 
					    returns uuid
 | 
				
			||||||
    called on null input
 | 
					    called on null input
 | 
				
			||||||
    language plpgsql as $$
 | 
					    language plpgsql as $$
 | 
				
			||||||
begin
 | 
					begin
 | 
				
			||||||
    return createRole(roleDescriptor, permissions, null, null, users);
 | 
					    return createRole(roleDescriptor, permissions, null, null, users, grantingRoleUuid);
 | 
				
			||||||
end; $$;
 | 
					end; $$;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
create or replace function createRole(
 | 
					create or replace function createRole(
 | 
				
			||||||
    roleDescriptor RbacRoleDescriptor,
 | 
					    roleDescriptor RbacRoleDescriptor,
 | 
				
			||||||
    permissions RbacPermissions,
 | 
					    permissions RbacPermissions,
 | 
				
			||||||
    subRoles RbacSubRoles,
 | 
					    subRoles RbacSubRoles,
 | 
				
			||||||
    users RbacUsers = null
 | 
					    users RbacUsers = null,
 | 
				
			||||||
 | 
					    grantingRoleUuid uuid = null
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
    returns uuid
 | 
					    returns uuid
 | 
				
			||||||
    called on null input
 | 
					    called on null input
 | 
				
			||||||
    language plpgsql as $$
 | 
					    language plpgsql as $$
 | 
				
			||||||
begin
 | 
					begin
 | 
				
			||||||
    return createRole(roleDescriptor, permissions, null, subRoles, users);
 | 
					    return createRole(roleDescriptor, permissions, null, subRoles, users, grantingRoleUuid);
 | 
				
			||||||
end; $$;
 | 
					end; $$;
 | 
				
			||||||
 | 
					 | 
				
			||||||
--//
 | 
					--//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- =================================================================
 | 
				
			||||||
 | 
					-- CREATE ROLE
 | 
				
			||||||
 | 
					--changeset rbac-role-builder-GRANTED-BY-ROLE:1 endDelimiter:--//
 | 
				
			||||||
 | 
					-- -----------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					    Used in role-builder-DSL to convert a role descriptor to it's uuid
 | 
				
			||||||
 | 
					    for use as `grantedByRoleUuid`.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					create or replace function grantedByRole(roleDescriptor RbacRoleDescriptor)
 | 
				
			||||||
 | 
					    returns uuid
 | 
				
			||||||
 | 
					    strict leakproof
 | 
				
			||||||
 | 
					    language plpgsql as $$
 | 
				
			||||||
 | 
					begin
 | 
				
			||||||
 | 
					    return getRoleId(roledescriptor, 'fail');
 | 
				
			||||||
 | 
					end; $$;
 | 
				
			||||||
 | 
					--//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -104,8 +104,8 @@ do language plpgsql $$
 | 
				
			|||||||
        admins uuid ;
 | 
					        admins uuid ;
 | 
				
			||||||
    begin
 | 
					    begin
 | 
				
			||||||
        admins = findRoleId(hostsharingAdmin());
 | 
					        admins = findRoleId(hostsharingAdmin());
 | 
				
			||||||
        call grantRoleToUser(admins, createRbacUser('mike@hostsharing.net'));
 | 
					        call grantRoleToUserUnchecked(admins, admins, createRbacUser('mike@hostsharing.net'));
 | 
				
			||||||
        call grantRoleToUser(admins, createRbacUser('sven@hostsharing.net'));
 | 
					        call grantRoleToUserUnchecked(admins, admins, createRbacUser('sven@hostsharing.net'));
 | 
				
			||||||
    end;
 | 
					    end;
 | 
				
			||||||
$$;
 | 
					$$;
 | 
				
			||||||
--//
 | 
					--//
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -77,7 +77,8 @@ begin
 | 
				
			|||||||
        customerAdmin(NEW),
 | 
					        customerAdmin(NEW),
 | 
				
			||||||
        grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['view', 'add-package']),
 | 
					        grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['view', 'add-package']),
 | 
				
			||||||
        -- NO auto assume for customer owner to avoid exploding permissions for administrators
 | 
					        -- NO auto assume for customer owner to avoid exploding permissions for administrators
 | 
				
			||||||
        withUser(NEW.adminUserName, 'create') -- implicitly ignored if null
 | 
					        withUser(NEW.adminUserName, 'create'), -- implicitly ignored if null
 | 
				
			||||||
 | 
					        grantedByRole(hostsharingAdmin())
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    -- allow the customer owner role (thus administrators) to assume the customer admin role
 | 
					    -- allow the customer owner role (thus administrators) to assume the customer admin role
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,8 +37,8 @@ begin
 | 
				
			|||||||
        loop
 | 
					        loop
 | 
				
			||||||
            currentTask = 'creating RBAC test customer #' || t;
 | 
					            currentTask = 'creating RBAC test customer #' || t;
 | 
				
			||||||
            set local hsadminng.currentUser to 'mike@hostsharing.net';
 | 
					            set local hsadminng.currentUser to 'mike@hostsharing.net';
 | 
				
			||||||
            set local hsadminng.assumedRoles = '';
 | 
					            set local hsadminng.assumedRoles to 'global#hostsharing.admin';
 | 
				
			||||||
            set local hsadminng.currentTask to currentTask;
 | 
					            execute format('set local hsadminng.currentTask to %L', currentTask);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            -- When a new customer is created,
 | 
					            -- When a new customer is created,
 | 
				
			||||||
            custReference = testCustomerReference(t);
 | 
					            custReference = testCustomerReference(t);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,30 +11,32 @@ create or replace procedure createPackageTestData(
 | 
				
			|||||||
    doCommitAfterEach boolean -- only for mass data creation outside of Liquibase
 | 
					    doCommitAfterEach boolean -- only for mass data creation outside of Liquibase
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
    language plpgsql as $$
 | 
					    language plpgsql as $$
 | 
				
			||||||
    declare
 | 
					declare
 | 
				
			||||||
    cust          customer;
 | 
					    cust          customer;
 | 
				
			||||||
 | 
					    custAdminUser varchar;
 | 
				
			||||||
 | 
					    custAdminRole varchar;
 | 
				
			||||||
    pacName       varchar;
 | 
					    pacName       varchar;
 | 
				
			||||||
    currentTask   varchar;
 | 
					    currentTask   varchar;
 | 
				
			||||||
        custAdmin       varchar;
 | 
					 | 
				
			||||||
    pac           package;
 | 
					    pac           package;
 | 
				
			||||||
    begin
 | 
					begin
 | 
				
			||||||
    set hsadminng.currentUser to '';
 | 
					    set hsadminng.currentUser to '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for cust in (select * from customer)
 | 
					    for cust in (select * from customer)
 | 
				
			||||||
        loop
 | 
					        loop
 | 
				
			||||||
                CONTINUE WHEN cust.reference < minCustomerReference;
 | 
					            continue when cust.reference < minCustomerReference;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            for t in 0..2
 | 
					            for t in 0..2
 | 
				
			||||||
                loop
 | 
					                loop
 | 
				
			||||||
                    pacName = cust.prefix || to_char(t, 'fm00');
 | 
					                    pacName = cust.prefix || to_char(t, 'fm00');
 | 
				
			||||||
                    currentTask = 'creating RBAC test package #' || pacName || ' for customer ' || cust.prefix || ' #' ||
 | 
					                    currentTask = 'creating RBAC test package #' || pacName || ' for customer ' || cust.prefix || ' #' ||
 | 
				
			||||||
                                  cust.uuid;
 | 
					                                  cust.uuid;
 | 
				
			||||||
                        raise notice 'task: %', currentTask;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        custAdmin = 'admin@' || cust.prefix || '.example.com';
 | 
					                    custAdminUser = 'admin@' || cust.prefix || '.example.com';
 | 
				
			||||||
                        set local hsadminng.currentUser to custAdmin;
 | 
					                    custAdminRole = 'customer#' || cust.prefix || '.admin';
 | 
				
			||||||
                        set local hsadminng.assumedRoles = '';
 | 
					                    execute format('set local hsadminng.currentUser to %L', custAdminUser);
 | 
				
			||||||
                        set local hsadminng.currentTask to currentTask;
 | 
					                    execute format('set local hsadminng.assumedRoles to %L', custAdminRole);
 | 
				
			||||||
 | 
					                    execute format('set local hsadminng.currentTask to %L', currentTask);
 | 
				
			||||||
 | 
					                    raise notice 'task: % by % as %', currentTask, custAdminUser, custAdminRole;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    insert
 | 
					                    insert
 | 
				
			||||||
                        into package (customerUuid, name, description)
 | 
					                        into package (customerUuid, name, description)
 | 
				
			||||||
@@ -42,8 +44,10 @@ create or replace procedure createPackageTestData(
 | 
				
			|||||||
                        returning * into pac;
 | 
					                        returning * into pac;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    call grantRoleToUser(
 | 
					                    call grantRoleToUser(
 | 
				
			||||||
 | 
					                        getRoleId(customerAdmin(cust), 'fail'),
 | 
				
			||||||
                        findRoleId(packageAdmin(pac)),
 | 
					                        findRoleId(packageAdmin(pac)),
 | 
				
			||||||
                            createRbacUser(pacName || '@' || cust.prefix || '.example.com'));
 | 
					                        createRbacUser(pacName || '@' || cust.prefix || '.example.com'),
 | 
				
			||||||
 | 
					                        true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                end loop;
 | 
					                end loop;
 | 
				
			||||||
        end loop;
 | 
					        end loop;
 | 
				
			||||||
@@ -51,7 +55,7 @@ create or replace procedure createPackageTestData(
 | 
				
			|||||||
    if doCommitAfterEach then
 | 
					    if doCommitAfterEach then
 | 
				
			||||||
        commit;
 | 
					        commit;
 | 
				
			||||||
    end if;
 | 
					    end if;
 | 
				
			||||||
    end;
 | 
					end ;
 | 
				
			||||||
$$;
 | 
					$$;
 | 
				
			||||||
--//
 | 
					--//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,7 +12,9 @@ databaseChangeLog:
 | 
				
			|||||||
    - include:
 | 
					    - include:
 | 
				
			||||||
        file: db/changelog/2022-07-28-006-rbac-current.sql
 | 
					        file: db/changelog/2022-07-28-006-rbac-current.sql
 | 
				
			||||||
    - include:
 | 
					    - include:
 | 
				
			||||||
        file: db/changelog/2022-07-28-007-rbac-views.sql
 | 
					        file: db/changelog/2022-07-28-007-rbac-user-grant.sql
 | 
				
			||||||
 | 
					    - include:
 | 
				
			||||||
 | 
					        file: db/changelog/2022-07-28-008-rbac-views.sql
 | 
				
			||||||
    - include:
 | 
					    - include:
 | 
				
			||||||
        file: db/changelog/2022-07-28-020-rbac-role-builder.sql
 | 
					        file: db/changelog/2022-07-28-020-rbac-role-builder.sql
 | 
				
			||||||
    - include:
 | 
					    - include:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test;
 | 
				
			|||||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
					import org.springframework.beans.factory.annotation.Autowired;
 | 
				
			||||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
 | 
					import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
 | 
				
			||||||
import org.springframework.context.annotation.ComponentScan;
 | 
					import org.springframework.context.annotation.ComponentScan;
 | 
				
			||||||
 | 
					import org.springframework.test.annotation.DirtiesContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javax.transaction.Transactional;
 | 
					import javax.transaction.Transactional;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -11,6 +12,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@DataJpaTest
 | 
					@DataJpaTest
 | 
				
			||||||
@ComponentScan(basePackageClasses = Context.class)
 | 
					@ComponentScan(basePackageClasses = Context.class)
 | 
				
			||||||
 | 
					@DirtiesContext
 | 
				
			||||||
class ContextIntegrationTests {
 | 
					class ContextIntegrationTests {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Autowired
 | 
					    @Autowired
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 | 
				
			|||||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
 | 
					import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
 | 
				
			||||||
import org.springframework.context.annotation.ComponentScan;
 | 
					import org.springframework.context.annotation.ComponentScan;
 | 
				
			||||||
import org.springframework.orm.jpa.JpaSystemException;
 | 
					import org.springframework.orm.jpa.JpaSystemException;
 | 
				
			||||||
 | 
					import org.springframework.test.annotation.DirtiesContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javax.persistence.EntityManager;
 | 
					import javax.persistence.EntityManager;
 | 
				
			||||||
import javax.persistence.PersistenceException;
 | 
					import javax.persistence.PersistenceException;
 | 
				
			||||||
@@ -19,6 +20,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@DataJpaTest
 | 
					@DataJpaTest
 | 
				
			||||||
@ComponentScan(basePackageClasses = { Context.class, CustomerRepository.class })
 | 
					@ComponentScan(basePackageClasses = { Context.class, CustomerRepository.class })
 | 
				
			||||||
 | 
					@DirtiesContext
 | 
				
			||||||
class CustomerRepositoryIntegrationTest {
 | 
					class CustomerRepositoryIntegrationTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Autowired
 | 
					    @Autowired
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 | 
				
			|||||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
 | 
					import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
 | 
				
			||||||
import org.springframework.context.annotation.ComponentScan;
 | 
					import org.springframework.context.annotation.ComponentScan;
 | 
				
			||||||
import org.springframework.orm.jpa.JpaSystemException;
 | 
					import org.springframework.orm.jpa.JpaSystemException;
 | 
				
			||||||
 | 
					import org.springframework.test.annotation.DirtiesContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javax.persistence.EntityManager;
 | 
					import javax.persistence.EntityManager;
 | 
				
			||||||
import javax.transaction.Transactional;
 | 
					import javax.transaction.Transactional;
 | 
				
			||||||
@@ -18,6 +19,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@DataJpaTest
 | 
					@DataJpaTest
 | 
				
			||||||
@ComponentScan(basePackageClasses = { Context.class, CustomerRepository.class })
 | 
					@ComponentScan(basePackageClasses = { Context.class, CustomerRepository.class })
 | 
				
			||||||
 | 
					@DirtiesContext
 | 
				
			||||||
class PackageRepositoryIntegrationTest {
 | 
					class PackageRepositoryIntegrationTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Autowired
 | 
					    @Autowired
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,19 +15,22 @@ import org.junit.jupiter.api.Test;
 | 
				
			|||||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
					import org.springframework.beans.factory.annotation.Autowired;
 | 
				
			||||||
import org.springframework.boot.test.context.SpringBootTest;
 | 
					import org.springframework.boot.test.context.SpringBootTest;
 | 
				
			||||||
import org.springframework.boot.test.web.server.LocalServerPort;
 | 
					import org.springframework.boot.test.web.server.LocalServerPort;
 | 
				
			||||||
 | 
					import org.springframework.transaction.annotation.Propagation;
 | 
				
			||||||
 | 
					import org.springframework.transaction.annotation.Transactional;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javax.persistence.EntityManager;
 | 
					import javax.persistence.EntityManager;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.UUID;
 | 
					import java.util.UUID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import static org.assertj.core.api.Assertions.assertThat;
 | 
					import static org.assertj.core.api.Assertions.assertThat;
 | 
				
			||||||
import static org.hamcrest.Matchers.is;
 | 
					import static org.hamcrest.CoreMatchers.containsString;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@SpringBootTest(
 | 
					@SpringBootTest(
 | 
				
			||||||
    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
 | 
					    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
 | 
				
			||||||
    classes = { HsadminNgApplication.class, JpaAttempt.class }
 | 
					    classes = { HsadminNgApplication.class, JpaAttempt.class }
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@Accepts({ "ROL:S(Schema)" })
 | 
					@Accepts({ "ROL:S(Schema)" })
 | 
				
			||||||
 | 
					@Transactional(propagation = Propagation.NEVER)
 | 
				
			||||||
class RbacGrantControllerAcceptanceTest {
 | 
					class RbacGrantControllerAcceptanceTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @LocalServerPort
 | 
					    @LocalServerPort
 | 
				
			||||||
@@ -57,23 +60,25 @@ class RbacGrantControllerAcceptanceTest {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        // given
 | 
					        // given
 | 
				
			||||||
        final var givenNewUserName = "test-user-" + RandomStringUtils.randomAlphabetic(8) + "@example.com";
 | 
					        final var givenNewUserName = "test-user-" + RandomStringUtils.randomAlphabetic(8) + "@example.com";
 | 
				
			||||||
        final String givenPackageAdmin = "aaa00@aaa.example.com";
 | 
					        final String givenCurrentUserPackageAdmin = "aaa00@aaa.example.com";
 | 
				
			||||||
 | 
					        final String givenAssumedRole = "package#aaa00.admin";
 | 
				
			||||||
        final var givenOwnPackageAdminRole = "package#aaa00.admin";
 | 
					        final var givenOwnPackageAdminRole = "package#aaa00.admin";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // when
 | 
					        // when
 | 
				
			||||||
        RestAssured // @formatter:off
 | 
					        RestAssured // @formatter:off
 | 
				
			||||||
            .given()
 | 
					            .given()
 | 
				
			||||||
                .header("current-user", givenPackageAdmin)
 | 
					                .header("current-user", givenCurrentUserPackageAdmin)
 | 
				
			||||||
 | 
					                .header("assumed-roles", givenAssumedRole)
 | 
				
			||||||
                .contentType(ContentType.JSON)
 | 
					                .contentType(ContentType.JSON)
 | 
				
			||||||
                .body("""
 | 
					                .body("""
 | 
				
			||||||
                      {
 | 
					                      {
 | 
				
			||||||
                        "userUuid": "%s",
 | 
					 | 
				
			||||||
                        "roleUuid": "%s",
 | 
					 | 
				
			||||||
                        "assumed": true,
 | 
					                        "assumed": true,
 | 
				
			||||||
                        "empowered": false
 | 
					                        "grantedRoleUuid": "%s",
 | 
				
			||||||
 | 
					                        "granteeUserUuid": "%s"
 | 
				
			||||||
                      }
 | 
					                      }
 | 
				
			||||||
                      """.formatted(
 | 
					                      """.formatted(
 | 
				
			||||||
                        createRBacUser(givenNewUserName).getUuid().toString(),
 | 
					                        findRbacRoleByName(givenOwnPackageAdminRole).getUuid().toString(),
 | 
				
			||||||
                        findRbacRoleByName(givenOwnPackageAdminRole).getUuid().toString())
 | 
					                        createRBacUser(givenNewUserName).getUuid().toString())
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
                .port(port)
 | 
					                .port(port)
 | 
				
			||||||
            .when()
 | 
					            .when()
 | 
				
			||||||
@@ -83,9 +88,11 @@ class RbacGrantControllerAcceptanceTest {
 | 
				
			|||||||
            // @formatter:on
 | 
					            // @formatter:on
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // then
 | 
					        // then
 | 
				
			||||||
        assertThat(findAllGrantsOfUser(givenPackageAdmin))
 | 
					        assertThat(findAllGrantsOfUser(givenCurrentUserPackageAdmin))
 | 
				
			||||||
            .extracting(RbacGrantEntity::toDisplay)
 | 
					            .extracting(RbacGrantEntity::toDisplay)
 | 
				
			||||||
            .contains("grant( " + givenNewUserName + " -> " + givenOwnPackageAdminRole + ": assumed )");
 | 
					            .contains("{ grant assumed role " + givenOwnPackageAdminRole +
 | 
				
			||||||
 | 
					                " to user " + givenNewUserName +
 | 
				
			||||||
 | 
					                " by role " + givenAssumedRole + " }");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
@@ -94,35 +101,38 @@ class RbacGrantControllerAcceptanceTest {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        // given
 | 
					        // given
 | 
				
			||||||
        final var givenNewUserName = "test-user-" + RandomStringUtils.randomAlphabetic(8) + "@example.com";
 | 
					        final var givenNewUserName = "test-user-" + RandomStringUtils.randomAlphabetic(8) + "@example.com";
 | 
				
			||||||
        final String givenPackageAdmin = "aaa00@aaa.example.com";
 | 
					        final String givenCurrentUserPackageAdmin = "aaa00@aaa.example.com";
 | 
				
			||||||
 | 
					        final String givenAssumedRole = "package#aaa00.admin";
 | 
				
			||||||
        final var givenAlienPackageAdminRole = "package#aab00.admin";
 | 
					        final var givenAlienPackageAdminRole = "package#aab00.admin";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // when
 | 
					        // when
 | 
				
			||||||
        RestAssured // @formatter:off
 | 
					        RestAssured // @formatter:off
 | 
				
			||||||
            .given()
 | 
					            .given()
 | 
				
			||||||
            .header("current-user", givenPackageAdmin)
 | 
					                .header("current-user", givenCurrentUserPackageAdmin)
 | 
				
			||||||
 | 
					                .header("assumed-roles", givenAssumedRole)
 | 
				
			||||||
                .contentType(ContentType.JSON)
 | 
					                .contentType(ContentType.JSON)
 | 
				
			||||||
                .body("""
 | 
					                .body("""
 | 
				
			||||||
                          {
 | 
					                          {
 | 
				
			||||||
                        "userUuid": "%s",
 | 
					 | 
				
			||||||
                        "roleUuid": "%s",
 | 
					 | 
				
			||||||
                            "assumed": true,
 | 
					                            "assumed": true,
 | 
				
			||||||
                        "empowered": false
 | 
					                            "grantedRoleUuid": "%s",
 | 
				
			||||||
 | 
					                            "granteeUserUuid": "%s"
 | 
				
			||||||
                          }
 | 
					                          }
 | 
				
			||||||
                          """.formatted(
 | 
					                          """.formatted(
 | 
				
			||||||
                createRBacUser(givenNewUserName).getUuid().toString(),
 | 
					                    findRbacRoleByName(givenAlienPackageAdminRole).getUuid().toString(),
 | 
				
			||||||
                findRbacRoleByName(givenAlienPackageAdminRole).getUuid().toString())
 | 
					                    createRBacUser(givenNewUserName).getUuid().toString())
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
                .port(port)
 | 
					                .port(port)
 | 
				
			||||||
            .when()
 | 
					            .when()
 | 
				
			||||||
                .post("http://localhost/api/rbac-grants")
 | 
					                .post("http://localhost/api/rbac-grants")
 | 
				
			||||||
            .then().assertThat()
 | 
					            .then().assertThat()
 | 
				
			||||||
 | 
					                .body("message", containsString("Access to granted role"))
 | 
				
			||||||
 | 
					                .body("message", containsString("forbidden for {package#aaa00.admin}"))
 | 
				
			||||||
                .statusCode(403);
 | 
					                .statusCode(403);
 | 
				
			||||||
            // @formatter:on
 | 
					            // @formatter:on
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // then
 | 
					        // then
 | 
				
			||||||
        assertThat(findAllGrantsOfUser(givenPackageAdmin))
 | 
					        assertThat(findAllGrantsOfUser(givenCurrentUserPackageAdmin))
 | 
				
			||||||
            .extracting(RbacGrantEntity::getUserName)
 | 
					            .extracting(RbacGrantEntity::getGranteeUserName)
 | 
				
			||||||
            .doesNotContain(givenNewUserName);
 | 
					            .doesNotContain(givenNewUserName);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -134,9 +144,9 @@ class RbacGrantControllerAcceptanceTest {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    RbacUserEntity createRBacUser(final String userName) {
 | 
					    RbacUserEntity createRBacUser(final String userName) {
 | 
				
			||||||
        return jpaAttempt.transacted(() -> {
 | 
					        return jpaAttempt.transacted(() ->
 | 
				
			||||||
            return rbacUserRepository.create(new RbacUserEntity(UUID.randomUUID(), userName));
 | 
					            rbacUserRepository.create(new RbacUserEntity(UUID.randomUUID(), userName))
 | 
				
			||||||
        }).returnedValue();
 | 
					        ).returnedValue();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    RbacRoleEntity findRbacRoleByName(final String roleName) {
 | 
					    RbacRoleEntity findRbacRoleByName(final String roleName) {
 | 
				
			||||||
@@ -145,5 +155,4 @@ class RbacGrantControllerAcceptanceTest {
 | 
				
			|||||||
            return rbacRoleRepository.findByRoleName(roleName);
 | 
					            return rbacRoleRepository.findByRoleName(roleName);
 | 
				
			||||||
        }).returnedValue();
 | 
					        }).returnedValue();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,9 @@ package net.hostsharing.hsadminng.rbac.rbacgrant;
 | 
				
			|||||||
import net.hostsharing.hsadminng.Accepts;
 | 
					import net.hostsharing.hsadminng.Accepts;
 | 
				
			||||||
import net.hostsharing.hsadminng.context.Context;
 | 
					import net.hostsharing.hsadminng.context.Context;
 | 
				
			||||||
import net.hostsharing.hsadminng.rbac.rbacrole.RbacRoleRepository;
 | 
					import net.hostsharing.hsadminng.rbac.rbacrole.RbacRoleRepository;
 | 
				
			||||||
 | 
					import net.hostsharing.hsadminng.rbac.rbacuser.RbacUserEntity;
 | 
				
			||||||
import net.hostsharing.hsadminng.rbac.rbacuser.RbacUserRepository;
 | 
					import net.hostsharing.hsadminng.rbac.rbacuser.RbacUserRepository;
 | 
				
			||||||
 | 
					import net.hostsharing.test.JpaAttempt;
 | 
				
			||||||
import org.junit.jupiter.api.Nested;
 | 
					import org.junit.jupiter.api.Nested;
 | 
				
			||||||
import org.junit.jupiter.api.Test;
 | 
					import org.junit.jupiter.api.Test;
 | 
				
			||||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
					import org.springframework.beans.factory.annotation.Autowired;
 | 
				
			||||||
@@ -11,15 +13,18 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
 | 
				
			|||||||
import org.springframework.context.annotation.ComponentScan;
 | 
					import org.springframework.context.annotation.ComponentScan;
 | 
				
			||||||
import org.springframework.orm.jpa.JpaSystemException;
 | 
					import org.springframework.orm.jpa.JpaSystemException;
 | 
				
			||||||
import org.springframework.test.annotation.DirtiesContext;
 | 
					import org.springframework.test.annotation.DirtiesContext;
 | 
				
			||||||
 | 
					import org.springframework.transaction.annotation.Propagation;
 | 
				
			||||||
 | 
					import org.springframework.transaction.annotation.Transactional;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javax.persistence.EntityManager;
 | 
					import javax.persistence.EntityManager;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.UUID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import static net.hostsharing.test.JpaAttempt.attempt;
 | 
					import static net.hostsharing.test.JpaAttempt.attempt;
 | 
				
			||||||
import static org.assertj.core.api.Assertions.assertThat;
 | 
					import static org.assertj.core.api.Assertions.assertThat;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@DataJpaTest
 | 
					@DataJpaTest
 | 
				
			||||||
@ComponentScan(basePackageClasses = { Context.class, RbacGrantRepository.class })
 | 
					@ComponentScan(basePackageClasses = { RbacGrantRepository.class, Context.class, JpaAttempt.class })
 | 
				
			||||||
@DirtiesContext
 | 
					@DirtiesContext
 | 
				
			||||||
@Accepts({ "GRT:S(Schema)" })
 | 
					@Accepts({ "GRT:S(Schema)" })
 | 
				
			||||||
class RbacGrantRepositoryIntegrationTest {
 | 
					class RbacGrantRepositoryIntegrationTest {
 | 
				
			||||||
@@ -39,6 +44,9 @@ class RbacGrantRepositoryIntegrationTest {
 | 
				
			|||||||
    @Autowired
 | 
					    @Autowired
 | 
				
			||||||
    EntityManager em;
 | 
					    EntityManager em;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Autowired
 | 
				
			||||||
 | 
					    JpaAttempt jpaAttempt;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Nested
 | 
					    @Nested
 | 
				
			||||||
    class FindAllRbacGrants {
 | 
					    class FindAllRbacGrants {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -54,7 +62,7 @@ class RbacGrantRepositoryIntegrationTest {
 | 
				
			|||||||
            // then
 | 
					            // then
 | 
				
			||||||
            exactlyTheseRbacGrantsAreReturned(
 | 
					            exactlyTheseRbacGrantsAreReturned(
 | 
				
			||||||
                result,
 | 
					                result,
 | 
				
			||||||
                "grant( aaa00@aaa.example.com -> package#aaa00.admin: managed assumed empowered )");
 | 
					                "{ grant assumed role package#aaa00.admin to user aaa00@aaa.example.com by role customer#aaa.admin }");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @Test
 | 
					        @Test
 | 
				
			||||||
@@ -69,28 +77,26 @@ class RbacGrantRepositoryIntegrationTest {
 | 
				
			|||||||
            // then
 | 
					            // then
 | 
				
			||||||
            exactlyTheseRbacGrantsAreReturned(
 | 
					            exactlyTheseRbacGrantsAreReturned(
 | 
				
			||||||
                result,
 | 
					                result,
 | 
				
			||||||
                "grant( admin@aaa.example.com -> customer#aaa.admin: managed assumed empowered )",
 | 
					                "{ grant assumed role customer#aaa.admin to user admin@aaa.example.com by role global#hostsharing.admin }",
 | 
				
			||||||
                "grant( aaa00@aaa.example.com -> package#aaa00.admin: managed assumed empowered )",
 | 
					                "{ grant assumed role package#aaa00.admin to user aaa00@aaa.example.com by role customer#aaa.admin }",
 | 
				
			||||||
                "grant( aaa01@aaa.example.com -> package#aaa01.admin: managed assumed empowered )",
 | 
					                "{ grant assumed role package#aaa01.admin to user aaa01@aaa.example.com by role customer#aaa.admin }",
 | 
				
			||||||
                "grant( aaa02@aaa.example.com -> package#aaa02.admin: managed assumed empowered )");
 | 
					                "{ grant assumed role package#aaa02.admin to user aaa02@aaa.example.com by role customer#aaa.admin }");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @Test
 | 
					        @Test
 | 
				
			||||||
        @Accepts({ "GRT:L(List)" })
 | 
					        @Accepts({ "GRT:L(List)" })
 | 
				
			||||||
        public void customerAdmin_withAssumedRole_cannotViewRbacGrants() {
 | 
					        public void customerAdmin_withAssumedRole_canOnlyViewRbacGrantsVisibleByAssumedRole() {
 | 
				
			||||||
            // given:
 | 
					            // given:
 | 
				
			||||||
            currentUser("admin@aaa.example.com");
 | 
					            currentUser("admin@aaa.example.com");
 | 
				
			||||||
            assumedRoles("package#aab00.admin");
 | 
					            assumedRoles("package#aaa00.admin");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // when
 | 
					            // when
 | 
				
			||||||
            final var result = attempt(
 | 
					            final var result = rbacGrantRepository.findAll();
 | 
				
			||||||
                em,
 | 
					 | 
				
			||||||
                () -> rbacGrantRepository.findAll());
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // then
 | 
					            // then
 | 
				
			||||||
            result.assertExceptionWithRootCauseMessage(
 | 
					            exactlyTheseRbacGrantsAreReturned(
 | 
				
			||||||
                JpaSystemException.class,
 | 
					                result,
 | 
				
			||||||
                "[403] user admin@aaa.example.com", "has no permission to assume role package#aab00#admin");
 | 
					                "{ grant assumed role package#aaa00.admin to user aaa00@aaa.example.com by role customer#aaa.admin }");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -102,24 +108,72 @@ class RbacGrantRepositoryIntegrationTest {
 | 
				
			|||||||
        public void customerAdmin_canGrantOwnPackageAdminRole_toArbitraryUser() {
 | 
					        public void customerAdmin_canGrantOwnPackageAdminRole_toArbitraryUser() {
 | 
				
			||||||
            // given
 | 
					            // given
 | 
				
			||||||
            currentUser("admin@aaa.example.com");
 | 
					            currentUser("admin@aaa.example.com");
 | 
				
			||||||
            final var userUuid = rbacUserRepository.findUuidByName("aac00@aac.example.com");
 | 
					            assumedRoles("customer#aaa.admin");
 | 
				
			||||||
            final var roleUuid = rbacRoleRepository.findByRoleName("package#aaa00.admin").getUuid();
 | 
					            final var givenArbitraryUserUuid = rbacUserRepository.findUuidByName("aac00@aac.example.com");
 | 
				
			||||||
 | 
					            final var givenOwnPackageRoleUuid = rbacRoleRepository.findByRoleName("package#aaa00.admin").getUuid();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // when
 | 
					            // when
 | 
				
			||||||
            final var grant = RbacGrantEntity.builder()
 | 
					            final var grant = RbacGrantEntity.builder()
 | 
				
			||||||
                .userUuid(userUuid).roleUuid(roleUuid)
 | 
					                .granteeUserUuid(givenArbitraryUserUuid).grantedRoleUuid(givenOwnPackageRoleUuid)
 | 
				
			||||||
                .assumed(true).empowered(false)
 | 
					                .assumed(true)
 | 
				
			||||||
                .build();
 | 
					                .build();
 | 
				
			||||||
            final var attempt = attempt(em, () ->
 | 
					            final var attempt = attempt(em, () ->
 | 
				
			||||||
                rbacGrantRepository.save(grant)
 | 
					                rbacGrantRepository.save(grant)
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // then
 | 
					            // then
 | 
				
			||||||
            assertThat(attempt.wasSuccessful()).isTrue();
 | 
					            assertThat(attempt.caughtException()).isNull();
 | 
				
			||||||
            assertThat(rbacGrantRepository.findAll())
 | 
					            assertThat(rbacGrantRepository.findAll())
 | 
				
			||||||
                .extracting(RbacGrantEntity::toDisplay)
 | 
					                .extracting(RbacGrantEntity::toDisplay)
 | 
				
			||||||
                .contains("grant( aac00@aac.example.com -> package#aaa00.admin: assumed )");
 | 
					                .contains("{ grant assumed role package#aaa00.admin to user aac00@aac.example.com by role customer#aaa.admin }");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @Test
 | 
				
			||||||
 | 
					        @Accepts({ "GRT:C(Create)" })
 | 
				
			||||||
 | 
					        @Transactional(propagation = Propagation.NEVER)
 | 
				
			||||||
 | 
					        public void packageAdmin_canNotGrantPackageOwnerRole() {
 | 
				
			||||||
 | 
					            // given
 | 
				
			||||||
 | 
					            record Given(RbacUserEntity arbitraryUser, UUID packageOwnerRoleUuid) {}
 | 
				
			||||||
 | 
					            final var given = jpaAttempt.transacted(() -> {
 | 
				
			||||||
 | 
					                // to find the uuids of we need to have access rights to these
 | 
				
			||||||
 | 
					                currentUser("admin@aaa.example.com");
 | 
				
			||||||
 | 
					                return new Given(
 | 
				
			||||||
 | 
					                    createNewUser(), // eigene Transaktion?
 | 
				
			||||||
 | 
					                    rbacRoleRepository.findByRoleName("package#aaa00.owner").getUuid()
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            }).returnedValue();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // when
 | 
				
			||||||
 | 
					            final var attempt = jpaAttempt.transacted(() -> {
 | 
				
			||||||
 | 
					                // now we try to use these uuids as a less privileged user
 | 
				
			||||||
 | 
					                currentUser("aaa00@aaa.example.com");
 | 
				
			||||||
 | 
					                assumedRoles("package#aaa00.admin");
 | 
				
			||||||
 | 
					                final var grant = RbacGrantEntity.builder()
 | 
				
			||||||
 | 
					                    .granteeUserUuid(given.arbitraryUser.getUuid())
 | 
				
			||||||
 | 
					                    .grantedRoleUuid(given.packageOwnerRoleUuid)
 | 
				
			||||||
 | 
					                    .assumed(true)
 | 
				
			||||||
 | 
					                    .build();
 | 
				
			||||||
 | 
					                rbacGrantRepository.save(grant);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // then
 | 
				
			||||||
 | 
					            attempt.assertExceptionWithRootCauseMessage(
 | 
				
			||||||
 | 
					                JpaSystemException.class,
 | 
				
			||||||
 | 
					                "ERROR: [403] Access to granted role " + given.packageOwnerRoleUuid
 | 
				
			||||||
 | 
					                    + " forbidden for {package#aaa00.admin}");
 | 
				
			||||||
 | 
					            jpaAttempt.transacted(() -> {
 | 
				
			||||||
 | 
					                currentUser(given.arbitraryUser.getName());
 | 
				
			||||||
 | 
					                assertThat(rbacGrantRepository.findAll())
 | 
				
			||||||
 | 
					                    .extracting(RbacGrantEntity::toDisplay)
 | 
				
			||||||
 | 
					                    .hasSize(0);
 | 
				
			||||||
 | 
					                // "{ grant assumed role package#aaa00.admin to user aac00@aac.example.com by role customer#aaa.admin }");
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private RbacUserEntity createNewUser() {
 | 
				
			||||||
 | 
					        return rbacUserRepository.create(
 | 
				
			||||||
 | 
					            new RbacUserEntity(null, "test-user-" + System.currentTimeMillis() + "@example.com"));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void currentUser(final String currentUser) {
 | 
					    void currentUser(final String currentUser) {
 | 
				
			||||||
@@ -134,7 +188,7 @@ class RbacGrantRepositoryIntegrationTest {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    void exactlyTheseRbacGrantsAreReturned(final List<RbacGrantEntity> actualResult, final String... expectedGrant) {
 | 
					    void exactlyTheseRbacGrantsAreReturned(final List<RbacGrantEntity> actualResult, final String... expectedGrant) {
 | 
				
			||||||
        assertThat(actualResult)
 | 
					        assertThat(actualResult)
 | 
				
			||||||
            .filteredOn(g -> !g.getUserName().startsWith("test-user-")) // ignore test-users created by other tests
 | 
					            .filteredOn(g -> !g.getGranteeUserName().startsWith("test-user-")) // ignore test-users created by other tests
 | 
				
			||||||
            .extracting(RbacGrantEntity::toDisplay)
 | 
					            .extracting(RbacGrantEntity::toDisplay)
 | 
				
			||||||
            .containsExactlyInAnyOrder(expectedGrant);
 | 
					            .containsExactlyInAnyOrder(expectedGrant);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,7 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
 | 
				
			|||||||
import org.springframework.context.annotation.ComponentScan;
 | 
					import org.springframework.context.annotation.ComponentScan;
 | 
				
			||||||
import org.springframework.orm.jpa.JpaSystemException;
 | 
					import org.springframework.orm.jpa.JpaSystemException;
 | 
				
			||||||
import org.springframework.test.annotation.Commit;
 | 
					import org.springframework.test.annotation.Commit;
 | 
				
			||||||
 | 
					import org.springframework.test.annotation.DirtiesContext;
 | 
				
			||||||
import org.springframework.transaction.annotation.Propagation;
 | 
					import org.springframework.transaction.annotation.Propagation;
 | 
				
			||||||
import org.springframework.transaction.annotation.Transactional;
 | 
					import org.springframework.transaction.annotation.Transactional;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -22,6 +23,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@DataJpaTest
 | 
					@DataJpaTest
 | 
				
			||||||
@ComponentScan(basePackageClasses = { RbacUserRepository.class, Context.class, JpaAttempt.class })
 | 
					@ComponentScan(basePackageClasses = { RbacUserRepository.class, Context.class, JpaAttempt.class })
 | 
				
			||||||
 | 
					@DirtiesContext
 | 
				
			||||||
class RbacUserRepositoryIntegrationTest {
 | 
					class RbacUserRepositoryIntegrationTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Autowired
 | 
					    @Autowired
 | 
				
			||||||
@@ -58,7 +60,7 @@ class RbacUserRepositoryIntegrationTest {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        @Test
 | 
					        @Test
 | 
				
			||||||
        @Commit
 | 
					        @Commit
 | 
				
			||||||
        @Transactional(propagation = Propagation.NOT_SUPPORTED)
 | 
					        @Transactional(propagation = Propagation.NEVER)
 | 
				
			||||||
        void anyoneCanCreateTheirOwnUser_committed() {
 | 
					        void anyoneCanCreateTheirOwnUser_committed() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // given:
 | 
					            // given:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,8 +4,7 @@ import junit.framework.AssertionFailedError;
 | 
				
			|||||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
					import org.springframework.beans.factory.annotation.Autowired;
 | 
				
			||||||
import org.springframework.core.NestedExceptionUtils;
 | 
					import org.springframework.core.NestedExceptionUtils;
 | 
				
			||||||
import org.springframework.stereotype.Service;
 | 
					import org.springframework.stereotype.Service;
 | 
				
			||||||
import org.springframework.transaction.annotation.Propagation;
 | 
					import org.springframework.transaction.support.TransactionTemplate;
 | 
				
			||||||
import org.springframework.transaction.annotation.Transactional;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javax.persistence.EntityManager;
 | 
					import javax.persistence.EntityManager;
 | 
				
			||||||
import java.util.Optional;
 | 
					import java.util.Optional;
 | 
				
			||||||
@@ -32,20 +31,16 @@ import static org.assertj.core.api.Assertions.assertThat;
 | 
				
			|||||||
public class JpaAttempt {
 | 
					public class JpaAttempt {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Autowired
 | 
					    @Autowired
 | 
				
			||||||
    private final EntityManager em;
 | 
					    private TransactionTemplate transactionTemplate;
 | 
				
			||||||
 | 
					 | 
				
			||||||
    public JpaAttempt(final EntityManager em) {
 | 
					 | 
				
			||||||
        this.em = em;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static <T> JpaResult<T> attempt(final EntityManager em, final Supplier<T> code) {
 | 
					    public static <T> JpaResult<T> attempt(final EntityManager em, final Supplier<T> code) {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            final var result = new JpaResult<T>(code.get(), null);
 | 
					            final var result = JpaResult.forValue(code.get());
 | 
				
			||||||
            em.flush();
 | 
					            em.flush();
 | 
				
			||||||
            em.clear();
 | 
					            em.clear();
 | 
				
			||||||
            return result;
 | 
					            return result;
 | 
				
			||||||
        } catch (RuntimeException exc) {
 | 
					        } catch (final RuntimeException exc) {
 | 
				
			||||||
            return new JpaResult<T>(null, exc);
 | 
					            return JpaResult.forException(exc);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -56,29 +51,50 @@ public class JpaAttempt {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Transactional(propagation = Propagation.REQUIRES_NEW)
 | 
					 | 
				
			||||||
    public <T> JpaResult<T> transacted(final Supplier<T> code) {
 | 
					    public <T> JpaResult<T> transacted(final Supplier<T> code) {
 | 
				
			||||||
        return attempt(em, code);
 | 
					        try {
 | 
				
			||||||
 | 
					            return JpaResult.forValue(
 | 
				
			||||||
 | 
					                transactionTemplate.execute(transactionStatus -> code.get()));
 | 
				
			||||||
 | 
					        } catch (final RuntimeException exc) {
 | 
				
			||||||
 | 
					            return JpaResult.forException(exc);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Transactional(propagation = Propagation.REQUIRES_NEW)
 | 
					    public JpaResult<Void> transacted(final Runnable code) {
 | 
				
			||||||
    public void transacted(final Runnable code) {
 | 
					        try {
 | 
				
			||||||
        attempt(em, () -> {
 | 
					            transactionTemplate.execute(transactionStatus -> {
 | 
				
			||||||
                code.run();
 | 
					                code.run();
 | 
				
			||||||
                return null;
 | 
					                return null;
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					            return JpaResult.forVoidValue();
 | 
				
			||||||
 | 
					        } catch (final RuntimeException exc) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return new JpaResult<>(null, exc);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static class JpaResult<T> {
 | 
					    public static class JpaResult<T> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        final T result;
 | 
					        private final T result;
 | 
				
			||||||
        final RuntimeException exception;
 | 
					        private final RuntimeException exception;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public JpaResult(final T result, final RuntimeException exception) {
 | 
					        private JpaResult(final T result, final RuntimeException exception) {
 | 
				
			||||||
            this.result = result;
 | 
					            this.result = result;
 | 
				
			||||||
            this.exception = exception;
 | 
					            this.exception = exception;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        static JpaResult<Void> forVoidValue() {
 | 
				
			||||||
 | 
					            return new JpaResult<>(null, null);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static <T> JpaResult<T> forValue(final T value) {
 | 
				
			||||||
 | 
					            return new JpaResult<>(value, null);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static <T> JpaResult<T> forException(final RuntimeException exception) {
 | 
				
			||||||
 | 
					            return new JpaResult<>(null, exception);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public boolean wasSuccessful() {
 | 
					        public boolean wasSuccessful() {
 | 
				
			||||||
            return exception == null;
 | 
					            return exception == null;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user