creating and viewing grants
This commit is contained in:
parent
c03697ccd9
commit
322736cd01
49
doc/rbac.md
49
doc/rbac.md
@ -461,12 +461,12 @@ actorHostmaster --> roleAdmins
|
|||||||
|
|
||||||
As you can see, there something special:
|
As you can see, there something special:
|
||||||
From the 'Role customer#xyz.owner' to the 'Role customer#xyz.admin' there is a dashed line, whereas all other lines are solid lines.
|
From the 'Role customer#xyz.owner' to the 'Role customer#xyz.admin' there is a dashed line, whereas all other lines are solid lines.
|
||||||
Solid lines means, that one role is granted to another and followed in all queries to the restricted views.
|
Solid lines means, that one role is granted to another and automatically assumed in all queries to the restricted views.
|
||||||
The dashed line means that one role is granted to another but not automatically followed in queries to the restricted views.
|
The dashed line means that one role is granted to another but not automatically assumed in queries to the restricted views.
|
||||||
|
|
||||||
The reason here is that otherwise simply too many objects would be accessible to those with the 'administrators' role and all queries would be slowed down vastly.
|
The reason here is that otherwise simply too many objects would be accessible to those with the 'administrators' role and all queries would be slowed down vastly.
|
||||||
|
|
||||||
Grants which are not followed are still valid grants for `hsadminng.assumedRoles`.
|
Grants which are not automatically assumed are still valid grants for `hsadminng.assumedRoles`.
|
||||||
Thus, if you want to access anything below a customer, assume its role first.
|
Thus, if you want to access anything below a customer, assume its role first.
|
||||||
|
|
||||||
There is actually another speciality in the customer roles:
|
There is actually another speciality in the customer roles:
|
||||||
@ -632,4 +632,47 @@ The WHERE-IN-variant is about 50% slower on the smaller dataset, but almost keep
|
|||||||
Both variants a viable option, depending on other needs, e.g. updatable views.
|
Both variants a viable option, depending on other needs, e.g. updatable views.
|
||||||
|
|
||||||
|
|
||||||
|
## Access Control to RBAC-Objects
|
||||||
|
|
||||||
|
Access Control for business objects checked according to the assigned roles.
|
||||||
|
But we decided not to create such roles and permissions for the RBAC-Objects itself.
|
||||||
|
It would have overcomplicated the system and the necessary information can easily be added to the RBAC-Objects itself, mostly the `RbacGrant`s.
|
||||||
|
|
||||||
|
### RbacUser
|
||||||
|
|
||||||
|
Users can self-register, thus to create a new RbacUser entity, no login is required.
|
||||||
|
But such a user has no access-rights except viewing itself.
|
||||||
|
|
||||||
|
Users can view themselves.
|
||||||
|
And any user can view all other users as long as they have the same roles assigned.
|
||||||
|
As an exception, users which are assigned to global roles are not visible by other users.
|
||||||
|
|
||||||
|
At least an indirect lookup of known user-names (e.g. email address of the user) is possible
|
||||||
|
by users who have an empowered assignment of any role.
|
||||||
|
Otherwise, it would not be possible to assign roles to new users.
|
||||||
|
|
||||||
|
### RbacRole
|
||||||
|
|
||||||
|
All roles are system-defined and cannot be created or modified by any external API.
|
||||||
|
|
||||||
|
Users can view only the roles to which they are assigned.
|
||||||
|
|
||||||
|
## RbacGrant
|
||||||
|
|
||||||
|
Grant can be `empowered`, this means that the grantee user can grant the granted role to other users
|
||||||
|
and revoke grants to that role.
|
||||||
|
(TODO: access control part not yet implemented)
|
||||||
|
|
||||||
|
Grants can be `managed`, which means they are created and deleted by system-defined rules.
|
||||||
|
If a grant is not managed, it was created by an empowered user and can be deleted by empowered users.
|
||||||
|
|
||||||
|
Grants can be `assumed`, which means that they are immediately active.
|
||||||
|
If a grant is not assumed, the grantee user needs to use `assumeRoles` to activate it.
|
||||||
|
|
||||||
|
Users can see only grants of roles to which they are (directly?) assigned themselves.
|
||||||
|
|
||||||
|
TODO: If a user grants an indirect role to another user, that grant would not be visible to the user.
|
||||||
|
But if we make indirect grants visible, this would reveal too much information.
|
||||||
|
We also cannot keep the granting user in the grant because grants must survive deleted users,
|
||||||
|
e.g. if after an account was transferred to another user.
|
||||||
|
|
||||||
|
@ -0,0 +1,64 @@
|
|||||||
|
package net.hostsharing.hsadminng.rbac.rbacgrant;
|
||||||
|
|
||||||
|
import net.hostsharing.hsadminng.context.Context;
|
||||||
|
import net.hostsharing.hsadminng.generated.api.v1.api.RbacgrantsApi;
|
||||||
|
import net.hostsharing.hsadminng.generated.api.v1.api.RbacrolesApi;
|
||||||
|
import net.hostsharing.hsadminng.generated.api.v1.model.RbacGrantResource;
|
||||||
|
import net.hostsharing.hsadminng.generated.api.v1.model.RbacRoleResource;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
|
||||||
|
|
||||||
|
import javax.transaction.Transactional;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static net.hostsharing.hsadminng.Mapper.map;
|
||||||
|
import static net.hostsharing.hsadminng.Mapper.mapList;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
|
||||||
|
public class RbacGrantController implements RbacgrantsApi {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RbacGrantRepository rbacGrantRepository;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public ResponseEntity<List<RbacGrantResource>> listUserGrants(
|
||||||
|
final String currentUser,
|
||||||
|
final String assumedRoles) {
|
||||||
|
|
||||||
|
context.setCurrentUser(currentUser);
|
||||||
|
if (assumedRoles != null && !assumedRoles.isBlank()) {
|
||||||
|
context.assumeRoles(assumedRoles);
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(mapList(rbacGrantRepository.findAll(), RbacGrantResource.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public ResponseEntity<Void> grantRoleToUser(
|
||||||
|
final String currentUser,
|
||||||
|
final String assumedRoles,
|
||||||
|
final RbacGrantResource body) {
|
||||||
|
|
||||||
|
context.setCurrentUser(currentUser);
|
||||||
|
if (assumedRoles != null && !assumedRoles.isBlank()) {
|
||||||
|
context.assumeRoles(assumedRoles);
|
||||||
|
}
|
||||||
|
|
||||||
|
rbacGrantRepository.save(map(body, RbacGrantEntity.class));
|
||||||
|
|
||||||
|
final var uri =
|
||||||
|
MvcUriComponentsBuilder.fromController(getClass())
|
||||||
|
.path("/api/rbac-grants/{roleUuid}")
|
||||||
|
.buildAndExpand(body.getRoleUuid())
|
||||||
|
.toUri();
|
||||||
|
return ResponseEntity.created(uri).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
package net.hostsharing.hsadminng.rbac.rbacgrant;
|
||||||
|
|
||||||
|
import lombok.*;
|
||||||
|
import net.hostsharing.hsadminng.rbac.rbacrole.RbacRoleType;
|
||||||
|
import org.springframework.data.annotation.Immutable;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "rbacgrants_rv")
|
||||||
|
@IdClass(RbacGrantId.class)
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Builder
|
||||||
|
@ToString
|
||||||
|
@Immutable
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class RbacGrantEntity {
|
||||||
|
|
||||||
|
@Column(name = "username", updatable = false, insertable = false)
|
||||||
|
private String userName;
|
||||||
|
|
||||||
|
@Column(name = "roleidname", updatable = false, insertable = false)
|
||||||
|
private String roleIdName;
|
||||||
|
|
||||||
|
private boolean managed;
|
||||||
|
private boolean assumed;
|
||||||
|
private boolean empowered;
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@Column(name = "useruuid")
|
||||||
|
private UUID userUuid;
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@Column(name = "roleuuid")
|
||||||
|
private UUID roleUuid;
|
||||||
|
|
||||||
|
@Column(name = "objecttable", updatable = false, insertable = false)
|
||||||
|
private String objectTable;
|
||||||
|
|
||||||
|
@Column(name = "objectuuid", updatable = false, insertable = false)
|
||||||
|
private UUID objectUuid;
|
||||||
|
|
||||||
|
@Column(name = "objectidname", updatable = false, insertable = false)
|
||||||
|
private String objectIdName;
|
||||||
|
|
||||||
|
@Column(name = "roletype", updatable = false, insertable = false)
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
private RbacRoleType roleType;
|
||||||
|
|
||||||
|
public String toDisplay() {
|
||||||
|
return "grant( " + userName + " -> " + roleIdName + ": " +
|
||||||
|
(managed ? "managed " : "") +
|
||||||
|
(assumed ? "assumed " : "") +
|
||||||
|
(empowered ? "empowered " : "") +
|
||||||
|
")";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package net.hostsharing.hsadminng.rbac.rbacgrant;
|
||||||
|
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@EqualsAndHashCode
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class RbacGrantId implements Serializable {
|
||||||
|
|
||||||
|
private UUID userUuid;
|
||||||
|
private UUID roleUuid;
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package net.hostsharing.hsadminng.rbac.rbacgrant;
|
||||||
|
|
||||||
|
import org.springframework.data.repository.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface RbacGrantRepository extends Repository<RbacGrantEntity, RbacGrantId> {
|
||||||
|
|
||||||
|
List<RbacGrantEntity> findAll();
|
||||||
|
|
||||||
|
void save(final RbacGrantEntity grant);
|
||||||
|
|
||||||
|
}
|
@ -24,11 +24,15 @@ public class RbacRoleController implements RbacrolesApi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public ResponseEntity<List<RbacRoleResource>> listRoles(final String currentUser, final String assumedRoles) {
|
public ResponseEntity<List<RbacRoleResource>> listRoles(
|
||||||
|
final String currentUser,
|
||||||
|
final String assumedRoles) {
|
||||||
|
|
||||||
context.setCurrentUser(currentUser);
|
context.setCurrentUser(currentUser);
|
||||||
if (assumedRoles != null && !assumedRoles.isBlank()) {
|
if (assumedRoles != null && !assumedRoles.isBlank()) {
|
||||||
context.assumeRoles(assumedRoles);
|
context.assumeRoles(assumedRoles);
|
||||||
}
|
}
|
||||||
return ResponseEntity.ok(mapList(rbacRoleRepository.findAll(), RbacRoleResource.class));
|
return ResponseEntity.ok(mapList(rbacRoleRepository.findAll(), RbacRoleResource.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,9 @@ public interface RbacUserRepository extends Repository<RbacUserEntity, UUID> {
|
|||||||
""")
|
""")
|
||||||
List<RbacUserEntity> findByOptionalNameLike(String userName);
|
List<RbacUserEntity> findByOptionalNameLike(String userName);
|
||||||
|
|
||||||
|
@Query(value = "select uuid from rbacuser where name=:userName", nativeQuery = true)
|
||||||
|
UUID findUuidByName(String userName);
|
||||||
|
|
||||||
RbacUserEntity findByUuid(UUID uuid);
|
RbacUserEntity findByUuid(UUID uuid);
|
||||||
|
|
||||||
@Query(value = "select * from grantedPermissions(:userName)", nativeQuery = true)
|
@Query(value = "select * from grantedPermissions(:userName)", nativeQuery = true)
|
||||||
|
21
src/main/resources/api-definition/rbac-grant-schemas.yaml
Normal file
21
src/main/resources/api-definition/rbac-grant-schemas.yaml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
components:
|
||||||
|
|
||||||
|
schemas:
|
||||||
|
|
||||||
|
RbacGrant:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
userUuid:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
roleUuid:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
assumed:
|
||||||
|
type: boolean
|
||||||
|
empowered:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- userUuid
|
||||||
|
- roleUuid
|
39
src/main/resources/api-definition/rbac-grants.yaml
Normal file
39
src/main/resources/api-definition/rbac-grants.yaml
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- rbacgrants
|
||||||
|
operationId: listUserGrants
|
||||||
|
parameters:
|
||||||
|
- $ref: './api-definition/auth.yaml#/components/parameters/currentUser'
|
||||||
|
- $ref: './api-definition/auth.yaml#/components/parameters/assumedRoles'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
'application/json':
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: './api-definition/rbac-grant-schemas.yaml#/components/schemas/RbacGrant'
|
||||||
|
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- rbacgrants
|
||||||
|
operationId: grantRoleToUser
|
||||||
|
parameters:
|
||||||
|
- $ref: './api-definition/auth.yaml#/components/parameters/currentUser'
|
||||||
|
- $ref: './api-definition/auth.yaml#/components/parameters/assumedRoles'
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: './api-definition/rbac-grant-schemas.yaml#/components/schemas/RbacGrant'
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: OK
|
||||||
|
"401":
|
||||||
|
$ref: './api-definition/error-responses.yaml#/components/responses/Unauthorized'
|
||||||
|
"403":
|
||||||
|
$ref: './api-definition/error-responses.yaml#/components/responses/Forbidden'
|
||||||
|
"409":
|
||||||
|
$ref: './api-definition/error-responses.yaml#/components/responses/Conflict'
|
@ -206,6 +206,33 @@ begin
|
|||||||
end;
|
end;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
|
create or replace function findRoleId(roleIdName varchar)
|
||||||
|
returns uuid
|
||||||
|
returns null on null input
|
||||||
|
language plpgsql as $$
|
||||||
|
declare
|
||||||
|
roleParts text;
|
||||||
|
roleTypeFromRoleIdName RbacRoleType;
|
||||||
|
objectNameFromRoleIdName text;
|
||||||
|
objectTableFromRoleIdName text;
|
||||||
|
objectUuidOfRole uuid;
|
||||||
|
roleUuid uuid;
|
||||||
|
begin
|
||||||
|
-- TODO: extract function toRbacRoleDescriptor(roleIdName varchar) + find other occurrences
|
||||||
|
roleParts = overlay(roleIdName placing '#' from length(roleIdName) + 1 - strpos(reverse(roleIdName), '.'));
|
||||||
|
objectTableFromRoleIdName = split_part(roleParts, '#', 1);
|
||||||
|
objectNameFromRoleIdName = split_part(roleParts, '#', 2);
|
||||||
|
roleTypeFromRoleIdName = split_part(roleParts, '#', 3);
|
||||||
|
objectUuidOfRole = findObjectUuidByIdName(objectTableFromRoleIdName, objectNameFromRoleIdName);
|
||||||
|
|
||||||
|
select uuid
|
||||||
|
from RbacRole
|
||||||
|
where objectUuid = objectUuidOfRole
|
||||||
|
and roleType = roleTypeFromRoleIdName
|
||||||
|
into roleUuid;
|
||||||
|
return roleUuid;
|
||||||
|
end; $$;
|
||||||
|
|
||||||
create or replace function findRoleId(roleDescriptor RbacRoleDescriptor)
|
create or replace function findRoleId(roleDescriptor RbacRoleDescriptor)
|
||||||
returns uuid
|
returns uuid
|
||||||
returns null on null input
|
returns null on null input
|
||||||
@ -322,13 +349,15 @@ $$;
|
|||||||
--changeset rbac-base-GRANTS:1 endDelimiter:--//
|
--changeset rbac-base-GRANTS:1 endDelimiter:--//
|
||||||
-- ----------------------------------------------------------------------------
|
-- ----------------------------------------------------------------------------
|
||||||
/*
|
/*
|
||||||
|
Table to store grants / role- or permission assignments to users or roles.
|
||||||
*/
|
*/
|
||||||
create table RbacGrants
|
create table RbacGrants
|
||||||
(
|
(
|
||||||
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,
|
||||||
follow boolean not null default true,
|
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)
|
primary key (ascendantUuid, descendantUuid)
|
||||||
);
|
);
|
||||||
create index on RbacGrants (ascendantUuid);
|
create index on RbacGrants (ascendantUuid);
|
||||||
@ -377,7 +406,8 @@ declare
|
|||||||
granteeId uuid;
|
granteeId uuid;
|
||||||
begin
|
begin
|
||||||
-- TODO: needs optimization
|
-- TODO: needs optimization
|
||||||
foreach granteeId in array granteeIds loop
|
foreach granteeId in array granteeIds
|
||||||
|
loop
|
||||||
if isGranted(granteeId, grantedId) then
|
if isGranted(granteeId, grantedId) then
|
||||||
return true;
|
return true;
|
||||||
end if;
|
end if;
|
||||||
@ -416,7 +446,8 @@ select exists(
|
|||||||
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 as o on o.uuid = r.objectuuid
|
join RbacObject as o on o.uuid = r.objectuuid
|
||||||
where g.ascendantuuid = userUuid and o.objecttable = 'global'
|
where g.ascendantuuid = userUuid
|
||||||
|
and o.objecttable = 'global'
|
||||||
);
|
);
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
@ -432,14 +463,14 @@ begin
|
|||||||
perform assertReferenceType('permissionId (descendant)', permissionIds[i], 'RbacPermission');
|
perform assertReferenceType('permissionId (descendant)', permissionIds[i], 'RbacPermission');
|
||||||
|
|
||||||
insert
|
insert
|
||||||
into RbacGrants (ascendantUuid, descendantUuid, follow)
|
into RbacGrants (ascendantUuid, descendantUuid, managed, assumed, empowered)
|
||||||
values (roleUuid, permissionIds[i], true)
|
values (roleUuid, permissionIds[i], true, true, false)
|
||||||
on conflict do nothing; -- allow granting multiple times
|
on conflict do nothing; -- allow granting multiple times
|
||||||
end loop;
|
end loop;
|
||||||
end;
|
end;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
create or replace procedure grantRoleToRole(subRoleId uuid, superRoleId uuid, doFollow bool = true)
|
create or replace procedure grantRoleToRole(subRoleId uuid, superRoleId uuid, doAssume bool = true)
|
||||||
language plpgsql as $$
|
language plpgsql as $$
|
||||||
begin
|
begin
|
||||||
perform assertReferenceType('superRoleId (ascendant)', superRoleId, 'RbacRole');
|
perform assertReferenceType('superRoleId (ascendant)', superRoleId, 'RbacRole');
|
||||||
@ -450,8 +481,8 @@ begin
|
|||||||
end if;
|
end if;
|
||||||
|
|
||||||
insert
|
insert
|
||||||
into RbacGrants (ascendantUuid, descendantUuid, follow)
|
into RbacGrants (ascendantUuid, descendantUuid, managed, assumed, empowered)
|
||||||
values (superRoleId, subRoleId, doFollow)
|
values (superRoleId, subRoleId, true, doAssume, false)
|
||||||
on conflict do nothing; -- allow granting multiple times
|
on conflict do nothing; -- allow granting multiple times
|
||||||
end; $$;
|
end; $$;
|
||||||
|
|
||||||
@ -466,16 +497,45 @@ begin
|
|||||||
end if;
|
end if;
|
||||||
end; $$;
|
end; $$;
|
||||||
|
|
||||||
create or replace procedure grantRoleToUser(roleId uuid, userId uuid)
|
create or replace procedure grantRoleToUser(roleUuid uuid, userUuid uuid)
|
||||||
language plpgsql as $$
|
language plpgsql as $$
|
||||||
begin
|
begin
|
||||||
perform assertReferenceType('roleId (ascendant)', roleId, 'RbacRole');
|
perform assertReferenceType('roleId (descendant)', roleUuid, 'RbacRole');
|
||||||
perform assertReferenceType('userId (descendant)', userId, 'RbacUser');
|
perform assertReferenceType('userId (ascendant)', userUuid, 'RbacUser');
|
||||||
|
|
||||||
insert
|
insert
|
||||||
into RbacGrants (ascendantUuid, descendantUuid, follow)
|
into RbacGrants (ascendantUuid, descendantUuid, managed, assumed, empowered)
|
||||||
values (userId, roleId, true)
|
values (userUuid, roleUuid, true, true, true);
|
||||||
on conflict do nothing; -- allow granting multiple times
|
-- 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; $$;
|
end; $$;
|
||||||
--//
|
--//
|
||||||
|
|
||||||
@ -499,14 +559,14 @@ begin
|
|||||||
return query select distinct perm.objectUuid
|
return query select distinct perm.objectUuid
|
||||||
from (with recursive grants as (select descendantUuid, ascendantUuid, 1 as level
|
from (with recursive grants as (select descendantUuid, ascendantUuid, 1 as level
|
||||||
from RbacGrants
|
from RbacGrants
|
||||||
where follow
|
where assumed
|
||||||
and ascendantUuid = any (subjectIds)
|
and ascendantUuid = any (subjectIds)
|
||||||
union
|
union
|
||||||
distinct
|
distinct
|
||||||
select "grant".descendantUuid, "grant".ascendantUuid, level + 1 as level
|
select "grant".descendantUuid, "grant".ascendantUuid, level + 1 as level
|
||||||
from RbacGrants "grant"
|
from RbacGrants "grant"
|
||||||
inner join grants recur on recur.descendantUuid = "grant".ascendantUuid
|
inner join grants recur on recur.descendantUuid = "grant".ascendantUuid
|
||||||
where follow)
|
where assumed)
|
||||||
select descendantUuid
|
select descendantUuid
|
||||||
from grants) as granted
|
from grants) as granted
|
||||||
join RbacPermission perm
|
join RbacPermission perm
|
||||||
|
@ -24,6 +24,72 @@ grant all privileges on rbacrole_rv to restricted;
|
|||||||
--//
|
--//
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
--changeset rbac-views-GRANT-RESTRICTED-VIEW:1 endDelimiter:--//
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
Creates a view to the grants table with row-level limitation
|
||||||
|
based on the direct grants of the current user.
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
from (
|
||||||
|
select g.*, 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
|
||||||
|
-- @formatter:on
|
||||||
|
order by objectIdName;
|
||||||
|
grant all privileges on rbacrole_rv to restricted;
|
||||||
|
--//
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
--changeset rbac-views-GRANTS-RV-INSERT-TRIGGER:1 endDelimiter:--//
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
Instead of insert trigger function for RbacGrants_RV.
|
||||||
|
*/
|
||||||
|
create or replace function insertRbacGrant()
|
||||||
|
returns trigger
|
||||||
|
language plpgsql as $$
|
||||||
|
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));
|
||||||
|
select grv.*
|
||||||
|
from RbacGrants_RV grv
|
||||||
|
where grv.userUuid=new.userUuid and grv.roleUuid=new.roleUuid
|
||||||
|
into newGrant;
|
||||||
|
return newGrant;
|
||||||
|
end; $$;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Creates an instead of insert trigger for the RbacGrants_rv view.
|
||||||
|
*/
|
||||||
|
create trigger insertRbacGrant_Trigger
|
||||||
|
instead of insert
|
||||||
|
on RbacGrants_rv
|
||||||
|
for each row
|
||||||
|
execute function insertRbacGrant();
|
||||||
|
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
--changeset rbac-views-USER-RESTRICTED-VIEW:1 endDelimiter:--//
|
--changeset rbac-views-USER-RESTRICTED-VIEW:1 endDelimiter:--//
|
||||||
-- ----------------------------------------------------------------------------
|
-- ----------------------------------------------------------------------------
|
||||||
|
@ -76,7 +76,7 @@ begin
|
|||||||
customerAdminUuid = createRole(
|
customerAdminUuid = createRole(
|
||||||
customerAdmin(NEW),
|
customerAdmin(NEW),
|
||||||
grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['view', 'add-package']),
|
grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['view', 'add-package']),
|
||||||
-- NO auto follow 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
|
||||||
);
|
);
|
||||||
|
|
||||||
|
13
src/test/java/net/hostsharing/hsadminng/Accepts.java
Normal file
13
src/test/java/net/hostsharing/hsadminng/Accepts.java
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package net.hostsharing.hsadminng;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||||
|
public @interface Accepts {
|
||||||
|
|
||||||
|
String[] value();
|
||||||
|
}
|
@ -32,7 +32,7 @@ class PackageRepositoryIntegrationTest {
|
|||||||
class FindAllByOptionalNameLike {
|
class FindAllByOptionalNameLike {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void hostsharingAdmin_withoutAssumedRole_canNotViewAnyPackages_becauseThoseGrantsAreNotFollowed() {
|
public void hostsharingAdmin_withoutAssumedRole_canNotViewAnyPackages_becauseThoseGrantsAreNotassumedd() {
|
||||||
// given
|
// given
|
||||||
currentUser("mike@hostsharing.net");
|
currentUser("mike@hostsharing.net");
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ class PackageRepositoryIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void hostsharingAdmin_withAssumedHostsharingAdminRole__canNotViewAnyPackages_becauseThoseGrantsAreNotFollowed() {
|
public void hostsharingAdmin_withAssumedHostsharingAdminRole__canNotViewAnyPackages_becauseThoseGrantsAreNotassumedd() {
|
||||||
given:
|
given:
|
||||||
currentUser("mike@hostsharing.net");
|
currentUser("mike@hostsharing.net");
|
||||||
assumedRoles("global#hostsharing.admin");
|
assumedRoles("global#hostsharing.admin");
|
||||||
|
@ -0,0 +1,168 @@
|
|||||||
|
package net.hostsharing.hsadminng.rbac.rbacgrant;
|
||||||
|
|
||||||
|
import io.restassured.RestAssured;
|
||||||
|
import io.restassured.http.ContentType;
|
||||||
|
import net.hostsharing.hsadminng.Accepts;
|
||||||
|
import net.hostsharing.hsadminng.HsadminNgApplication;
|
||||||
|
import net.hostsharing.hsadminng.context.Context;
|
||||||
|
import net.hostsharing.hsadminng.rbac.rbacrole.RbacRoleEntity;
|
||||||
|
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.apache.commons.lang3.RandomStringUtils;
|
||||||
|
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 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;
|
||||||
|
|
||||||
|
@SpringBootTest(
|
||||||
|
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||||
|
classes = { HsadminNgApplication.class, JpaAttempt.class }
|
||||||
|
)
|
||||||
|
@Accepts({ "ROL:S(Schema)" })
|
||||||
|
class RbacGrantControllerAcceptanceTest {
|
||||||
|
|
||||||
|
@LocalServerPort
|
||||||
|
Integer port;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
EntityManager em;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
Context context;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
RbacUserRepository rbacUserRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
RbacRoleRepository rbacRoleRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
RbacGrantRepository rbacGrantRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
JpaAttempt jpaAttempt;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Accepts({ "ROL:L(List)" })
|
||||||
|
void returnsRbacGrantsForPackageAdmin() {
|
||||||
|
|
||||||
|
RestAssured // @formatter:off
|
||||||
|
.given()
|
||||||
|
.header("current-user", "aaa00@aaa.example.com")
|
||||||
|
.port(port)
|
||||||
|
.when()
|
||||||
|
.get("http://localhost/api/rbac-roles")
|
||||||
|
.then().assertThat()
|
||||||
|
.statusCode(200)
|
||||||
|
.contentType("application/json")
|
||||||
|
.body("[0].roleName", is("customer#aaa.tenant"))
|
||||||
|
.body("[1].roleName", is("package#aaa00.admin"))
|
||||||
|
.body("[2].roleName", is("package#aaa00.tenant"));
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Accepts({ "ROL:C(Create)" })
|
||||||
|
void packageAdmin_canGrantOwnPackageAdminRole_toArbitraryUser() {
|
||||||
|
|
||||||
|
// given
|
||||||
|
final var givenNewUserName = "test-user-" + RandomStringUtils.randomAlphabetic(8) + "@example.com";
|
||||||
|
final String givenPackageAdmin = "aaa00@aaa.example.com";
|
||||||
|
final var givenOwnPackageAdminRole = "package#aaa00.admin";
|
||||||
|
// when
|
||||||
|
RestAssured // @formatter:off
|
||||||
|
.given()
|
||||||
|
.header("current-user", givenPackageAdmin)
|
||||||
|
.contentType(ContentType.JSON)
|
||||||
|
.body("""
|
||||||
|
{
|
||||||
|
"userUuid": "%s",
|
||||||
|
"roleUuid": "%s",
|
||||||
|
"assumed": true,
|
||||||
|
"empowered": false
|
||||||
|
}
|
||||||
|
""".formatted(
|
||||||
|
createRBacUser(givenNewUserName).getUuid().toString(),
|
||||||
|
findRbacRoleByName(givenOwnPackageAdminRole).getUuid().toString())
|
||||||
|
)
|
||||||
|
.port(port)
|
||||||
|
.when()
|
||||||
|
.post("http://localhost/api/rbac-grants")
|
||||||
|
.then().assertThat()
|
||||||
|
.statusCode(201);
|
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(findAllGrantsOfUser(givenPackageAdmin))
|
||||||
|
.extracting(RbacGrantEntity::toDisplay)
|
||||||
|
.contains("grant( " + givenNewUserName + " -> " + givenOwnPackageAdminRole + ": assumed )");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Accepts({ "ROL:C(Create)", "ROL:X(Access Control)" })
|
||||||
|
void packageAdmin_canNotGrantAlienPackageAdminRole_toArbitraryUser() {
|
||||||
|
|
||||||
|
// given
|
||||||
|
final var givenNewUserName = "test-user-" + RandomStringUtils.randomAlphabetic(8) + "@example.com";
|
||||||
|
final String givenPackageAdmin = "aaa00@aaa.example.com";
|
||||||
|
final var givenAlienPackageAdminRole = "package#aab00.admin";
|
||||||
|
|
||||||
|
// when
|
||||||
|
RestAssured // @formatter:off
|
||||||
|
.given()
|
||||||
|
.header("current-user", givenPackageAdmin)
|
||||||
|
.contentType(ContentType.JSON)
|
||||||
|
.body("""
|
||||||
|
{
|
||||||
|
"userUuid": "%s",
|
||||||
|
"roleUuid": "%s",
|
||||||
|
"assumed": true,
|
||||||
|
"empowered": false
|
||||||
|
}
|
||||||
|
""".formatted(
|
||||||
|
createRBacUser(givenNewUserName).getUuid().toString(),
|
||||||
|
findRbacRoleByName(givenAlienPackageAdminRole).getUuid().toString())
|
||||||
|
)
|
||||||
|
.port(port)
|
||||||
|
.when()
|
||||||
|
.post("http://localhost/api/rbac-grants")
|
||||||
|
.then().assertThat()
|
||||||
|
.statusCode(403);
|
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(findAllGrantsOfUser(givenPackageAdmin))
|
||||||
|
.extracting(RbacGrantEntity::getUserName)
|
||||||
|
.doesNotContain(givenNewUserName);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<RbacGrantEntity> findAllGrantsOfUser(final String userName) {
|
||||||
|
return jpaAttempt.transacted(() -> {
|
||||||
|
context.setCurrentUser(userName);
|
||||||
|
return rbacGrantRepository.findAll();
|
||||||
|
}).returnedValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
RbacUserEntity createRBacUser(final String userName) {
|
||||||
|
return jpaAttempt.transacted(() -> {
|
||||||
|
return rbacUserRepository.create(new RbacUserEntity(UUID.randomUUID(), userName));
|
||||||
|
}).returnedValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
RbacRoleEntity findRbacRoleByName(final String roleName) {
|
||||||
|
return jpaAttempt.transacted(() -> {
|
||||||
|
context.setCurrentUser("mike@hostsharing.net");
|
||||||
|
return rbacRoleRepository.findByRoleName(roleName);
|
||||||
|
}).returnedValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,142 @@
|
|||||||
|
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.RbacUserRepository;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
|
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.orm.jpa.JpaSystemException;
|
||||||
|
import org.springframework.test.annotation.DirtiesContext;
|
||||||
|
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static net.hostsharing.test.JpaAttempt.attempt;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
@DataJpaTest
|
||||||
|
@ComponentScan(basePackageClasses = { Context.class, RbacGrantRepository.class })
|
||||||
|
@DirtiesContext
|
||||||
|
@Accepts({ "GRT:S(Schema)" })
|
||||||
|
class RbacGrantRepositoryIntegrationTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
Context context;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
RbacGrantRepository rbacGrantRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
RbacUserRepository rbacUserRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
RbacRoleRepository rbacRoleRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
EntityManager em;
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class FindAllRbacGrants {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Accepts({ "GRT:L(List)" })
|
||||||
|
public void packageAdmin_canViewItsRbacGrants() {
|
||||||
|
// given
|
||||||
|
currentUser("aaa00@aaa.example.com");
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var result = rbacGrantRepository.findAll();
|
||||||
|
|
||||||
|
// then
|
||||||
|
exactlyTheseRbacGrantsAreReturned(
|
||||||
|
result,
|
||||||
|
"grant( aaa00@aaa.example.com -> package#aaa00.admin: managed assumed empowered )");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Accepts({ "GRT:L(List)" })
|
||||||
|
public void customerAdmin_canViewItsRbacGrants() {
|
||||||
|
// given
|
||||||
|
currentUser("admin@aaa.example.com");
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var result = rbacGrantRepository.findAll();
|
||||||
|
|
||||||
|
// 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 )");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Accepts({ "GRT:L(List)" })
|
||||||
|
public void customerAdmin_withAssumedRole_cannotViewRbacGrants() {
|
||||||
|
// given:
|
||||||
|
currentUser("admin@aaa.example.com");
|
||||||
|
assumedRoles("package#aab00.admin");
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var result = attempt(
|
||||||
|
em,
|
||||||
|
() -> rbacGrantRepository.findAll());
|
||||||
|
|
||||||
|
// then
|
||||||
|
result.assertExceptionWithRootCauseMessage(
|
||||||
|
JpaSystemException.class,
|
||||||
|
"[403] user admin@aaa.example.com", "has no permission to assume role package#aab00#admin");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class CreateRbacGrant {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Accepts({ "GRT:C(Create)" })
|
||||||
|
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();
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var grant = RbacGrantEntity.builder()
|
||||||
|
.userUuid(userUuid).roleUuid(roleUuid)
|
||||||
|
.assumed(true).empowered(false)
|
||||||
|
.build();
|
||||||
|
final var attempt = attempt(em, () ->
|
||||||
|
rbacGrantRepository.save(grant)
|
||||||
|
);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(attempt.wasSuccessful()).isTrue();
|
||||||
|
assertThat(rbacGrantRepository.findAll())
|
||||||
|
.extracting(RbacGrantEntity::toDisplay)
|
||||||
|
.contains("grant( aac00@aac.example.com -> package#aaa00.admin: assumed )");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void currentUser(final String currentUser) {
|
||||||
|
context.setCurrentUser(currentUser);
|
||||||
|
assertThat(context.getCurrentUser()).as("precondition").isEqualTo(currentUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
void assumedRoles(final String assumedRoles) {
|
||||||
|
context.assumeRoles(assumedRoles);
|
||||||
|
assertThat(context.getAssumedRoles()).as("precondition").containsExactly(assumedRoles.split(";"));
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
.extracting(RbacGrantEntity::toDisplay)
|
||||||
|
.containsExactlyInAnyOrder(expectedGrant);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
package net.hostsharing.hsadminng.rbac.rbacrole;
|
||||||
|
|
||||||
|
import io.restassured.RestAssured;
|
||||||
|
import net.hostsharing.hsadminng.Accepts;
|
||||||
|
import net.hostsharing.hsadminng.HsadminNgApplication;
|
||||||
|
import net.hostsharing.hsadminng.context.Context;
|
||||||
|
import net.hostsharing.hsadminng.rbac.rbacuser.RbacUserRepository;
|
||||||
|
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 javax.persistence.EntityManager;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
|
||||||
|
@SpringBootTest(
|
||||||
|
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||||
|
classes = HsadminNgApplication.class
|
||||||
|
)
|
||||||
|
@Accepts({ "ROL:*:S:Schema" })
|
||||||
|
class RbacRoleControllerAcceptanceTest {
|
||||||
|
|
||||||
|
@LocalServerPort
|
||||||
|
private Integer port;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
EntityManager em;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
Context context;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
RbacUserRepository rbacUserRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
RbacRoleRepository rbacRoleRepository;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Accepts({ "ROL:*:L:List" })
|
||||||
|
void returnsRbacRolesForAssumedPackageAdmin() {
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
|
RestAssured
|
||||||
|
.given()
|
||||||
|
.header("current-user", "mike@hostsharing.net")
|
||||||
|
.header("assumed-roles", "package#aaa00.admin")
|
||||||
|
.port(port)
|
||||||
|
.when()
|
||||||
|
.get("http://localhost/api/rbac-roles")
|
||||||
|
.then().assertThat()
|
||||||
|
.statusCode(200)
|
||||||
|
.contentType("application/json")
|
||||||
|
.body("[0].roleName", is("customer#aaa.tenant"))
|
||||||
|
.body("[1].roleName", is("package#aaa00.admin"))
|
||||||
|
.body("[2].roleName", is("package#aaa00.tenant"));
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -15,8 +15,7 @@ import javax.transaction.Transactional;
|
|||||||
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.Matchers.*;
|
||||||
import static org.hamcrest.Matchers.startsWith;
|
|
||||||
|
|
||||||
@SpringBootTest(
|
@SpringBootTest(
|
||||||
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||||
@ -56,7 +55,12 @@ class RbacUserControllerAcceptanceTest {
|
|||||||
.body("[0].name", is("aaa00@aaa.example.com"))
|
.body("[0].name", is("aaa00@aaa.example.com"))
|
||||||
.body("[1].name", is("aaa01@aaa.example.com"))
|
.body("[1].name", is("aaa01@aaa.example.com"))
|
||||||
.body("[2].name", is("aaa02@aaa.example.com"))
|
.body("[2].name", is("aaa02@aaa.example.com"))
|
||||||
.body("size()", is(14));
|
.body("[3].name", is("aab00@aab.example.com"))
|
||||||
|
// ...
|
||||||
|
.body("[11].name", is("admin@aac.example.com"))
|
||||||
|
.body("[12].name", is("mike@hostsharing.net"))
|
||||||
|
.body("[13].name", is("sven@hostsharing.net"))
|
||||||
|
.body("size()", greaterThanOrEqualTo(14));
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -371,6 +371,7 @@ class RbacUserRepositoryIntegrationTest {
|
|||||||
|
|
||||||
void exactlyTheseRbacUsersAreReturned(final List<RbacUserEntity> actualResult, final String... expectedUserNames) {
|
void exactlyTheseRbacUsersAreReturned(final List<RbacUserEntity> actualResult, final String... expectedUserNames) {
|
||||||
assertThat(actualResult)
|
assertThat(actualResult)
|
||||||
|
.filteredOn(u -> !u.getName().startsWith("test-user-"))
|
||||||
.extracting(RbacUserEntity::getName)
|
.extracting(RbacUserEntity::getName)
|
||||||
.containsExactlyInAnyOrder(expectedUserNames);
|
.containsExactlyInAnyOrder(expectedUserNames);
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,13 @@ public class JpaAttempt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static JpaResult<Void> attempt(final EntityManager em, final Runnable code) {
|
||||||
|
return attempt(em, () -> {
|
||||||
|
code.run();
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
@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);
|
return attempt(em, code);
|
||||||
|
Loading…
Reference in New Issue
Block a user