diff --git a/src/main/java/org/tkit/onecx/permission/domain/daos/AssignmentDAO.java b/src/main/java/org/tkit/onecx/permission/domain/daos/AssignmentDAO.java index 711a78d..23a42c5 100644 --- a/src/main/java/org/tkit/onecx/permission/domain/daos/AssignmentDAO.java +++ b/src/main/java/org/tkit/onecx/permission/domain/daos/AssignmentDAO.java @@ -1,16 +1,19 @@ package org.tkit.onecx.permission.domain.daos; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.NoResultException; +import jakarta.persistence.criteria.Predicate; import jakarta.transaction.Transactional; import org.tkit.onecx.permission.domain.criteria.AssignmentSearchCriteria; import org.tkit.onecx.permission.domain.models.Assignment; import org.tkit.onecx.permission.domain.models.Assignment_; import org.tkit.onecx.permission.domain.models.Permission_; +import org.tkit.onecx.permission.domain.models.Role_; import org.tkit.quarkus.jpa.daos.AbstractDAO; import org.tkit.quarkus.jpa.daos.Page; import org.tkit.quarkus.jpa.daos.PageResult; @@ -59,6 +62,24 @@ public PageResult findByCriteria(AssignmentSearchCriteria criteria) } } + public void deleteByRoleAndPermissionId(String roleId, List permissionId) { + var cb = getEntityManager().getCriteriaBuilder(); + var dq = this.deleteQuery(); + var root = dq.from(Assignment.class); + + List predicates = new ArrayList<>(); + + predicates.add(cb.equal(root.get(Assignment_.ROLE).get(Role_.ID), roleId)); + + if (permissionId != null) { + predicates.add(root.get(Assignment_.PERMISSION).get(Permission_.ID).in(permissionId)); + } + + dq.where(cb.and(predicates.toArray(new Predicate[0]))); + + this.getEntityManager().createQuery(dq).executeUpdate(); + } + public enum ErrorKeys { FIND_ENTITY_BY_ID_FAILED, diff --git a/src/main/java/org/tkit/onecx/permission/domain/daos/PermissionDAO.java b/src/main/java/org/tkit/onecx/permission/domain/daos/PermissionDAO.java index 413d383..829a678 100644 --- a/src/main/java/org/tkit/onecx/permission/domain/daos/PermissionDAO.java +++ b/src/main/java/org/tkit/onecx/permission/domain/daos/PermissionDAO.java @@ -53,6 +53,18 @@ public List loadByAppId(String appId) { } } + public List loadByAppIds(List appId) { + try { + var cb = this.getEntityManager().getCriteriaBuilder(); + var cq = cb.createQuery(Permission.class); + var root = cq.from(Permission.class); + cq.where(root.get(Permission_.APP_ID).in(appId)); + return this.getEntityManager().createQuery(cq).getResultList(); + } catch (Exception ex) { + throw new DAOException(ErrorKeys.ERROR_LOAD_BY_APP_ID, ex); + } + } + public List findPermissionForUser(String appId, List roles) { try { var cb = this.getEntityManager().getCriteriaBuilder(); diff --git a/src/main/java/org/tkit/onecx/permission/rs/internal/controllers/AssignmentRestController.java b/src/main/java/org/tkit/onecx/permission/rs/internal/controllers/AssignmentRestController.java index 9186688..4f34752 100644 --- a/src/main/java/org/tkit/onecx/permission/rs/internal/controllers/AssignmentRestController.java +++ b/src/main/java/org/tkit/onecx/permission/rs/internal/controllers/AssignmentRestController.java @@ -1,7 +1,10 @@ package org.tkit.onecx.permission.rs.internal.controllers; +import java.util.List; + import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import jakarta.transaction.Transactional; import jakarta.validation.ConstraintViolationException; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.Response; @@ -15,11 +18,12 @@ import org.tkit.onecx.permission.rs.internal.mappers.AssignmentMapper; import org.tkit.onecx.permission.rs.internal.mappers.ExceptionMapper; import org.tkit.quarkus.jpa.exceptions.ConstraintException; +import org.tkit.quarkus.jpa.models.TraceableEntity; import org.tkit.quarkus.log.cdi.LogService; import gen.org.tkit.onecx.permission.rs.internal.AssignmentInternalApi; import gen.org.tkit.onecx.permission.rs.internal.model.AssignmentSearchCriteriaDTO; -import gen.org.tkit.onecx.permission.rs.internal.model.CreateAssignmentRequestDTO; +import gen.org.tkit.onecx.permission.rs.internal.model.CreateRevokeAssignmentRequestDTO; import gen.org.tkit.onecx.permission.rs.internal.model.ProblemDetailResponseDTO; @LogService @@ -53,6 +57,39 @@ public Response getAssignment(String id) { return Response.ok(mapper.map(data)).build(); } + @Override + @Transactional + public Response revokeAssignments(CreateRevokeAssignmentRequestDTO createRevokeAssignmentRequestDTO) { + var role = roleDAO.findById(createRevokeAssignmentRequestDTO.getRoleId()); + if (role == null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + // case 1 ONLY ROLE ID + if (createRevokeAssignmentRequestDTO.getPermissionId() == null && createRevokeAssignmentRequestDTO.getAppId() == null) { + dao.deleteByRoleAndPermissionId(role.getId(), null); + } + + // case 2 ROLE ID + APP ID + if (createRevokeAssignmentRequestDTO.getAppId() != null) { + var permissions = permissionDAO.loadByAppIds(createRevokeAssignmentRequestDTO.getAppId()); + if (permissions.isEmpty()) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + dao.deleteByRoleAndPermissionId(role.getId(), permissions.stream().map(TraceableEntity::getId).toList()); + } + + // case 3 ROLE ID + permissionID + if (createRevokeAssignmentRequestDTO.getPermissionId() != null) { + var permission = permissionDAO.findById(createRevokeAssignmentRequestDTO.getPermissionId()); + if (permission == null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + dao.deleteByRoleAndPermissionId(role.getId(), List.of(permission.getId())); + } + return Response.status(Response.Status.NO_CONTENT).build(); + } + @Override public Response searchAssignments(AssignmentSearchCriteriaDTO assignmentSearchCriteriaDTO) { var criteria = mapper.map(assignmentSearchCriteriaDTO); @@ -61,22 +98,42 @@ public Response searchAssignments(AssignmentSearchCriteriaDTO assignmentSearchCr } @Override - public Response createAssignment(CreateAssignmentRequestDTO createAssignmentRequestDTO) { + @Transactional + public Response createAssignment(CreateRevokeAssignmentRequestDTO createAssignmentRequestDTO) { var role = roleDAO.findById(createAssignmentRequestDTO.getRoleId()); if (role == null) { return Response.status(Response.Status.NOT_FOUND).build(); } - var permission = permissionDAO.findById(createAssignmentRequestDTO.getPermissionId()); - if (permission == null) { - return Response.status(Response.Status.NOT_FOUND).build(); + + // single assignment + if (createAssignmentRequestDTO.getPermissionId() != null) { + var permission = permissionDAO.findById(createAssignmentRequestDTO.getPermissionId()); + if (permission == null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + var data = mapper.create(role, permission); + data = dao.create(data); + return Response + .created(uriInfo.getAbsolutePathBuilder().path(data.getId()).build()) + .entity(mapper.mapResponseList(null, data)) + .build(); + } + + // batch operation for all permissions by appId + if (createAssignmentRequestDTO.getAppId() != null) { + var permissions = permissionDAO.loadByAppIds(createAssignmentRequestDTO.getAppId()); + if (permissions.isEmpty()) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + var data = mapper.createList(role, permissions); + + dao.deleteByRoleAndPermissionId(role.getId(), null); + var result = dao.create(data).toList(); + return Response.status(Response.Status.CREATED).entity(mapper.mapResponseList(result, null)).build(); } - var data = mapper.create(role, permission); - data = dao.create(data); - return Response - .created(uriInfo.getAbsolutePathBuilder().path(data.getId()).build()) - .entity(mapper.map(data)) - .build(); + return Response.status(Response.Status.NOT_FOUND).build(); } @Override diff --git a/src/main/java/org/tkit/onecx/permission/rs/internal/log/InternalLogParam.java b/src/main/java/org/tkit/onecx/permission/rs/internal/log/InternalLogParam.java index 5fb056c..14663bc 100644 --- a/src/main/java/org/tkit/onecx/permission/rs/internal/log/InternalLogParam.java +++ b/src/main/java/org/tkit/onecx/permission/rs/internal/log/InternalLogParam.java @@ -29,10 +29,9 @@ public List getClasses() { return RoleSearchCriteriaDTO.class.getSimpleName() + "[" + d.getPageNumber() + "," + d.getPageSize() + "]"; }), - item(10, CreateAssignmentRequestDTO.class, x -> { - CreateAssignmentRequestDTO d = (CreateAssignmentRequestDTO) x; - return CreateAssignmentRequestDTO.class.getSimpleName() + ":r=" + d.getRoleId() + ",p=" - + d.getPermissionId(); + item(10, CreateRevokeAssignmentRequestDTO.class, x -> { + CreateRevokeAssignmentRequestDTO d = (CreateRevokeAssignmentRequestDTO) x; + return CreateRevokeAssignmentRequestDTO.class.getSimpleName() + ":r=" + d.getRoleId(); }), item(10, CreateRolesRequestDTO.class, x -> x.getClass().getSimpleName() + ": size: " + ((CreateRolesRequestDTO) x).getRoles().size()), diff --git a/src/main/java/org/tkit/onecx/permission/rs/internal/mappers/AssignmentMapper.java b/src/main/java/org/tkit/onecx/permission/rs/internal/mappers/AssignmentMapper.java index 2885012..64126fa 100644 --- a/src/main/java/org/tkit/onecx/permission/rs/internal/mappers/AssignmentMapper.java +++ b/src/main/java/org/tkit/onecx/permission/rs/internal/mappers/AssignmentMapper.java @@ -1,5 +1,8 @@ package org.tkit.onecx.permission.rs.internal.mappers; +import java.util.ArrayList; +import java.util.List; + import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.tkit.onecx.permission.domain.criteria.AssignmentSearchCriteria; @@ -12,6 +15,7 @@ import gen.org.tkit.onecx.permission.rs.internal.model.AssignmentDTO; import gen.org.tkit.onecx.permission.rs.internal.model.AssignmentPageResultDTO; import gen.org.tkit.onecx.permission.rs.internal.model.AssignmentSearchCriteriaDTO; +import gen.org.tkit.onecx.permission.rs.internal.model.CreateAssignmentResponseDTO; @Mapper(uses = { OffsetDateTimeMapper.class }) public interface AssignmentMapper { @@ -36,4 +40,22 @@ public interface AssignmentMapper { @Mapping(target = "appId", source = "permission.appId") AssignmentDTO map(Assignment data); + + default List createList(Role role, List permissions) { + List assignments = new ArrayList<>(); + permissions.forEach(permission -> assignments.add(create(role, permission))); + return assignments; + } + + default CreateAssignmentResponseDTO mapResponseList(List assignmentList, Assignment singleAssignment) { + CreateAssignmentResponseDTO responseDTO = new CreateAssignmentResponseDTO(); + if (assignmentList != null) { + responseDTO.setAssignments(mapList(assignmentList)); + } else { + responseDTO.setAssignments(List.of(map(singleAssignment))); + } + return responseDTO; + } + + List mapList(List data); } diff --git a/src/main/openapi/onecx-permission-internal-openapi.yaml b/src/main/openapi/onecx-permission-internal-openapi.yaml index 40b52cc..07cedca 100644 --- a/src/main/openapi/onecx-permission-internal-openapi.yaml +++ b/src/main/openapi/onecx-permission-internal-openapi.yaml @@ -49,7 +49,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/CreateAssignmentRequest' + $ref: '#/components/schemas/CreateRevokeAssignmentRequest' responses: 201: description: New assignment created @@ -62,7 +62,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Assignment' + $ref: '#/components/schemas/CreateAssignmentResponse' 404: description: Data not found 400: @@ -71,6 +71,27 @@ paths: application/json: schema: $ref: '#/components/schemas/ProblemDetailResponse' + /internal/assignments/revoke: + post: + tags: + - assignmentInternal + description: delete assignments by criteria + operationId: revokeAssignments + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateRevokeAssignmentRequest' + responses: + 204: + description: New assignment created + 400: + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetailResponse' /internal/assignments/{id}: get: tags: @@ -121,12 +142,6 @@ paths: responses: 201: description: New role created - headers: - Location: - required: true - schema: - type: string - format: url content: application/json: schema: @@ -318,16 +333,26 @@ components: type: array items: $ref: '#/components/schemas/Assignment' - CreateAssignmentRequest: + CreateRevokeAssignmentRequest: type: object required: - roleId - - permissionId properties: roleId: type: string permissionId: type: string + appId: + type: array + items: + type: string + CreateAssignmentResponse: + type: object + properties: + assignments: + type: array + items: + $ref: '#/components/schemas/Assignment' Assignment: type: object properties: @@ -348,6 +373,8 @@ components: type: string appId: type: string + id: + type: string UpdateRoleRequest: type: object required: diff --git a/src/test/java/org/tkit/onecx/permission/domain/daos/PermissionDAOTest.java b/src/test/java/org/tkit/onecx/permission/domain/daos/PermissionDAOTest.java index e37a4bc..64907e8 100644 --- a/src/test/java/org/tkit/onecx/permission/domain/daos/PermissionDAOTest.java +++ b/src/test/java/org/tkit/onecx/permission/domain/daos/PermissionDAOTest.java @@ -19,6 +19,8 @@ void methodExceptionTests() { PermissionDAO.ErrorKeys.ERROR_FIND_PERMISSION_FOR_USER); methodExceptionTests(() -> dao.loadByAppId(null), PermissionDAO.ErrorKeys.ERROR_LOAD_BY_APP_ID); + methodExceptionTests(() -> dao.loadByAppIds(null), + PermissionDAO.ErrorKeys.ERROR_LOAD_BY_APP_ID); methodExceptionTests(() -> dao.findByCriteria(null), PermissionDAO.ErrorKeys.ERROR_FIND_PERMISSION_BY_CRITERIA); } diff --git a/src/test/java/org/tkit/onecx/permission/rs/internal/controllers/AssignmentRestControllerTest.java b/src/test/java/org/tkit/onecx/permission/rs/internal/controllers/AssignmentRestControllerTest.java index 3dc5a84..5a4bdc9 100644 --- a/src/test/java/org/tkit/onecx/permission/rs/internal/controllers/AssignmentRestControllerTest.java +++ b/src/test/java/org/tkit/onecx/permission/rs/internal/controllers/AssignmentRestControllerTest.java @@ -3,14 +3,11 @@ import static io.restassured.RestAssured.given; import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.from; import static org.jboss.resteasy.reactive.RestResponse.Status.*; import static org.jboss.resteasy.reactive.RestResponse.Status.BAD_REQUEST; import java.util.List; -import jakarta.ws.rs.core.HttpHeaders; - import org.junit.jupiter.api.Test; import org.tkit.onecx.permission.rs.internal.mappers.ExceptionMapper; import org.tkit.onecx.permission.test.AbstractTest; @@ -28,7 +25,7 @@ class AssignmentRestControllerTest extends AbstractTest { @Test void createAssignment() { // create Assignment - var requestDTO = new CreateAssignmentRequestDTO(); + var requestDTO = new CreateRevokeAssignmentRequestDTO(); requestDTO.setPermissionId("p11"); requestDTO.setRoleId("r11"); @@ -39,19 +36,11 @@ void createAssignment() { .post() .then() .statusCode(CREATED.getStatusCode()) - .extract().header(HttpHeaders.LOCATION); + .extract().as(CreateAssignmentResponseDTO.class); - var dto = given() - .contentType(APPLICATION_JSON) - .get(uri) - .then() - .statusCode(OK.getStatusCode()) - .extract() - .body().as(AssignmentDTO.class); - - assertThat(dto).isNotNull() - .returns(requestDTO.getRoleId(), from(AssignmentDTO::getRoleId)) - .returns(requestDTO.getPermissionId(), from(AssignmentDTO::getPermissionId)); + assertThat(uri).isNotNull(); + assertThat(uri.getAssignments().get(0).getRoleId()).isEqualTo(requestDTO.getRoleId()); + assertThat(uri.getAssignments().get(0).getPermissionId()).isEqualTo(requestDTO.getPermissionId()); // create Role without body var exception = given() @@ -63,10 +52,10 @@ void createAssignment() { .extract().as(ProblemDetailResponseDTO.class); assertThat(exception.getErrorCode()).isEqualTo(ExceptionMapper.ErrorKeys.CONSTRAINT_VIOLATIONS.name()); - assertThat(exception.getDetail()).isEqualTo("createAssignment.createAssignmentRequestDTO: must not be null"); + assertThat(exception.getDetail()).isEqualTo("createAssignment.createRevokeAssignmentRequestDTO: must not be null"); // create Role with existing name - requestDTO = new CreateAssignmentRequestDTO(); + requestDTO = new CreateRevokeAssignmentRequestDTO(); requestDTO.setPermissionId("p13"); requestDTO.setRoleId("r13"); @@ -87,7 +76,7 @@ void createAssignment() { @Test void createAssignmentWrong() { // create Assignment - var requestDTO = new CreateAssignmentRequestDTO(); + var requestDTO = new CreateRevokeAssignmentRequestDTO(); requestDTO.setPermissionId("does-not-exists"); requestDTO.setRoleId("r11"); @@ -111,6 +100,142 @@ void createAssignmentWrong() { .statusCode(NOT_FOUND.getStatusCode()); } + @Test + void batchCreateAssignmentsTest() { + // create Assignment + var requestDTO = new CreateRevokeAssignmentRequestDTO(); + requestDTO.setRoleId("r14"); + requestDTO.setAppId(List.of("app1", "app2")); + + var output = given() + .when() + .contentType(APPLICATION_JSON) + .body(requestDTO) + .post() + .then() + .statusCode(CREATED.getStatusCode()) + .extract().as(CreateAssignmentResponseDTO.class); + assertThat(output.getAssignments()).hasSize(7); + + //should return not-found when no permission-id and app-ids are set + var invalidRequestDTO = new CreateRevokeAssignmentRequestDTO(); + invalidRequestDTO.setRoleId("r12"); + given() + .when() + .contentType(APPLICATION_JSON) + .body(invalidRequestDTO) + .post() + .then() + .statusCode(NOT_FOUND.getStatusCode()); + + //should return not-found when no permissions with given appId exists + + var request = new CreateRevokeAssignmentRequestDTO(); + request.setRoleId("r12"); + request.setAppId(List.of("randomAppId")); + + given() + .when() + .contentType(APPLICATION_JSON) + .body(request) + .post() + .then() + .statusCode(NOT_FOUND.getStatusCode()); + + } + + @Test + void revokeAssignmentsByOnlyRoleIdTest() { + var requestDTO = new CreateRevokeAssignmentRequestDTO(); + requestDTO.roleId("r14"); + given() + .when() + .contentType(APPLICATION_JSON) + .body(requestDTO) + .post("/revoke") + .then() + .statusCode(NO_CONTENT.getStatusCode()); + + //check if assignment is gone + given() + .when() + .get("a12") + .then() + .statusCode(NOT_FOUND.getStatusCode()); + + //not-exiting role id + requestDTO.setRoleId("not-existing"); + given() + .when() + .contentType(APPLICATION_JSON) + .body(requestDTO) + .post("/revoke") + .then() + .statusCode(NOT_FOUND.getStatusCode()); + } + + @Test + void revokeAssignmentsByRoleIdAndPermissionIdTest() { + var requestDTO = new CreateRevokeAssignmentRequestDTO(); + requestDTO.roleId("r14"); + requestDTO.permissionId("p13"); + given() + .when() + .contentType(APPLICATION_JSON) + .body(requestDTO) + .post("/revoke") + .then() + .statusCode(NO_CONTENT.getStatusCode()); + + //check if assignment is gone + given() + .when() + .get("a12") + .then() + .statusCode(NOT_FOUND.getStatusCode()); + + //not-existing permissionId + requestDTO.setPermissionId("not-existing"); + given() + .when() + .contentType(APPLICATION_JSON) + .body(requestDTO) + .post("/revoke") + .then() + .statusCode(NOT_FOUND.getStatusCode()); + } + + @Test + void revokeAssignmentsByRoleIdAndAppIdsTest() { + var requestDTO = new CreateRevokeAssignmentRequestDTO(); + requestDTO.roleId("r14"); + requestDTO.appId(List.of("app1")); + given() + .when() + .contentType(APPLICATION_JSON) + .body(requestDTO) + .post("/revoke") + .then() + .statusCode(NO_CONTENT.getStatusCode()); + + //check if assignment is gone + given() + .when() + .get("a12") + .then() + .statusCode(NOT_FOUND.getStatusCode()); + + //not-existing appIds + requestDTO.setAppId(List.of("not-existing")); + given() + .when() + .contentType(APPLICATION_JSON) + .body(requestDTO) + .post("/revoke") + .then() + .statusCode(NOT_FOUND.getStatusCode()); + } + @Test void getNotFoundAssignment() { given()