implements rbac-grants get-by-id

This commit is contained in:
Michael Hoennig 2022-08-19 17:39:41 +02:00
parent a66ed8e59f
commit 5ea8069608
14 changed files with 292 additions and 78 deletions

View File

@ -25,11 +25,32 @@ public class RbacGrantController implements RbacgrantsApi {
@Autowired
private RbacGrantRepository rbacGrantRepository;
@Override
@Transactional(readOnly = true)
public ResponseEntity<RbacGrantResource> getGrantById(
final String currentUser,
final String assumedRoles,
final UUID grantedRoleUuid,
final UUID granteeUserUuid) {
context.setCurrentUser(currentUser);
if (assumedRoles != null && !assumedRoles.isBlank()) {
context.assumeRoles(assumedRoles);
}
final var id = new RbacGrantId(granteeUserUuid, grantedRoleUuid);
final var result = rbacGrantRepository.findById(id);
if (result == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(map(result, RbacGrantResource.class));
}
@Override
@Transactional(readOnly = true)
public ResponseEntity<List<RbacGrantResource>> listUserGrants(
final String currentUser,
final String assumedRoles) {
final String currentUser,
final String assumedRoles) {
context.setCurrentUser(currentUser);
if (assumedRoles != null && !assumedRoles.isBlank()) {
@ -41,9 +62,9 @@ public class RbacGrantController implements RbacgrantsApi {
@Override
@Transactional
public ResponseEntity<Void> grantRoleToUser(
final String currentUser,
final String assumedRoles,
final RbacGrantResource body) {
final String currentUser,
final String assumedRoles,
final RbacGrantResource body) {
context.setCurrentUser(currentUser);
if (assumedRoles != null && !assumedRoles.isBlank()) {
@ -53,20 +74,20 @@ public class RbacGrantController implements RbacgrantsApi {
rbacGrantRepository.save(map(body, RbacGrantEntity.class));
final var uri =
MvcUriComponentsBuilder.fromController(getClass())
.path("/api/rbac-grants/{roleUuid}")
.buildAndExpand(body.getGrantedRoleUuid())
.toUri();
MvcUriComponentsBuilder.fromController(getClass())
.path("/api/rbac-grants/{roleUuid}")
.buildAndExpand(body.getGrantedRoleUuid())
.toUri();
return ResponseEntity.created(uri).build();
}
@Override
@Transactional
public ResponseEntity<Void> revokeRoleFromUser(
final String currentUser,
final String assumedRoles,
final UUID grantedRoleUuid,
final UUID granteeUserUuid) {
final String currentUser,
final String assumedRoles,
final UUID grantedRoleUuid,
final UUID granteeUserUuid) {
context.setCurrentUser(currentUser);
if (assumedRoles != null && !assumedRoles.isBlank()) {

View File

@ -18,6 +18,7 @@ import java.util.UUID;
@NoArgsConstructor
@AllArgsConstructor
public class RbacGrantEntity {
@Column(name = "grantedbyroleidname", updatable = false, insertable = false)
private String grantedByRoleIdName;
@ -59,6 +60,6 @@ public class RbacGrantEntity {
public String toDisplay() {
return "{ grant " + (assumed ? "assumed " : "") +
"role " + grantedRoleIdName + " to user " + granteeUserName + " by role " + grantedByRoleIdName + " }";
"role " + grantedRoleIdName + " to user " + granteeUserName + " by role " + grantedByRoleIdName + " }";
}
}

View File

@ -8,15 +8,22 @@ import java.util.List;
public interface RbacGrantRepository extends Repository<RbacGrantEntity, RbacGrantId> {
@Query(value = """
select g from RbacGrantEntity as g
where g.grantedRoleUuid=:#{#rbacGrantId.grantedRoleUuid}
and g.granteeUserUuid=:#{#rbacGrantId.granteeUserUuid}
""")
RbacGrantEntity findById(RbacGrantId rbacGrantId);
List<RbacGrantEntity> findAll();
void save(final RbacGrantEntity grant);
@Modifying
@Query(value = """
delete from RbacGrantEntity as g
where g.grantedRoleUuid=:#{#rbacGrantId.grantedRoleUuid}
and g.granteeUserUuid=:#{#rbacGrantId.granteeUserUuid}
""")
delete from RbacGrantEntity as g
where g.grantedRoleUuid=:#{#rbacGrantId.grantedRoleUuid}
and g.granteeUserUuid=:#{#rbacGrantId.granteeUserUuid}
""")
void deleteByRbacGrantId(RbacGrantId rbacGrantId);
}

View File

@ -32,7 +32,7 @@ public class RbacUserController implements RbacusersApi {
@Override
@Transactional
public ResponseEntity<RbacUserResource> createUser(
@RequestBody final RbacUserResource body
@RequestBody final RbacUserResource body
) {
if (body.getUuid() == null) {
body.setUuid(UUID.randomUUID());
@ -40,19 +40,27 @@ public class RbacUserController implements RbacusersApi {
final var saved = map(body, RbacUserEntity.class);
rbacUserRepository.create(saved);
final var uri =
MvcUriComponentsBuilder.fromController(getClass())
.path("/api/rbac-users/{id}")
.buildAndExpand(saved.getUuid())
.toUri();
MvcUriComponentsBuilder.fromController(getClass())
.path("/api/rbac-users/{id}")
.buildAndExpand(saved.getUuid())
.toUri();
return ResponseEntity.created(uri).body(map(saved, RbacUserResource.class));
}
@Override
@Transactional(readOnly=true)
public ResponseEntity<List<RbacUserPermissionResource>> getUserById(
final String currentUser,
final String assumedRoles,
final String userName) {
return null;
}
@Override
@Transactional(readOnly = true)
public ResponseEntity<List<RbacUserResource>> listUsers(
@RequestHeader(name = "current-user") final String currentUserName,
@RequestHeader(name = "assumed-roles", required = false) final String assumedRoles,
@RequestParam(name = "name", required = false) final String userName
@RequestHeader(name = "current-user") final String currentUserName,
@RequestHeader(name = "assumed-roles", required = false) final String assumedRoles,
@RequestParam(name = "name", required = false) final String userName
) {
context.setCurrentUser(currentUserName);
if (assumedRoles != null && !assumedRoles.isBlank()) {
@ -62,11 +70,11 @@ public class RbacUserController implements RbacusersApi {
}
@Override
@Transactional(readOnly=true)
@Transactional(readOnly = true)
public ResponseEntity<List<RbacUserPermissionResource>> listUserPermissions(
@RequestHeader(name = "current-user") final String currentUserName,
@RequestHeader(name = "assumed-roles", required = false) final String assumedRoles,
@PathVariable(name = "userName") final String userName
@RequestHeader(name = "current-user") final String currentUserName,
@RequestHeader(name = "assumed-roles", required = false) final String assumedRoles,
@PathVariable(name = "userName") final String userName
) {
context.setCurrentUser(currentUserName);
if (assumedRoles != null && !assumedRoles.isBlank()) {

View File

@ -3,7 +3,6 @@ package net.hostsharing.hsadminng.rbac.rbacuser;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.query.Param;
import java.util.List;
import java.util.UUID;
@ -11,14 +10,15 @@ import java.util.UUID;
public interface RbacUserRepository extends Repository<RbacUserEntity, UUID> {
@Query("""
select u from RbacUserEntity u
where :userName is null or u.name like concat(:userName, '%')
order by u.name
""")
select u from RbacUserEntity u
where :userName is null or u.name like concat(:userName, '%')
order by u.name
""")
List<RbacUserEntity> findByOptionalNameLike(String userName);
@Query(value = "select uuid from rbacuser where name=:userName", nativeQuery = true)
UUID findUuidByName(String userName);
// bypasses the restricted view, to be able to grant rights to arbitrary user
@Query(value = "select * from rbacuser where name=:userName", nativeQuery = true)
RbacUserEntity findByName(String userName);
RbacUserEntity findByUuid(UUID uuid);
@ -32,7 +32,7 @@ public interface RbacUserRepository extends Repository<RbacUserEntity, UUID> {
*/
@Modifying
@Query(value = "insert into RBacUser_RV (uuid, name) values( :#{#newUser.uuid}, :#{#newUser.name})", nativeQuery = true)
void insert(@Param("newUser") final RbacUserEntity newUser);
void insert(final RbacUserEntity newUser);
default RbacUserEntity create(final RbacUserEntity rbacUserEntity) {
if (rbacUserEntity.getUuid() == null) {

View File

@ -13,8 +13,11 @@ paths:
/api/rbac-users:
$ref: "./api-definition/rbac-users.yaml"
/api/rbac-users/{userName}/permissions:
$ref: "./api-definition/rbac-users-permissions.yaml"
/api/rbac-users/{userUuid}:
$ref: "./api-definition/rbac-users-with-id.yaml"
/api/rbac-users/{userUuid}/permissions:
$ref: "./api-definition/rbac-users-with-id-permissions.yaml"
/api/rbac-roles:
$ref: "./api-definition/rbac-roles.yaml"
@ -23,7 +26,7 @@ paths:
$ref: "./api-definition/rbac-grants.yaml"
/api/rbac-grants/{grantedRoleUuid}/{granteeUserUuid}:
$ref: "./api-definition/rbac-grants-id.yaml"
$ref: "./api-definition/rbac-grants-with-id.yaml"
# HS

View File

@ -6,11 +6,20 @@ components:
RbacGrant:
type: object
properties:
grantedByRoleIdName:
type: string
grantedByRoleUuid:
type: string
format: uuid
assumed:
type: boolean
grantedRoleIdName:
type: string
grantedRoleUuid:
type: string
format: uuid
granteeUserName:
type: string
granteeUserUuid:
type: string
format: uuid

View File

@ -1,30 +0,0 @@
delete:
tags:
- rbacgrants
operationId: revokeRoleFromUser
parameters:
- $ref: './api-definition/auth.yaml#/components/parameters/currentUser'
- $ref: './api-definition/auth.yaml#/components/parameters/assumedRoles'
- name: grantedRoleUuid
in: path
required: true
schema:
type: string
format: uuid
description: UUID of the granted role.
- name: granteeUserUuid
in: path
required: true
schema:
type: string
format: uuid
description: UUID of the user to whom the role was granted.
responses:
"204":
description: No Content
"401":
$ref: './api-definition/error-responses.yaml#/components/responses/Unauthorized'
"403":
$ref: './api-definition/error-responses.yaml#/components/responses/Forbidden'
"404":
$ref: './api-definition/error-responses.yaml#/components/responses/NotFound'

View File

@ -0,0 +1,65 @@
get:
tags:
- rbacgrants
operationId: getGrantById
parameters:
- $ref: './api-definition/auth.yaml#/components/parameters/currentUser'
- $ref: './api-definition/auth.yaml#/components/parameters/assumedRoles'
- name: grantedRoleUuid
in: path
required: true
schema:
type: string
format: uuid
description: UUID of the granted role.
- name: granteeUserUuid
in: path
required: true
schema:
type: string
format: uuid
description: UUID of the user to whom the role was granted.
responses:
"200":
description: OK
content:
'application/json':
schema:
$ref: './api-definition/rbac-grant-schemas.yaml#/components/schemas/RbacGrant'
"401":
$ref: './api-definition/error-responses.yaml#/components/responses/Unauthorized'
"403":
$ref: './api-definition/error-responses.yaml#/components/responses/Forbidden'
"404":
$ref: './api-definition/error-responses.yaml#/components/responses/NotFound'
delete:
tags:
- rbacgrants
operationId: revokeRoleFromUser
parameters:
- $ref: './api-definition/auth.yaml#/components/parameters/currentUser'
- $ref: './api-definition/auth.yaml#/components/parameters/assumedRoles'
- name: grantedRoleUuid
in: path
required: true
schema:
type: string
format: uuid
description: UUID of the granted role.
- name: granteeUserUuid
in: path
required: true
schema:
type: string
format: uuid
description: UUID of the user to whom the role was granted.
responses:
"204":
description: No Content
"401":
$ref: './api-definition/error-responses.yaml#/components/responses/Unauthorized'
"403":
$ref: './api-definition/error-responses.yaml#/components/responses/Forbidden'
"404":
$ref: './api-definition/error-responses.yaml#/components/responses/NotFound'

View File

@ -13,7 +13,7 @@ get:
schema:
type: array
items:
$ref: './api-definition/rbac-grant-schemas.yaml#/components/schemas/RbacGrant'
$ref: './rbac-grant-schemas.yaml#/components/schemas/RbacGrant'
post:
tags:

View File

@ -0,0 +1,27 @@
get:
tags:
- rbacusers
description: 'Fetch a single user by its id, if visible for the current subject.'
operationId: getUserById
parameters:
- $ref: './api-definition/auth.yaml#/components/parameters/currentUser'
- $ref: './api-definition/auth.yaml#/components/parameters/assumedRoles'
- name: userName
in: path
required: true
schema:
type: string
responses:
"200":
description: OK
content:
'application/json':
schema:
type: array
items:
$ref: './api-definition/rbac-user-schemas.yaml#/components/schemas/RbacUserPermission'
"401":
$ref: './api-definition/error-responses.yaml#/components/responses/Unauthorized'
"403":
$ref: './api-definition/error-responses.yaml#/components/responses/Forbidden'

View File

@ -27,6 +27,7 @@ import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assumptions.assumeThat;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
@ -57,6 +58,67 @@ class RbacGrantControllerAcceptanceTest {
@Autowired
JpaAttempt jpaAttempt;
@Nested
class GetGrantById {
@Test
@Accepts({ "GRT:R(Read)" })
void customerAdmin_withAssumedPacketAdminRole_canReadPacketAdminsGrantById() {
// given
final var givenCurrentUserAsPackageAdmin = new Subject("admin@aaa.example.com");
final var givenGranteeUser = findRbacUserByName("aaa00@aaa.example.com");
final var givenGrantedRole = findRbacRoleByName("package#aaa00.admin");
// when
final var grant = givenCurrentUserAsPackageAdmin.getGrantById()
.forGrantedRole(givenGrantedRole).toGranteeUser(givenGranteeUser);
// then
grant.assertThat()
.statusCode(200)
.body("grantedByRoleIdName", is("customer#aaa.admin"))
.body("grantedRoleIdName", is("package#aaa00.admin"))
.body("granteeUserName", is("aaa00@aaa.example.com"));
}
@Test
@Accepts({ "GRT:R(Read)" })
void packageAdmin_withoutAssumedRole_canReadItsOwnGrantById() {
// given
final var givenCurrentUserAsPackageAdmin = new Subject("aaa00@aaa.example.com");
final var givenGranteeUser = findRbacUserByName("aaa00@aaa.example.com");
final var givenGrantedRole = findRbacRoleByName("package#aaa00.admin");
// when
final var grant = givenCurrentUserAsPackageAdmin.getGrantById()
.forGrantedRole(givenGrantedRole).toGranteeUser(givenGranteeUser);
// then
grant.assertThat()
.statusCode(200)
.body("grantedByRoleIdName", is("customer#aaa.admin"))
.body("grantedRoleIdName", is("package#aaa00.admin"))
.body("granteeUserName", is("aaa00@aaa.example.com"));
}
@Test
@Accepts({ "GRT:R(Read)" })
void packageAdmin_withAssumedUnixUserAdmin_canNotReadItsOwnGrantById() {
// given
final var givenCurrentUserAsPackageAdmin = new Subject("aaa00@aaa.example.com", "unixuser#aaa00-aaaa.admin");
final var givenGranteeUser = findRbacUserByName("aaa00@aaa.example.com");
final var givenGrantedRole = findRbacRoleByName("package#aaa00.admin");
// when
final var grant = givenCurrentUserAsPackageAdmin.getGrantById()
.forGrantedRole(givenGrantedRole).toGranteeUser(givenGranteeUser);
// then
grant.assertThat()
.statusCode(404);
}
}
@Nested
class GrantRoleToUser {
@ -166,6 +228,10 @@ class RbacGrantControllerAcceptanceTest {
this.assumedRole = assumedRole;
}
public Subject(final String currentUser) {
this(currentUser, "");
}
GrantFixture grantsRole(final RbacRoleEntity givenOwnPackageAdminRole) {
return new GrantFixture(givenOwnPackageAdminRole);
}
@ -174,6 +240,10 @@ class RbacGrantControllerAcceptanceTest {
return new RevokeFixture(givenOwnPackageAdminRole);
}
GetGrantByIdFixture getGrantById() {
return new GetGrantByIdFixture();
}
class GrantFixture {
private Subject grantingSubject = Subject.this;
@ -252,6 +322,34 @@ class RbacGrantControllerAcceptanceTest {
.then(); // @formatter:on
}
}
private class GetGrantByIdFixture {
private Subject currentSubject = Subject.this;
private RbacRoleEntity grantedRole;
private boolean assumed;
private RbacUserEntity granteeUser;
GetGrantByIdFixture forGrantedRole(final RbacRoleEntity grantedRole) {
this.grantedRole = grantedRole;
return this;
}
ValidatableResponse toGranteeUser(final RbacUserEntity granteeUser) {
this.granteeUser = granteeUser;
return RestAssured // @formatter:ff
.given()
.header("current-user", currentSubject.currentUser)
.header("assumed-roles", currentSubject.assumedRole)
.port(port)
.when()
.get("http://localhost/api/rbac-grants/%s/%s".formatted(
grantedRole.getUuid(), granteeUser.getUuid()
))
.then(); // @formatter:on
}
}
}
private void assumeGrantExists(final Subject grantingSubject, final String expectedGrant) {
@ -275,6 +373,13 @@ class RbacGrantControllerAcceptanceTest {
).returnedValue();
}
RbacUserEntity findRbacUserByName(final String userName) {
return jpaAttempt.transacted(() -> {
context.setCurrentUser("mike@hostsharing.net");
return rbacUserRepository.findByName(userName);
}).returnedValue();
}
RbacRoleEntity findRbacRoleByName(final String roleName) {
return jpaAttempt.transacted(() -> {
context.setCurrentUser("mike@hostsharing.net");

View File

@ -108,7 +108,7 @@ class RbacGrantRepositoryIntegrationTest {
// given
currentUser("admin@aaa.example.com");
assumedRoles("customer#aaa.admin");
final var givenArbitraryUserUuid = rbacUserRepository.findUuidByName("aac00@aac.example.com");
final var givenArbitraryUserUuid = rbacUserRepository.findByName("aac00@aac.example.com").getUuid();
final var givenOwnPackageRoleUuid = rbacRoleRepository.findByRoleName("package#aaa00.admin").getUuid();
// when
@ -132,9 +132,7 @@ class RbacGrantRepositoryIntegrationTest {
@Transactional(propagation = Propagation.NEVER)
public void packageAdmin_canNotGrantPackageOwnerRole() {
// given
record Given(RbacUserEntity arbitraryUser, UUID packageOwnerRoleUuid) {
}
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");
@ -247,7 +245,7 @@ class RbacGrantRepositoryIntegrationTest {
private RbacGrantEntity create(GrantBuilder with) {
currentUser(with.byUserName);
assumedRoles(with.assumedRole);
final var givenArbitraryUserUuid = rbacUserRepository.findUuidByName(with.granteeUserName);
final var givenArbitraryUserUuid = rbacUserRepository.findByName(with.granteeUserName).getUuid();
final var givenOwnPackageRoleUuid = rbacRoleRepository.findByRoleName(with.grantedRole).getUuid();
final var grant = RbacGrantEntity.builder()