implements delete rbacuser
This commit is contained in:
parent
a06feff42e
commit
df48bfc0da
@ -45,6 +45,20 @@ public class RbacUserController implements RbacusersApi {
|
||||
return ResponseEntity.created(uri).body(map(saved, RbacUserResource.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public ResponseEntity<Void> deleteUserByUuid(
|
||||
final String currentUser,
|
||||
final String assumedRoles,
|
||||
final UUID userUuid
|
||||
) {
|
||||
context.define(currentUser, assumedRoles);
|
||||
|
||||
rbacUserRepository.deleteByUuid(userUuid);
|
||||
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public ResponseEntity<RbacUserResource> getUserById(
|
||||
|
@ -41,4 +41,6 @@ public interface RbacUserRepository extends Repository<RbacUserEntity, UUID> {
|
||||
insert(rbacUserEntity);
|
||||
return rbacUserEntity;
|
||||
}
|
||||
|
||||
void deleteByUuid(UUID userUuid);
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ paths:
|
||||
$ref: "./api-definition/rbac-users-with-id-permissions.yaml"
|
||||
|
||||
/api/rbac-users/{userUuid}:
|
||||
$ref: "./api-definition/rbac-users-with-id.yaml"
|
||||
$ref: "./api-definition/rbac-users-with-uuid.yaml"
|
||||
|
||||
/api/rbac-roles:
|
||||
$ref: "./api-definition/rbac-roles.yaml"
|
||||
|
@ -24,3 +24,28 @@ get:
|
||||
$ref: './api-definition/error-responses.yaml#/components/responses/Unauthorized'
|
||||
"403":
|
||||
$ref: './api-definition/error-responses.yaml#/components/responses/Forbidden'
|
||||
|
||||
|
||||
delete:
|
||||
tags:
|
||||
- rbacusers
|
||||
operationId: deleteUserByUuid
|
||||
parameters:
|
||||
- $ref: './api-definition/auth.yaml#/components/parameters/currentUser'
|
||||
- $ref: './api-definition/auth.yaml#/components/parameters/assumedRoles'
|
||||
- name: userUuid
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
description: UUID of the user to delete.
|
||||
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'
|
@ -209,7 +209,9 @@ create or replace view RbacUser_rv as
|
||||
union
|
||||
select users.*
|
||||
from RbacUser as users
|
||||
where cardinality(assumedRoles()) = 0 and currentUserUuid() = users.uuid
|
||||
where cardinality(assumedRoles()) = 0 and
|
||||
(currentUserUuid() = users.uuid or hasGlobalRoleGranted(currentUserUuid()))
|
||||
|
||||
) as unordered
|
||||
-- @formatter:on
|
||||
order by unordered.name;
|
||||
@ -250,7 +252,35 @@ create trigger insertRbacUser_Trigger
|
||||
on RbacUser_rv
|
||||
for each row
|
||||
execute function insertRbacUser();
|
||||
--//
|
||||
|
||||
-- ============================================================================
|
||||
--changeset rbac-views-USER-RV-DELETE-TRIGGER:1 endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
Instead of delete trigger function for RbacUser_RV.
|
||||
*/
|
||||
create or replace function deleteRbacUser()
|
||||
returns trigger
|
||||
language plpgsql as $$
|
||||
begin
|
||||
if currentUserUuid() = old.uuid or hasGlobalRoleGranted(currentUserUuid()) then
|
||||
delete from RbacUser where uuid = old.uuid;
|
||||
return old;
|
||||
end if;
|
||||
raise exception '[403] User % not allowed to delete user uuid %', currentUser(), old.uuid;
|
||||
end; $$;
|
||||
|
||||
/*
|
||||
Creates an instead of delete trigger for the RbacUser_rv view.
|
||||
*/
|
||||
create trigger deleteRbacUser_Trigger
|
||||
instead of delete
|
||||
on RbacUser_rv
|
||||
for each row
|
||||
execute function deleteRbacUser();
|
||||
--/
|
||||
|
||||
-- ============================================================================
|
||||
--changeset rbac-views-OWN-GRANTED-PERMISSIONS-VIEW:1 endDelimiter:--//
|
||||
|
@ -397,10 +397,92 @@ class RbacUserControllerAcceptanceTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class DeleteRbacUser {
|
||||
|
||||
@Test
|
||||
@Accepts({ "USR:D(Create)" })
|
||||
void anybody_canDeleteTheirOwnUser() {
|
||||
|
||||
// given
|
||||
final var givenUser = givenANewUser();
|
||||
|
||||
// @formatter:off
|
||||
final var location = RestAssured
|
||||
.given()
|
||||
.header("current-user", givenUser.getName())
|
||||
.port(port)
|
||||
.when()
|
||||
.delete("http://localhost/api/rbac-users/" + givenUser.getUuid())
|
||||
.then().log().all().assertThat()
|
||||
.statusCode(204);
|
||||
// @formatter:on
|
||||
|
||||
// finally, the user is actually deleted
|
||||
assertThat(rbacUserRepository.findByName(givenUser.getName())).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Accepts({ "USR:D(Create)", "USR:X(Access Control)" })
|
||||
void customerAdmin_canNotDeleteOtherUser() {
|
||||
|
||||
// given
|
||||
final var givenUser = givenANewUser();
|
||||
|
||||
// @formatter:off
|
||||
final var location = RestAssured
|
||||
.given()
|
||||
.header("current-user", "customer-admin@xxx.example.com")
|
||||
.port(port)
|
||||
.when()
|
||||
.delete("http://localhost/api/rbac-users/" + givenUser.getUuid())
|
||||
.then().log().all().assertThat()
|
||||
// that user cannot even see other users, thus the system won't even try to delete
|
||||
.statusCode(204);
|
||||
// @formatter:on
|
||||
|
||||
// finally, the user is still there
|
||||
assertThat(rbacUserRepository.findByName(givenUser.getName())).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Accepts({ "USR:D(Create)", "USR:X(Access Control)" })
|
||||
void globalAdmin_canDeleteArbitraryUser() {
|
||||
|
||||
// given
|
||||
final var givenUser = givenANewUser();
|
||||
|
||||
// @formatter:off
|
||||
final var location = RestAssured
|
||||
.given()
|
||||
.header("current-user", "mike@example.org")
|
||||
.port(port)
|
||||
.when()
|
||||
.delete("http://localhost/api/rbac-users/" + givenUser.getUuid())
|
||||
.then().log().all().assertThat()
|
||||
.statusCode(204);
|
||||
// @formatter:on
|
||||
|
||||
// finally, the user is actually deleted
|
||||
assertThat(rbacUserRepository.findByName(givenUser.getName())).isNull();
|
||||
}
|
||||
}
|
||||
|
||||
RbacUserEntity findRbacUserByName(final String userName) {
|
||||
return jpaAttempt.transacted(() -> {
|
||||
context.define("mike@example.org");
|
||||
return rbacUserRepository.findByName(userName);
|
||||
}).returnedValue();
|
||||
}
|
||||
|
||||
RbacUserEntity givenANewUser() {
|
||||
final var givenUserName = "test-user-" + System.currentTimeMillis() + "@example.com";
|
||||
final var givenUser = jpaAttempt.transacted(() -> {
|
||||
context.define(null);
|
||||
return rbacUserRepository.create(new RbacUserEntity(UUID.randomUUID(), givenUserName));
|
||||
}).assumeSuccessful().returnedValue();
|
||||
assertThat(rbacUserRepository.findByName(givenUser.getName())).isNotNull();
|
||||
return givenUser;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -43,28 +43,9 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest {
|
||||
@Nested
|
||||
class CreateUser {
|
||||
|
||||
@Test
|
||||
public void anyoneCanCreateTheirOwnUser() {
|
||||
// given
|
||||
final var givenNewUserName = "test-user-" + System.currentTimeMillis() + "@example.com";
|
||||
context(null);
|
||||
|
||||
// when
|
||||
final var result = rbacUserRepository.create(
|
||||
new RbacUserEntity(null, givenNewUserName));
|
||||
|
||||
// then the persisted user is returned
|
||||
assertThat(result).isNotNull().extracting(RbacUserEntity::getName).isEqualTo(givenNewUserName);
|
||||
|
||||
// and the new user entity can be fetched by the user itself
|
||||
context(givenNewUserName);
|
||||
assertThat(em.find(RbacUserEntity.class, result.getUuid()))
|
||||
.isNotNull().extracting(RbacUserEntity::getName).isEqualTo(givenNewUserName);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional(propagation = Propagation.NEVER)
|
||||
void anyoneCanCreateTheirOwnUser_committed() {
|
||||
void anyoneCanCreateTheirOwnUser() {
|
||||
|
||||
// given:
|
||||
final var givenUuid = UUID.randomUUID();
|
||||
@ -72,7 +53,7 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest {
|
||||
|
||||
// when:
|
||||
final var result = jpaAttempt.transacted(() -> {
|
||||
context("customer-admin@xxx.example.com");
|
||||
context(null);
|
||||
return rbacUserRepository.create(new RbacUserEntity(givenUuid, newUserName));
|
||||
});
|
||||
|
||||
@ -80,11 +61,36 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest {
|
||||
assertThat(result.wasSuccessful()).isTrue();
|
||||
assertThat(result.returnedValue()).isNotNull()
|
||||
.extracting(RbacUserEntity::getUuid).isEqualTo(givenUuid);
|
||||
jpaAttempt.transacted(() -> {
|
||||
context(newUserName);
|
||||
assertThat(em.find(RbacUserEntity.class, givenUuid))
|
||||
.isNotNull().extracting(RbacUserEntity::getName).isEqualTo(newUserName);
|
||||
assertThat(rbacUserRepository.findByName(result.returnedValue().getName())).isNotNull();
|
||||
// jpaAttempt.transacted(() -> {
|
||||
// context(givenUser.getName());
|
||||
// assertThat(em.find(RbacUserEntity.class, givenUser.getUuid()))
|
||||
// .isNotNull().extracting(RbacUserEntity::getName).isEqualTo(givenUser.getName());
|
||||
// }).assertSuccessful();
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class DeleteUser {
|
||||
|
||||
@Test
|
||||
@Transactional(propagation = Propagation.NEVER)
|
||||
public void anyoneCanDeleteTheirOwnUser() {
|
||||
// given
|
||||
final RbacUserEntity givenUser = givenANewUser();
|
||||
|
||||
// when
|
||||
final var result = jpaAttempt.transacted(() -> {
|
||||
context(givenUser.getName());
|
||||
rbacUserRepository.deleteByUuid(givenUser.getUuid());
|
||||
});
|
||||
|
||||
// then the user is deleted
|
||||
result.assertSuccessful();
|
||||
assertThat(rbacUserRepository.findByName(givenUser.getName())).isNull();
|
||||
// jpaAttempt.transacted(() -> {
|
||||
// assertThat(rbacUserRepository.findByName(givenUser.getName())).isNull();
|
||||
// }).assertSuccessful();
|
||||
}
|
||||
}
|
||||
|
||||
@ -392,6 +398,16 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest {
|
||||
return rbacUserRepository.findByName(userName).getUuid();
|
||||
}
|
||||
|
||||
RbacUserEntity givenANewUser() {
|
||||
final var givenUserName = "test-user-" + System.currentTimeMillis() + "@example.com";
|
||||
final var givenUser = jpaAttempt.transacted(() -> {
|
||||
context(null);
|
||||
return rbacUserRepository.create(new RbacUserEntity(UUID.randomUUID(), givenUserName));
|
||||
}).assumeSuccessful().returnedValue();
|
||||
assertThat(rbacUserRepository.findByName(givenUser.getName())).isNotNull();
|
||||
return givenUser;
|
||||
}
|
||||
|
||||
void exactlyTheseRbacUsersAreReturned(final List<RbacUserEntity> actualResult, final String... expectedUserNames) {
|
||||
assertThat(actualResult)
|
||||
.extracting(RbacUserEntity::getName)
|
||||
|
@ -11,6 +11,7 @@ import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assumptions.assumeThat;
|
||||
|
||||
/**
|
||||
* Wraps the 'when' part of a DataJpaTest to improve readability of tests.
|
||||
@ -53,6 +54,7 @@ public class JpaAttempt {
|
||||
|
||||
public <T> JpaResult<T> transacted(final Supplier<T> code) {
|
||||
try {
|
||||
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
return JpaResult.forValue(
|
||||
transactionTemplate.execute(transactionStatus -> code.get()));
|
||||
} catch (final RuntimeException exc) {
|
||||
@ -131,11 +133,19 @@ public class JpaAttempt {
|
||||
}
|
||||
|
||||
public JpaResult<T> assumeSuccessful() {
|
||||
assertThat(exception).isNull();
|
||||
;
|
||||
assumeThat(exception).as(getSensibleMessage(exception)).isNull();
|
||||
return this;
|
||||
}
|
||||
|
||||
public JpaResult<T> assertSuccessful() {
|
||||
assertThat(exception).as(getSensibleMessage(exception)).isNull();
|
||||
return this;
|
||||
}
|
||||
|
||||
private String getSensibleMessage(final RuntimeException exception) {
|
||||
return exception != null ? NestedExceptionUtils.getRootCause(exception).getMessage() : null;
|
||||
}
|
||||
|
||||
private String firstRootCauseMessageLineOf(final RuntimeException exception) {
|
||||
final var rootCause = NestedExceptionUtils.getRootCause(exception);
|
||||
return Optional.ofNullable(rootCause)
|
||||
|
Loading…
Reference in New Issue
Block a user