From 879d1e3a657f6860c09f4511bbb95750df10d2dd Mon Sep 17 00:00:00 2001 From: Andrej Petras Date: Fri, 12 Jan 2024 16:05:29 +0100 Subject: [PATCH] feat: add external v1 api --- pom.xml | 4 +- .../permission/common/models/TokenConfig.java | 7 +- .../common/services/TokenService.java | 65 ++++++----- .../criteria/AssignmentSearchCriteria.java | 1 + .../criteria/PermissionSearchCriteria.java | 2 - .../WorkspaceAssignmentSearchCriteria.java | 1 + .../WorkspacePermissionSearchCriteria.java | 2 - .../permission/domain/daos/AssignmentDAO.java | 16 +++ .../permission/domain/daos/PermissionDAO.java | 28 +++-- .../domain/daos/WorkspaceAssignmentDAO.java | 17 ++- .../domain/daos/WorkspacePermissionDAO.java | 34 ++++-- .../controllers/PermissionRestController.java | 39 +++++-- .../external/v1/mappers/ExceptionMapper.java | 63 ++++++++++ .../external/v1/mappers/PermissionMapper.java | 40 ++++++- .../onecx-permission-internal-openapi.yaml | 12 +- src/main/openapi/onecx-permission-v1.yaml | 19 ++- src/main/resources/application.properties | 2 - .../daos/WorkspacePermissionDAOTest.java | 2 + .../v1/PermissionRestControllerTest.java | 109 +++++++++++++++++- .../AssignmentRestControllerTest.java | 32 +++++ .../PermissionRestControllerTest.java | 11 +- ...WorkspaceAssignmentRestControllerTest.java | 31 +++++ ...WorkspacePermissionRestControllerTest.java | 10 +- src/test/resources/data/test-internal.xml | 4 + 24 files changed, 453 insertions(+), 98 deletions(-) create mode 100644 src/main/java/io/github/onecx/permission/rs/external/v1/mappers/ExceptionMapper.java diff --git a/pom.xml b/pom.xml index 92d2aa8..e1438ef 100644 --- a/pom.xml +++ b/pom.xml @@ -218,8 +218,8 @@ src/main/openapi/onecx-permission-v1.yaml - gen.io.github.onecx.permission.rs.v1 - gen.io.github.onecx.permission.rs.v1.model + gen.io.github.onecx.permission.rs.external.v1 + gen.io.github.onecx.permission.rs.external.v1.model DTOV1 ApiV1 diff --git a/src/main/java/io/github/onecx/permission/common/models/TokenConfig.java b/src/main/java/io/github/onecx/permission/common/models/TokenConfig.java index c52ab98..e1d0bef 100644 --- a/src/main/java/io/github/onecx/permission/common/models/TokenConfig.java +++ b/src/main/java/io/github/onecx/permission/common/models/TokenConfig.java @@ -1,7 +1,5 @@ package io.github.onecx.permission.common.models; -import java.util.Optional; - import io.smallrye.config.ConfigMapping; import io.smallrye.config.WithDefault; import io.smallrye.config.WithName; @@ -19,9 +17,10 @@ public interface TokenConfig { boolean tokenPublicKeyEnabled(); @WithName("token.claim.separator") - Optional tokenClaimSeparator(); + @WithDefault(" ") + String tokenClaimSeparator(); @WithName("token.claim.path") - @WithDefault("realm_access.roles") + @WithDefault("realm_access/roles") String tokenClaimPath(); } diff --git a/src/main/java/io/github/onecx/permission/common/services/TokenService.java b/src/main/java/io/github/onecx/permission/common/services/TokenService.java index 9f4fa1b..2a7384c 100644 --- a/src/main/java/io/github/onecx/permission/common/services/TokenService.java +++ b/src/main/java/io/github/onecx/permission/common/services/TokenService.java @@ -8,9 +8,12 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import jakarta.json.Json; import jakarta.json.JsonArray; import jakarta.json.JsonObject; +import jakarta.json.JsonValue; +import org.eclipse.microprofile.config.ConfigProvider; import org.jose4j.jws.JsonWebSignature; import org.jose4j.jwt.JwtClaims; import org.jose4j.jwt.MalformedClaimException; @@ -19,6 +22,7 @@ import org.jose4j.lang.JoseException; import io.github.onecx.permission.common.models.TokenConfig; +import io.smallrye.jwt.JsonUtils; import io.smallrye.jwt.auth.principal.JWTAuthContextInfo; import io.smallrye.jwt.auth.principal.JWTParser; import io.smallrye.jwt.auth.principal.ParseException; @@ -30,6 +34,9 @@ public class TokenService { private static final Pattern CLAIM_PATH_PATTERN = Pattern.compile("\\/(?=(?:(?:[^\"]*\"){2})*[^\"]*$)"); + private static final String[] CLAIM_PATH = splitClaimPath( + ConfigProvider.getConfig().getValue("onecx.permission.token.claim.path", String.class)); + @Inject JWTAuthContextInfo authContextInfo; @@ -60,40 +67,41 @@ private List getRoles(String tokenData) var publicKeyLocation = jwtClaims.getIssuer() + config.tokenPublicKeyLocationSuffix(); info = new JWTAuthContextInfo(authContextInfo); info.setPublicKeyLocation(publicKeyLocation); - } - info.setVerifyCertificateThumbprint(false); var token = parser.parse(tokenData, info); - - // return findClaimWithRoles(config, token); - return List.of(); + var first = (JsonValue) token.getClaim(CLAIM_PATH[0]); + return findClaimWithRoles(config, first, CLAIM_PATH); } else { var jws = (JsonWebSignature) JsonWebStructure.fromCompactSerialization(tokenData); + var jwtClaims = JwtClaims.parse(jws.getUnverifiedPayload()); - List list = (List) jwtClaims.flattenClaims().get(config.tokenClaimPath()); - return (List) list; - // jwtClaims.flattenClaims() - // return findClaimWithRoles(config, jwtClaims); - // return List.of(); + var tmp = jwtClaims.getClaimValue(CLAIM_PATH[0]); + var first = replaceClaimValueWithJsonValue(tmp); + return findClaimWithRoles(config, first, CLAIM_PATH); + } + } + + private JsonValue replaceClaimValueWithJsonValue(Object value) { + if (value instanceof String) { + return Json.createValue((String) value); } + return JsonUtils.wrapValue(value); } - private static List findClaimWithRoles(TokenConfig tokenConfig, JsonObject json) { + private static List findClaimWithRoles(TokenConfig config, JsonValue first, String[] path) { - var path = tokenConfig.tokenClaimPath(); - Object claimValue = findClaimValue(path, json, splitClaimPath(path), 0); + JsonValue claimValue = findClaimValue(first, path, 1); if (claimValue instanceof JsonArray) { return convertJsonArrayToList((JsonArray) claimValue); } else if (claimValue != null) { - String sep = tokenConfig.tokenClaimSeparator().isPresent() ? tokenConfig.tokenClaimSeparator().get() : " "; if (claimValue.toString().isBlank()) { return Collections.emptyList(); } - return Arrays.asList(claimValue.toString().split(sep)); + return Arrays.asList(claimValue.toString().split(config.tokenClaimSeparator())); } else { return Collections.emptyList(); } @@ -115,28 +123,25 @@ private static String[] splitClaimPath(String claimPath) { return claimPath.indexOf('/') > 0 ? CLAIM_PATH_PATTERN.split(claimPath) : new String[] { claimPath }; } - private static Object findClaimValue(String claimPath, JsonObject json, String[] pathArray, int step) { - Object claimValue = json.getValue(pathArray[step].replace("\"", "")); - if (claimValue == null) { - log.debug("No claim exists at the path '{}' at the path segment '{}'", claimPath, pathArray[step]); - } else if (step + 1 < pathArray.length) { - if (claimValue instanceof JsonObject) { - int nextStep = step + 1; - return findClaimValue(claimPath, (JsonObject) claimValue, pathArray, nextStep); + private static JsonValue findClaimValue(JsonValue json, String[] pathArray, int step) { + if (json == null) { + log.debug("No claim exists at the path '{}' at the path segment '{}'", pathArray, pathArray[step]); + return null; + } + + if (step < pathArray.length) { + if (json instanceof JsonObject) { + JsonValue claimValue = json.asJsonObject().get(pathArray[step].replace("\"", "")); + return findClaimValue(claimValue, pathArray, step + 1); } else { - log.debug("Claim value at the path '{}' is not a json object", claimPath); + log.debug("Claim value at the path '{}' is not a json object. Step: {}", pathArray, step); } } - - return claimValue; + return json; } public static class TokenException extends RuntimeException { - public TokenException(String message) { - super(message); - } - public TokenException(String message, Throwable t) { super(message, t); } diff --git a/src/main/java/io/github/onecx/permission/domain/criteria/AssignmentSearchCriteria.java b/src/main/java/io/github/onecx/permission/domain/criteria/AssignmentSearchCriteria.java index 4db28ba..2a9bfab 100644 --- a/src/main/java/io/github/onecx/permission/domain/criteria/AssignmentSearchCriteria.java +++ b/src/main/java/io/github/onecx/permission/domain/criteria/AssignmentSearchCriteria.java @@ -7,6 +7,7 @@ @Setter public class AssignmentSearchCriteria { + private String appId; private Integer pageNumber; private Integer pageSize; } diff --git a/src/main/java/io/github/onecx/permission/domain/criteria/PermissionSearchCriteria.java b/src/main/java/io/github/onecx/permission/domain/criteria/PermissionSearchCriteria.java index 7ad0901..f7bca5a 100644 --- a/src/main/java/io/github/onecx/permission/domain/criteria/PermissionSearchCriteria.java +++ b/src/main/java/io/github/onecx/permission/domain/criteria/PermissionSearchCriteria.java @@ -8,8 +8,6 @@ public class PermissionSearchCriteria { private String appId; - private String resource; - private String action; private Integer pageNumber; private Integer pageSize; } diff --git a/src/main/java/io/github/onecx/permission/domain/criteria/WorkspaceAssignmentSearchCriteria.java b/src/main/java/io/github/onecx/permission/domain/criteria/WorkspaceAssignmentSearchCriteria.java index 019ebb7..bf4ca66 100644 --- a/src/main/java/io/github/onecx/permission/domain/criteria/WorkspaceAssignmentSearchCriteria.java +++ b/src/main/java/io/github/onecx/permission/domain/criteria/WorkspaceAssignmentSearchCriteria.java @@ -7,6 +7,7 @@ @Setter public class WorkspaceAssignmentSearchCriteria { + private String workspaceId; private Integer pageNumber; private Integer pageSize; } diff --git a/src/main/java/io/github/onecx/permission/domain/criteria/WorkspacePermissionSearchCriteria.java b/src/main/java/io/github/onecx/permission/domain/criteria/WorkspacePermissionSearchCriteria.java index 4b57d89..852861a 100644 --- a/src/main/java/io/github/onecx/permission/domain/criteria/WorkspacePermissionSearchCriteria.java +++ b/src/main/java/io/github/onecx/permission/domain/criteria/WorkspacePermissionSearchCriteria.java @@ -8,8 +8,6 @@ public class WorkspacePermissionSearchCriteria { private String workspaceId; - private String resource; - private String action; private Integer pageNumber; private Integer pageSize; } diff --git a/src/main/java/io/github/onecx/permission/domain/daos/AssignmentDAO.java b/src/main/java/io/github/onecx/permission/domain/daos/AssignmentDAO.java index 695396b..10aab56 100644 --- a/src/main/java/io/github/onecx/permission/domain/daos/AssignmentDAO.java +++ b/src/main/java/io/github/onecx/permission/domain/daos/AssignmentDAO.java @@ -1,7 +1,11 @@ package io.github.onecx.permission.domain.daos; +import java.util.ArrayList; +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.quarkus.jpa.daos.AbstractDAO; @@ -12,6 +16,8 @@ import io.github.onecx.permission.domain.criteria.AssignmentSearchCriteria; import io.github.onecx.permission.domain.models.Assignment; +import io.github.onecx.permission.domain.models.Assignment_; +import io.github.onecx.permission.domain.models.Permission_; @ApplicationScoped public class AssignmentDAO extends AbstractDAO { @@ -39,6 +45,16 @@ public PageResult findByCriteria(AssignmentSearchCriteria criteria) var cq = cb.createQuery(Assignment.class); var root = cq.from(Assignment.class); + List predicates = new ArrayList<>(); + + if (criteria.getAppId() != null && !criteria.getAppId().isBlank()) { + predicates.add(cb.equal(root.get(Assignment_.permission).get(Permission_.APP_ID), criteria.getAppId())); + } + + if (!predicates.isEmpty()) { + cq.where(predicates.toArray(new Predicate[] {})); + } + cq.orderBy(cb.asc(root.get(TraceableEntity_.CREATION_DATE))); return createPageQuery(cq, Page.of(criteria.getPageNumber(), criteria.getPageSize())).getPageResult(); diff --git a/src/main/java/io/github/onecx/permission/domain/daos/PermissionDAO.java b/src/main/java/io/github/onecx/permission/domain/daos/PermissionDAO.java index 19dc376..8e5187b 100644 --- a/src/main/java/io/github/onecx/permission/domain/daos/PermissionDAO.java +++ b/src/main/java/io/github/onecx/permission/domain/daos/PermissionDAO.java @@ -29,15 +29,9 @@ public PageResult findByCriteria(PermissionSearchCriteria criteria) List predicates = new ArrayList<>(); - if (criteria.getAction() != null && !criteria.getAction().isBlank()) { - predicates.add(cb.like(root.get(Permission_.action), QueryCriteriaUtil.wildcard(criteria.getAction()))); - } if (criteria.getAppId() != null && !criteria.getAppId().isBlank()) { predicates.add(cb.like(root.get(Permission_.appId), QueryCriteriaUtil.wildcard(criteria.getAppId()))); } - if (criteria.getResource() != null && !criteria.getResource().isBlank()) { - predicates.add(cb.like(root.get(Permission_.resource), QueryCriteriaUtil.wildcard(criteria.getResource()))); - } if (!predicates.isEmpty()) { cq.where(predicates.toArray(new Predicate[] {})); @@ -63,7 +57,6 @@ public List loadByAppId(String appId) { public List findPermissionForUser(String appId, List roles) { try { - System.out.println("# " + appId + " R " + roles); var cb = this.getEntityManager().getCriteriaBuilder(); var cq = cb.createQuery(Permission.class); var root = cq.from(Permission.class); @@ -83,8 +76,29 @@ public List findPermissionForUser(String appId, List roles) } } + public List findAllPermissionForUser(List roles) { + try { + var cb = this.getEntityManager().getCriteriaBuilder(); + var cq = cb.createQuery(Permission.class); + var root = cq.from(Permission.class); + + Subquery sq = cq.subquery(String.class); + var subRoot = sq.from(Assignment.class); + sq.select(subRoot.get(Assignment_.PERMISSION_ID)); + sq.where( + subRoot.get(Assignment_.role).get(Role_.name).in(roles)); + + cq.where(root.get(Permission_.id).in(sq)); + + return this.getEntityManager().createQuery(cq).getResultList(); + } catch (Exception ex) { + throw new DAOException(ErrorKeys.ERROR_FIND_ALL_PERMISSION_FOR_USER, ex); + } + } + public enum ErrorKeys { + ERROR_FIND_ALL_PERMISSION_FOR_USER, ERROR_FIND_PERMISSION_FOR_USER, ERROR_LOAD_BY_APP_ID, ERROR_FIND_PERMISSION_BY_CRITERIA; diff --git a/src/main/java/io/github/onecx/permission/domain/daos/WorkspaceAssignmentDAO.java b/src/main/java/io/github/onecx/permission/domain/daos/WorkspaceAssignmentDAO.java index 6ffeaed..7d255ba 100644 --- a/src/main/java/io/github/onecx/permission/domain/daos/WorkspaceAssignmentDAO.java +++ b/src/main/java/io/github/onecx/permission/domain/daos/WorkspaceAssignmentDAO.java @@ -1,7 +1,11 @@ package io.github.onecx.permission.domain.daos; +import java.util.ArrayList; +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.quarkus.jpa.daos.AbstractDAO; @@ -11,7 +15,7 @@ import org.tkit.quarkus.jpa.models.TraceableEntity_; import io.github.onecx.permission.domain.criteria.WorkspaceAssignmentSearchCriteria; -import io.github.onecx.permission.domain.models.WorkspaceAssignment; +import io.github.onecx.permission.domain.models.*; @ApplicationScoped public class WorkspaceAssignmentDAO extends AbstractDAO { @@ -39,6 +43,17 @@ public PageResult findByCriteria(WorkspaceAssignmentSearchC var cq = cb.createQuery(WorkspaceAssignment.class); var root = cq.from(WorkspaceAssignment.class); + List predicates = new ArrayList<>(); + + if (criteria.getWorkspaceId() != null && !criteria.getWorkspaceId().isBlank()) { + predicates.add(cb.equal(root.get(WorkspaceAssignment_.permission).get(WorkspacePermission_.WORKSPACE_ID), + criteria.getWorkspaceId())); + } + + if (!predicates.isEmpty()) { + cq.where(predicates.toArray(new Predicate[] {})); + } + cq.orderBy(cb.asc(root.get(TraceableEntity_.CREATION_DATE))); return createPageQuery(cq, Page.of(criteria.getPageNumber(), criteria.getPageSize())).getPageResult(); diff --git a/src/main/java/io/github/onecx/permission/domain/daos/WorkspacePermissionDAO.java b/src/main/java/io/github/onecx/permission/domain/daos/WorkspacePermissionDAO.java index 49cefa5..ad30359 100644 --- a/src/main/java/io/github/onecx/permission/domain/daos/WorkspacePermissionDAO.java +++ b/src/main/java/io/github/onecx/permission/domain/daos/WorkspacePermissionDAO.java @@ -6,6 +6,7 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.NoResultException; import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Subquery; import jakarta.transaction.Transactional; import org.tkit.quarkus.jpa.daos.AbstractDAO; @@ -16,8 +17,7 @@ import org.tkit.quarkus.jpa.utils.QueryCriteriaUtil; import io.github.onecx.permission.domain.criteria.WorkspacePermissionSearchCriteria; -import io.github.onecx.permission.domain.models.WorkspacePermission; -import io.github.onecx.permission.domain.models.WorkspacePermission_; +import io.github.onecx.permission.domain.models.*; @ApplicationScoped public class WorkspacePermissionDAO extends AbstractDAO { @@ -47,18 +47,10 @@ public PageResult findByCriteria(WorkspacePermissionSearchC List predicates = new ArrayList<>(); - if (criteria.getAction() != null && !criteria.getAction().isBlank()) { - predicates - .add(cb.like(root.get(WorkspacePermission_.action), QueryCriteriaUtil.wildcard(criteria.getAction()))); - } if (criteria.getWorkspaceId() != null && !criteria.getWorkspaceId().isBlank()) { predicates.add(cb.like(root.get(WorkspacePermission_.workspaceId), QueryCriteriaUtil.wildcard(criteria.getWorkspaceId()))); } - if (criteria.getResource() != null && !criteria.getResource().isBlank()) { - predicates.add( - cb.like(root.get(WorkspacePermission_.resource), QueryCriteriaUtil.wildcard(criteria.getResource()))); - } if (!predicates.isEmpty()) { cq.where(predicates.toArray(new Predicate[] {})); @@ -70,8 +62,30 @@ public PageResult findByCriteria(WorkspacePermissionSearchC } } + public List findWorkspacePermissionForUser(String workspaceId, List roles) { + try { + var cb = this.getEntityManager().getCriteriaBuilder(); + var cq = cb.createQuery(WorkspacePermission.class); + var root = cq.from(WorkspacePermission.class); + + Subquery sq = cq.subquery(String.class); + var subRoot = sq.from(WorkspaceAssignment.class); + sq.select(subRoot.get(WorkspaceAssignment_.PERMISSION_ID)); + sq.where( + subRoot.get(WorkspaceAssignment_.role).get(Role_.name).in(roles), + cb.equal(subRoot.get(WorkspaceAssignment_.permission).get(WorkspacePermission_.workspaceId), workspaceId)); + + cq.where(root.get(Permission_.id).in(sq)); + + return this.getEntityManager().createQuery(cq).getResultList(); + } catch (Exception ex) { + throw new DAOException(ErrorKeys.ERROR_FIND_WORKSPACE_PERMISSION_FOR_USER, ex); + } + } + public enum ErrorKeys { + ERROR_FIND_WORKSPACE_PERMISSION_FOR_USER, ERROR_FIND_PERMISSION_BY_CRITERIA, FIND_ENTITY_BY_ID_FAILED; } diff --git a/src/main/java/io/github/onecx/permission/rs/external/v1/controllers/PermissionRestController.java b/src/main/java/io/github/onecx/permission/rs/external/v1/controllers/PermissionRestController.java index fdd5abc..cb62ed4 100644 --- a/src/main/java/io/github/onecx/permission/rs/external/v1/controllers/PermissionRestController.java +++ b/src/main/java/io/github/onecx/permission/rs/external/v1/controllers/PermissionRestController.java @@ -2,14 +2,21 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import jakarta.validation.ConstraintViolationException; import jakarta.ws.rs.core.Response; +import org.jboss.resteasy.reactive.RestResponse; +import org.jboss.resteasy.reactive.server.ServerExceptionMapper; import org.tkit.quarkus.log.cdi.LogExclude; import org.tkit.quarkus.log.cdi.LogService; -import gen.io.github.onecx.permission.rs.v1.PermissionApiV1; +import gen.io.github.onecx.permission.rs.external.v1.PermissionApiV1; +import gen.io.github.onecx.permission.rs.external.v1.model.PermissionRequestDTOV1; +import gen.io.github.onecx.permission.rs.external.v1.model.ProblemDetailResponseDTOV1; import io.github.onecx.permission.common.services.TokenService; import io.github.onecx.permission.domain.daos.PermissionDAO; +import io.github.onecx.permission.domain.daos.WorkspacePermissionDAO; +import io.github.onecx.permission.rs.external.v1.mappers.ExceptionMapper; import io.github.onecx.permission.rs.external.v1.mappers.PermissionMapper; @LogService @@ -25,21 +32,39 @@ public class PermissionRestController implements PermissionApiV1 { @Inject PermissionMapper mapper; + @Inject + WorkspacePermissionDAO workspacePermissionDAO; + + @Inject + ExceptionMapper exceptionMapper; + @Override - public Response getApplicationPermissions(String appId, @LogExclude String body) { - var roles = tokenService.getTokenRoles(body); + public Response getApplicationPermissions(String appId, @LogExclude PermissionRequestDTOV1 permissionRequestDTOV1) { + var roles = tokenService.getTokenRoles(permissionRequestDTOV1.getToken()); var permissions = permissionDAO.findPermissionForUser(appId, roles); return Response.ok(mapper.create(appId, permissions)).build(); } @Override - public Response getWorkspacePermission(String workspace, @LogExclude String body) { - return null; + public Response getWorkspacePermission(String workspace, @LogExclude PermissionRequestDTOV1 permissionRequestDTOV1) { + var roles = tokenService.getTokenRoles(permissionRequestDTOV1.getToken()); + var permissions = workspacePermissionDAO.findWorkspacePermissionForUser(workspace, roles); + return Response.ok(mapper.createWorkspace(workspace, permissions)).build(); } @Override - public Response getWorkspacePermissionApplications(String workspace, @LogExclude String body) { - return null; + public Response getWorkspacePermissionApplications(String workspace, + @LogExclude PermissionRequestDTOV1 permissionRequestDTOV1) { + + var roles = tokenService.getTokenRoles(permissionRequestDTOV1.getToken()); + var workspacePermissions = workspacePermissionDAO.findWorkspacePermissionForUser(workspace, roles); + var permissions = permissionDAO.findAllPermissionForUser(roles); + + return Response.ok(mapper.createWorkspaceApps(workspace, workspacePermissions, permissions)).build(); } + @ServerExceptionMapper + public RestResponse constraint(ConstraintViolationException ex) { + return exceptionMapper.constraint(ex); + } } diff --git a/src/main/java/io/github/onecx/permission/rs/external/v1/mappers/ExceptionMapper.java b/src/main/java/io/github/onecx/permission/rs/external/v1/mappers/ExceptionMapper.java new file mode 100644 index 0000000..2eb84cc --- /dev/null +++ b/src/main/java/io/github/onecx/permission/rs/external/v1/mappers/ExceptionMapper.java @@ -0,0 +1,63 @@ +package io.github.onecx.permission.rs.external.v1.mappers; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.Path; +import jakarta.ws.rs.core.Response; + +import org.jboss.resteasy.reactive.RestResponse; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +import gen.io.github.onecx.permission.rs.external.v1.model.ProblemDetailInvalidParamDTOV1; +import gen.io.github.onecx.permission.rs.external.v1.model.ProblemDetailParamDTOV1; +import gen.io.github.onecx.permission.rs.external.v1.model.ProblemDetailResponseDTOV1; + +@Mapper +public abstract class ExceptionMapper { + + public RestResponse constraint(ConstraintViolationException ex) { + var dto = exception(ErrorKeys.CONSTRAINT_VIOLATIONS.name(), ex.getMessage()); + dto.setInvalidParams(createErrorValidationResponse(ex.getConstraintViolations())); + return RestResponse.status(Response.Status.BAD_REQUEST, dto); + } + + @Mapping(target = "removeParamsItem", ignore = true) + @Mapping(target = "params", ignore = true) + @Mapping(target = "invalidParams", ignore = true) + @Mapping(target = "removeInvalidParamsItem", ignore = true) + public abstract ProblemDetailResponseDTOV1 exception(String errorCode, String detail); + + public List map(Map params) { + if (params == null) { + return List.of(); + } + return params.entrySet().stream().map(e -> { + var item = new ProblemDetailParamDTOV1(); + item.setKey(e.getKey()); + if (e.getValue() != null) { + item.setValue(e.getValue().toString()); + } + return item; + }).toList(); + } + + public abstract List createErrorValidationResponse( + Set> constraintViolation); + + @Mapping(target = "name", source = "propertyPath") + @Mapping(target = "message", source = "message") + public abstract ProblemDetailInvalidParamDTOV1 createError(ConstraintViolation constraintViolation); + + public String mapPath(Path path) { + return path.toString(); + } + + public enum ErrorKeys { + CONSTRAINT_VIOLATIONS; + } +} diff --git a/src/main/java/io/github/onecx/permission/rs/external/v1/mappers/PermissionMapper.java b/src/main/java/io/github/onecx/permission/rs/external/v1/mappers/PermissionMapper.java index 9a6627d..12cd14b 100644 --- a/src/main/java/io/github/onecx/permission/rs/external/v1/mappers/PermissionMapper.java +++ b/src/main/java/io/github/onecx/permission/rs/external/v1/mappers/PermissionMapper.java @@ -4,12 +4,32 @@ import org.mapstruct.Mapper; -import gen.io.github.onecx.permission.rs.v1.model.ApplicationPermissionsDTOV1; +import gen.io.github.onecx.permission.rs.external.v1.model.ApplicationPermissionsDTOV1; +import gen.io.github.onecx.permission.rs.external.v1.model.WorkspacePermissionApplicationsDTOV1; +import gen.io.github.onecx.permission.rs.external.v1.model.WorkspacePermissionsDTOV1; import io.github.onecx.permission.domain.models.Permission; +import io.github.onecx.permission.domain.models.WorkspacePermission; @Mapper public interface PermissionMapper { + default WorkspacePermissionApplicationsDTOV1 createWorkspaceApps(String workspaceId, + List workspacePermissions, List permissions) { + return new WorkspacePermissionApplicationsDTOV1() + .workspace(createWorkspace(workspaceId, workspacePermissions)) + .applications(splitToApps(permissions)); + } + + default List splitToApps(List permissions) { + if (permissions == null) { + return null; + } + Map> items = new HashMap<>(); + permissions.forEach(permission -> items.computeIfAbsent(permission.getAppId(), k -> new ArrayList<>()) + .add(permission)); + return items.entrySet().stream().map(e -> create(e.getKey(), e.getValue())).toList(); + } + default Map> permissions(List permissions) { if (permissions == null) { return null; @@ -27,4 +47,22 @@ default ApplicationPermissionsDTOV1 create(String appId, Map default ApplicationPermissionsDTOV1 create(String appId, List permissions) { return create(appId, permissions(permissions)); } + + default Map> permissionsWorkspace(List permissions) { + if (permissions == null) { + return null; + } + Map> result = new HashMap<>(); + permissions.forEach(permission -> result.computeIfAbsent(permission.getResource(), k -> new HashSet<>()) + .add(permission.getAction())); + return result; + } + + default WorkspacePermissionsDTOV1 createWorkspace(String workspaceId, Map> permissions) { + return new WorkspacePermissionsDTOV1().workspaceId(workspaceId).permissions(permissions); + } + + default WorkspacePermissionsDTOV1 createWorkspace(String workspaceId, List permissions) { + return createWorkspace(workspaceId, permissionsWorkspace(permissions)); + } } diff --git a/src/main/openapi/onecx-permission-internal-openapi.yaml b/src/main/openapi/onecx-permission-internal-openapi.yaml index e44a6cf..e2599e5 100644 --- a/src/main/openapi/onecx-permission-internal-openapi.yaml +++ b/src/main/openapi/onecx-permission-internal-openapi.yaml @@ -511,10 +511,6 @@ components: properties: workspaceId: type: string - resource: - type: string - action: - type: string pageNumber: format: int32 description: The number of page. @@ -606,6 +602,8 @@ components: WorkspaceAssignmentSearchCriteria: type: object properties: + workspaceId: + type: string pageNumber: format: int32 description: The number of page. @@ -639,6 +637,8 @@ components: AssignmentSearchCriteria: type: object properties: + appId: + type: string pageNumber: format: int32 description: The number of page. @@ -780,10 +780,6 @@ components: properties: appId: type: string - resource: - type: string - action: - type: string pageNumber: format: int32 description: The number of page. diff --git a/src/main/openapi/onecx-permission-v1.yaml b/src/main/openapi/onecx-permission-v1.yaml index d4982c9..86fcb60 100644 --- a/src/main/openapi/onecx-permission-v1.yaml +++ b/src/main/openapi/onecx-permission-v1.yaml @@ -24,9 +24,9 @@ paths: requestBody: required: true content: - application/*: + application/json: schema: - type: string + $ref: '#/components/schemas/PermissionRequest' responses: 200: description: Workspace permission @@ -58,9 +58,9 @@ paths: requestBody: required: true content: - application/*: + application/json: schema: - type: string + $ref: '#/components/schemas/PermissionRequest' responses: 200: description: Workspace permission @@ -92,9 +92,9 @@ paths: requestBody: required: true content: - application/*: + application/json: schema: - type: string + $ref: '#/components/schemas/PermissionRequest' responses: 200: description: Permission updated @@ -112,6 +112,13 @@ paths: $ref: '#/components/schemas/ProblemDetailResponse' components: schemas: + PermissionRequest: + type: object + required: + - token + properties: + token: + type: string WorkspacePermissionApplications: type: object properties: diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 1dc660c..fb95686 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -5,8 +5,6 @@ quarkus.datasource.jdbc.min-size=10 quarkus.hibernate-orm.database.generation=validate quarkus.hibernate-orm.multitenant=DISCRIMINATOR -quarkus.hibernate-orm.log.format-sql=true -quarkus.hibernate-orm.log.sql=true quarkus.liquibase.migrate-at-start=true quarkus.liquibase.validate-on-migrate=true diff --git a/src/test/java/io/github/onecx/permission/domain/daos/WorkspacePermissionDAOTest.java b/src/test/java/io/github/onecx/permission/domain/daos/WorkspacePermissionDAOTest.java index 0d1d00f..6a9ed8a 100644 --- a/src/test/java/io/github/onecx/permission/domain/daos/WorkspacePermissionDAOTest.java +++ b/src/test/java/io/github/onecx/permission/domain/daos/WorkspacePermissionDAOTest.java @@ -14,6 +14,8 @@ class WorkspacePermissionDAOTest extends AbstractDAOTest { @Test void methodExceptionTests() { + methodExceptionTests(() -> dao.findWorkspacePermissionForUser(null, null), + WorkspacePermissionDAO.ErrorKeys.ERROR_FIND_WORKSPACE_PERMISSION_FOR_USER); methodExceptionTests(() -> dao.findByCriteria(null), WorkspacePermissionDAO.ErrorKeys.ERROR_FIND_PERMISSION_BY_CRITERIA); methodExceptionTests(() -> dao.findById(null), diff --git a/src/test/java/io/github/onecx/permission/rs/external/v1/PermissionRestControllerTest.java b/src/test/java/io/github/onecx/permission/rs/external/v1/PermissionRestControllerTest.java index 8e5461b..c003211 100644 --- a/src/test/java/io/github/onecx/permission/rs/external/v1/PermissionRestControllerTest.java +++ b/src/test/java/io/github/onecx/permission/rs/external/v1/PermissionRestControllerTest.java @@ -2,6 +2,8 @@ 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.jboss.resteasy.reactive.RestResponse.Status.BAD_REQUEST; import static org.jboss.resteasy.reactive.RestResponse.Status.OK; import java.util.List; @@ -9,8 +11,9 @@ import org.junit.jupiter.api.Test; import org.tkit.quarkus.test.WithDBData; -import gen.io.github.onecx.permission.rs.v1.model.ApplicationPermissionsDTOV1; +import gen.io.github.onecx.permission.rs.external.v1.model.*; import io.github.onecx.permission.rs.external.v1.controllers.PermissionRestController; +import io.github.onecx.permission.rs.internal.mappers.ExceptionMapper; import io.github.onecx.permission.test.AbstractTest; import io.quarkus.test.common.http.TestHTTPEndpoint; import io.quarkus.test.junit.QuarkusTest; @@ -27,12 +30,112 @@ void getApplicationPermissionsTest() { var dto = given() .contentType(APPLICATION_JSON) - .body(accessToken) + .body(new PermissionRequestDTOV1().token(accessToken)) .post("/application/app1") - .then().log().all() + .then() .statusCode(OK.getStatusCode()) .extract() .body().as(ApplicationPermissionsDTOV1.class); + assertThat(dto).isNotNull(); + assertThat(dto.getPermissions()).isNotNull().hasSize(1); + assertThat(dto.getPermissions().get("o1")).isNotNull().hasSize(1).containsExactly("a3"); + + } + + @Test + void getApplicationPermissionsNoBodyTest() { + + var exception = given() + .contentType(APPLICATION_JSON) + .post("/application/app1") + .then() + .statusCode(BAD_REQUEST.getStatusCode()) + .extract() + .body().as(ProblemDetailResponseDTOV1.class); + + assertThat(exception).isNotNull(); + assertThat(exception.getErrorCode()).isEqualTo(ExceptionMapper.ErrorKeys.CONSTRAINT_VIOLATIONS.name()); + assertThat(exception.getDetail()).isEqualTo("getApplicationPermissions.permissionRequestDTOV1: must not be null"); + + } + + @Test + void getWorkspacePermissionsTest() { + + var accessToken = createToken(List.of("n3")); + + var dto = given() + .contentType(APPLICATION_JSON) + .body(new PermissionRequestDTOV1().token(accessToken)) + .post("/workspace/wapp1") + .then() + .statusCode(OK.getStatusCode()) + .extract() + .body().as(WorkspacePermissionsDTOV1.class); + + assertThat(dto).isNotNull(); + assertThat(dto.getPermissions()).isNotNull().hasSize(1); + assertThat(dto.getPermissions().get("o1")).isNotNull().hasSize(1).containsExactly("a3"); + + } + + @Test + void getWorkspacePermissionsNoBodyTest() { + + var exception = given() + .contentType(APPLICATION_JSON) + .post("/workspace/wapp1") + .then() + .statusCode(BAD_REQUEST.getStatusCode()) + .extract() + .body().as(ProblemDetailResponseDTOV1.class); + + assertThat(exception).isNotNull(); + assertThat(exception.getErrorCode()).isEqualTo(ExceptionMapper.ErrorKeys.CONSTRAINT_VIOLATIONS.name()); + assertThat(exception.getDetail()).isEqualTo("getWorkspacePermission.permissionRequestDTOV1: must not be null"); + + } + + @Test + void getWorkspacePermissionAppsNoBodyTest() { + + var exception = given() + .contentType(APPLICATION_JSON) + .post("/workspace/wapp1/applications") + .then() + .statusCode(BAD_REQUEST.getStatusCode()) + .extract() + .body().as(ProblemDetailResponseDTOV1.class); + + assertThat(exception).isNotNull(); + assertThat(exception.getErrorCode()).isEqualTo(ExceptionMapper.ErrorKeys.CONSTRAINT_VIOLATIONS.name()); + assertThat(exception.getDetail()) + .isEqualTo("getWorkspacePermissionApplications.permissionRequestDTOV1: must not be null"); + + } + + @Test + void getWorkspacePermissionApplicationsTest() { + + var accessToken = createToken(List.of("n3")); + + var dto = given() + .contentType(APPLICATION_JSON) + .body(new PermissionRequestDTOV1().token(accessToken)) + .post("/workspace/wapp1/applications") + .then() + .statusCode(OK.getStatusCode()) + .extract() + .body().as(WorkspacePermissionApplicationsDTOV1.class); + + assertThat(dto).isNotNull(); + assertThat(dto.getWorkspace()).isNotNull(); + assertThat(dto.getWorkspace().getPermissions()).isNotNull().hasSize(1); + assertThat(dto.getWorkspace().getPermissions().get("o1")).isNotNull().hasSize(1).containsExactly("a3"); + assertThat(dto.getApplications()).isNotNull().hasSize(1); + assertThat(dto.getApplications().get(0)).isNotNull(); + assertThat(dto.getApplications().get(0).getPermissions()).isNotNull().hasSize(1); + assertThat(dto.getApplications().get(0).getPermissions().get("o1")).isNotNull().hasSize(1).containsExactly("a3"); } } diff --git a/src/test/java/io/github/onecx/permission/rs/internal/controllers/AssignmentRestControllerTest.java b/src/test/java/io/github/onecx/permission/rs/internal/controllers/AssignmentRestControllerTest.java index 82d3837..36d046e 100644 --- a/src/test/java/io/github/onecx/permission/rs/internal/controllers/AssignmentRestControllerTest.java +++ b/src/test/java/io/github/onecx/permission/rs/internal/controllers/AssignmentRestControllerTest.java @@ -132,6 +132,38 @@ void searchAssignmentTest() { .extract() .as(AssignmentPageResultDTO.class); + assertThat(data).isNotNull(); + assertThat(data.getTotalElements()).isEqualTo(3); + assertThat(data.getStream()).isNotNull().hasSize(3); + + criteria.setAppId(" "); + + data = given() + .contentType(APPLICATION_JSON) + .body(criteria) + .post("/search") + .then() + .statusCode(OK.getStatusCode()) + .contentType(APPLICATION_JSON) + .extract() + .as(AssignmentPageResultDTO.class); + + assertThat(data).isNotNull(); + assertThat(data.getTotalElements()).isEqualTo(3); + assertThat(data.getStream()).isNotNull().hasSize(3); + + criteria.setAppId("app1"); + + data = given() + .contentType(APPLICATION_JSON) + .body(criteria) + .post("/search") + .then().log().all() + .statusCode(OK.getStatusCode()) + .contentType(APPLICATION_JSON) + .extract() + .as(AssignmentPageResultDTO.class); + assertThat(data).isNotNull(); assertThat(data.getTotalElements()).isEqualTo(2); assertThat(data.getStream()).isNotNull().hasSize(2); diff --git a/src/test/java/io/github/onecx/permission/rs/internal/controllers/PermissionRestControllerTest.java b/src/test/java/io/github/onecx/permission/rs/internal/controllers/PermissionRestControllerTest.java index b757631..e85cb32 100644 --- a/src/test/java/io/github/onecx/permission/rs/internal/controllers/PermissionRestControllerTest.java +++ b/src/test/java/io/github/onecx/permission/rs/internal/controllers/PermissionRestControllerTest.java @@ -41,8 +41,6 @@ void searchTest() { assertThat(data.getStream()).isNotNull().hasSize(7); criteria.setAppId(" "); - criteria.setResource(" "); - criteria.setAction(" "); data = given() .contentType(APPLICATION_JSON) @@ -63,8 +61,6 @@ void searchTest() { void searchCriteriaTest() { var criteria = new PermissionSearchCriteriaDTO(); criteria.setAppId("app1"); - criteria.setResource("o1"); - criteria.setAction("a1"); var data = given() .contentType(APPLICATION_JSON) @@ -77,8 +73,11 @@ void searchCriteriaTest() { .as(PermissionPageResultDTO.class); assertThat(data).isNotNull(); - assertThat(data.getTotalElements()).isEqualTo(1); - assertThat(data.getStream()).isNotNull().hasSize(1); + assertThat(data.getTotalElements()).isEqualTo(5); + assertThat(data.getStream()).isNotNull().hasSize(5); + + criteria = new PermissionSearchCriteriaDTO(); + criteria.setAppId("app1"); } @Test diff --git a/src/test/java/io/github/onecx/permission/rs/internal/controllers/WorkspaceAssignmentRestControllerTest.java b/src/test/java/io/github/onecx/permission/rs/internal/controllers/WorkspaceAssignmentRestControllerTest.java index 99bbdc6..ccc910d 100644 --- a/src/test/java/io/github/onecx/permission/rs/internal/controllers/WorkspaceAssignmentRestControllerTest.java +++ b/src/test/java/io/github/onecx/permission/rs/internal/controllers/WorkspaceAssignmentRestControllerTest.java @@ -132,6 +132,37 @@ void searchWorkspaceAssignmentTest() { .extract() .as(WorkspaceAssignmentPageResultDTO.class); + assertThat(data).isNotNull(); + assertThat(data.getTotalElements()).isEqualTo(3); + assertThat(data.getStream()).isNotNull().hasSize(3); + + criteria.setWorkspaceId(" "); + data = given() + .contentType(APPLICATION_JSON) + .body(criteria) + .post("/search") + .then() + .statusCode(OK.getStatusCode()) + .contentType(APPLICATION_JSON) + .extract() + .as(WorkspaceAssignmentPageResultDTO.class); + + assertThat(data).isNotNull(); + assertThat(data.getTotalElements()).isEqualTo(3); + assertThat(data.getStream()).isNotNull().hasSize(3); + + criteria.setWorkspaceId("wapp1"); + + data = given() + .contentType(APPLICATION_JSON) + .body(criteria) + .post("/search") + .then() + .statusCode(OK.getStatusCode()) + .contentType(APPLICATION_JSON) + .extract() + .as(WorkspaceAssignmentPageResultDTO.class); + assertThat(data).isNotNull(); assertThat(data.getTotalElements()).isEqualTo(2); assertThat(data.getStream()).isNotNull().hasSize(2); diff --git a/src/test/java/io/github/onecx/permission/rs/internal/controllers/WorkspacePermissionRestControllerTest.java b/src/test/java/io/github/onecx/permission/rs/internal/controllers/WorkspacePermissionRestControllerTest.java index 64c646b..cb29d7c 100644 --- a/src/test/java/io/github/onecx/permission/rs/internal/controllers/WorkspacePermissionRestControllerTest.java +++ b/src/test/java/io/github/onecx/permission/rs/internal/controllers/WorkspacePermissionRestControllerTest.java @@ -42,8 +42,6 @@ void searchTest() { assertThat(data.getStream()).isNotNull().hasSize(7); criteria.setWorkspaceId(" "); - criteria.setResource(" "); - criteria.setAction(" "); data = given() .contentType(APPLICATION_JSON) @@ -64,8 +62,6 @@ void searchTest() { void searchCriteriaTest() { var criteria = new WorkspacePermissionSearchCriteriaDTO(); criteria.setWorkspaceId("wapp1"); - criteria.setResource("o1"); - criteria.setAction("a1"); var data = given() .contentType(APPLICATION_JSON) @@ -78,8 +74,8 @@ void searchCriteriaTest() { .as(WorkspacePermissionPageResultDTO.class); assertThat(data).isNotNull(); - assertThat(data.getTotalElements()).isEqualTo(1); - assertThat(data.getStream()).isNotNull().hasSize(1); + assertThat(data.getTotalElements()).isEqualTo(5); + assertThat(data.getStream()).isNotNull().hasSize(5); } @Test @@ -117,7 +113,7 @@ void deleteWorkspacePermissionTest() { // delete Role in portal given() .contentType(APPLICATION_JSON) - .delete("wp22") + .delete("wp21") .then() .statusCode(NO_CONTENT.getStatusCode()); diff --git a/src/test/resources/data/test-internal.xml b/src/test/resources/data/test-internal.xml index 92cd883..8f99e13 100644 --- a/src/test/resources/data/test-internal.xml +++ b/src/test/resources/data/test-internal.xml @@ -25,6 +25,8 @@ + + @@ -37,4 +39,6 @@ + + \ No newline at end of file