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));
|
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
|
@Override
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public ResponseEntity<RbacUserResource> getUserById(
|
public ResponseEntity<RbacUserResource> getUserById(
|
||||||
|
@ -41,4 +41,6 @@ public interface RbacUserRepository extends Repository<RbacUserEntity, UUID> {
|
|||||||
insert(rbacUserEntity);
|
insert(rbacUserEntity);
|
||||||
return rbacUserEntity;
|
return rbacUserEntity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void deleteByUuid(UUID userUuid);
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ paths:
|
|||||||
$ref: "./api-definition/rbac-users-with-id-permissions.yaml"
|
$ref: "./api-definition/rbac-users-with-id-permissions.yaml"
|
||||||
|
|
||||||
/api/rbac-users/{userUuid}:
|
/api/rbac-users/{userUuid}:
|
||||||
$ref: "./api-definition/rbac-users-with-id.yaml"
|
$ref: "./api-definition/rbac-users-with-uuid.yaml"
|
||||||
|
|
||||||
/api/rbac-roles:
|
/api/rbac-roles:
|
||||||
$ref: "./api-definition/rbac-roles.yaml"
|
$ref: "./api-definition/rbac-roles.yaml"
|
||||||
|
@ -24,3 +24,28 @@ get:
|
|||||||
$ref: './api-definition/error-responses.yaml#/components/responses/Unauthorized'
|
$ref: './api-definition/error-responses.yaml#/components/responses/Unauthorized'
|
||||||
"403":
|
"403":
|
||||||
$ref: './api-definition/error-responses.yaml#/components/responses/Forbidden'
|
$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
|
union
|
||||||
select users.*
|
select users.*
|
||||||
from RbacUser as 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
|
) as unordered
|
||||||
-- @formatter:on
|
-- @formatter:on
|
||||||
order by unordered.name;
|
order by unordered.name;
|
||||||
@ -250,7 +252,35 @@ create trigger insertRbacUser_Trigger
|
|||||||
on RbacUser_rv
|
on RbacUser_rv
|
||||||
for each row
|
for each row
|
||||||
execute function insertRbacUser();
|
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:--//
|
--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) {
|
RbacUserEntity findRbacUserByName(final String userName) {
|
||||||
return jpaAttempt.transacted(() -> {
|
return jpaAttempt.transacted(() -> {
|
||||||
context.define("mike@example.org");
|
context.define("mike@example.org");
|
||||||
return rbacUserRepository.findByName(userName);
|
return rbacUserRepository.findByName(userName);
|
||||||
}).returnedValue();
|
}).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
|
@Nested
|
||||||
class CreateUser {
|
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
|
@Test
|
||||||
@Transactional(propagation = Propagation.NEVER)
|
@Transactional(propagation = Propagation.NEVER)
|
||||||
void anyoneCanCreateTheirOwnUser_committed() {
|
void anyoneCanCreateTheirOwnUser() {
|
||||||
|
|
||||||
// given:
|
// given:
|
||||||
final var givenUuid = UUID.randomUUID();
|
final var givenUuid = UUID.randomUUID();
|
||||||
@ -72,7 +53,7 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
|
|
||||||
// when:
|
// when:
|
||||||
final var result = jpaAttempt.transacted(() -> {
|
final var result = jpaAttempt.transacted(() -> {
|
||||||
context("customer-admin@xxx.example.com");
|
context(null);
|
||||||
return rbacUserRepository.create(new RbacUserEntity(givenUuid, newUserName));
|
return rbacUserRepository.create(new RbacUserEntity(givenUuid, newUserName));
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -80,11 +61,36 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
assertThat(result.wasSuccessful()).isTrue();
|
assertThat(result.wasSuccessful()).isTrue();
|
||||||
assertThat(result.returnedValue()).isNotNull()
|
assertThat(result.returnedValue()).isNotNull()
|
||||||
.extracting(RbacUserEntity::getUuid).isEqualTo(givenUuid);
|
.extracting(RbacUserEntity::getUuid).isEqualTo(givenUuid);
|
||||||
jpaAttempt.transacted(() -> {
|
assertThat(rbacUserRepository.findByName(result.returnedValue().getName())).isNotNull();
|
||||||
context(newUserName);
|
// jpaAttempt.transacted(() -> {
|
||||||
assertThat(em.find(RbacUserEntity.class, givenUuid))
|
// context(givenUser.getName());
|
||||||
.isNotNull().extracting(RbacUserEntity::getName).isEqualTo(newUserName);
|
// 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();
|
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) {
|
void exactlyTheseRbacUsersAreReturned(final List<RbacUserEntity> actualResult, final String... expectedUserNames) {
|
||||||
assertThat(actualResult)
|
assertThat(actualResult)
|
||||||
.extracting(RbacUserEntity::getName)
|
.extracting(RbacUserEntity::getName)
|
||||||
|
@ -11,6 +11,7 @@ import java.util.Optional;
|
|||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
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.
|
* 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) {
|
public <T> JpaResult<T> transacted(final Supplier<T> code) {
|
||||||
try {
|
try {
|
||||||
|
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||||
return JpaResult.forValue(
|
return JpaResult.forValue(
|
||||||
transactionTemplate.execute(transactionStatus -> code.get()));
|
transactionTemplate.execute(transactionStatus -> code.get()));
|
||||||
} catch (final RuntimeException exc) {
|
} catch (final RuntimeException exc) {
|
||||||
@ -131,11 +133,19 @@ public class JpaAttempt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public JpaResult<T> assumeSuccessful() {
|
public JpaResult<T> assumeSuccessful() {
|
||||||
assertThat(exception).isNull();
|
assumeThat(exception).as(getSensibleMessage(exception)).isNull();
|
||||||
;
|
|
||||||
return this;
|
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) {
|
private String firstRootCauseMessageLineOf(final RuntimeException exception) {
|
||||||
final var rootCause = NestedExceptionUtils.getRootCause(exception);
|
final var rootCause = NestedExceptionUtils.getRootCause(exception);
|
||||||
return Optional.ofNullable(rootCause)
|
return Optional.ofNullable(rootCause)
|
||||||
|
Loading…
Reference in New Issue
Block a user