implements user granting roles to other users
This commit is contained in:
parent
7869d07d30
commit
c8e835f880
@ -56,7 +56,7 @@ public class RbacGrantController implements RbacgrantsApi {
|
||||
final var uri =
|
||||
MvcUriComponentsBuilder.fromController(getClass())
|
||||
.path("/api/rbac-grants/{roleUuid}")
|
||||
.buildAndExpand(body.getRoleUuid())
|
||||
.buildAndExpand(body.getGrantedRoleUuid())
|
||||
.toUri();
|
||||
return ResponseEntity.created(uri).build();
|
||||
}
|
||||
|
@ -18,24 +18,27 @@ import java.util.UUID;
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
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)
|
||||
private String userName;
|
||||
private String granteeUserName;
|
||||
|
||||
@Column(name = "roleidname", updatable = false, insertable = false)
|
||||
private String roleIdName;
|
||||
|
||||
private boolean managed;
|
||||
private boolean assumed;
|
||||
private boolean empowered;
|
||||
|
||||
@Column(name = "grantedbyroleuuid", updatable = false, insertable = false)
|
||||
private UUID grantedByRoleUuid;
|
||||
|
||||
@Id
|
||||
@Column(name = "grantedroleuuid")
|
||||
private UUID grantedRoleUuid;
|
||||
|
||||
@Id
|
||||
@Column(name = "useruuid")
|
||||
private UUID userUuid;
|
||||
|
||||
@Id
|
||||
@Column(name = "roleuuid")
|
||||
private UUID roleUuid;
|
||||
private UUID granteeUserUuid;
|
||||
|
||||
@Column(name = "objecttable", updatable = false, insertable = false)
|
||||
private String objectTable;
|
||||
@ -46,15 +49,12 @@ public class RbacGrantEntity {
|
||||
@Column(name = "objectidname", updatable = false, insertable = false)
|
||||
private String objectIdName;
|
||||
|
||||
@Column(name = "roletype", updatable = false, insertable = false)
|
||||
@Column(name = "grantedroletype", updatable = false, insertable = false)
|
||||
@Enumerated(EnumType.STRING)
|
||||
private RbacRoleType roleType;
|
||||
private RbacRoleType grantedRoleType;
|
||||
|
||||
public String toDisplay() {
|
||||
return "grant( " + userName + " -> " + roleIdName + ": " +
|
||||
(managed ? "managed " : "") +
|
||||
(assumed ? "assumed " : "") +
|
||||
(empowered ? "empowered " : "") +
|
||||
")";
|
||||
return "{ grant " + (assumed ? "assumed " : "") +
|
||||
"role " + grantedRoleIdName + " to user " + granteeUserName + " by role " + grantedByRoleIdName + " }";
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,6 @@ import java.util.UUID;
|
||||
@NoArgsConstructor
|
||||
public class RbacGrantId implements Serializable {
|
||||
|
||||
private UUID userUuid;
|
||||
private UUID roleUuid;
|
||||
private UUID granteeUserUuid;
|
||||
private UUID grantedRoleUuid;
|
||||
}
|
||||
|
@ -6,16 +6,14 @@ components:
|
||||
RbacGrant:
|
||||
type: object
|
||||
properties:
|
||||
userUuid:
|
||||
type: string
|
||||
format: uuid
|
||||
roleUuid:
|
||||
type: string
|
||||
format: uuid
|
||||
assumed:
|
||||
type: boolean
|
||||
empowered:
|
||||
type: boolean
|
||||
grantedRoleUuid:
|
||||
type: string
|
||||
format: uuid
|
||||
granteeUserUuid:
|
||||
type: string
|
||||
format: uuid
|
||||
required:
|
||||
- userUuid
|
||||
- roleUuid
|
||||
- grantedRoleUuid
|
||||
- granteeUserUuid
|
||||
|
@ -353,11 +353,10 @@ $$;
|
||||
*/
|
||||
create table RbacGrants
|
||||
(
|
||||
grantedByRoleUuid uuid references RbacRole (uuid) on delete cascade,
|
||||
ascendantUuid 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)
|
||||
empowered boolean not null default false, -- true: allows grant+revoke for descendant role
|
||||
primary key (ascendantUuid, descendantUuid)
|
||||
);
|
||||
create index on RbacGrants (ascendantUuid);
|
||||
@ -463,8 +462,8 @@ begin
|
||||
perform assertReferenceType('permissionId (descendant)', permissionIds[i], 'RbacPermission');
|
||||
|
||||
insert
|
||||
into RbacGrants (ascendantUuid, descendantUuid, managed, assumed, empowered)
|
||||
values (roleUuid, permissionIds[i], true, true, false)
|
||||
into RbacGrants (ascendantUuid, descendantUuid, assumed)
|
||||
values (roleUuid, permissionIds[i], true)
|
||||
on conflict do nothing; -- allow granting multiple times
|
||||
end loop;
|
||||
end;
|
||||
@ -476,13 +475,13 @@ begin
|
||||
perform assertReferenceType('superRoleId (ascendant)', superRoleId, '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;
|
||||
end if;
|
||||
|
||||
insert
|
||||
into RbacGrants (ascendantUuid, descendantUuid, managed, assumed, empowered)
|
||||
values (superRoleId, subRoleId, true, doAssume, false)
|
||||
into RbacGrants (ascendantuuid, descendantUuid, assumed)
|
||||
values (superRoleId, subRoleId, doAssume)
|
||||
on conflict do nothing; -- allow granting multiple times
|
||||
end; $$;
|
||||
|
||||
@ -497,48 +496,6 @@ begin
|
||||
end if;
|
||||
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:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
@ -23,6 +23,7 @@ begin
|
||||
if (currentUser is null or currentUser = '') then
|
||||
raise exception '[401] hsadminng.currentUser must be defined, please use "SET LOCAL ...;"';
|
||||
end if;
|
||||
raise debug 'currentUser: %', currentUser;
|
||||
return currentUser;
|
||||
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;
|
||||
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
|
||||
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 (
|
||||
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
|
||||
from rbacgrants as g
|
||||
join rbacrole as r on r.uuid = g.descendantUuid
|
||||
join rbacobject o on o.uuid = r.objectuuid
|
||||
join rbacuser u on u.uuid = g.ascendantuuid
|
||||
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
|
||||
order by roleIdName;
|
||||
grant all privileges on rbacrole_rv to restricted;
|
||||
--//
|
||||
|
||||
@ -67,15 +69,10 @@ create or replace function insertRbacGrant()
|
||||
declare
|
||||
newGrant RbacGrants_RV;
|
||||
begin
|
||||
if new.managed then
|
||||
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));
|
||||
call grantRoleToUser(assumedRoleUuid(), new.grantedRoleUuid, new.userUuid, new.assumed);
|
||||
select 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;
|
||||
return newGrant;
|
||||
end; $$;
|
||||
@ -88,6 +85,33 @@ create trigger insertRbacGrant_Trigger
|
||||
on RbacGrants_rv
|
||||
for each row
|
||||
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;
|
||||
-- @formatter:on
|
||||
end; $$;
|
||||
--//
|
@ -26,7 +26,7 @@ create or replace function withoutPermissions()
|
||||
language plpgsql
|
||||
strict as $$
|
||||
begin
|
||||
return row (array[]::uuid[]);
|
||||
return row (array []::uuid[]);
|
||||
end; $$;
|
||||
|
||||
--//
|
||||
@ -167,7 +167,8 @@ create or replace function createRole(
|
||||
permissions RbacPermissions,
|
||||
superRoles RbacSuperRoles,
|
||||
subRoles RbacSubRoles = null,
|
||||
users RbacUsers = null
|
||||
users RbacUsers = null,
|
||||
grantingRoleUuid uuid = null
|
||||
)
|
||||
returns uuid
|
||||
called on null input
|
||||
@ -200,7 +201,7 @@ begin
|
||||
if users is not null then
|
||||
foreach userUuid in array users.useruUids
|
||||
loop
|
||||
call grantRoleToUser(roleUuid, userUuid);
|
||||
call grantRoleToUserUnchecked(grantingRoleUuid, roleUuid, userUuid);
|
||||
end loop;
|
||||
end if;
|
||||
|
||||
@ -210,26 +211,47 @@ end; $$;
|
||||
create or replace function createRole(
|
||||
roleDescriptor RbacRoleDescriptor,
|
||||
permissions RbacPermissions,
|
||||
users RbacUsers = null
|
||||
users RbacUsers = null,
|
||||
grantingRoleUuid uuid = null
|
||||
)
|
||||
returns uuid
|
||||
called on null input
|
||||
language plpgsql as $$
|
||||
begin
|
||||
return createRole(roleDescriptor, permissions, null, null, users);
|
||||
return createRole(roleDescriptor, permissions, null, null, users, grantingRoleUuid);
|
||||
end; $$;
|
||||
|
||||
create or replace function createRole(
|
||||
roleDescriptor RbacRoleDescriptor,
|
||||
permissions RbacPermissions,
|
||||
subRoles RbacSubRoles,
|
||||
users RbacUsers = null
|
||||
users RbacUsers = null,
|
||||
grantingRoleUuid uuid = null
|
||||
)
|
||||
returns uuid
|
||||
called on null input
|
||||
language plpgsql as $$
|
||||
begin
|
||||
return createRole(roleDescriptor, permissions, null, subRoles, users);
|
||||
return createRole(roleDescriptor, permissions, null, subRoles, users, grantingRoleUuid);
|
||||
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 ;
|
||||
begin
|
||||
admins = findRoleId(hostsharingAdmin());
|
||||
call grantRoleToUser(admins, createRbacUser('mike@hostsharing.net'));
|
||||
call grantRoleToUser(admins, createRbacUser('sven@hostsharing.net'));
|
||||
call grantRoleToUserUnchecked(admins, admins, createRbacUser('mike@hostsharing.net'));
|
||||
call grantRoleToUserUnchecked(admins, admins, createRbacUser('sven@hostsharing.net'));
|
||||
end;
|
||||
$$;
|
||||
--//
|
||||
|
@ -77,7 +77,8 @@ begin
|
||||
customerAdmin(NEW),
|
||||
grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['view', 'add-package']),
|
||||
-- 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
|
||||
|
@ -37,8 +37,8 @@ begin
|
||||
loop
|
||||
currentTask = 'creating RBAC test customer #' || t;
|
||||
set local hsadminng.currentUser to 'mike@hostsharing.net';
|
||||
set local hsadminng.assumedRoles = '';
|
||||
set local hsadminng.currentTask to currentTask;
|
||||
set local hsadminng.assumedRoles to 'global#hostsharing.admin';
|
||||
execute format('set local hsadminng.currentTask to %L', currentTask);
|
||||
|
||||
-- When a new customer is created,
|
||||
custReference = testCustomerReference(t);
|
||||
|
@ -11,30 +11,32 @@ create or replace procedure createPackageTestData(
|
||||
doCommitAfterEach boolean -- only for mass data creation outside of Liquibase
|
||||
)
|
||||
language plpgsql as $$
|
||||
declare
|
||||
declare
|
||||
cust customer;
|
||||
custAdminUser varchar;
|
||||
custAdminRole varchar;
|
||||
pacName varchar;
|
||||
currentTask varchar;
|
||||
custAdmin varchar;
|
||||
pac package;
|
||||
begin
|
||||
begin
|
||||
set hsadminng.currentUser to '';
|
||||
|
||||
for cust in (select * from customer)
|
||||
loop
|
||||
CONTINUE WHEN cust.reference < minCustomerReference;
|
||||
continue when cust.reference < minCustomerReference;
|
||||
|
||||
for t in 0..2
|
||||
loop
|
||||
pacName = cust.prefix || to_char(t, 'fm00');
|
||||
currentTask = 'creating RBAC test package #' || pacName || ' for customer ' || cust.prefix || ' #' ||
|
||||
cust.uuid;
|
||||
raise notice 'task: %', currentTask;
|
||||
|
||||
custAdmin = 'admin@' || cust.prefix || '.example.com';
|
||||
set local hsadminng.currentUser to custAdmin;
|
||||
set local hsadminng.assumedRoles = '';
|
||||
set local hsadminng.currentTask to currentTask;
|
||||
custAdminUser = 'admin@' || cust.prefix || '.example.com';
|
||||
custAdminRole = 'customer#' || cust.prefix || '.admin';
|
||||
execute format('set local hsadminng.currentUser to %L', custAdminUser);
|
||||
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
|
||||
into package (customerUuid, name, description)
|
||||
@ -42,8 +44,10 @@ create or replace procedure createPackageTestData(
|
||||
returning * into pac;
|
||||
|
||||
call grantRoleToUser(
|
||||
getRoleId(customerAdmin(cust), 'fail'),
|
||||
findRoleId(packageAdmin(pac)),
|
||||
createRbacUser(pacName || '@' || cust.prefix || '.example.com'));
|
||||
createRbacUser(pacName || '@' || cust.prefix || '.example.com'),
|
||||
true);
|
||||
|
||||
end loop;
|
||||
end loop;
|
||||
@ -51,7 +55,7 @@ create or replace procedure createPackageTestData(
|
||||
if doCommitAfterEach then
|
||||
commit;
|
||||
end if;
|
||||
end;
|
||||
end ;
|
||||
$$;
|
||||
--//
|
||||
|
||||
|
@ -12,7 +12,9 @@ databaseChangeLog:
|
||||
- include:
|
||||
file: db/changelog/2022-07-28-006-rbac-current.sql
|
||||
- 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:
|
||||
file: db/changelog/2022-07-28-020-rbac-role-builder.sql
|
||||
- include:
|
||||
|
@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
|
||||
import javax.transaction.Transactional;
|
||||
|
||||
@ -11,6 +12,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DataJpaTest
|
||||
@ComponentScan(basePackageClasses = Context.class)
|
||||
@DirtiesContext
|
||||
class ContextIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
|
@ -7,6 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.orm.jpa.JpaSystemException;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceException;
|
||||
@ -19,6 +20,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DataJpaTest
|
||||
@ComponentScan(basePackageClasses = { Context.class, CustomerRepository.class })
|
||||
@DirtiesContext
|
||||
class CustomerRepositoryIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
|
@ -8,6 +8,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.orm.jpa.JpaSystemException;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.transaction.Transactional;
|
||||
@ -18,6 +19,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DataJpaTest
|
||||
@ComponentScan(basePackageClasses = { Context.class, CustomerRepository.class })
|
||||
@DirtiesContext
|
||||
class PackageRepositoryIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
|
@ -15,19 +15,22 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
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 java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
|
||||
@SpringBootTest(
|
||||
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||
classes = { HsadminNgApplication.class, JpaAttempt.class }
|
||||
)
|
||||
@Accepts({ "ROL:S(Schema)" })
|
||||
@Transactional(propagation = Propagation.NEVER)
|
||||
class RbacGrantControllerAcceptanceTest {
|
||||
|
||||
@LocalServerPort
|
||||
@ -57,23 +60,25 @@ class RbacGrantControllerAcceptanceTest {
|
||||
|
||||
// given
|
||||
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";
|
||||
|
||||
// when
|
||||
RestAssured // @formatter:off
|
||||
.given()
|
||||
.header("current-user", givenPackageAdmin)
|
||||
.header("current-user", givenCurrentUserPackageAdmin)
|
||||
.header("assumed-roles", givenAssumedRole)
|
||||
.contentType(ContentType.JSON)
|
||||
.body("""
|
||||
{
|
||||
"userUuid": "%s",
|
||||
"roleUuid": "%s",
|
||||
"assumed": true,
|
||||
"empowered": false
|
||||
"grantedRoleUuid": "%s",
|
||||
"granteeUserUuid": "%s"
|
||||
}
|
||||
""".formatted(
|
||||
createRBacUser(givenNewUserName).getUuid().toString(),
|
||||
findRbacRoleByName(givenOwnPackageAdminRole).getUuid().toString())
|
||||
findRbacRoleByName(givenOwnPackageAdminRole).getUuid().toString(),
|
||||
createRBacUser(givenNewUserName).getUuid().toString())
|
||||
)
|
||||
.port(port)
|
||||
.when()
|
||||
@ -83,9 +88,11 @@ class RbacGrantControllerAcceptanceTest {
|
||||
// @formatter:on
|
||||
|
||||
// then
|
||||
assertThat(findAllGrantsOfUser(givenPackageAdmin))
|
||||
assertThat(findAllGrantsOfUser(givenCurrentUserPackageAdmin))
|
||||
.extracting(RbacGrantEntity::toDisplay)
|
||||
.contains("grant( " + givenNewUserName + " -> " + givenOwnPackageAdminRole + ": assumed )");
|
||||
.contains("{ grant assumed role " + givenOwnPackageAdminRole +
|
||||
" to user " + givenNewUserName +
|
||||
" by role " + givenAssumedRole + " }");
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -94,35 +101,38 @@ class RbacGrantControllerAcceptanceTest {
|
||||
|
||||
// given
|
||||
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";
|
||||
|
||||
// when
|
||||
RestAssured // @formatter:off
|
||||
.given()
|
||||
.header("current-user", givenPackageAdmin)
|
||||
.header("current-user", givenCurrentUserPackageAdmin)
|
||||
.header("assumed-roles", givenAssumedRole)
|
||||
.contentType(ContentType.JSON)
|
||||
.body("""
|
||||
{
|
||||
"userUuid": "%s",
|
||||
"roleUuid": "%s",
|
||||
"assumed": true,
|
||||
"empowered": false
|
||||
"grantedRoleUuid": "%s",
|
||||
"granteeUserUuid": "%s"
|
||||
}
|
||||
""".formatted(
|
||||
createRBacUser(givenNewUserName).getUuid().toString(),
|
||||
findRbacRoleByName(givenAlienPackageAdminRole).getUuid().toString())
|
||||
findRbacRoleByName(givenAlienPackageAdminRole).getUuid().toString(),
|
||||
createRBacUser(givenNewUserName).getUuid().toString())
|
||||
)
|
||||
.port(port)
|
||||
.when()
|
||||
.post("http://localhost/api/rbac-grants")
|
||||
.then().assertThat()
|
||||
.body("message", containsString("Access to granted role"))
|
||||
.body("message", containsString("forbidden for {package#aaa00.admin}"))
|
||||
.statusCode(403);
|
||||
// @formatter:on
|
||||
|
||||
// then
|
||||
assertThat(findAllGrantsOfUser(givenPackageAdmin))
|
||||
.extracting(RbacGrantEntity::getUserName)
|
||||
assertThat(findAllGrantsOfUser(givenCurrentUserPackageAdmin))
|
||||
.extracting(RbacGrantEntity::getGranteeUserName)
|
||||
.doesNotContain(givenNewUserName);
|
||||
}
|
||||
|
||||
@ -134,9 +144,9 @@ class RbacGrantControllerAcceptanceTest {
|
||||
}
|
||||
|
||||
RbacUserEntity createRBacUser(final String userName) {
|
||||
return jpaAttempt.transacted(() -> {
|
||||
return rbacUserRepository.create(new RbacUserEntity(UUID.randomUUID(), userName));
|
||||
}).returnedValue();
|
||||
return jpaAttempt.transacted(() ->
|
||||
rbacUserRepository.create(new RbacUserEntity(UUID.randomUUID(), userName))
|
||||
).returnedValue();
|
||||
}
|
||||
|
||||
RbacRoleEntity findRbacRoleByName(final String roleName) {
|
||||
@ -145,5 +155,4 @@ class RbacGrantControllerAcceptanceTest {
|
||||
return rbacRoleRepository.findByRoleName(roleName);
|
||||
}).returnedValue();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,7 +3,9 @@ package net.hostsharing.hsadminng.rbac.rbacgrant;
|
||||
import net.hostsharing.hsadminng.Accepts;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.rbac.rbacrole.RbacRoleRepository;
|
||||
import net.hostsharing.hsadminng.rbac.rbacuser.RbacUserEntity;
|
||||
import net.hostsharing.hsadminng.rbac.rbacuser.RbacUserRepository;
|
||||
import net.hostsharing.test.JpaAttempt;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
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.orm.jpa.JpaSystemException;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static net.hostsharing.test.JpaAttempt.attempt;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DataJpaTest
|
||||
@ComponentScan(basePackageClasses = { Context.class, RbacGrantRepository.class })
|
||||
@ComponentScan(basePackageClasses = { RbacGrantRepository.class, Context.class, JpaAttempt.class })
|
||||
@DirtiesContext
|
||||
@Accepts({ "GRT:S(Schema)" })
|
||||
class RbacGrantRepositoryIntegrationTest {
|
||||
@ -39,6 +44,9 @@ class RbacGrantRepositoryIntegrationTest {
|
||||
@Autowired
|
||||
EntityManager em;
|
||||
|
||||
@Autowired
|
||||
JpaAttempt jpaAttempt;
|
||||
|
||||
@Nested
|
||||
class FindAllRbacGrants {
|
||||
|
||||
@ -54,7 +62,7 @@ class RbacGrantRepositoryIntegrationTest {
|
||||
// then
|
||||
exactlyTheseRbacGrantsAreReturned(
|
||||
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
|
||||
@ -69,28 +77,26 @@ class RbacGrantRepositoryIntegrationTest {
|
||||
// then
|
||||
exactlyTheseRbacGrantsAreReturned(
|
||||
result,
|
||||
"grant( admin@aaa.example.com -> customer#aaa.admin: managed assumed empowered )",
|
||||
"grant( aaa00@aaa.example.com -> package#aaa00.admin: managed assumed empowered )",
|
||||
"grant( aaa01@aaa.example.com -> package#aaa01.admin: managed assumed empowered )",
|
||||
"grant( aaa02@aaa.example.com -> package#aaa02.admin: managed assumed empowered )");
|
||||
"{ grant assumed role customer#aaa.admin to user admin@aaa.example.com by role global#hostsharing.admin }",
|
||||
"{ grant assumed role package#aaa00.admin to user aaa00@aaa.example.com by role customer#aaa.admin }",
|
||||
"{ grant assumed role package#aaa01.admin to user aaa01@aaa.example.com by role customer#aaa.admin }",
|
||||
"{ grant assumed role package#aaa02.admin to user aaa02@aaa.example.com by role customer#aaa.admin }");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Accepts({ "GRT:L(List)" })
|
||||
public void customerAdmin_withAssumedRole_cannotViewRbacGrants() {
|
||||
public void customerAdmin_withAssumedRole_canOnlyViewRbacGrantsVisibleByAssumedRole() {
|
||||
// given:
|
||||
currentUser("admin@aaa.example.com");
|
||||
assumedRoles("package#aab00.admin");
|
||||
assumedRoles("package#aaa00.admin");
|
||||
|
||||
// when
|
||||
final var result = attempt(
|
||||
em,
|
||||
() -> rbacGrantRepository.findAll());
|
||||
final var result = rbacGrantRepository.findAll();
|
||||
|
||||
// then
|
||||
result.assertExceptionWithRootCauseMessage(
|
||||
JpaSystemException.class,
|
||||
"[403] user admin@aaa.example.com", "has no permission to assume role package#aab00#admin");
|
||||
exactlyTheseRbacGrantsAreReturned(
|
||||
result,
|
||||
"{ 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() {
|
||||
// given
|
||||
currentUser("admin@aaa.example.com");
|
||||
final var userUuid = rbacUserRepository.findUuidByName("aac00@aac.example.com");
|
||||
final var roleUuid = rbacRoleRepository.findByRoleName("package#aaa00.admin").getUuid();
|
||||
assumedRoles("customer#aaa.admin");
|
||||
final var givenArbitraryUserUuid = rbacUserRepository.findUuidByName("aac00@aac.example.com");
|
||||
final var givenOwnPackageRoleUuid = rbacRoleRepository.findByRoleName("package#aaa00.admin").getUuid();
|
||||
|
||||
// when
|
||||
final var grant = RbacGrantEntity.builder()
|
||||
.userUuid(userUuid).roleUuid(roleUuid)
|
||||
.assumed(true).empowered(false)
|
||||
.granteeUserUuid(givenArbitraryUserUuid).grantedRoleUuid(givenOwnPackageRoleUuid)
|
||||
.assumed(true)
|
||||
.build();
|
||||
final var attempt = attempt(em, () ->
|
||||
rbacGrantRepository.save(grant)
|
||||
);
|
||||
|
||||
// then
|
||||
assertThat(attempt.wasSuccessful()).isTrue();
|
||||
assertThat(attempt.caughtException()).isNull();
|
||||
assertThat(rbacGrantRepository.findAll())
|
||||
.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) {
|
||||
@ -134,7 +188,7 @@ class RbacGrantRepositoryIntegrationTest {
|
||||
|
||||
void exactlyTheseRbacGrantsAreReturned(final List<RbacGrantEntity> actualResult, final String... expectedGrant) {
|
||||
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)
|
||||
.containsExactlyInAnyOrder(expectedGrant);
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.orm.jpa.JpaSystemException;
|
||||
import org.springframework.test.annotation.Commit;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@ -22,6 +23,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DataJpaTest
|
||||
@ComponentScan(basePackageClasses = { RbacUserRepository.class, Context.class, JpaAttempt.class })
|
||||
@DirtiesContext
|
||||
class RbacUserRepositoryIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
@ -58,7 +60,7 @@ class RbacUserRepositoryIntegrationTest {
|
||||
|
||||
@Test
|
||||
@Commit
|
||||
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||
@Transactional(propagation = Propagation.NEVER)
|
||||
void anyoneCanCreateTheirOwnUser_committed() {
|
||||
|
||||
// given:
|
||||
|
@ -4,8 +4,7 @@ import junit.framework.AssertionFailedError;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.NestedExceptionUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import java.util.Optional;
|
||||
@ -32,20 +31,16 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
public class JpaAttempt {
|
||||
|
||||
@Autowired
|
||||
private final EntityManager em;
|
||||
|
||||
public JpaAttempt(final EntityManager em) {
|
||||
this.em = em;
|
||||
}
|
||||
private TransactionTemplate transactionTemplate;
|
||||
|
||||
public static <T> JpaResult<T> attempt(final EntityManager em, final Supplier<T> code) {
|
||||
try {
|
||||
final var result = new JpaResult<T>(code.get(), null);
|
||||
final var result = JpaResult.forValue(code.get());
|
||||
em.flush();
|
||||
em.clear();
|
||||
return result;
|
||||
} catch (RuntimeException exc) {
|
||||
return new JpaResult<T>(null, exc);
|
||||
} catch (final RuntimeException 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) {
|
||||
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 void transacted(final Runnable code) {
|
||||
attempt(em, () -> {
|
||||
public JpaResult<Void> transacted(final Runnable code) {
|
||||
try {
|
||||
transactionTemplate.execute(transactionStatus -> {
|
||||
code.run();
|
||||
return null;
|
||||
});
|
||||
return JpaResult.forVoidValue();
|
||||
} catch (final RuntimeException exc) {
|
||||
|
||||
return new JpaResult<>(null, exc);
|
||||
}
|
||||
}
|
||||
|
||||
public static class JpaResult<T> {
|
||||
|
||||
final T result;
|
||||
final RuntimeException exception;
|
||||
private final T result;
|
||||
private final RuntimeException exception;
|
||||
|
||||
public JpaResult(final T result, final RuntimeException exception) {
|
||||
private JpaResult(final T result, final RuntimeException exception) {
|
||||
this.result = result;
|
||||
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() {
|
||||
return exception == null;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user