implements create new rbac-user and transacted JpaAttemp
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
package net.hostsharing.hsadminng.config;
|
||||
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import org.openapitools.jackson.nullable.JsonNullableModule;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@ -13,6 +14,6 @@ public class JsonObjectMapperConfiguration {
|
||||
@Primary
|
||||
public Jackson2ObjectMapperBuilder customObjectMapper() {
|
||||
return new Jackson2ObjectMapperBuilder()
|
||||
.modules(new JsonNullableModule());
|
||||
.modules(new JsonNullableModule(), new JavaTimeModule());
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,13 @@ public class RestResponseEntityExceptionHandler
|
||||
return errorResponse(request, httpStatus(message).orElse(HttpStatus.FORBIDDEN), message);
|
||||
}
|
||||
|
||||
@ExceptionHandler(Throwable.class)
|
||||
protected ResponseEntity<CustomErrorResponse> handleOtherExceptions(
|
||||
final RuntimeException exc, final WebRequest request) {
|
||||
final var message = firstLine(NestedExceptionUtils.getMostSpecificCause(exc).getMessage());
|
||||
return errorResponse(request, httpStatus(message).orElse(HttpStatus.FORBIDDEN), message);
|
||||
}
|
||||
|
||||
private Optional<HttpStatus> httpStatus(final String message) {
|
||||
if (message.startsWith("ERROR: [")) {
|
||||
for (HttpStatus status : HttpStatus.values()) {
|
||||
@ -48,10 +55,10 @@ public class RestResponseEntityExceptionHandler
|
||||
|
||||
private static ResponseEntity<CustomErrorResponse> errorResponse(
|
||||
final WebRequest request,
|
||||
final HttpStatus conflict,
|
||||
final HttpStatus httpStatus,
|
||||
final String message) {
|
||||
return new ResponseEntity<>(
|
||||
new CustomErrorResponse(request.getContextPath(), conflict, message), conflict);
|
||||
new CustomErrorResponse(request.getContextPath(), httpStatus, message), httpStatus);
|
||||
}
|
||||
|
||||
private String firstLine(final String message) {
|
||||
|
@ -17,4 +17,5 @@ public interface CustomerRepository extends Repository<CustomerEntity, UUID> {
|
||||
|
||||
CustomerEntity save(final CustomerEntity entity);
|
||||
|
||||
long count();
|
||||
}
|
||||
|
@ -7,10 +7,14 @@ import net.hostsharing.hsadminng.generated.api.v1.model.RbacUserResource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.transaction.Transactional;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static net.hostsharing.hsadminng.Mapper.map;
|
||||
import static net.hostsharing.hsadminng.Mapper.mapList;
|
||||
|
||||
@RestController
|
||||
@ -19,15 +23,36 @@ public class RbacUserController implements RbacusersApi {
|
||||
@Autowired
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private EntityManager em;
|
||||
|
||||
@Autowired
|
||||
private RbacUserRepository rbacUserRepository;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public ResponseEntity<RbacUserResource> createUser(
|
||||
@RequestBody final RbacUserResource body
|
||||
) {
|
||||
if (body.getUuid() == null) {
|
||||
body.setUuid(UUID.randomUUID());
|
||||
}
|
||||
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();
|
||||
return ResponseEntity.created(uri).body(map(saved, RbacUserResource.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public ResponseEntity<List<RbacUserResource>> listUsers(
|
||||
@RequestHeader(name = "current-user") String currentUserName,
|
||||
@RequestHeader(name = "assumed-roles", required = false) String assumedRoles,
|
||||
@RequestParam(name="name", required = false) 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()) {
|
||||
@ -39,9 +64,9 @@ public class RbacUserController implements RbacusersApi {
|
||||
@Override
|
||||
@Transactional
|
||||
public ResponseEntity<List<RbacUserPermissionResource>> listUserPermissions(
|
||||
@RequestHeader(name = "current-user") String currentUserName,
|
||||
@RequestHeader(name = "assumed-roles", required = false) String assumedRoles,
|
||||
@PathVariable(name= "userName") 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()) {
|
||||
|
@ -1,16 +1,41 @@
|
||||
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;
|
||||
|
||||
public interface RbacUserRepository extends Repository<RbacUserEntity, UUID> {
|
||||
|
||||
@Query("SELECT u FROM RbacUserEntity u WHERE :userName is null or u.name like concat(:userName, '%')")
|
||||
List<RbacUserEntity> findByOptionalNameLike(final String userName);
|
||||
@Query("""
|
||||
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 * FROM grantedPermissions(:userName)", nativeQuery = true)
|
||||
RbacUserEntity findByUuid(UUID uuid);
|
||||
|
||||
@Query(value = "select * from grantedPermissions(:userName)", nativeQuery = true)
|
||||
List<RbacUserPermission> findPermissionsOfUser(String userName);
|
||||
|
||||
/*
|
||||
Can't use save/saveAndFlush from SpringData because the uuid is not generated on the entity level,
|
||||
but explicitly, and then SpringData check's if it exists using an SQL SELECT.
|
||||
And SQL SELECT needs a currentUser which we don't yet have in the case of self registration.
|
||||
*/
|
||||
@Modifying
|
||||
@Query(value = "insert into RBacUser_RV (uuid, name) values( :#{#newUser.uuid}, :#{#newUser.name})", nativeQuery = true)
|
||||
void insert(@Param("newUser") final RbacUserEntity newUser);
|
||||
|
||||
default RbacUserEntity create(final RbacUserEntity rbacUserEntity) {
|
||||
if (rbacUserEntity.getUuid() == null) {
|
||||
rbacUserEntity.setUuid(UUID.randomUUID());
|
||||
}
|
||||
insert(rbacUserEntity);
|
||||
return rbacUserEntity;
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,13 @@ components:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
Forbidden:
|
||||
description: The current user or none of the assumed or roles is granted access to the .
|
||||
description: The current user or none of the assumed or roles is granted access to the resource.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
Conflict:
|
||||
description: The request could not be completed due to a conflict with the current state of the target resource.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
|
@ -21,19 +21,28 @@ get:
|
||||
items:
|
||||
$ref: './api-definition/rbac-user-schemas.yaml#/components/schemas/RbacUser'
|
||||
"401":
|
||||
description: if the 'current-user' cannot be identified
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: './api-definition/rbac-user-schemas.yaml#/components/schemas/RbacUser'
|
||||
$ref: './api-definition/error-responses.yaml#/components/responses/Unauthorized'
|
||||
"403":
|
||||
description: if the 'current-user' is not allowed to assume any of the roles
|
||||
from 'assumed-roles'
|
||||
$ref: './api-definition/error-responses.yaml#/components/responses/Forbidden'
|
||||
|
||||
post:
|
||||
tags:
|
||||
- rbacusers
|
||||
description: Create a new RBAC user.
|
||||
operationId: createUser
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: './api-definition/rbac-user-schemas.yaml#/components/schemas/RbacUser'
|
||||
responses:
|
||||
"201":
|
||||
description: Created
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: './api-definition/rbac-user-schemas.yaml#/components/schemas/RbacUser'
|
||||
$ref: './api-definition/rbac-user-schemas.yaml#/components/schemas/RbacUser'
|
||||
"409":
|
||||
$ref: './api-definition/error-responses.yaml#/components/responses/Conflict'
|
||||
|
||||
|
@ -59,6 +59,22 @@ begin
|
||||
end;
|
||||
$$;
|
||||
|
||||
create or replace function createRbacUser(refUuid uuid, userName varchar)
|
||||
returns uuid
|
||||
called on null input
|
||||
language plpgsql as $$
|
||||
begin
|
||||
insert
|
||||
into RbacReference as r (uuid, type)
|
||||
values ( coalesce(refUuid, uuid_generate_v4()), 'RbacUser')
|
||||
returning r.uuid into refUuid;
|
||||
insert
|
||||
into RbacUser (uuid, name)
|
||||
values (refUuid, userName);
|
||||
return refUuid;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create or replace function findRbacUserId(userName varchar)
|
||||
returns uuid
|
||||
returns null on null input
|
||||
|
@ -9,11 +9,17 @@
|
||||
*/
|
||||
drop view if exists rbacrole_rv;
|
||||
create or replace view rbacrole_rv as
|
||||
select DISTINCT r.*, o.objectTable,
|
||||
findIdNameByObjectUuid(o.objectTable, o.uuid) as objectIdName
|
||||
from rbacrole as r
|
||||
join rbacobject as o on o.uuid=r.objectuuid
|
||||
where isGranted(currentSubjectIds(), r.uuid);
|
||||
select *
|
||||
-- @formatter:off
|
||||
from (
|
||||
select r.*, o.objectTable,
|
||||
findIdNameByObjectUuid(o.objectTable, o.uuid) as objectIdName
|
||||
from rbacrole as r
|
||||
join rbacobject as o on o.uuid = r.objectuuid
|
||||
where isGranted(currentSubjectIds(), r.uuid)
|
||||
) as unordered
|
||||
-- @formatter:on
|
||||
order by objectIdName;
|
||||
grant all privileges on rbacrole_rv to restricted;
|
||||
--//
|
||||
|
||||
@ -27,13 +33,58 @@ grant all privileges on rbacrole_rv to restricted;
|
||||
*/
|
||||
drop view if exists RbacUser_rv;
|
||||
create or replace view RbacUser_rv as
|
||||
select u.*
|
||||
from RbacUser as u
|
||||
join RbacGrants as g on g.ascendantuuid = u.uuid
|
||||
join rbacrole_rv as r on r.uuid = g.descendantuuid;
|
||||
select distinct *
|
||||
-- @formatter:off
|
||||
from (
|
||||
select usersInRolesOfCurrentUser.*
|
||||
from RbacUser as usersInRolesOfCurrentUser
|
||||
join RbacGrants as g on g.ascendantuuid = usersInRolesOfCurrentUser.uuid
|
||||
join rbacrole_rv as r on r.uuid = g.descendantuuid
|
||||
union
|
||||
select users.*
|
||||
from RbacUser as users
|
||||
where cardinality(assumedRoles()) = 0 and currentUserId() = users.uuid
|
||||
) as unordered
|
||||
-- @formatter:on
|
||||
order by unordered.name;
|
||||
grant all privileges on RbacUser_rv to restricted;
|
||||
--//
|
||||
|
||||
-- ============================================================================
|
||||
--changeset rbac-views-USER-RV-INSERT-TRIGGER:1 endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
Instead of insert trigger function for RbacUser_rv.
|
||||
*/
|
||||
create or replace function insertRbacUser()
|
||||
returns trigger
|
||||
language plpgsql as $$
|
||||
declare
|
||||
refUuid uuid;
|
||||
newUser RbacUser;
|
||||
begin
|
||||
insert
|
||||
into RbacReference as r (uuid, type)
|
||||
values( new.uuid, 'RbacUser')
|
||||
returning r.uuid into refUuid;
|
||||
insert
|
||||
into RbacUser (uuid, name)
|
||||
values (refUuid, new.name)
|
||||
returning * into newUser;
|
||||
return newUser;
|
||||
end;
|
||||
$$;
|
||||
|
||||
/*
|
||||
Creates an instead of insert trigger for the RbacUser_rv view.
|
||||
*/
|
||||
create trigger insertRbacUser_Trigger
|
||||
instead of insert
|
||||
on RbacUser_rv
|
||||
for each row
|
||||
execute function insertRbacUser();
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset rbac-views-OWN-GRANTED-PERMISSIONS-VIEW:1 endDelimiter:--//
|
||||
|
Reference in New Issue
Block a user