diff --git a/pom.xml b/pom.xml
index c46d4a5..c42d3f8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,6 +23,10 @@
onecx-tenant
+
+ org.tkit.quarkus.lib
+ tkit-quarkus-data-import
+
org.tkit.quarkus.lib
tkit-quarkus-rest-context
@@ -105,6 +109,10 @@
io.quarkus
quarkus-opentelemetry
+
+ io.quarkus
+ quarkus-oidc
+
@@ -137,6 +145,11 @@
tkit-quarkus-test-db-import
test
+
+ io.quarkus
+ quarkus-test-keycloak-server
+ test
+
@@ -170,6 +183,19 @@
+
+ di-v1
+
+ generate
+
+
+ src/main/openapi/onecx-permission-di-v1.yaml
+ gen.io.github.onecx.permission.domain.di.v1
+ gen.io.github.onecx.permission.domain.di.v1.model
+ DTOV1
+ false
+
+
internal
@@ -194,6 +220,19 @@
DTOV1
+
+ v1
+
+ generate
+
+
+ src/main/openapi/onecx-permission-v1.yaml
+ 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
new file mode 100644
index 0000000..6930ea2
--- /dev/null
+++ b/src/main/java/io/github/onecx/permission/common/models/TokenConfig.java
@@ -0,0 +1,29 @@
+package io.github.onecx.permission.common.models;
+
+import java.util.Optional;
+
+import io.quarkus.runtime.annotations.StaticInitSafe;
+import io.smallrye.config.ConfigMapping;
+import io.smallrye.config.WithDefault;
+import io.smallrye.config.WithName;
+
+@StaticInitSafe
+@ConfigMapping(prefix = "onecx.permission")
+public interface TokenConfig {
+
+ @WithName("token.verified")
+ boolean tokenVerified();
+
+ @WithName("token.issuer.public-key-location.suffix")
+ String tokenPublicKeyLocationSuffix();
+
+ @WithName("token.issuer.public-key-location.enabled")
+ boolean tokenPublicKeyEnabled();
+
+ @WithName("token.claim.separator")
+ Optional tokenClaimSeparator();
+
+ @WithName("token.claim.path")
+ @WithDefault("realm_access/roles")
+ String tokenClaimPath();
+}
diff --git a/src/main/java/io/github/onecx/permission/common/services/ClaimService.java b/src/main/java/io/github/onecx/permission/common/services/ClaimService.java
new file mode 100644
index 0000000..35567ef
--- /dev/null
+++ b/src/main/java/io/github/onecx/permission/common/services/ClaimService.java
@@ -0,0 +1,36 @@
+package io.github.onecx.permission.common.services;
+
+import java.util.regex.Pattern;
+
+import jakarta.annotation.PostConstruct;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+import io.github.onecx.permission.common.models.TokenConfig;
+
+@ApplicationScoped
+public class ClaimService {
+
+ @SuppressWarnings("java:S5998")
+ private static final Pattern CLAIM_PATH_PATTERN = Pattern.compile("\\/(?=(?:(?:[^\"]*\"){2})*[^\"]*$)");
+
+ private static String[] claimPath;
+
+ @Inject
+ TokenConfig config;
+
+ @PostConstruct
+ @SuppressWarnings("java:S2696")
+ public void init() {
+ claimPath = splitClaimPath(config.tokenClaimPath());
+ }
+
+ public String[] getClaimPath() {
+ return claimPath;
+ }
+
+ @SuppressWarnings("java:S2692")
+ static String[] splitClaimPath(String claimPath) {
+ return claimPath.indexOf('/') > 0 ? CLAIM_PATH_PATTERN.split(claimPath) : new String[] { claimPath };
+ }
+}
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
new file mode 100644
index 0000000..ba42a16
--- /dev/null
+++ b/src/main/java/io/github/onecx/permission/common/services/TokenService.java
@@ -0,0 +1,85 @@
+package io.github.onecx.permission.common.services;
+
+import static io.github.onecx.permission.common.utils.TokenUtil.findClaimWithRoles;
+
+import java.util.List;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+import org.jose4j.jws.JsonWebSignature;
+import org.jose4j.jwt.JwtClaims;
+import org.jose4j.jwt.MalformedClaimException;
+import org.jose4j.jwt.consumer.InvalidJwtException;
+import org.jose4j.jwx.JsonWebStructure;
+import org.jose4j.lang.JoseException;
+
+import io.github.onecx.permission.common.models.TokenConfig;
+import io.smallrye.jwt.auth.principal.JWTAuthContextInfo;
+import io.smallrye.jwt.auth.principal.JWTParser;
+import io.smallrye.jwt.auth.principal.ParseException;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@ApplicationScoped
+public class TokenService {
+
+ @Inject
+ JWTAuthContextInfo authContextInfo;
+
+ @Inject
+ TokenConfig config;
+
+ @Inject
+ JWTParser parser;
+
+ @Inject
+ ClaimService claimService;
+
+ public List getTokenRoles(String tokenData) {
+ try {
+ return getRoles(tokenData);
+ } catch (Exception ex) {
+ throw new TokenException("Error parsing principal token", ex);
+ }
+ }
+
+ private List getRoles(String tokenData)
+ throws JoseException, InvalidJwtException, MalformedClaimException, ParseException {
+
+ var claimPath = claimService.getClaimPath();
+
+ if (config.tokenVerified()) {
+ var info = authContextInfo;
+
+ // get public key location from issuer URL
+ if (config.tokenPublicKeyEnabled()) {
+ var jws = (JsonWebSignature) JsonWebStructure.fromCompactSerialization(tokenData);
+ var jwtClaims = JwtClaims.parse(jws.getUnverifiedPayload());
+ var publicKeyLocation = jwtClaims.getIssuer() + config.tokenPublicKeyLocationSuffix();
+ info = new JWTAuthContextInfo(authContextInfo);
+ info.setPublicKeyLocation(publicKeyLocation);
+ }
+
+ var token = parser.parse(tokenData, info);
+ var first = token.getClaim(claimPath[0]);
+
+ return findClaimWithRoles(config, first, claimPath);
+
+ } else {
+
+ var jws = (JsonWebSignature) JsonWebStructure.fromCompactSerialization(tokenData);
+
+ var jwtClaims = JwtClaims.parse(jws.getUnverifiedPayload());
+ var first = jwtClaims.getClaimValue(claimPath[0]);
+ return findClaimWithRoles(config, first, claimPath);
+ }
+ }
+
+ public static class TokenException extends RuntimeException {
+
+ public TokenException(String message, Throwable t) {
+ super(message, t);
+ }
+ }
+}
diff --git a/src/main/java/io/github/onecx/permission/common/utils/TokenUtil.java b/src/main/java/io/github/onecx/permission/common/utils/TokenUtil.java
new file mode 100644
index 0000000..a298caa
--- /dev/null
+++ b/src/main/java/io/github/onecx/permission/common/utils/TokenUtil.java
@@ -0,0 +1,59 @@
+package io.github.onecx.permission.common.utils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import jakarta.json.JsonArray;
+import jakarta.json.JsonObject;
+import jakarta.json.JsonValue;
+
+import io.github.onecx.permission.common.models.TokenConfig;
+import io.smallrye.jwt.JsonUtils;
+
+public final class TokenUtil {
+
+ private TokenUtil() {
+ }
+
+ public static List findClaimWithRoles(TokenConfig config, Object value, String[] path) {
+ JsonValue first = JsonUtils.wrapValue(value);
+ JsonValue claimValue = findClaimValue(first, path, 1);
+
+ if (claimValue instanceof JsonArray jsonArray) {
+ return convertJsonArrayToList(jsonArray);
+ } else if (claimValue != null) {
+ if (claimValue.toString().isBlank()) {
+ return Collections.emptyList();
+ }
+ return Arrays.asList(claimValue.toString().split(config.tokenClaimSeparator().orElse(" ")));
+ } else {
+ return Collections.emptyList();
+ }
+ }
+
+ static List convertJsonArrayToList(JsonArray claimValue) {
+ List list = new ArrayList<>(claimValue.size());
+ for (int i = 0; i < claimValue.size(); i++) {
+ String claimValueStr = claimValue.getString(i);
+ if (claimValueStr.isBlank()) {
+ continue;
+ }
+ list.add(claimValue.getString(i));
+ }
+ return list;
+ }
+
+ private static JsonValue findClaimValue(JsonValue json, String[] pathArray, int step) {
+ if (json == null) {
+ return null;
+ }
+ if (step < pathArray.length && (json instanceof JsonObject)) {
+ JsonValue claimValue = json.asJsonObject().get(pathArray[step].replace("\"", ""));
+ return findClaimValue(claimValue, pathArray, step + 1);
+ }
+ return json;
+ }
+
+}
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
new file mode 100644
index 0000000..2a9bfab
--- /dev/null
+++ b/src/main/java/io/github/onecx/permission/domain/criteria/AssignmentSearchCriteria.java
@@ -0,0 +1,13 @@
+package io.github.onecx.permission.domain.criteria;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@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 55e17d3..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,9 +8,6 @@
public class PermissionSearchCriteria {
private String appId;
- private String name;
- private String resource;
- private String action;
private Integer pageNumber;
private Integer pageSize;
}
diff --git a/src/main/java/io/github/onecx/permission/domain/criteria/RoleSearchCriteria.java b/src/main/java/io/github/onecx/permission/domain/criteria/RoleSearchCriteria.java
new file mode 100644
index 0000000..90dd3fa
--- /dev/null
+++ b/src/main/java/io/github/onecx/permission/domain/criteria/RoleSearchCriteria.java
@@ -0,0 +1,14 @@
+package io.github.onecx.permission.domain.criteria;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class RoleSearchCriteria {
+
+ private String name;
+ private String description;
+ 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
new file mode 100644
index 0000000..bf4ca66
--- /dev/null
+++ b/src/main/java/io/github/onecx/permission/domain/criteria/WorkspaceAssignmentSearchCriteria.java
@@ -0,0 +1,13 @@
+package io.github.onecx.permission.domain.criteria;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@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
new file mode 100644
index 0000000..852861a
--- /dev/null
+++ b/src/main/java/io/github/onecx/permission/domain/criteria/WorkspacePermissionSearchCriteria.java
@@ -0,0 +1,13 @@
+package io.github.onecx.permission.domain.criteria;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class WorkspacePermissionSearchCriteria {
+
+ private String workspaceId;
+ 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
new file mode 100644
index 0000000..7df5327
--- /dev/null
+++ b/src/main/java/io/github/onecx/permission/domain/daos/AssignmentDAO.java
@@ -0,0 +1,72 @@
+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;
+import org.tkit.quarkus.jpa.daos.Page;
+import org.tkit.quarkus.jpa.daos.PageResult;
+import org.tkit.quarkus.jpa.exceptions.DAOException;
+import org.tkit.quarkus.jpa.models.AbstractTraceableEntity_;
+import org.tkit.quarkus.jpa.models.TraceableEntity_;
+
+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 {
+
+ // https://hibernate.atlassian.net/browse/HHH-16830#icft=HHH-16830
+ @Override
+ public Assignment findById(Object id) throws DAOException {
+ try {
+ var cb = this.getEntityManager().getCriteriaBuilder();
+ var cq = cb.createQuery(Assignment.class);
+ var root = cq.from(Assignment.class);
+ cq.where(cb.equal(root.get(TraceableEntity_.ID), id));
+ return this.getEntityManager().createQuery(cq).getSingleResult();
+ } catch (NoResultException nre) {
+ return null;
+ } catch (Exception e) {
+ throw new DAOException(ErrorKeys.FIND_ENTITY_BY_ID_FAILED, e, entityName, id);
+ }
+ }
+
+ @Transactional(Transactional.TxType.NOT_SUPPORTED)
+ public PageResult findByCriteria(AssignmentSearchCriteria criteria) {
+ try {
+ var cb = this.getEntityManager().getCriteriaBuilder();
+ 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(AbstractTraceableEntity_.creationDate)));
+
+ return createPageQuery(cq, Page.of(criteria.getPageNumber(), criteria.getPageSize())).getPageResult();
+ } catch (Exception ex) {
+ throw new DAOException(ErrorKeys.ERROR_FIND_ASSIGNMENT_BY_CRITERIA, ex);
+ }
+ }
+
+ public enum ErrorKeys {
+
+ FIND_ENTITY_BY_ID_FAILED,
+ ERROR_FIND_ASSIGNMENT_BY_CRITERIA;
+ }
+}
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 24421cf..e5e5f47 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
@@ -5,17 +5,18 @@
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.persistence.criteria.Predicate;
+import jakarta.persistence.criteria.Subquery;
import jakarta.transaction.Transactional;
import org.tkit.quarkus.jpa.daos.AbstractDAO;
import org.tkit.quarkus.jpa.daos.Page;
import org.tkit.quarkus.jpa.daos.PageResult;
import org.tkit.quarkus.jpa.exceptions.DAOException;
+import org.tkit.quarkus.jpa.models.TraceableEntity_;
import org.tkit.quarkus.jpa.utils.QueryCriteriaUtil;
import io.github.onecx.permission.domain.criteria.PermissionSearchCriteria;
-import io.github.onecx.permission.domain.models.Permission;
-import io.github.onecx.permission.domain.models.Permission_;
+import io.github.onecx.permission.domain.models.*;
@ApplicationScoped
public class PermissionDAO extends AbstractDAO {
@@ -29,18 +30,9 @@ public PageResult findByCriteria(PermissionSearchCriteria criteria)
List predicates = new ArrayList<>();
- if (criteria.getName() != null && !criteria.getName().isBlank()) {
- predicates.add(cb.like(root.get(Permission_.name), QueryCriteriaUtil.wildcard(criteria.getName())));
- }
- 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[] {}));
@@ -64,8 +56,51 @@ public List loadByAppId(String appId) {
}
}
+ public List findPermissionForUser(String appId, 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),
+ cb.equal(subRoot.get(Assignment_.permission).get(Permission_.appId), appId));
+
+ cq.where(root.get(TraceableEntity_.id).in(sq));
+
+ return this.getEntityManager().createQuery(cq).getResultList();
+ } catch (Exception ex) {
+ throw new DAOException(ErrorKeys.ERROR_FIND_PERMISSION_FOR_USER, ex);
+ }
+ }
+
+ 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(TraceableEntity_.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/RoleDAO.java b/src/main/java/io/github/onecx/permission/domain/daos/RoleDAO.java
new file mode 100644
index 0000000..462b765
--- /dev/null
+++ b/src/main/java/io/github/onecx/permission/domain/daos/RoleDAO.java
@@ -0,0 +1,72 @@
+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;
+import org.tkit.quarkus.jpa.daos.Page;
+import org.tkit.quarkus.jpa.daos.PageResult;
+import org.tkit.quarkus.jpa.exceptions.DAOException;
+import org.tkit.quarkus.jpa.models.TraceableEntity_;
+import org.tkit.quarkus.jpa.utils.QueryCriteriaUtil;
+
+import io.github.onecx.permission.domain.criteria.RoleSearchCriteria;
+import io.github.onecx.permission.domain.models.Role;
+import io.github.onecx.permission.domain.models.Role_;
+
+@ApplicationScoped
+public class RoleDAO extends AbstractDAO {
+
+ // https://hibernate.atlassian.net/browse/HHH-16830#icft=HHH-16830
+ @Override
+ public Role findById(Object id) throws DAOException {
+ try {
+ var cb = this.getEntityManager().getCriteriaBuilder();
+ var cq = cb.createQuery(Role.class);
+ var root = cq.from(Role.class);
+ cq.where(cb.equal(root.get(TraceableEntity_.ID), id));
+ return this.getEntityManager().createQuery(cq).getSingleResult();
+ } catch (NoResultException nre) {
+ return null;
+ } catch (Exception e) {
+ throw new DAOException(ErrorKeys.FIND_ENTITY_BY_ID_FAILED, e, entityName, id);
+ }
+ }
+
+ @Transactional(Transactional.TxType.NOT_SUPPORTED)
+ public PageResult findByCriteria(RoleSearchCriteria criteria) {
+ try {
+ var cb = this.getEntityManager().getCriteriaBuilder();
+ var cq = cb.createQuery(Role.class);
+ var root = cq.from(Role.class);
+
+ List predicates = new ArrayList<>();
+
+ if (criteria.getName() != null && !criteria.getName().isBlank()) {
+ predicates.add(cb.like(root.get(Role_.name), QueryCriteriaUtil.wildcard(criteria.getName())));
+ }
+ if (criteria.getDescription() != null && !criteria.getDescription().isBlank()) {
+ predicates.add(cb.like(root.get(Role_.description), QueryCriteriaUtil.wildcard(criteria.getDescription())));
+ }
+
+ if (!predicates.isEmpty()) {
+ cq.where(predicates.toArray(new Predicate[] {}));
+ }
+
+ return createPageQuery(cq, Page.of(criteria.getPageNumber(), criteria.getPageSize())).getPageResult();
+ } catch (Exception ex) {
+ throw new DAOException(ErrorKeys.ERROR_FIND_ROLE_BY_CRITERIA, ex);
+ }
+ }
+
+ public enum ErrorKeys {
+
+ FIND_ENTITY_BY_ID_FAILED,
+ ERROR_FIND_ROLE_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
new file mode 100644
index 0000000..1e3631f
--- /dev/null
+++ b/src/main/java/io/github/onecx/permission/domain/daos/WorkspaceAssignmentDAO.java
@@ -0,0 +1,72 @@
+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;
+import org.tkit.quarkus.jpa.daos.Page;
+import org.tkit.quarkus.jpa.daos.PageResult;
+import org.tkit.quarkus.jpa.exceptions.DAOException;
+import org.tkit.quarkus.jpa.models.AbstractTraceableEntity_;
+import org.tkit.quarkus.jpa.models.TraceableEntity_;
+
+import io.github.onecx.permission.domain.criteria.WorkspaceAssignmentSearchCriteria;
+import io.github.onecx.permission.domain.models.*;
+
+@ApplicationScoped
+public class WorkspaceAssignmentDAO extends AbstractDAO {
+
+ // https://hibernate.atlassian.net/browse/HHH-16830#icft=HHH-16830
+ @Override
+ public WorkspaceAssignment findById(Object id) throws DAOException {
+ try {
+ var cb = this.getEntityManager().getCriteriaBuilder();
+ var cq = cb.createQuery(WorkspaceAssignment.class);
+ var root = cq.from(WorkspaceAssignment.class);
+ cq.where(cb.equal(root.get(TraceableEntity_.ID), id));
+ return this.getEntityManager().createQuery(cq).getSingleResult();
+ } catch (NoResultException nre) {
+ return null;
+ } catch (Exception e) {
+ throw new DAOException(ErrorKeys.FIND_ENTITY_BY_ID_FAILED, e, entityName, id);
+ }
+ }
+
+ @Transactional(Transactional.TxType.NOT_SUPPORTED)
+ public PageResult findByCriteria(WorkspaceAssignmentSearchCriteria criteria) {
+ try {
+ var cb = this.getEntityManager().getCriteriaBuilder();
+ 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(AbstractTraceableEntity_.creationDate)));
+
+ return createPageQuery(cq, Page.of(criteria.getPageNumber(), criteria.getPageSize())).getPageResult();
+ } catch (Exception ex) {
+ throw new DAOException(ErrorKeys.ERROR_FIND_ASSIGNMENT_BY_CRITERIA, ex);
+ }
+ }
+
+ public enum ErrorKeys {
+
+ ERROR_FIND_ASSIGNMENT_BY_CRITERIA,
+
+ FIND_ENTITY_BY_ID_FAILED;
+ }
+}
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
new file mode 100644
index 0000000..f5d3ea0
--- /dev/null
+++ b/src/main/java/io/github/onecx/permission/domain/daos/WorkspacePermissionDAO.java
@@ -0,0 +1,92 @@
+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.persistence.criteria.Subquery;
+import jakarta.transaction.Transactional;
+
+import org.tkit.quarkus.jpa.daos.AbstractDAO;
+import org.tkit.quarkus.jpa.daos.Page;
+import org.tkit.quarkus.jpa.daos.PageResult;
+import org.tkit.quarkus.jpa.exceptions.DAOException;
+import org.tkit.quarkus.jpa.models.TraceableEntity_;
+import org.tkit.quarkus.jpa.utils.QueryCriteriaUtil;
+
+import io.github.onecx.permission.domain.criteria.WorkspacePermissionSearchCriteria;
+import io.github.onecx.permission.domain.models.*;
+
+@ApplicationScoped
+public class WorkspacePermissionDAO extends AbstractDAO {
+
+ // https://hibernate.atlassian.net/browse/HHH-16830#icft=HHH-16830
+ @Override
+ public WorkspacePermission findById(Object id) throws DAOException {
+ try {
+ var cb = this.getEntityManager().getCriteriaBuilder();
+ var cq = cb.createQuery(WorkspacePermission.class);
+ var root = cq.from(WorkspacePermission.class);
+ cq.where(cb.equal(root.get(TraceableEntity_.ID), id));
+ return this.getEntityManager().createQuery(cq).getSingleResult();
+ } catch (NoResultException nre) {
+ return null;
+ } catch (Exception e) {
+ throw new DAOException(ErrorKeys.FIND_ENTITY_BY_ID_FAILED, e, entityName, id);
+ }
+ }
+
+ @Transactional(Transactional.TxType.NOT_SUPPORTED)
+ public PageResult findByCriteria(WorkspacePermissionSearchCriteria criteria) {
+ try {
+ var cb = this.getEntityManager().getCriteriaBuilder();
+ var cq = cb.createQuery(WorkspacePermission.class);
+ var root = cq.from(WorkspacePermission.class);
+
+ List predicates = new ArrayList<>();
+
+ if (criteria.getWorkspaceId() != null && !criteria.getWorkspaceId().isBlank()) {
+ predicates.add(cb.like(root.get(WorkspacePermission_.workspaceId),
+ QueryCriteriaUtil.wildcard(criteria.getWorkspaceId())));
+ }
+
+ if (!predicates.isEmpty()) {
+ cq.where(predicates.toArray(new Predicate[] {}));
+ }
+
+ return createPageQuery(cq, Page.of(criteria.getPageNumber(), criteria.getPageSize())).getPageResult();
+ } catch (Exception ex) {
+ throw new DAOException(ErrorKeys.ERROR_FIND_PERMISSION_BY_CRITERIA, ex);
+ }
+ }
+
+ 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(TraceableEntity_.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/domain/di/PermissionDataImportV1.java b/src/main/java/io/github/onecx/permission/domain/di/PermissionDataImportV1.java
new file mode 100644
index 0000000..c21ffe9
--- /dev/null
+++ b/src/main/java/io/github/onecx/permission/domain/di/PermissionDataImportV1.java
@@ -0,0 +1,91 @@
+package io.github.onecx.permission.domain.di;
+
+import java.util.function.Consumer;
+
+import jakarta.inject.Inject;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.tkit.quarkus.dataimport.DataImport;
+import org.tkit.quarkus.dataimport.DataImportConfig;
+import org.tkit.quarkus.dataimport.DataImportService;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import gen.io.github.onecx.permission.domain.di.v1.model.DataImportDTOV1;
+
+@DataImport("permission")
+public class PermissionDataImportV1 implements DataImportService {
+
+ private static final Logger log = LoggerFactory.getLogger(PermissionDataImportV1.class);
+
+ @Inject
+ ObjectMapper objectMapper;
+
+ @Inject
+ PermissionImportService service;
+
+ @Override
+ public void importData(DataImportConfig config) {
+ log.info("Import permissions from configuration {}", config);
+ try {
+ var operation = config.getMetadata().getOrDefault("operation", "NONE");
+
+ Consumer action = null;
+ if ("CLEAN_INSERT".equals(operation)) {
+ action = this::cleanInsert;
+ }
+
+ if (action == null) {
+ log.warn("Not supported operation '{}' for the import configuration key '{}'", operation, config.getKey());
+ return;
+ }
+
+ if (config.getData() == null || config.getData().length == 0) {
+ log.warn("Import configuration key {} does not contains any data to import", config.getKey());
+ return;
+ }
+
+ DataImportDTOV1 data = objectMapper.readValue(config.getData(), DataImportDTOV1.class);
+
+ if (checkIsEmpty(data)) {
+ log.warn("Import configuration key {} does not contains any JSON data to import", config.getKey());
+ return;
+ }
+
+ // execute the import
+ action.accept(data);
+ } catch (Exception ex) {
+ throw new ImportException(ex.getMessage(), ex);
+ }
+ }
+
+ static boolean checkIsEmpty(DataImportDTOV1 data) {
+ return (data.getPermissions() == null || data.getPermissions().isEmpty())
+ && (data.getTenants() == null || data.getTenants().isEmpty());
+ }
+
+ public void cleanInsert(DataImportDTOV1 data) {
+
+ // delete all tenant data
+ var tenants = data.getTenants().keySet();
+ tenants.forEach(tenant -> service.deleteAllData(tenant));
+
+ // delete all permission
+ service.deleteAllPermissions();
+
+ // create permissions
+ var permissionMap = service.createAllPermissions(data.getPermissions());
+
+ // create tenant data
+ data.getTenants().forEach((tenantId, dto) -> service.createTenantData(tenantId, dto, permissionMap));
+
+ }
+
+ public static class ImportException extends RuntimeException {
+
+ public ImportException(String message, Throwable ex) {
+ super(message, ex);
+ }
+ }
+}
diff --git a/src/main/java/io/github/onecx/permission/domain/di/PermissionImportService.java b/src/main/java/io/github/onecx/permission/domain/di/PermissionImportService.java
new file mode 100644
index 0000000..ecb6953
--- /dev/null
+++ b/src/main/java/io/github/onecx/permission/domain/di/PermissionImportService.java
@@ -0,0 +1,107 @@
+package io.github.onecx.permission.domain.di;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.transaction.Transactional;
+
+import org.tkit.quarkus.context.ApplicationContext;
+import org.tkit.quarkus.context.Context;
+
+import gen.io.github.onecx.permission.domain.di.v1.model.DataImportTenantWrapperDTOV1;
+import io.github.onecx.permission.domain.daos.*;
+import io.github.onecx.permission.domain.di.mappers.DataImportV1Mapper;
+import io.github.onecx.permission.domain.models.Permission;
+import io.github.onecx.permission.domain.models.Role;
+
+@ApplicationScoped
+public class PermissionImportService {
+
+ @Inject
+ PermissionDAO permissionDAO;
+
+ @Inject
+ AssignmentDAO assignmentDAO;
+
+ @Inject
+ RoleDAO roleDAO;
+
+ @Inject
+ WorkspacePermissionDAO workspacePermissionDAO;
+
+ @Inject
+ WorkspaceAssignmentDAO workspaceAssignmentDAO;
+
+ @Inject
+ DataImportV1Mapper mapper;
+
+ @Transactional(Transactional.TxType.REQUIRES_NEW)
+ public void deleteAllData(String tenantId) {
+ try {
+ var ctx = Context.builder()
+ .principal("data-import")
+ .tenantId(tenantId)
+ .build();
+
+ ApplicationContext.start(ctx);
+
+ assignmentDAO.deleteQueryAll();
+ workspaceAssignmentDAO.deleteQueryAll();
+ workspacePermissionDAO.deleteQueryAll();
+ roleDAO.deleteQueryAll();
+
+ } finally {
+ ApplicationContext.close();
+ }
+ }
+
+ @Transactional(Transactional.TxType.REQUIRES_NEW)
+ public void deleteAllPermissions() {
+ permissionDAO.deleteQueryAll();
+ }
+
+ @Transactional(Transactional.TxType.REQUIRES_NEW)
+ public Map createAllPermissions(Map>> permissions) {
+ var items = mapper.map(permissions);
+ permissionDAO.create(items);
+ return items.stream().collect(Collectors.toMap(r -> r.getAppId() + r.getResource() + r.getAction(), r -> r));
+ }
+
+ @Transactional(Transactional.TxType.REQUIRES_NEW)
+ public void createTenantData(String tenantId, DataImportTenantWrapperDTOV1 dto, Map permissionMap) {
+ try {
+ var ctx = Context.builder()
+ .principal("data-import")
+ .tenantId(tenantId)
+ .build();
+
+ ApplicationContext.start(ctx);
+
+ // create workspace permissions
+ var workspacePermissions = mapper.mapWorkspace(dto.getWorkspacesPermissions());
+ workspacePermissionDAO.create(workspacePermissions);
+ var workspacePermissionsMap = workspacePermissions.stream()
+ .collect(Collectors.toMap(r -> r.getWorkspaceId() + r.getResource() + r.getAction(), r -> r));
+
+ // create tenant roles
+ var roles = mapper.createRoles(dto.getRoles());
+ roleDAO.create(roles);
+ var rolesMap = roles.stream().collect(Collectors.toMap(Role::getName, r -> r));
+
+ // create tenant assignments
+ var mapping = mapper.createMapping(dto.getRoles());
+ var assignments = mapper.createAssignments(mapping, rolesMap, permissionMap);
+ assignmentDAO.create(assignments);
+
+ // create tenant workspace assignments
+ var workspaceMapping = mapper.createWorkspaceMapping(dto.getRoles());
+ var workspaceAssignments = mapper.createWorkspaceAssignments(workspaceMapping, rolesMap, workspacePermissionsMap);
+ workspaceAssignmentDAO.create(workspaceAssignments);
+
+ } finally {
+ ApplicationContext.close();
+ }
+ }
+}
diff --git a/src/main/java/io/github/onecx/permission/domain/di/mappers/DataImportV1Mapper.java b/src/main/java/io/github/onecx/permission/domain/di/mappers/DataImportV1Mapper.java
new file mode 100644
index 0000000..ece39a7
--- /dev/null
+++ b/src/main/java/io/github/onecx/permission/domain/di/mappers/DataImportV1Mapper.java
@@ -0,0 +1,165 @@
+package io.github.onecx.permission.domain.di.mappers;
+
+import java.util.*;
+
+import org.mapstruct.*;
+
+import gen.io.github.onecx.permission.domain.di.v1.model.DataImportRoleDTOV1;
+import io.github.onecx.permission.domain.models.*;
+
+@Mapper
+public interface DataImportV1Mapper {
+
+ default List createAssignments(Map> mapping, Map roles,
+ Map permissions) {
+ if (permissions == null || roles == null || mapping == null) {
+ return List.of();
+ }
+ List result = new ArrayList<>();
+ mapping.forEach(
+ (role, perms) -> perms.forEach(perm -> result.add(createAssignment(roles.get(role), permissions.get(perm)))));
+ return result;
+ }
+
+ default Map> createMapping(Map dtoRoles) {
+ if (dtoRoles == null || dtoRoles.isEmpty()) {
+ return Map.of();
+ }
+ Map> mapping = new HashMap<>();
+ dtoRoles.forEach((role, item) -> {
+ Set perms = new HashSet<>();
+
+ item.getAssignments().forEach((appId, an) -> an
+ .forEach((resource, actions) -> actions.forEach(action -> perms.add(appId + resource + action))));
+
+ mapping.put(role, perms);
+ });
+ return mapping;
+ }
+
+ default Map> createWorkspaceMapping(Map dtoRoles) {
+ if (dtoRoles == null || dtoRoles.isEmpty()) {
+ return Map.of();
+ }
+ Map> mapping = new HashMap<>();
+ dtoRoles.forEach((role, item) -> {
+ Set perms = new HashSet<>();
+
+ item.getWorkspacesAssignments().forEach((workspaceId, an) -> an
+ .forEach((resource, actions) -> actions.forEach(action -> perms.add(workspaceId + resource + action))));
+
+ mapping.put(role, perms);
+ });
+ return mapping;
+ }
+
+ default List createWorkspaceAssignments(Map> mapping, Map roles,
+ Map permissions) {
+ if (permissions == null || roles == null || mapping == null) {
+ return List.of();
+ }
+
+ List result = new ArrayList<>();
+ mapping.forEach((role, perms) -> perms.forEach(perm -> result.add(create(roles.get(role), permissions.get(perm)))));
+ return result;
+ }
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "creationDate", ignore = true)
+ @Mapping(target = "creationUser", ignore = true)
+ @Mapping(target = "modificationDate", ignore = true)
+ @Mapping(target = "modificationUser", ignore = true)
+ @Mapping(target = "controlTraceabilityManual", ignore = true)
+ @Mapping(target = "modificationCount", ignore = true)
+ @Mapping(target = "persisted", ignore = true)
+ @Mapping(target = "roleId", ignore = true)
+ @Mapping(target = "permissionId", ignore = true)
+ @Mapping(target = "tenantId", ignore = true)
+ Assignment createAssignment(Role role, Permission permission);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "creationDate", ignore = true)
+ @Mapping(target = "creationUser", ignore = true)
+ @Mapping(target = "modificationDate", ignore = true)
+ @Mapping(target = "modificationUser", ignore = true)
+ @Mapping(target = "controlTraceabilityManual", ignore = true)
+ @Mapping(target = "modificationCount", ignore = true)
+ @Mapping(target = "persisted", ignore = true)
+ @Mapping(target = "roleId", ignore = true)
+ @Mapping(target = "permissionId", ignore = true)
+ @Mapping(target = "tenantId", ignore = true)
+ WorkspaceAssignment create(Role role, WorkspacePermission permission);
+
+ default List createRoles(Map dto) {
+ if (dto == null) {
+ return List.of();
+ }
+ return dto.entrySet().stream().map(entry -> create(entry.getKey(), entry.getValue().getDescription())).toList();
+ }
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "creationDate", ignore = true)
+ @Mapping(target = "creationUser", ignore = true)
+ @Mapping(target = "modificationDate", ignore = true)
+ @Mapping(target = "modificationUser", ignore = true)
+ @Mapping(target = "controlTraceabilityManual", ignore = true)
+ @Mapping(target = "modificationCount", ignore = true)
+ @Mapping(target = "persisted", ignore = true)
+ @Mapping(target = "tenantId", ignore = true)
+ Role create(String name, String description);
+
+ default List mapWorkspace(Map>> permissions) {
+ if (permissions == null) {
+ return List.of();
+ }
+ List result = new ArrayList<>();
+ permissions.forEach((workspaceId, perm) -> perm.forEach((resource, actions) -> actions
+ .forEach((action, description) -> {
+ var tmp = mapWorkspace(workspaceId, resource, action);
+ if (tmp != null) {
+ tmp.setDescription(description);
+ result.add(tmp);
+ }
+ })));
+ return result;
+ }
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "creationDate", ignore = true)
+ @Mapping(target = "creationUser", ignore = true)
+ @Mapping(target = "modificationDate", ignore = true)
+ @Mapping(target = "modificationUser", ignore = true)
+ @Mapping(target = "controlTraceabilityManual", ignore = true)
+ @Mapping(target = "modificationCount", ignore = true)
+ @Mapping(target = "persisted", ignore = true)
+ @Mapping(target = "tenantId", ignore = true)
+ @Mapping(target = "description", ignore = true)
+ WorkspacePermission mapWorkspace(String workspaceId, String resource, String action);
+
+ default List map(Map>> permissions) {
+ if (permissions == null) {
+ return List.of();
+ }
+ List result = new ArrayList<>();
+ permissions.forEach((appId, perm) -> perm.forEach((resource, actions) -> actions
+ .forEach((action, description) -> {
+ var tmp = map(appId, resource, action);
+ if (tmp != null) {
+ tmp.setDescription(description);
+ result.add(tmp);
+ }
+ })));
+ return result;
+ }
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "creationDate", ignore = true)
+ @Mapping(target = "creationUser", ignore = true)
+ @Mapping(target = "modificationDate", ignore = true)
+ @Mapping(target = "modificationUser", ignore = true)
+ @Mapping(target = "controlTraceabilityManual", ignore = true)
+ @Mapping(target = "modificationCount", ignore = true)
+ @Mapping(target = "persisted", ignore = true)
+ @Mapping(target = "description", ignore = true)
+ Permission map(String appId, String resource, String action);
+}
diff --git a/src/main/java/io/github/onecx/permission/domain/models/Assignment.java b/src/main/java/io/github/onecx/permission/domain/models/Assignment.java
new file mode 100644
index 0000000..85c013a
--- /dev/null
+++ b/src/main/java/io/github/onecx/permission/domain/models/Assignment.java
@@ -0,0 +1,45 @@
+package io.github.onecx.permission.domain.models;
+
+import jakarta.persistence.*;
+
+import org.hibernate.annotations.TenantId;
+import org.tkit.quarkus.jpa.models.TraceableEntity;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Entity
+@Table(name = "ASSIGNMENT", uniqueConstraints = {
+ @UniqueConstraint(name = "ASSIGNMENT_KEY", columnNames = { "TENANT_ID", "ROLE_ID", "PERMISSION_ID" })
+}, indexes = {
+ @Index(name = "ASSIGNMENT_TENANT_ID", columnList = "TENANT_ID")
+})
+@SuppressWarnings("java:S2160")
+public class Assignment extends TraceableEntity {
+
+ @TenantId
+ @Column(name = "TENANT_ID")
+ private String tenantId;
+
+ @Column(name = "ROLE_ID", insertable = false, updatable = false)
+ private String roleId;
+
+ @Column(name = "PERMISSION_ID", insertable = false, updatable = false)
+ private String permissionId;
+
+ @ManyToOne(fetch = FetchType.LAZY, optional = false)
+ @JoinColumn(name = "ROLE_ID")
+ private Role role;
+
+ @ManyToOne(fetch = FetchType.LAZY, optional = false)
+ @JoinColumn(name = "PERMISSION_ID")
+ private Permission permission;
+
+ @PostPersist
+ void postPersist() {
+ roleId = role.getId();
+ permissionId = permission.getId();
+ }
+}
diff --git a/src/main/java/io/github/onecx/permission/domain/models/Permission.java b/src/main/java/io/github/onecx/permission/domain/models/Permission.java
index 0da39ee..bd9c60b 100644
--- a/src/main/java/io/github/onecx/permission/domain/models/Permission.java
+++ b/src/main/java/io/github/onecx/permission/domain/models/Permission.java
@@ -1,9 +1,6 @@
package io.github.onecx.permission.domain.models;
-import jakarta.persistence.Column;
-import jakarta.persistence.Entity;
-import jakarta.persistence.Table;
-import jakarta.persistence.UniqueConstraint;
+import jakarta.persistence.*;
import org.tkit.quarkus.jpa.models.TraceableEntity;
@@ -14,7 +11,9 @@
@Setter
@Entity
@Table(name = "PERMISSION", uniqueConstraints = {
- @UniqueConstraint(name = "PERMISSION_KEY", columnNames = { "APP_ID", "RESOURCE", "ACTION" })
+ @UniqueConstraint(name = "PERMISSION_KEY", columnNames = { "APP_ID", "RESOURCE", "ACTION" }),
+}, indexes = {
+ @Index(name = "PERMISSION_APP_ID", columnList = "APP_ID")
})
@SuppressWarnings("squid:S2160")
public class Permission extends TraceableEntity {
@@ -34,12 +33,6 @@ public class Permission extends TraceableEntity {
@Column(name = "RESOURCE")
private String resource;
- /**
- * The permission name.
- */
- @Column(name = "NAME")
- private String name;
-
/**
* The permission description.
*/
diff --git a/src/main/java/io/github/onecx/permission/domain/models/Role.java b/src/main/java/io/github/onecx/permission/domain/models/Role.java
new file mode 100644
index 0000000..633c706
--- /dev/null
+++ b/src/main/java/io/github/onecx/permission/domain/models/Role.java
@@ -0,0 +1,38 @@
+package io.github.onecx.permission.domain.models;
+
+import jakarta.persistence.*;
+
+import org.hibernate.annotations.TenantId;
+import org.tkit.quarkus.jpa.models.TraceableEntity;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Entity
+@Table(name = "ROLE", uniqueConstraints = {
+ @UniqueConstraint(name = "ROLE_NAME", columnNames = { "TENANT_ID", "NAME" })
+}, indexes = {
+ @Index(name = "ROLE_NAME", columnList = "NAME")
+})
+@SuppressWarnings("java:S2160")
+public class Role extends TraceableEntity {
+
+ @TenantId
+ @Column(name = "TENANT_ID")
+ private String tenantId;
+
+ /**
+ * The role name.
+ */
+ @Column(name = "NAME")
+ private String name;
+
+ /**
+ * The role description.
+ */
+ @Column(name = "DESCRIPTION")
+ private String description;
+
+}
diff --git a/src/main/java/io/github/onecx/permission/domain/models/WorkspaceAssignment.java b/src/main/java/io/github/onecx/permission/domain/models/WorkspaceAssignment.java
new file mode 100644
index 0000000..0a9cd2c
--- /dev/null
+++ b/src/main/java/io/github/onecx/permission/domain/models/WorkspaceAssignment.java
@@ -0,0 +1,43 @@
+package io.github.onecx.permission.domain.models;
+
+import jakarta.persistence.*;
+
+import org.hibernate.annotations.TenantId;
+import org.tkit.quarkus.jpa.models.TraceableEntity;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Entity
+@Table(name = "WORKSPACE_ASSIGNMENT", uniqueConstraints = {
+ @UniqueConstraint(name = "WORKSPACE_ASSIGNMENT_KEY", columnNames = { "TENANT_ID", "ROLE_ID", "PERMISSION_ID" })
+})
+@SuppressWarnings("java:S2160")
+public class WorkspaceAssignment extends TraceableEntity {
+
+ @TenantId
+ @Column(name = "TENANT_ID")
+ private String tenantId;
+
+ @ManyToOne(fetch = FetchType.LAZY, optional = false)
+ @JoinColumn(name = "ROLE_ID")
+ private Role role;
+
+ @ManyToOne(fetch = FetchType.LAZY, optional = false)
+ @JoinColumn(name = "PERMISSION_ID")
+ private WorkspacePermission permission;
+
+ @Column(name = "ROLE_ID", insertable = false, updatable = false)
+ private String roleId;
+
+ @Column(name = "PERMISSION_ID", insertable = false, updatable = false)
+ private String permissionId;
+
+ @PostPersist
+ void postPersist() {
+ roleId = role.getId();
+ permissionId = permission.getId();
+ }
+}
diff --git a/src/main/java/io/github/onecx/permission/domain/models/WorkspacePermission.java b/src/main/java/io/github/onecx/permission/domain/models/WorkspacePermission.java
new file mode 100644
index 0000000..9f4b839
--- /dev/null
+++ b/src/main/java/io/github/onecx/permission/domain/models/WorkspacePermission.java
@@ -0,0 +1,49 @@
+package io.github.onecx.permission.domain.models;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Table;
+import jakarta.persistence.UniqueConstraint;
+
+import org.hibernate.annotations.TenantId;
+import org.tkit.quarkus.jpa.models.TraceableEntity;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Entity
+@Table(name = "WORKSPACE_PERMISSION", uniqueConstraints = {
+ @UniqueConstraint(name = "WORKSPACE_PERMISSION_KEY", columnNames = { "TENANT_ID", "WORKSPACE_ID", "RESOURCE",
+ "ACTION" })
+})
+@SuppressWarnings("squid:S2160")
+public class WorkspacePermission extends TraceableEntity {
+
+ @Column(name = "WORKSPACE_ID")
+ private String workspaceId;
+
+ @TenantId
+ @Column(name = "TENANT_ID")
+ private String tenantId;
+
+ /**
+ * The permission action.
+ */
+ @Column(name = "ACTION")
+ private String action;
+
+ /**
+ * The permission object.
+ */
+ @Column(name = "RESOURCE")
+ private String resource;
+
+ /**
+ * The permission description.
+ */
+ @Column(name = "DESCRIPTION")
+ private String description;
+
+}
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
new file mode 100644
index 0000000..cb62ed4
--- /dev/null
+++ b/src/main/java/io/github/onecx/permission/rs/external/v1/controllers/PermissionRestController.java
@@ -0,0 +1,70 @@
+package io.github.onecx.permission.rs.external.v1.controllers;
+
+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.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
+@ApplicationScoped
+public class PermissionRestController implements PermissionApiV1 {
+
+ @Inject
+ TokenService tokenService;
+
+ @Inject
+ PermissionDAO permissionDAO;
+
+ @Inject
+ PermissionMapper mapper;
+
+ @Inject
+ WorkspacePermissionDAO workspacePermissionDAO;
+
+ @Inject
+ ExceptionMapper exceptionMapper;
+
+ @Override
+ 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 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 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..713a58d
--- /dev/null
+++ b/src/main/java/io/github/onecx/permission/rs/external/v1/mappers/ExceptionMapper.java
@@ -0,0 +1,68 @@
+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 {
+
+ @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 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);
+ }
+
+ 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;
+ }
+
+ public List map(Map params) {
+ if (params == null) {
+ return List.of();
+ }
+ return params.entrySet().stream().map(e -> {
+ var item = create(e.getKey());
+ if (e.getValue() != null) {
+ item.setValue(e.getValue().toString());
+ }
+ return item;
+ }).toList();
+ }
+
+ private ProblemDetailParamDTOV1 create(String key) {
+ var item = new ProblemDetailParamDTOV1();
+ item.setKey(key);
+ return item;
+ }
+}
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
new file mode 100644
index 0000000..c3f5b00
--- /dev/null
+++ b/src/main/java/io/github/onecx/permission/rs/external/v1/mappers/PermissionMapper.java
@@ -0,0 +1,68 @@
+package io.github.onecx.permission.rs.external.v1.mappers;
+
+import java.util.*;
+
+import org.mapstruct.Mapper;
+
+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 List.of();
+ }
+ 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 Map.of();
+ }
+ Map> result = new HashMap<>();
+ permissions.forEach(permission -> result.computeIfAbsent(permission.getResource(), k -> new HashSet<>())
+ .add(permission.getAction()));
+ return result;
+ }
+
+ default ApplicationPermissionsDTOV1 create(String appId, Map> permissions) {
+ return new ApplicationPermissionsDTOV1().appId(appId).permissions(permissions);
+ }
+
+ default ApplicationPermissionsDTOV1 create(String appId, List permissions) {
+ return create(appId, permissions(permissions));
+ }
+
+ default Map> permissionsWorkspace(List permissions) {
+ if (permissions == null) {
+ return Map.of();
+ }
+ 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/java/io/github/onecx/permission/rs/internal/controllers/AssignmentRestController.java b/src/main/java/io/github/onecx/permission/rs/internal/controllers/AssignmentRestController.java
new file mode 100644
index 0000000..be202ac
--- /dev/null
+++ b/src/main/java/io/github/onecx/permission/rs/internal/controllers/AssignmentRestController.java
@@ -0,0 +1,100 @@
+package io.github.onecx.permission.rs.internal.controllers;
+
+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;
+import jakarta.ws.rs.core.UriInfo;
+
+import org.jboss.resteasy.reactive.RestResponse;
+import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
+import org.tkit.quarkus.jpa.exceptions.ConstraintException;
+import org.tkit.quarkus.log.cdi.LogService;
+
+import gen.io.github.onecx.permission.rs.internal.AssignmentInternalApi;
+import gen.io.github.onecx.permission.rs.internal.model.AssignmentSearchCriteriaDTO;
+import gen.io.github.onecx.permission.rs.internal.model.CreateAssignmentRequestDTO;
+import gen.io.github.onecx.permission.rs.internal.model.ProblemDetailResponseDTO;
+import io.github.onecx.permission.domain.daos.AssignmentDAO;
+import io.github.onecx.permission.domain.daos.PermissionDAO;
+import io.github.onecx.permission.domain.daos.RoleDAO;
+import io.github.onecx.permission.rs.internal.mappers.AssignmentMapper;
+import io.github.onecx.permission.rs.internal.mappers.ExceptionMapper;
+
+@LogService
+@ApplicationScoped
+public class AssignmentRestController implements AssignmentInternalApi {
+
+ @Inject
+ AssignmentMapper mapper;
+
+ @Inject
+ AssignmentDAO dao;
+
+ @Inject
+ ExceptionMapper exceptionMapper;
+
+ @Context
+ UriInfo uriInfo;
+
+ @Inject
+ RoleDAO roleDAO;
+
+ @Inject
+ PermissionDAO permissionDAO;
+
+ @Override
+ public Response getAssignment(String id) {
+ var data = dao.findById(id);
+ if (data == null) {
+ return Response.status(Response.Status.NOT_FOUND).build();
+ }
+ return Response.ok(mapper.map(data)).build();
+ }
+
+ @Override
+ public Response searchAssignments(AssignmentSearchCriteriaDTO assignmentSearchCriteriaDTO) {
+ var criteria = mapper.map(assignmentSearchCriteriaDTO);
+ var result = dao.findByCriteria(criteria);
+ return Response.ok(mapper.map(result)).build();
+ }
+
+ @Override
+ @Transactional
+ public Response createAssignment(CreateAssignmentRequestDTO 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();
+ }
+
+ var data = mapper.create(role, permission);
+ data = dao.create(data);
+ return Response
+ .created(uriInfo.getAbsolutePathBuilder().path(data.getId()).build())
+ .entity(mapper.map(data))
+ .build();
+ }
+
+ @Override
+ @Transactional
+ public Response deleteAssignment(String id) {
+ dao.deleteQueryById(id);
+ return Response.noContent().build();
+ }
+
+ @ServerExceptionMapper
+ public RestResponse constraint(ConstraintViolationException ex) {
+ return exceptionMapper.constraint(ex);
+ }
+
+ @ServerExceptionMapper
+ public RestResponse exception(ConstraintException ex) {
+ return exceptionMapper.exception(ex);
+ }
+}
diff --git a/src/main/java/io/github/onecx/permission/rs/internal/controllers/RoleRestController.java b/src/main/java/io/github/onecx/permission/rs/internal/controllers/RoleRestController.java
new file mode 100644
index 0000000..f54ac19
--- /dev/null
+++ b/src/main/java/io/github/onecx/permission/rs/internal/controllers/RoleRestController.java
@@ -0,0 +1,97 @@
+package io.github.onecx.permission.rs.internal.controllers;
+
+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;
+import jakarta.ws.rs.core.UriInfo;
+
+import org.jboss.resteasy.reactive.RestResponse;
+import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
+import org.tkit.quarkus.jpa.exceptions.ConstraintException;
+import org.tkit.quarkus.log.cdi.LogService;
+
+import gen.io.github.onecx.permission.rs.internal.RoleInternalApi;
+import gen.io.github.onecx.permission.rs.internal.model.CreateRoleRequestDTO;
+import gen.io.github.onecx.permission.rs.internal.model.ProblemDetailResponseDTO;
+import gen.io.github.onecx.permission.rs.internal.model.RoleSearchCriteriaDTO;
+import gen.io.github.onecx.permission.rs.internal.model.UpdateRoleRequestDTO;
+import io.github.onecx.permission.domain.daos.RoleDAO;
+import io.github.onecx.permission.rs.internal.mappers.ExceptionMapper;
+import io.github.onecx.permission.rs.internal.mappers.RoleMapper;
+
+@LogService
+@ApplicationScoped
+public class RoleRestController implements RoleInternalApi {
+
+ @Inject
+ ExceptionMapper exceptionMapper;
+
+ @Inject
+ RoleDAO dao;
+
+ @Inject
+ RoleMapper mapper;
+
+ @Context
+ UriInfo uriInfo;
+
+ @Override
+ @Transactional
+ public Response createRole(CreateRoleRequestDTO createRoleRequestDTO) {
+ var role = mapper.create(createRoleRequestDTO);
+ role = dao.create(role);
+ return Response
+ .created(uriInfo.getAbsolutePathBuilder().path(role.getId()).build())
+ .entity(mapper.map(role))
+ .build();
+ }
+
+ @Override
+ @Transactional
+ public Response deleteRole(String id) {
+ dao.deleteQueryById(id);
+ return Response.noContent().build();
+ }
+
+ @Override
+ public Response getRoleById(String id) {
+ var role = dao.findById(id);
+ if (role == null) {
+ return Response.status(Response.Status.NOT_FOUND).build();
+ }
+ return Response.ok(mapper.map(role)).build();
+ }
+
+ @Override
+ public Response searchRoles(RoleSearchCriteriaDTO roleSearchCriteriaDTO) {
+ var criteria = mapper.map(roleSearchCriteriaDTO);
+ var result = dao.findByCriteria(criteria);
+ return Response.ok(mapper.mapPage(result)).build();
+ }
+
+ @Override
+ @Transactional
+ public Response updateRole(String id, UpdateRoleRequestDTO updateRoleRequestDTO) {
+ var role = dao.findById(id);
+ if (role == null) {
+ return Response.status(Response.Status.NOT_FOUND).build();
+ }
+
+ mapper.update(updateRoleRequestDTO, role);
+ dao.update(role);
+ return Response.ok(mapper.map(role)).build();
+ }
+
+ @ServerExceptionMapper
+ public RestResponse constraint(ConstraintViolationException ex) {
+ return exceptionMapper.constraint(ex);
+ }
+
+ @ServerExceptionMapper
+ public RestResponse exception(ConstraintException ex) {
+ return exceptionMapper.exception(ex);
+ }
+}
diff --git a/src/main/java/io/github/onecx/permission/rs/internal/controllers/WorkspaceAssignmentRestController.java b/src/main/java/io/github/onecx/permission/rs/internal/controllers/WorkspaceAssignmentRestController.java
new file mode 100644
index 0000000..a0d1e12
--- /dev/null
+++ b/src/main/java/io/github/onecx/permission/rs/internal/controllers/WorkspaceAssignmentRestController.java
@@ -0,0 +1,97 @@
+package io.github.onecx.permission.rs.internal.controllers;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.validation.ConstraintViolationException;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.UriInfo;
+
+import org.jboss.resteasy.reactive.RestResponse;
+import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
+import org.tkit.quarkus.jpa.exceptions.ConstraintException;
+import org.tkit.quarkus.log.cdi.LogService;
+
+import gen.io.github.onecx.permission.rs.internal.WorkspaceAssignmentInternalApi;
+import gen.io.github.onecx.permission.rs.internal.model.CreateWorkspaceAssignmentRequestDTO;
+import gen.io.github.onecx.permission.rs.internal.model.ProblemDetailResponseDTO;
+import gen.io.github.onecx.permission.rs.internal.model.WorkspaceAssignmentSearchCriteriaDTO;
+import io.github.onecx.permission.domain.daos.RoleDAO;
+import io.github.onecx.permission.domain.daos.WorkspaceAssignmentDAO;
+import io.github.onecx.permission.domain.daos.WorkspacePermissionDAO;
+import io.github.onecx.permission.rs.internal.mappers.ExceptionMapper;
+import io.github.onecx.permission.rs.internal.mappers.WorkspaceAssignmentMapper;
+
+@LogService
+@ApplicationScoped
+public class WorkspaceAssignmentRestController implements WorkspaceAssignmentInternalApi {
+
+ @Inject
+ ExceptionMapper exceptionMapper;
+
+ @Inject
+ RoleDAO roleDAO;
+
+ @Inject
+ WorkspacePermissionDAO workspacePermissionDAO;
+
+ @Inject
+ WorkspaceAssignmentMapper mapper;
+
+ @Context
+ UriInfo uriInfo;
+
+ @Inject
+ WorkspaceAssignmentDAO dao;
+
+ @Override
+ public Response createWorkspaceAssignment(CreateWorkspaceAssignmentRequestDTO createWorkspaceAssignmentRequestDTO) {
+ var role = roleDAO.findById(createWorkspaceAssignmentRequestDTO.getRoleId());
+ if (role == null) {
+ return Response.status(Response.Status.NOT_FOUND).build();
+ }
+ var permission = workspacePermissionDAO.findById(createWorkspaceAssignmentRequestDTO.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.map(data))
+ .build();
+ }
+
+ @Override
+ public Response deleteWorkspaceAssignment(String id) {
+ dao.deleteQueryById(id);
+ return Response.noContent().build();
+ }
+
+ @Override
+ public Response getWorkspaceAssignment(String id) {
+ var data = dao.findById(id);
+ if (data == null) {
+ return Response.status(Response.Status.NOT_FOUND).build();
+ }
+ return Response.ok(mapper.map(data)).build();
+ }
+
+ @Override
+ public Response searchWorkspaceAssignments(WorkspaceAssignmentSearchCriteriaDTO workspaceAssignmentSearchCriteriaDTO) {
+ var criteria = mapper.map(workspaceAssignmentSearchCriteriaDTO);
+ var result = dao.findByCriteria(criteria);
+ return Response.ok(mapper.map(result)).build();
+ }
+
+ @ServerExceptionMapper
+ public RestResponse constraint(ConstraintViolationException ex) {
+ return exceptionMapper.constraint(ex);
+ }
+
+ @ServerExceptionMapper
+ public RestResponse exception(ConstraintException ex) {
+ return exceptionMapper.exception(ex);
+ }
+}
diff --git a/src/main/java/io/github/onecx/permission/rs/internal/controllers/WorkspacePermissionRestController.java b/src/main/java/io/github/onecx/permission/rs/internal/controllers/WorkspacePermissionRestController.java
new file mode 100644
index 0000000..2778f9a
--- /dev/null
+++ b/src/main/java/io/github/onecx/permission/rs/internal/controllers/WorkspacePermissionRestController.java
@@ -0,0 +1,94 @@
+package io.github.onecx.permission.rs.internal.controllers;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.validation.ConstraintViolationException;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.UriInfo;
+
+import org.jboss.resteasy.reactive.RestResponse;
+import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
+import org.tkit.quarkus.jpa.exceptions.ConstraintException;
+import org.tkit.quarkus.log.cdi.LogService;
+
+import gen.io.github.onecx.permission.rs.internal.WorkspacePermissionInternalApi;
+import gen.io.github.onecx.permission.rs.internal.model.CreateWorkspacePermissionRequestDTO;
+import gen.io.github.onecx.permission.rs.internal.model.ProblemDetailResponseDTO;
+import gen.io.github.onecx.permission.rs.internal.model.UpdateWorkspacePermissionRequestDTO;
+import gen.io.github.onecx.permission.rs.internal.model.WorkspacePermissionSearchCriteriaDTO;
+import io.github.onecx.permission.domain.daos.WorkspacePermissionDAO;
+import io.github.onecx.permission.rs.internal.mappers.ExceptionMapper;
+import io.github.onecx.permission.rs.internal.mappers.WorkspacePermissionMapper;
+
+@LogService
+@ApplicationScoped
+public class WorkspacePermissionRestController implements WorkspacePermissionInternalApi {
+
+ @Inject
+ ExceptionMapper exceptionMapper;
+
+ @Inject
+ WorkspacePermissionMapper mapper;
+
+ @Inject
+ WorkspacePermissionDAO dao;
+
+ @Context
+ UriInfo uriInfo;
+
+ @Override
+ public Response createWorkspacePermission(CreateWorkspacePermissionRequestDTO createWorkspacePermissionRequestDTO) {
+ var item = mapper.create(createWorkspacePermissionRequestDTO);
+ item = dao.create(item);
+ return Response
+ .created(uriInfo.getAbsolutePathBuilder().path(item.getId()).build())
+ .entity(mapper.map(item))
+ .build();
+ }
+
+ @Override
+ public Response deleteWorkspacePermission(String id) {
+ dao.deleteQueryById(id);
+ return Response.noContent().build();
+ }
+
+ @Override
+ public Response getWorkspacePermissionById(String id) {
+ var data = dao.findById(id);
+ if (data == null) {
+ return Response.status(Response.Status.NOT_FOUND).build();
+ }
+ return Response.ok(mapper.map(data)).build();
+ }
+
+ @Override
+ public Response searchWorkspacePermissions(WorkspacePermissionSearchCriteriaDTO workspacePermissionSearchCriteriaDTO) {
+ var criteria = mapper.map(workspacePermissionSearchCriteriaDTO);
+ var result = dao.findByCriteria(criteria);
+ return Response.ok(mapper.map(result)).build();
+ }
+
+ @Override
+ public Response updateWorkspacePermission(String id,
+ UpdateWorkspacePermissionRequestDTO updateWorkspacePermissionRequestDTO) {
+ var item = dao.findById(id);
+ if (item == null) {
+ return Response.status(Response.Status.NOT_FOUND).build();
+ }
+
+ mapper.update(updateWorkspacePermissionRequestDTO, item);
+ dao.update(item);
+ return Response.ok(mapper.map(item)).build();
+ }
+
+ @ServerExceptionMapper
+ public RestResponse constraint(ConstraintViolationException ex) {
+ return exceptionMapper.constraint(ex);
+ }
+
+ @ServerExceptionMapper
+ public RestResponse exception(ConstraintException ex) {
+ return exceptionMapper.exception(ex);
+ }
+}
diff --git a/src/main/java/io/github/onecx/permission/rs/internal/log/InternalLogParam.java b/src/main/java/io/github/onecx/permission/rs/internal/log/InternalLogParam.java
index 9cddc46..b47759d 100644
--- a/src/main/java/io/github/onecx/permission/rs/internal/log/InternalLogParam.java
+++ b/src/main/java/io/github/onecx/permission/rs/internal/log/InternalLogParam.java
@@ -6,7 +6,7 @@
import org.tkit.quarkus.log.cdi.LogParam;
-import gen.io.github.onecx.permission.rs.internal.model.PermissionSearchCriteriaDTO;
+import gen.io.github.onecx.permission.rs.internal.model.*;
@ApplicationScoped
public class InternalLogParam implements LogParam {
@@ -18,6 +18,25 @@ public List- getClasses() {
PermissionSearchCriteriaDTO d = (PermissionSearchCriteriaDTO) x;
return PermissionSearchCriteriaDTO.class.getSimpleName() + "[" + d.getPageNumber() + "," + d.getPageSize()
+ "]";
- }));
+ }),
+ item(10, AssignmentSearchCriteriaDTO.class, x -> {
+ AssignmentSearchCriteriaDTO d = (AssignmentSearchCriteriaDTO) x;
+ return AssignmentSearchCriteriaDTO.class.getSimpleName() + "[" + d.getPageNumber() + "," + d.getPageSize()
+ + "]";
+ }),
+ item(10, RoleSearchCriteriaDTO.class, x -> {
+ RoleSearchCriteriaDTO d = (RoleSearchCriteriaDTO) x;
+ 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, CreateRoleRequestDTO.class,
+ x -> x.getClass().getSimpleName() + ":" + ((CreateRoleRequestDTO) x).getName()),
+ item(10, UpdateRoleRequestDTO.class,
+ x -> x.getClass().getSimpleName() + ":" + ((UpdateRoleRequestDTO) x).getName()));
}
}
diff --git a/src/main/java/io/github/onecx/permission/rs/internal/mappers/AssignmentMapper.java b/src/main/java/io/github/onecx/permission/rs/internal/mappers/AssignmentMapper.java
new file mode 100644
index 0000000..b00b583
--- /dev/null
+++ b/src/main/java/io/github/onecx/permission/rs/internal/mappers/AssignmentMapper.java
@@ -0,0 +1,38 @@
+package io.github.onecx.permission.rs.internal.mappers;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.tkit.quarkus.jpa.daos.PageResult;
+import org.tkit.quarkus.rs.mappers.OffsetDateTimeMapper;
+
+import gen.io.github.onecx.permission.rs.internal.model.AssignmentDTO;
+import gen.io.github.onecx.permission.rs.internal.model.AssignmentPageResultDTO;
+import gen.io.github.onecx.permission.rs.internal.model.AssignmentSearchCriteriaDTO;
+import io.github.onecx.permission.domain.criteria.AssignmentSearchCriteria;
+import io.github.onecx.permission.domain.models.Assignment;
+import io.github.onecx.permission.domain.models.Permission;
+import io.github.onecx.permission.domain.models.Role;
+
+@Mapper(uses = { OffsetDateTimeMapper.class })
+public interface AssignmentMapper {
+
+ @Mapping(target = "removeStreamItem", ignore = true)
+ AssignmentPageResultDTO map(PageResult page);
+
+ AssignmentSearchCriteria map(AssignmentSearchCriteriaDTO dto);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "creationDate", ignore = true)
+ @Mapping(target = "creationUser", ignore = true)
+ @Mapping(target = "modificationDate", ignore = true)
+ @Mapping(target = "modificationUser", ignore = true)
+ @Mapping(target = "controlTraceabilityManual", ignore = true)
+ @Mapping(target = "modificationCount", ignore = true)
+ @Mapping(target = "persisted", ignore = true)
+ @Mapping(target = "tenantId", ignore = true)
+ @Mapping(target = "roleId", ignore = true)
+ @Mapping(target = "permissionId", ignore = true)
+ Assignment create(Role role, Permission permission);
+
+ AssignmentDTO map(Assignment data);
+}
diff --git a/src/main/java/io/github/onecx/permission/rs/internal/mappers/ExceptionMapper.java b/src/main/java/io/github/onecx/permission/rs/internal/mappers/ExceptionMapper.java
index 9e3fe83..7badaea 100644
--- a/src/main/java/io/github/onecx/permission/rs/internal/mappers/ExceptionMapper.java
+++ b/src/main/java/io/github/onecx/permission/rs/internal/mappers/ExceptionMapper.java
@@ -12,6 +12,7 @@
import org.jboss.resteasy.reactive.RestResponse;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
+import org.tkit.quarkus.jpa.exceptions.ConstraintException;
import org.tkit.quarkus.rs.mappers.OffsetDateTimeMapper;
import gen.io.github.onecx.permission.rs.internal.model.ProblemDetailInvalidParamDTO;
@@ -27,6 +28,12 @@ public RestResponse constraint(ConstraintViolationExce
return RestResponse.status(Response.Status.BAD_REQUEST, dto);
}
+ public RestResponse exception(ConstraintException ex) {
+ var dto = exception(ex.getMessageKey().name(), ex.getConstraints());
+ dto.setParams(map(ex.namedParameters));
+ return RestResponse.status(Response.Status.BAD_REQUEST, dto);
+ }
+
@Mapping(target = "removeParamsItem", ignore = true)
@Mapping(target = "params", ignore = true)
@Mapping(target = "invalidParams", ignore = true)
diff --git a/src/main/java/io/github/onecx/permission/rs/internal/mappers/RoleMapper.java b/src/main/java/io/github/onecx/permission/rs/internal/mappers/RoleMapper.java
new file mode 100644
index 0000000..d6c4bb4
--- /dev/null
+++ b/src/main/java/io/github/onecx/permission/rs/internal/mappers/RoleMapper.java
@@ -0,0 +1,43 @@
+package io.github.onecx.permission.rs.internal.mappers;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingTarget;
+import org.tkit.quarkus.jpa.daos.PageResult;
+import org.tkit.quarkus.rs.mappers.OffsetDateTimeMapper;
+
+import gen.io.github.onecx.permission.rs.internal.model.*;
+import io.github.onecx.permission.domain.criteria.RoleSearchCriteria;
+import io.github.onecx.permission.domain.models.Role;
+
+@Mapper(uses = { OffsetDateTimeMapper.class })
+public interface RoleMapper {
+
+ @Mapping(target = "removeStreamItem", ignore = true)
+ public abstract RolePageResultDTO mapPage(PageResult page);
+
+ RoleSearchCriteria map(RoleSearchCriteriaDTO dto);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "creationDate", ignore = true)
+ @Mapping(target = "creationUser", ignore = true)
+ @Mapping(target = "modificationDate", ignore = true)
+ @Mapping(target = "modificationUser", ignore = true)
+ @Mapping(target = "controlTraceabilityManual", ignore = true)
+ @Mapping(target = "modificationCount", ignore = true)
+ @Mapping(target = "persisted", ignore = true)
+ @Mapping(target = "tenantId", ignore = true)
+ Role create(CreateRoleRequestDTO dto);
+
+ RoleDTO map(Role data);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "creationDate", ignore = true)
+ @Mapping(target = "creationUser", ignore = true)
+ @Mapping(target = "modificationDate", ignore = true)
+ @Mapping(target = "modificationUser", ignore = true)
+ @Mapping(target = "controlTraceabilityManual", ignore = true)
+ @Mapping(target = "persisted", ignore = true)
+ @Mapping(target = "tenantId", ignore = true)
+ void update(UpdateRoleRequestDTO dto, @MappingTarget Role role);
+}
diff --git a/src/main/java/io/github/onecx/permission/rs/internal/mappers/WorkspaceAssignmentMapper.java b/src/main/java/io/github/onecx/permission/rs/internal/mappers/WorkspaceAssignmentMapper.java
new file mode 100644
index 0000000..fef7a98
--- /dev/null
+++ b/src/main/java/io/github/onecx/permission/rs/internal/mappers/WorkspaceAssignmentMapper.java
@@ -0,0 +1,36 @@
+package io.github.onecx.permission.rs.internal.mappers;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.tkit.quarkus.jpa.daos.PageResult;
+import org.tkit.quarkus.rs.mappers.OffsetDateTimeMapper;
+
+import gen.io.github.onecx.permission.rs.internal.model.*;
+import io.github.onecx.permission.domain.criteria.WorkspaceAssignmentSearchCriteria;
+import io.github.onecx.permission.domain.models.Role;
+import io.github.onecx.permission.domain.models.WorkspaceAssignment;
+import io.github.onecx.permission.domain.models.WorkspacePermission;
+
+@Mapper(uses = { OffsetDateTimeMapper.class })
+public interface WorkspaceAssignmentMapper {
+
+ @Mapping(target = "removeStreamItem", ignore = true)
+ WorkspaceAssignmentPageResultDTO map(PageResult page);
+
+ WorkspaceAssignmentSearchCriteria map(WorkspaceAssignmentSearchCriteriaDTO dto);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "creationDate", ignore = true)
+ @Mapping(target = "creationUser", ignore = true)
+ @Mapping(target = "modificationDate", ignore = true)
+ @Mapping(target = "modificationUser", ignore = true)
+ @Mapping(target = "controlTraceabilityManual", ignore = true)
+ @Mapping(target = "modificationCount", ignore = true)
+ @Mapping(target = "persisted", ignore = true)
+ @Mapping(target = "tenantId", ignore = true)
+ @Mapping(target = "roleId", ignore = true)
+ @Mapping(target = "permissionId", ignore = true)
+ WorkspaceAssignment create(Role role, WorkspacePermission permission);
+
+ WorkspaceAssignmentDTO map(WorkspaceAssignment data);
+}
diff --git a/src/main/java/io/github/onecx/permission/rs/internal/mappers/WorkspacePermissionMapper.java b/src/main/java/io/github/onecx/permission/rs/internal/mappers/WorkspacePermissionMapper.java
new file mode 100644
index 0000000..bdc042d
--- /dev/null
+++ b/src/main/java/io/github/onecx/permission/rs/internal/mappers/WorkspacePermissionMapper.java
@@ -0,0 +1,46 @@
+package io.github.onecx.permission.rs.internal.mappers;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingTarget;
+import org.tkit.quarkus.jpa.daos.PageResult;
+import org.tkit.quarkus.rs.mappers.OffsetDateTimeMapper;
+
+import gen.io.github.onecx.permission.rs.internal.model.*;
+import io.github.onecx.permission.domain.criteria.WorkspacePermissionSearchCriteria;
+import io.github.onecx.permission.domain.models.WorkspacePermission;
+
+@Mapper(uses = { OffsetDateTimeMapper.class })
+public interface WorkspacePermissionMapper {
+
+ WorkspacePermissionSearchCriteria map(WorkspacePermissionSearchCriteriaDTO dto);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "creationDate", ignore = true)
+ @Mapping(target = "creationUser", ignore = true)
+ @Mapping(target = "modificationDate", ignore = true)
+ @Mapping(target = "modificationUser", ignore = true)
+ @Mapping(target = "controlTraceabilityManual", ignore = true)
+ @Mapping(target = "persisted", ignore = true)
+ @Mapping(target = "tenantId", ignore = true)
+ @Mapping(target = "workspaceId", ignore = true)
+ @Mapping(target = "action", ignore = true)
+ @Mapping(target = "resource", ignore = true)
+ void update(UpdateWorkspacePermissionRequestDTO dto, @MappingTarget WorkspacePermission data);
+
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "creationDate", ignore = true)
+ @Mapping(target = "creationUser", ignore = true)
+ @Mapping(target = "modificationDate", ignore = true)
+ @Mapping(target = "modificationUser", ignore = true)
+ @Mapping(target = "controlTraceabilityManual", ignore = true)
+ @Mapping(target = "modificationCount", ignore = true)
+ @Mapping(target = "persisted", ignore = true)
+ @Mapping(target = "tenantId", ignore = true)
+ WorkspacePermission create(CreateWorkspacePermissionRequestDTO dto);
+
+ @Mapping(target = "removeStreamItem", ignore = true)
+ WorkspacePermissionPageResultDTO map(PageResult page);
+
+ WorkspacePermissionDTO map(WorkspacePermission data);
+}
diff --git a/src/main/java/io/github/onecx/permission/rs/operator/v1/controllers/OperatorRestController.java b/src/main/java/io/github/onecx/permission/rs/operator/v1/controllers/OperatorRestController.java
index 7de4280..910a733 100644
--- a/src/main/java/io/github/onecx/permission/rs/operator/v1/controllers/OperatorRestController.java
+++ b/src/main/java/io/github/onecx/permission/rs/operator/v1/controllers/OperatorRestController.java
@@ -53,7 +53,6 @@ public Response createOrUpdatePermission(String appId, PermissionRequestDTOV1 pe
} else {
// update existing permission
permission.setDescription(item.getDescription());
- permission.setName(item.getName());
dao.update(permission);
}
}
diff --git a/src/main/openapi/onecx-permission-di-v1.yaml b/src/main/openapi/onecx-permission-di-v1.yaml
new file mode 100644
index 0000000..ba6049b
--- /dev/null
+++ b/src/main/openapi/onecx-permission-di-v1.yaml
@@ -0,0 +1,88 @@
+---
+openapi: 3.0.3
+info:
+ title: onecx-permission permission import from file during the start of the application
+ version: 1.0.0
+servers:
+ - url: "http://localhost"
+paths:
+ /import/permission:
+ post:
+ operationId: importPermission
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/DataImport'
+ responses:
+ 200:
+ description: ok
+components:
+ schemas:
+ DataImport:
+ type: object
+ properties:
+ tenants:
+ $ref: '#/components/schemas/DataImportTenant'
+ permissions:
+ $ref: '#/components/schemas/DataImportPermissionWrapper'
+ DataImportPermissionWrapper:
+ type: object
+ nullable: false
+ description: application or workspace id
+ additionalProperties:
+ $ref: '#/components/schemas/DataImportPermission'
+ DataImportPermission:
+ type: object
+ nullable: false
+ description: resource id
+ additionalProperties:
+ $ref: '#/components/schemas/DataImportPermissionAction'
+ DataImportPermissionAction:
+ type: object
+ nullable: false
+ description: action id
+ additionalProperties:
+ type: string
+ DataImportTenant:
+ type: object
+ nullable: false
+ description: tenant id
+ additionalProperties:
+ $ref: '#/components/schemas/DataImportTenantWrapper'
+ DataImportTenantWrapper:
+ type: object
+ properties:
+ workspaces-permissions:
+ $ref: '#/components/schemas/DataImportPermissionWrapper'
+ roles:
+ $ref: '#/components/schemas/DataImportRoleWrapper'
+ DataImportRoleWrapper:
+ type: object
+ nullable: false
+ description: role name
+ additionalProperties:
+ $ref: '#/components/schemas/DataImportRole'
+ DataImportRole:
+ type: object
+ properties:
+ description:
+ type: string
+ assignments:
+ $ref: '#/components/schemas/DataImportAssignmentWrapper'
+ workspaces-assignments:
+ $ref: '#/components/schemas/DataImportAssignmentWrapper'
+ DataImportAssignmentWrapper:
+ type: object
+ nullable: false
+ description: application or workspaces id
+ additionalProperties:
+ $ref: '#/components/schemas/DataImportAssignment'
+ DataImportAssignment:
+ type: object
+ nullable: false
+ description: action
+ additionalProperties:
+ type: array
+ items:
+ type: string
\ No newline at end of file
diff --git a/src/main/openapi/onecx-permission-internal-openapi.yaml b/src/main/openapi/onecx-permission-internal-openapi.yaml
index ca80c07..e2599e5 100644
--- a/src/main/openapi/onecx-permission-internal-openapi.yaml
+++ b/src/main/openapi/onecx-permission-internal-openapi.yaml
@@ -7,7 +7,451 @@ servers:
- url: "http://onecx-permission-svc:8080"
tags:
- name: permissionInternal
+ - name: roleInternal
+ - name: assignmentInternal
+ - name: workspaceAssignmentInternal
+ - name: workspacePermissionInternal
paths:
+ /internal/workspace/permissions/search:
+ post:
+ tags:
+ - workspacePermissionInternal
+ description: Search for workspace permissions
+ operationId: searchWorkspacePermissions
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/WorkspacePermissionSearchCriteria'
+ responses:
+ 200:
+ description: OK
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/WorkspacePermissionPageResult'
+ 400:
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ /internal/workspace/permissions:
+ post:
+ tags:
+ - workspacePermissionInternal
+ description: Create workspace permission
+ operationId: createWorkspacePermission
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CreateWorkspacePermissionRequest'
+ responses:
+ 201:
+ description: New role created
+ headers:
+ Location:
+ required: true
+ schema:
+ type: string
+ format: url
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/WorkspacePermission'
+ 400:
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ /internal/workspace/permissions/{id}:
+ get:
+ tags:
+ - workspacePermissionInternal
+ description: Return Workspace permission by ID
+ operationId: getWorkspacePermissionById
+ parameters:
+ - name: id
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ 200:
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/WorkspacePermission'
+ 404:
+ description: Workspace permission not found
+ put:
+ tags:
+ - workspacePermissionInternal
+ description: Update workspace permission by ID
+ operationId: updateWorkspacePermission
+ parameters:
+ - name: id
+ in: path
+ required: true
+ schema:
+ type: string
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UpdateWorkspacePermissionRequest'
+ responses:
+ 204:
+ description: Theme updated
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/WorkspacePermission'
+ 400:
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ 404:
+ description: Workspace permission not found
+ delete:
+ tags:
+ - workspacePermissionInternal
+ description: Delete workspace permission by ID
+ operationId: deleteWorkspacePermission
+ parameters:
+ - name: id
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ 204:
+ description: Workspace permission deleted
+ /internal/workspace/assignments/search:
+ post:
+ tags:
+ - workspaceAssignmentInternal
+ description: Search for workspace assignments
+ operationId: searchWorkspaceAssignments
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/WorkspaceAssignmentSearchCriteria'
+ responses:
+ 200:
+ description: OK
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/WorkspaceAssignmentPageResult'
+ 400:
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ /internal/workspace/assignments:
+ post:
+ tags:
+ - workspaceAssignmentInternal
+ description: Create new workspace assignment
+ operationId: createWorkspaceAssignment
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CreateWorkspaceAssignmentRequest'
+ responses:
+ 201:
+ description: New workspace assignment created
+ headers:
+ Location:
+ required: true
+ schema:
+ type: string
+ format: url
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/WorkspaceAssignment'
+ 404:
+ description: Data not found
+ 400:
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ /internal/workspace/assignments/{id}:
+ get:
+ tags:
+ - workspaceAssignmentInternal
+ description: Get workspace assignment
+ operationId: getWorkspaceAssignment
+ parameters:
+ - name: id
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ 200:
+ description: Get workspace assignment
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/WorkspaceAssignment'
+ 404:
+ description: Workspace assignment not found
+ delete:
+ tags:
+ - workspaceAssignmentInternal
+ description: Delete workspace assignment
+ operationId: deleteWorkspaceAssignment
+ parameters:
+ - name: id
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ 204:
+ description: Workspace assignment deleted
+ /internal/assignments/search:
+ post:
+ tags:
+ - assignmentInternal
+ description: Search for assignments
+ operationId: searchAssignments
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AssignmentSearchCriteria'
+ responses:
+ 200:
+ description: OK
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/AssignmentPageResult'
+ 400:
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ /internal/assignments:
+ post:
+ tags:
+ - assignmentInternal
+ description: Create new assignment
+ operationId: createAssignment
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CreateAssignmentRequest'
+ responses:
+ 201:
+ description: New assignment created
+ headers:
+ Location:
+ required: true
+ schema:
+ type: string
+ format: url
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Assignment'
+ 404:
+ description: Data not found
+ 400:
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ /internal/assignments/{id}:
+ get:
+ tags:
+ - assignmentInternal
+ description: Get assignment
+ operationId: getAssignment
+ parameters:
+ - name: id
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ 200:
+ description: Get assignment
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Assignment'
+ 404:
+ description: Assignment not found
+ delete:
+ tags:
+ - assignmentInternal
+ description: Delete assignment
+ operationId: deleteAssignment
+ parameters:
+ - name: id
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ 204:
+ description: Assignment deleted
+ /internal/roles:
+ post:
+ tags:
+ - roleInternal
+ description: Create new role
+ operationId: createRole
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CreateRoleRequest'
+ responses:
+ 201:
+ description: New role created
+ headers:
+ Location:
+ required: true
+ schema:
+ type: string
+ format: url
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Role'
+ 400:
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ /internal/roles/{id}:
+ get:
+ tags:
+ - roleInternal
+ description: Return role by ID
+ operationId: getRoleById
+ parameters:
+ - name: id
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ 200:
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Role'
+ 404:
+ description: Role not found
+ put:
+ tags:
+ - roleInternal
+ description: Update role by ID
+ operationId: updateRole
+ parameters:
+ - name: id
+ in: path
+ required: true
+ schema:
+ type: string
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UpdateRoleRequest'
+ responses:
+ 204:
+ description: Theme updated
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Role'
+ 400:
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ 404:
+ description: Role not found
+ delete:
+ tags:
+ - roleInternal
+ description: Delete role by ID
+ operationId: deleteRole
+ parameters:
+ - name: id
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ 204:
+ description: Role deleted
+ /internal/roles/search:
+ post:
+ tags:
+ - roleInternal
+ description: Search for roles
+ operationId: searchRoles
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/RoleSearchCriteria'
+ responses:
+ 200:
+ description: OK
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/RolePageResult'
+ 400:
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
/internal/permissions/search:
post:
tags:
@@ -21,7 +465,7 @@ paths:
schema:
$ref: '#/components/schemas/PermissionSearchCriteria'
responses:
- "200":
+ 200:
description: OK
content:
application/json:
@@ -29,7 +473,7 @@ paths:
type: array
items:
$ref: '#/components/schemas/PermissionPageResult'
- "400":
+ 400:
description: Bad request
content:
application/json:
@@ -37,17 +481,290 @@ paths:
$ref: '#/components/schemas/ProblemDetailResponse'
components:
schemas:
- PermissionSearchCriteria:
+ UpdateWorkspacePermissionRequest:
type: object
+ required:
+ - description
properties:
- appId:
+ modificationCount:
+ format: int32
+ type: integer
+ description:
type: string
- name:
+ CreateWorkspacePermissionRequest:
+ type: object
+ required:
+ - resource
+ - action
+ - workspaceId
+ properties:
+ workspaceId:
+ type: string
+ resource:
+ type: string
+ action:
+ type: string
+ description:
+ type: string
+ WorkspacePermissionSearchCriteria:
+ type: object
+ properties:
+ workspaceId:
+ type: string
+ pageNumber:
+ format: int32
+ description: The number of page.
+ default: 0
+ type: integer
+ pageSize:
+ format: int32
+ description: The size of page
+ default: 100
+ type: integer
+ WorkspacePermissionPageResult:
+ type: object
+ properties:
+ totalElements:
+ format: int64
+ description: The total elements in the resource.
+ type: integer
+ number:
+ format: int32
+ type: integer
+ size:
+ format: int32
+ type: integer
+ totalPages:
+ format: int64
+ type: integer
+ stream:
+ type: array
+ items:
+ $ref: '#/components/schemas/WorkspacePermission'
+ WorkspacePermission:
+ type: object
+ required:
+ - resource
+ - action
+ - workspaceId
+ properties:
+ modificationCount:
+ format: int32
+ type: integer
+ creationDate:
+ $ref: '#/components/schemas/OffsetDateTime'
+ creationUser:
+ type: string
+ modificationDate:
+ $ref: '#/components/schemas/OffsetDateTime'
+ modificationUser:
+ type: string
+ id:
+ type: string
+ workspaceId:
type: string
resource:
type: string
action:
type: string
+ description:
+ type: string
+ CreateWorkspaceAssignmentRequest:
+ type: object
+ required:
+ - roleId
+ - permissionId
+ properties:
+ roleId:
+ type: string
+ permissionId:
+ type: string
+ WorkspaceAssignment:
+ type: object
+ properties:
+ modificationCount:
+ format: int32
+ type: integer
+ creationDate:
+ $ref: '#/components/schemas/OffsetDateTime'
+ creationUser:
+ type: string
+ modificationDate:
+ $ref: '#/components/schemas/OffsetDateTime'
+ modificationUser:
+ type: string
+ id:
+ type: string
+ roleId:
+ type: string
+ permissionId:
+ type: string
+ WorkspaceAssignmentSearchCriteria:
+ type: object
+ properties:
+ workspaceId:
+ type: string
+ pageNumber:
+ format: int32
+ description: The number of page.
+ default: 0
+ type: integer
+ pageSize:
+ format: int32
+ description: The size of page
+ default: 100
+ type: integer
+ WorkspaceAssignmentPageResult:
+ type: object
+ properties:
+ totalElements:
+ format: int64
+ description: The total elements in the resource.
+ type: integer
+ number:
+ format: int32
+ type: integer
+ size:
+ format: int32
+ type: integer
+ totalPages:
+ format: int64
+ type: integer
+ stream:
+ type: array
+ items:
+ $ref: '#/components/schemas/WorkspaceAssignment'
+ AssignmentSearchCriteria:
+ type: object
+ properties:
+ appId:
+ type: string
+ pageNumber:
+ format: int32
+ description: The number of page.
+ default: 0
+ type: integer
+ pageSize:
+ format: int32
+ description: The size of page
+ default: 100
+ type: integer
+ AssignmentPageResult:
+ type: object
+ properties:
+ totalElements:
+ format: int64
+ description: The total elements in the resource.
+ type: integer
+ number:
+ format: int32
+ type: integer
+ size:
+ format: int32
+ type: integer
+ totalPages:
+ format: int64
+ type: integer
+ stream:
+ type: array
+ items:
+ $ref: '#/components/schemas/Assignment'
+ CreateAssignmentRequest:
+ type: object
+ required:
+ - roleId
+ - permissionId
+ properties:
+ roleId:
+ type: string
+ permissionId:
+ type: string
+ Assignment:
+ type: object
+ properties:
+ modificationCount:
+ format: int32
+ type: integer
+ creationDate:
+ $ref: '#/components/schemas/OffsetDateTime'
+ creationUser:
+ type: string
+ modificationDate:
+ $ref: '#/components/schemas/OffsetDateTime'
+ modificationUser:
+ type: string
+ roleId:
+ type: string
+ permissionId:
+ type: string
+ UpdateRoleRequest:
+ type: object
+ properties:
+ modificationCount:
+ format: int32
+ type: integer
+ name:
+ type: string
+ shortDescription:
+ type: string
+ description:
+ type: string
+ CreateRoleRequest:
+ type: object
+ properties:
+ name:
+ type: string
+ shortDescription:
+ type: string
+ description:
+ type: string
+ Role:
+ type: object
+ properties:
+ modificationCount:
+ format: int32
+ type: integer
+ creationDate:
+ $ref: '#/components/schemas/OffsetDateTime'
+ creationUser:
+ type: string
+ modificationDate:
+ $ref: '#/components/schemas/OffsetDateTime'
+ modificationUser:
+ type: string
+ id:
+ type: string
+ name:
+ type: string
+ description:
+ type: string
+ RolePageResult:
+ type: object
+ properties:
+ totalElements:
+ format: int64
+ description: The total elements in the resource.
+ type: integer
+ number:
+ format: int32
+ type: integer
+ size:
+ format: int32
+ type: integer
+ totalPages:
+ format: int64
+ type: integer
+ stream:
+ type: array
+ items:
+ $ref: '#/components/schemas/Role'
+ RoleSearchCriteria:
+ type: object
+ properties:
+ name:
+ type: string
+ description:
+ type: string
pageNumber:
format: int32
description: The number of page.
@@ -56,7 +773,22 @@ components:
pageSize:
format: int32
description: The size of page
- default: 10
+ default: 100
+ type: integer
+ PermissionSearchCriteria:
+ type: object
+ properties:
+ appId:
+ type: string
+ pageNumber:
+ format: int32
+ description: The number of page.
+ default: 0
+ type: integer
+ pageSize:
+ format: int32
+ description: The size of page
+ default: 100
type: integer
PermissionPageResult:
type: object
@@ -96,8 +828,6 @@ components:
type: string
appId:
type: string
- name:
- type: string
resource:
type: string
action:
diff --git a/src/main/openapi/onecx-permission-operator-v1.yaml b/src/main/openapi/onecx-permission-operator-v1.yaml
index 55b13f4..b017e25 100644
--- a/src/main/openapi/onecx-permission-operator-v1.yaml
+++ b/src/main/openapi/onecx-permission-operator-v1.yaml
@@ -51,8 +51,6 @@ components:
- resource
- action
properties:
- name:
- type: string
resource:
type: string
action:
diff --git a/src/main/openapi/onecx-permission-v1.yaml b/src/main/openapi/onecx-permission-v1.yaml
new file mode 100644
index 0000000..86fcb60
--- /dev/null
+++ b/src/main/openapi/onecx-permission-v1.yaml
@@ -0,0 +1,187 @@
+---
+openapi: 3.0.3
+info:
+ title: onecx-permission service
+ version: 1.0.0
+servers:
+ - url: "http://onecx-permission-svc:8080"
+tags:
+ - name: permission
+paths:
+ /v1/permissions/user/workspace/{workspace}:
+ post:
+ tags:
+ - permission
+ description: Get permission of the workspace for the user
+ operationId: getWorkspacePermission
+ parameters:
+ - name: workspace
+ in: path
+ description: Workspace ID
+ required: true
+ schema:
+ type: string
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PermissionRequest'
+ responses:
+ 200:
+ description: Workspace permission
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/WorkspacePermissions'
+ 400:
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ /v1/permissions/user/workspace/{workspace}/applications:
+ post:
+ tags:
+ - permission
+ description: Get permission of the workspace and application for the user
+ operationId: getWorkspacePermissionApplications
+ parameters:
+ - name: workspace
+ in: path
+ description: Workspace ID
+ required: true
+ schema:
+ type: string
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PermissionRequest'
+ responses:
+ 200:
+ description: Workspace permission
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/WorkspacePermissionApplications'
+ 400:
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+ /v1/permissions/user/application/{appId}:
+ post:
+ tags:
+ - permission
+ description: Get permissions of the application for the user
+ operationId: getApplicationPermissions
+ parameters:
+ - name: appId
+ in: path
+ description: Application ID
+ required: true
+ schema:
+ type: string
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PermissionRequest'
+ responses:
+ 200:
+ description: Permission updated
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/ApplicationPermissions'
+ 400:
+ description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProblemDetailResponse'
+components:
+ schemas:
+ PermissionRequest:
+ type: object
+ required:
+ - token
+ properties:
+ token:
+ type: string
+ WorkspacePermissionApplications:
+ type: object
+ properties:
+ workspace:
+ $ref: '#/components/schemas/WorkspacePermissions'
+ applications:
+ type: array
+ items:
+ $ref: '#/components/schemas/ApplicationPermissions'
+ WorkspacePermissions:
+ type: object
+ properties:
+ workspaceId:
+ type: string
+ permissions:
+ type: object
+ nullable: false
+ description: resources
+ additionalProperties:
+ type: array
+ items:
+ type: string
+ uniqueItems: true
+ ApplicationPermissions:
+ type: object
+ properties:
+ appId:
+ type: string
+ permissions:
+ type: object
+ nullable: false
+ description: resources
+ additionalProperties:
+ type: array
+ items:
+ type: string
+ uniqueItems: true
+ ProblemDetailResponse:
+ type: object
+ properties:
+ errorCode:
+ type: string
+ detail:
+ type: string
+ params:
+ type: array
+ items:
+ $ref: '#/components/schemas/ProblemDetailParam'
+ invalidParams:
+ type: array
+ items:
+ $ref: '#/components/schemas/ProblemDetailInvalidParam'
+ ProblemDetailParam:
+ type: object
+ properties:
+ key:
+ type: string
+ value:
+ type: string
+ ProblemDetailInvalidParam:
+ type: object
+ properties:
+ name:
+ type: string
+ message:
+ type: string
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 4cfda36..17e8343 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -11,34 +11,48 @@ quarkus.liquibase.validate-on-migrate=true
# enable or disable multi-tenancy support
tkit.rs.context.tenant-id.enabled=true
+onecx.permission.token.verified=true
+onecx.permission.token.issuer.public-key-location.suffix=/protocol/openid-connect/certs
+onecx.permission.token.issuer.public-key-location.enabled=false
+onecx.permission.token.claim.path=realm_access/roles
+
+tkit.dataimport.enabled=false
+tkit.dataimport.configurations.permission.file=dev-data.import.json
+tkit.dataimport.configurations.permission.metadata.operation=CLEAN_INSERT
+tkit.dataimport.configurations.permission.enabled=false
+tkit.dataimport.configurations.permission.stop-at-error=true
+
# PROD
%prod.quarkus.datasource.jdbc.url=${DB_URL:jdbc:postgresql://postgresdb:5432/onecx-permission?sslmode=disable}
%prod.quarkus.datasource.username=${DB_USER:onecx-permission}
%prod.quarkus.datasource.password=${DB_PWD:onecx-permission}
+
# DEV
+%dev.onecx.permission.token.verified=false
%dev.tkit.rs.context.tenant-id.enabled=true
%dev.tkit.rs.context.tenant-id.mock.enabled=true
%dev.tkit.rs.context.tenant-id.mock.default-tenant=test
%dev.tkit.rs.context.tenant-id.mock.data.org1=tenant100
# TEST
+quarkus.test.integration-test-profile=test
+%test.onecx.permission.token.verified=false
%test.tkit.rs.context.tenant-id.enabled=true
%test.tkit.rs.context.tenant-id.mock.enabled=true
%test.tkit.rs.context.tenant-id.mock.default-tenant=default
%test.tkit.rs.context.tenant-id.mock.claim-org-id=orgId
%test.tkit.rs.context.tenant-id.mock.token-header-param=apm-principal-token
-%test.tkit.rs.context.tenant-id.mock.data.org1=tenant-100
-%test.tkit.rs.context.tenant-id.mock.data.org2=tenant-200
-
-# TEST-IT
-quarkus.test.integration-test-profile=test-it
-%test-it.tkit.log.json.enabled=false
-%test-it.tkit.rs.context.tenant-id.mock.enabled=true
-%test-it.tkit.rs.context.tenant-id.mock.default-tenant=default
-%test-it.tkit.rs.context.tenant-id.mock.claim-org-id=orgId
-%test-it.tkit.rs.context.tenant-id.mock.token-header-param=apm-principal-token
-%test-it.tkit.rs.context.tenant-id.mock.data.org1=tenant-100
-%test-it.tkit.rs.context.tenant-id.mock.data.org2=tenant-200
+%test.tkit.rs.context.tenant-id.mock.data.org1=100
+%test.tkit.rs.context.tenant-id.mock.data.org2=200
+%test.tkit.rs.context.tenant-id.mock.data.i100=i100
+%test.quarkus.keycloak.devservices.roles.bob=n3
+%test.smallrye.jwt.verify.key.location=${keycloak.url}/realms/quarkus/protocol/openid-connect/certs
+
+%test.tkit.dataimport.enabled=true
+%test.tkit.dataimport.configurations.permission.enabled=true
+%test.tkit.dataimport.configurations.permission.file=./src/test/resources/import/permission-import.json
+%test.tkit.dataimport.configurations.permission.metadata.operation=CLEAN_INSERT
+%test.tkit.dataimport.configurations.permission.stop-at-error=true
# PIPE CONFIG
diff --git a/src/main/resources/db/changeLog.xml b/src/main/resources/db/changeLog.xml
index 44c297b..889fa2b 100644
--- a/src/main/resources/db/changeLog.xml
+++ b/src/main/resources/db/changeLog.xml
@@ -4,5 +4,5 @@
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
-
+
\ No newline at end of file
diff --git a/src/main/resources/db/v1/2024-01-04-create-tables.xml b/src/main/resources/db/v1/2024-01-04-create-tables.xml
index 0ce4e72..88c912e 100644
--- a/src/main/resources/db/v1/2024-01-04-create-tables.xml
+++ b/src/main/resources/db/v1/2024-01-04-create-tables.xml
@@ -19,12 +19,110 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/db/v1/2024-01-10-data-import-log.xml b/src/main/resources/db/v1/2024-01-10-data-import-log.xml
new file mode 100644
index 0000000..7132b55
--- /dev/null
+++ b/src/main/resources/db/v1/2024-01-10-data-import-log.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/java/io/github/onecx/permission/AfterStartPermissionDataImportTest.java b/src/test/java/io/github/onecx/permission/AfterStartPermissionDataImportTest.java
new file mode 100644
index 0000000..3f5c8a9
--- /dev/null
+++ b/src/test/java/io/github/onecx/permission/AfterStartPermissionDataImportTest.java
@@ -0,0 +1,65 @@
+package io.github.onecx.permission;
+
+import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
+
+import jakarta.inject.Inject;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.tkit.quarkus.context.ApplicationContext;
+import org.tkit.quarkus.context.Context;
+
+import io.github.onecx.permission.domain.daos.*;
+import io.github.onecx.permission.test.AbstractTest;
+import io.quarkus.test.junit.QuarkusTest;
+
+@QuarkusTest
+@DisplayName("Permission data import test from example file")
+class AfterStartPermissionDataImportTest extends AbstractTest {
+
+ @Inject
+ PermissionDAO permissionDAO;
+
+ @Inject
+ RoleDAO roleDAO;
+
+ @Inject
+ WorkspacePermissionDAO workspacePermissionDAO;
+
+ @Inject
+ WorkspaceAssignmentDAO workspaceAssignmentDAO;
+
+ @Inject
+ AssignmentDAO assignmentDAO;
+
+ @Test
+ @DisplayName("Import permission from data file")
+ void importDataFromFileTest() {
+ try {
+ var ctx = Context.builder()
+ .principal("data-import")
+ .tenantId("i100")
+ .build();
+
+ ApplicationContext.start(ctx);
+
+ var permissions = permissionDAO.findAll().toList();
+ assertThat(permissions).hasSize(8);
+
+ var roles = roleDAO.findAll().toList();
+ assertThat(roles).hasSize(2);
+
+ var workspacePermissions = workspacePermissionDAO.findAll();
+ assertThat(workspacePermissions).hasSize(3);
+
+ var assignments = assignmentDAO.findAll();
+ assertThat(assignments).hasSize(6);
+
+ var workspaceAssignments = workspaceAssignmentDAO.findAll();
+ assertThat(workspaceAssignments).hasSize(4);
+
+ } finally {
+ ApplicationContext.close();
+ }
+ }
+}
diff --git a/src/test/java/io/github/onecx/permission/common/services/ClaimServiceTest.java b/src/test/java/io/github/onecx/permission/common/services/ClaimServiceTest.java
new file mode 100644
index 0000000..076e181
--- /dev/null
+++ b/src/test/java/io/github/onecx/permission/common/services/ClaimServiceTest.java
@@ -0,0 +1,22 @@
+package io.github.onecx.permission.common.services;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Test;
+
+import io.github.onecx.permission.test.AbstractTest;
+import io.quarkus.test.junit.QuarkusTest;
+
+@QuarkusTest
+class ClaimServiceTest extends AbstractTest {
+
+ @Test
+ void claimPathTest() {
+
+ var out = ClaimService.splitClaimPath("realms/roles");
+ assertThat(out).isNotNull().hasSize(2).containsExactly("realms", "roles");
+
+ out = ClaimService.splitClaimPath("groups");
+ assertThat(out).isNotNull().hasSize(1);
+ }
+}
diff --git a/src/test/java/io/github/onecx/permission/common/utils/TokenUtilTest.java b/src/test/java/io/github/onecx/permission/common/utils/TokenUtilTest.java
new file mode 100644
index 0000000..0c31c29
--- /dev/null
+++ b/src/test/java/io/github/onecx/permission/common/utils/TokenUtilTest.java
@@ -0,0 +1,79 @@
+package io.github.onecx.permission.common.utils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Optional;
+
+import jakarta.json.Json;
+import jakarta.json.JsonValue;
+
+import org.junit.jupiter.api.Test;
+
+import io.github.onecx.permission.common.models.TokenConfig;
+import io.github.onecx.permission.test.AbstractTest;
+import io.quarkus.test.junit.QuarkusTest;
+
+@QuarkusTest
+class TokenUtilTest extends AbstractTest {
+
+ @Test
+ void tokenUtilityTest() {
+
+ var config = new TokenConfig() {
+ @Override
+ public boolean tokenVerified() {
+ return false;
+ }
+
+ @Override
+ public String tokenPublicKeyLocationSuffix() {
+ return null;
+ }
+
+ @Override
+ public boolean tokenPublicKeyEnabled() {
+ return false;
+ }
+
+ @Override
+ public Optional tokenClaimSeparator() {
+ return Optional.empty();
+ }
+
+ @Override
+ public String tokenClaimPath() {
+ return null;
+ }
+ };
+
+ var tmp = TokenUtil.findClaimWithRoles(config, null, new String[] { "test" });
+ assertThat(tmp).isNotNull().isEmpty();
+
+ var value = Json.createValue(32);
+ tmp = TokenUtil.findClaimWithRoles(config, value, new String[] { "test1", "test2" });
+ assertThat(tmp).isNotNull().containsExactly("32");
+
+ JsonValue emptyValue = new JsonValue() {
+ @Override
+ public ValueType getValueType() {
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return " ";
+ }
+ };
+
+ tmp = TokenUtil.findClaimWithRoles(config, emptyValue, new String[] { "test1", "test2" });
+ assertThat(tmp).isNotNull().isEmpty();
+
+ var list = Json.createArrayBuilder();
+ list.add("s1");
+ list.add(" ");
+ list.add("");
+
+ tmp = TokenUtil.findClaimWithRoles(config, list.build(), new String[] { "test1", "test2" });
+ assertThat(tmp).isNotNull().containsExactly("s1");
+ }
+}
diff --git a/src/test/java/io/github/onecx/permission/domain/daos/AbstractDAOTest.java b/src/test/java/io/github/onecx/permission/domain/daos/AbstractDAOTest.java
new file mode 100644
index 0000000..098ccf9
--- /dev/null
+++ b/src/test/java/io/github/onecx/permission/domain/daos/AbstractDAOTest.java
@@ -0,0 +1,28 @@
+package io.github.onecx.permission.domain.daos;
+
+import jakarta.persistence.EntityManager;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.function.Executable;
+import org.mockito.Mockito;
+import org.tkit.quarkus.jpa.exceptions.DAOException;
+
+import io.github.onecx.permission.test.AbstractTest;
+import io.quarkus.test.InjectMock;
+
+abstract class AbstractDAOTest extends AbstractTest {
+
+ @InjectMock
+ EntityManager em;
+
+ @BeforeEach
+ void beforeAll() {
+ Mockito.when(em.getCriteriaBuilder()).thenThrow(new RuntimeException("Test technical error exception"));
+ }
+
+ void methodExceptionTests(Executable fn, Enum> key) {
+ var exc = Assertions.assertThrows(DAOException.class, fn);
+ Assertions.assertEquals(key, exc.key);
+ }
+}
diff --git a/src/test/java/io/github/onecx/permission/domain/daos/AssignmentDAOTest.java b/src/test/java/io/github/onecx/permission/domain/daos/AssignmentDAOTest.java
new file mode 100644
index 0000000..2c4dc38
--- /dev/null
+++ b/src/test/java/io/github/onecx/permission/domain/daos/AssignmentDAOTest.java
@@ -0,0 +1,24 @@
+package io.github.onecx.permission.domain.daos;
+
+import jakarta.inject.Inject;
+
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.test.junit.QuarkusTest;
+
+@QuarkusTest
+class AssignmentDAOTest extends AbstractDAOTest {
+
+ @Inject
+ AssignmentDAO dao;
+
+ @Test
+ @SuppressWarnings("java:S2699")
+ void methodExceptionTests() {
+ methodExceptionTests(() -> dao.findById(null),
+ AssignmentDAO.ErrorKeys.FIND_ENTITY_BY_ID_FAILED);
+ methodExceptionTests(() -> dao.findByCriteria(null),
+ AssignmentDAO.ErrorKeys.ERROR_FIND_ASSIGNMENT_BY_CRITERIA);
+ }
+
+}
diff --git a/src/test/java/io/github/onecx/permission/domain/daos/PermissionDAOTest.java b/src/test/java/io/github/onecx/permission/domain/daos/PermissionDAOTest.java
index 5a3a4a0..f8f061d 100644
--- a/src/test/java/io/github/onecx/permission/domain/daos/PermissionDAOTest.java
+++ b/src/test/java/io/github/onecx/permission/domain/daos/PermissionDAOTest.java
@@ -1,43 +1,28 @@
package io.github.onecx.permission.domain.daos;
import jakarta.inject.Inject;
-import jakarta.persistence.EntityManager;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.function.Executable;
-import org.mockito.Mockito;
-import org.tkit.quarkus.jpa.exceptions.DAOException;
-import io.github.onecx.permission.test.AbstractTest;
-import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
-class PermissionDAOTest extends AbstractTest {
+class PermissionDAOTest extends AbstractDAOTest {
@Inject
PermissionDAO dao;
- @InjectMock
- EntityManager em;
-
- @BeforeEach
- void beforeAll() {
- Mockito.when(em.getCriteriaBuilder()).thenThrow(new RuntimeException("Test technical error exception"));
- }
-
@Test
+ @SuppressWarnings("java:S2699")
void methodExceptionTests() {
+ methodExceptionTests(() -> dao.findAllPermissionForUser(null),
+ PermissionDAO.ErrorKeys.ERROR_FIND_ALL_PERMISSION_FOR_USER);
+ methodExceptionTests(() -> dao.findPermissionForUser(null, null),
+ PermissionDAO.ErrorKeys.ERROR_FIND_PERMISSION_FOR_USER);
methodExceptionTests(() -> dao.loadByAppId(null),
PermissionDAO.ErrorKeys.ERROR_LOAD_BY_APP_ID);
methodExceptionTests(() -> dao.findByCriteria(null),
PermissionDAO.ErrorKeys.ERROR_FIND_PERMISSION_BY_CRITERIA);
}
- void methodExceptionTests(Executable fn, Enum> key) {
- var exc = Assertions.assertThrows(DAOException.class, fn);
- Assertions.assertEquals(key, exc.key);
- }
}
diff --git a/src/test/java/io/github/onecx/permission/domain/daos/RoleDAOTest.java b/src/test/java/io/github/onecx/permission/domain/daos/RoleDAOTest.java
new file mode 100644
index 0000000..a65972d
--- /dev/null
+++ b/src/test/java/io/github/onecx/permission/domain/daos/RoleDAOTest.java
@@ -0,0 +1,24 @@
+package io.github.onecx.permission.domain.daos;
+
+import jakarta.inject.Inject;
+
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.test.junit.QuarkusTest;
+
+@QuarkusTest
+class RoleDAOTest extends AbstractDAOTest {
+
+ @Inject
+ RoleDAO dao;
+
+ @Test
+ @SuppressWarnings("java:S2699")
+ void methodExceptionTests() {
+ methodExceptionTests(() -> dao.findById(null),
+ RoleDAO.ErrorKeys.FIND_ENTITY_BY_ID_FAILED);
+ methodExceptionTests(() -> dao.findByCriteria(null),
+ RoleDAO.ErrorKeys.ERROR_FIND_ROLE_BY_CRITERIA);
+ }
+
+}
diff --git a/src/test/java/io/github/onecx/permission/domain/daos/WorkspaceAssignmentDAOTest.java b/src/test/java/io/github/onecx/permission/domain/daos/WorkspaceAssignmentDAOTest.java
new file mode 100644
index 0000000..f09df75
--- /dev/null
+++ b/src/test/java/io/github/onecx/permission/domain/daos/WorkspaceAssignmentDAOTest.java
@@ -0,0 +1,24 @@
+package io.github.onecx.permission.domain.daos;
+
+import jakarta.inject.Inject;
+
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.test.junit.QuarkusTest;
+
+@QuarkusTest
+class WorkspaceAssignmentDAOTest extends AbstractDAOTest {
+
+ @Inject
+ WorkspaceAssignmentDAO dao;
+
+ @Test
+ @SuppressWarnings("java:S2699")
+ void methodExceptionTests() {
+ methodExceptionTests(() -> dao.findByCriteria(null),
+ WorkspaceAssignmentDAO.ErrorKeys.ERROR_FIND_ASSIGNMENT_BY_CRITERIA);
+ methodExceptionTests(() -> dao.findById(null),
+ WorkspaceAssignmentDAO.ErrorKeys.FIND_ENTITY_BY_ID_FAILED);
+ }
+
+}
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
new file mode 100644
index 0000000..f9fe9ef
--- /dev/null
+++ b/src/test/java/io/github/onecx/permission/domain/daos/WorkspacePermissionDAOTest.java
@@ -0,0 +1,25 @@
+package io.github.onecx.permission.domain.daos;
+
+import jakarta.inject.Inject;
+
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.test.junit.QuarkusTest;
+
+@QuarkusTest
+class WorkspacePermissionDAOTest extends AbstractDAOTest {
+
+ @Inject
+ WorkspacePermissionDAO dao;
+
+ @Test
+ @SuppressWarnings("java:S2699")
+ 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),
+ WorkspacePermissionDAO.ErrorKeys.FIND_ENTITY_BY_ID_FAILED);
+ }
+}
diff --git a/src/test/java/io/github/onecx/permission/domain/di/PermissionDataImportServiceTest.java b/src/test/java/io/github/onecx/permission/domain/di/PermissionDataImportServiceTest.java
new file mode 100644
index 0000000..43d76f5
--- /dev/null
+++ b/src/test/java/io/github/onecx/permission/domain/di/PermissionDataImportServiceTest.java
@@ -0,0 +1,181 @@
+package io.github.onecx.permission.domain.di;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import jakarta.inject.Inject;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.tkit.quarkus.dataimport.DataImportConfig;
+import org.tkit.quarkus.test.WithDBData;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import gen.io.github.onecx.permission.domain.di.v1.model.DataImportDTOV1;
+import gen.io.github.onecx.permission.domain.di.v1.model.DataImportTenantWrapperDTOV1;
+import io.github.onecx.permission.domain.daos.PermissionDAO;
+import io.github.onecx.permission.domain.models.Permission;
+import io.github.onecx.permission.test.AbstractTest;
+import io.quarkus.test.junit.QuarkusTest;
+
+@QuarkusTest
+@WithDBData(value = "data/test-internal.xml", deleteBeforeInsert = true, deleteAfterTest = true, rinseAndRepeat = true)
+class PermissionDataImportServiceTest extends AbstractTest {
+
+ @Inject
+ PermissionDataImportV1 service;
+
+ @Inject
+ ObjectMapper mapper;
+
+ @Inject
+ PermissionDAO permissionDAO;
+
+ @Test
+ void checkTest() {
+ var data = new DataImportDTOV1();
+ data.setTenants(null);
+ data.setPermissions(null);
+ assertThat(PermissionDataImportV1.checkIsEmpty(data)).isTrue();
+
+ data.setTenants(null);
+ data.setPermissions(Map.of());
+ assertThat(PermissionDataImportV1.checkIsEmpty(data)).isTrue();
+
+ data.setTenants(Map.of());
+ data.setPermissions(Map.of());
+ assertThat(PermissionDataImportV1.checkIsEmpty(data)).isTrue();
+
+ data.setTenants(Map.of());
+ data.setPermissions(null);
+ assertThat(PermissionDataImportV1.checkIsEmpty(data)).isTrue();
+
+ data.setTenants(Map.of("2", new DataImportTenantWrapperDTOV1()));
+ data.setPermissions(Map.of("2", new HashMap<>()));
+ assertThat(PermissionDataImportV1.checkIsEmpty(data)).isFalse();
+
+ data.setTenants(Map.of("2", new DataImportTenantWrapperDTOV1()));
+ data.setPermissions(null);
+ assertThat(PermissionDataImportV1.checkIsEmpty(data)).isFalse();
+
+ data.setTenants(null);
+ data.setPermissions(Map.of("2", new HashMap<>()));
+ assertThat(PermissionDataImportV1.checkIsEmpty(data)).isFalse();
+ }
+
+ @Test
+ void importDataNotSupportedActionTest() {
+
+ Map metadata = new HashMap<>();
+ metadata.put("operation", "CUSTOM_NOT_SUPPORTED");
+ DataImportConfig config = new DataImportConfig() {
+ @Override
+ public Map getMetadata() {
+ return metadata;
+ }
+ };
+
+ service.importData(config);
+
+ List data = permissionDAO.findAll().toList();
+ assertThat(data).isNotNull().hasSize(7);
+
+ }
+
+ @Test
+ void importEmptyDataTest() {
+ Assertions.assertDoesNotThrow(() -> {
+ service.importData(new DataImportConfig() {
+ @Override
+ public Map getMetadata() {
+ return Map.of("operation", "CLEAN_INSERT");
+ }
+ });
+
+ service.importData(new DataImportConfig() {
+ @Override
+ public Map getMetadata() {
+ return Map.of("operation", "CLEAN_INSERT");
+ }
+
+ @Override
+ public byte[] getData() {
+ return new byte[] {};
+ }
+ });
+
+ service.importData(new DataImportConfig() {
+ @Override
+ public Map getMetadata() {
+ return Map.of("operation", "CLEAN_INSERT");
+ }
+
+ @Override
+ public byte[] getData() {
+ try {
+ return mapper.writeValueAsBytes(new DataImportDTOV1());
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+ });
+
+ service.importData(new DataImportConfig() {
+ @Override
+ public Map getMetadata() {
+ return Map.of("operation", "CLEAN_INSERT");
+ }
+
+ @Override
+ public byte[] getData() {
+ try {
+ var data = new DataImportDTOV1();
+ data.setPermissions(null);
+ data.setTenants(null);
+ return mapper.writeValueAsBytes(data);
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+ });
+
+ service.importData(new DataImportConfig() {
+ @Override
+ public Map getMetadata() {
+ return Map.of("operation", "CLEAN_INSERT");
+ }
+
+ @Override
+ public byte[] getData() {
+ try {
+ var data = new DataImportDTOV1();
+ data.setPermissions(null);
+ data.setTenants(Map.of());
+ return mapper.writeValueAsBytes(data);
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+ });
+
+ });
+
+ var config = new DataImportConfig() {
+ @Override
+ public Map getMetadata() {
+ return Map.of("operation", "CLEAN_INSERT");
+ }
+
+ @Override
+ public byte[] getData() {
+ return new byte[] { 0 };
+ }
+ };
+ Assertions.assertThrows(PermissionDataImportV1.ImportException.class, () -> service.importData(config));
+
+ }
+}
diff --git a/src/test/java/io/github/onecx/permission/rs/external/v1/PermissionRestControllerConfigIssuerTest.java b/src/test/java/io/github/onecx/permission/rs/external/v1/PermissionRestControllerConfigIssuerTest.java
new file mode 100644
index 0000000..0448e0b
--- /dev/null
+++ b/src/test/java/io/github/onecx/permission/rs/external/v1/PermissionRestControllerConfigIssuerTest.java
@@ -0,0 +1,72 @@
+package io.github.onecx.permission.rs.external.v1;
+
+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.OK;
+
+import jakarta.inject.Inject;
+
+import org.eclipse.microprofile.config.Config;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import org.tkit.quarkus.test.WithDBData;
+
+import gen.io.github.onecx.permission.rs.external.v1.model.ApplicationPermissionsDTOV1;
+import gen.io.github.onecx.permission.rs.external.v1.model.PermissionRequestDTOV1;
+import io.github.onecx.permission.common.models.TokenConfig;
+import io.github.onecx.permission.common.services.ClaimService;
+import io.github.onecx.permission.rs.external.v1.controllers.PermissionRestController;
+import io.github.onecx.permission.test.AbstractTest;
+import io.quarkus.test.InjectMock;
+import io.quarkus.test.common.http.TestHTTPEndpoint;
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.keycloak.client.KeycloakTestClient;
+import io.smallrye.config.SmallRyeConfig;
+
+@QuarkusTest
+@TestHTTPEndpoint(PermissionRestController.class)
+@WithDBData(value = "data/test-v1.xml", deleteBeforeInsert = true, deleteAfterTest = true, rinseAndRepeat = true)
+class PermissionRestControllerConfigIssuerTest extends AbstractTest {
+
+ @InjectMock
+ TokenConfig tokenConfig;
+
+ @InjectMock
+ ClaimService claimService;
+
+ @Inject
+ Config config;
+
+ @BeforeEach
+ void beforeEach() {
+ Mockito.when(claimService.getClaimPath()).thenReturn(new String[] { "groups" });
+ var tmp = config.unwrap(SmallRyeConfig.class).getConfigMapping(TokenConfig.class);
+ Mockito.when(tokenConfig.tokenClaimSeparator()).thenReturn(tmp.tokenClaimSeparator());
+ Mockito.when(tokenConfig.tokenClaimPath()).thenReturn(tmp.tokenClaimPath());
+ Mockito.when(tokenConfig.tokenVerified()).thenReturn(true);
+ Mockito.when(tokenConfig.tokenPublicKeyLocationSuffix()).thenReturn(tmp.tokenPublicKeyLocationSuffix());
+ Mockito.when(tokenConfig.tokenPublicKeyEnabled()).thenReturn(true);
+ }
+
+ @Test
+ void skipTokenVerified() {
+
+ KeycloakTestClient keycloakClient = new KeycloakTestClient();
+ var accessToken = keycloakClient.getAccessToken("bob");
+
+ var dto = given()
+ .contentType(APPLICATION_JSON)
+ .body(new PermissionRequestDTOV1().token(accessToken))
+ .post("/application/app1")
+ .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");
+ }
+}
diff --git a/src/test/java/io/github/onecx/permission/rs/external/v1/PermissionRestControllerConfigPublicKeyTest.java b/src/test/java/io/github/onecx/permission/rs/external/v1/PermissionRestControllerConfigPublicKeyTest.java
new file mode 100644
index 0000000..2942522
--- /dev/null
+++ b/src/test/java/io/github/onecx/permission/rs/external/v1/PermissionRestControllerConfigPublicKeyTest.java
@@ -0,0 +1,72 @@
+package io.github.onecx.permission.rs.external.v1;
+
+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.OK;
+
+import jakarta.inject.Inject;
+
+import org.eclipse.microprofile.config.Config;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import org.tkit.quarkus.test.WithDBData;
+
+import gen.io.github.onecx.permission.rs.external.v1.model.ApplicationPermissionsDTOV1;
+import gen.io.github.onecx.permission.rs.external.v1.model.PermissionRequestDTOV1;
+import io.github.onecx.permission.common.models.TokenConfig;
+import io.github.onecx.permission.common.services.ClaimService;
+import io.github.onecx.permission.rs.external.v1.controllers.PermissionRestController;
+import io.github.onecx.permission.test.AbstractTest;
+import io.quarkus.test.InjectMock;
+import io.quarkus.test.common.http.TestHTTPEndpoint;
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.keycloak.client.KeycloakTestClient;
+import io.smallrye.config.SmallRyeConfig;
+
+@QuarkusTest
+@TestHTTPEndpoint(PermissionRestController.class)
+@WithDBData(value = "data/test-v1.xml", deleteBeforeInsert = true, deleteAfterTest = true, rinseAndRepeat = true)
+class PermissionRestControllerConfigPublicKeyTest extends AbstractTest {
+
+ @InjectMock
+ TokenConfig tokenConfig;
+
+ @InjectMock
+ ClaimService claimService;
+
+ @Inject
+ Config config;
+
+ @BeforeEach
+ void beforeEach() {
+ Mockito.when(claimService.getClaimPath()).thenReturn(new String[] { "groups" });
+ var tmp = config.unwrap(SmallRyeConfig.class).getConfigMapping(TokenConfig.class);
+ Mockito.when(tokenConfig.tokenClaimSeparator()).thenReturn(tmp.tokenClaimSeparator());
+ Mockito.when(tokenConfig.tokenClaimPath()).thenReturn(tmp.tokenClaimPath());
+ Mockito.when(tokenConfig.tokenVerified()).thenReturn(true);
+ Mockito.when(tokenConfig.tokenPublicKeyLocationSuffix()).thenReturn(tmp.tokenPublicKeyLocationSuffix());
+ Mockito.when(tokenConfig.tokenPublicKeyEnabled()).thenReturn(false);
+ }
+
+ @Test
+ void skipTokenVerified() {
+
+ KeycloakTestClient keycloakClient = new KeycloakTestClient();
+ var accessToken = keycloakClient.getAccessToken("bob");
+
+ var dto = given()
+ .contentType(APPLICATION_JSON)
+ .body(new PermissionRequestDTOV1().token(accessToken))
+ .post("/application/app1")
+ .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");
+ }
+}
diff --git a/src/test/java/io/github/onecx/permission/rs/external/v1/PermissionRestControllerTenantTest.java b/src/test/java/io/github/onecx/permission/rs/external/v1/PermissionRestControllerTenantTest.java
new file mode 100644
index 0000000..544836d
--- /dev/null
+++ b/src/test/java/io/github/onecx/permission/rs/external/v1/PermissionRestControllerTenantTest.java
@@ -0,0 +1,117 @@
+package io.github.onecx.permission.rs.external.v1;
+
+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.*;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+import org.tkit.quarkus.test.WithDBData;
+
+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.test.AbstractTest;
+import io.quarkus.test.common.http.TestHTTPEndpoint;
+import io.quarkus.test.junit.QuarkusTest;
+
+@QuarkusTest
+@TestHTTPEndpoint(PermissionRestController.class)
+@WithDBData(value = "data/test-v1.xml", deleteBeforeInsert = true, deleteAfterTest = true, rinseAndRepeat = true)
+class PermissionRestControllerTenantTest extends AbstractTest {
+
+ @Test
+ void getApplicationPermissionsTest() {
+
+ var accessToken = createToken("org1", List.of("n3-100"));
+
+ var dto = given()
+ .contentType(APPLICATION_JSON)
+ .header(APM_HEADER_PARAM, accessToken)
+ .body(new PermissionRequestDTOV1().token(accessToken))
+ .post("/application/app1")
+ .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("a2");
+
+ }
+
+ @Test
+ void getWorkspacePermissionsTest() {
+
+ var accessToken = createToken("org1", List.of("n3-100"));
+
+ var dto = given()
+ .contentType(APPLICATION_JSON)
+ .header(APM_HEADER_PARAM, accessToken)
+ .body(new PermissionRequestDTOV1().token(accessToken))
+ .post("/workspace/wapp1")
+ .then()
+ .statusCode(OK.getStatusCode())
+ .extract()
+ .body().as(WorkspacePermissionsDTOV1.class);
+
+ assertThat(dto).isNotNull();
+ assertThat(dto.getPermissions()).isNotNull().isEmpty();
+
+ dto = given()
+ .contentType(APPLICATION_JSON)
+ .header(APM_HEADER_PARAM, accessToken)
+ .body(new PermissionRequestDTOV1().token(accessToken))
+ .post("/workspace/wapp1-100")
+ .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("a1");
+
+ }
+
+ @Test
+ void getWorkspacePermissionApplicationsTest() {
+
+ var accessToken = createToken("org1", List.of("n3-100"));
+
+ var dto = given()
+ .contentType(APPLICATION_JSON)
+ .header(APM_HEADER_PARAM, accessToken)
+ .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().isEmpty();
+
+ dto = given()
+ .contentType(APPLICATION_JSON)
+ .header(APM_HEADER_PARAM, accessToken)
+ .body(new PermissionRequestDTOV1().token(accessToken))
+ .post("/workspace/wapp1-100/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("a1");
+ 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("a2");
+ }
+}
diff --git a/src/test/java/io/github/onecx/permission/rs/external/v1/PermissionRestControllerTenantTestIT.java b/src/test/java/io/github/onecx/permission/rs/external/v1/PermissionRestControllerTenantTestIT.java
new file mode 100644
index 0000000..9f39bb2
--- /dev/null
+++ b/src/test/java/io/github/onecx/permission/rs/external/v1/PermissionRestControllerTenantTestIT.java
@@ -0,0 +1,7 @@
+package io.github.onecx.permission.rs.external.v1;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+
+@QuarkusIntegrationTest
+class PermissionRestControllerTenantTestIT extends PermissionRestControllerTenantTest {
+}
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
new file mode 100644
index 0000000..25b0949
--- /dev/null
+++ b/src/test/java/io/github/onecx/permission/rs/external/v1/PermissionRestControllerTest.java
@@ -0,0 +1,129 @@
+package io.github.onecx.permission.rs.external.v1;
+
+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.*;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.tkit.quarkus.test.WithDBData;
+
+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;
+
+@QuarkusTest
+@TestHTTPEndpoint(PermissionRestController.class)
+@WithDBData(value = "data/test-v1.xml", deleteBeforeInsert = true, deleteAfterTest = true, rinseAndRepeat = true)
+class PermissionRestControllerTest extends AbstractTest {
+
+ @Test
+ void getApplicationPermissionsTest() {
+
+ var accessToken = createToken(List.of("n3"));
+
+ var dto = given()
+ .contentType(APPLICATION_JSON)
+ .body(new PermissionRequestDTOV1().token(accessToken))
+ .post("/application/app1")
+ .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");
+
+ }
+
+ private static Stream badRequestData() {
+ return Stream.of(
+ Arguments.of("/application/app1", "getApplicationPermissions.permissionRequestDTOV1: must not be null"),
+ Arguments.of("/workspace/wapp1", "getWorkspacePermission.permissionRequestDTOV1: must not be null"),
+ Arguments.of("/workspace/wapp1/applications",
+ "getWorkspacePermissionApplications.permissionRequestDTOV1: must not be null"));
+ }
+
+ @ParameterizedTest
+ @MethodSource("badRequestData")
+ void getApplicationPermissionsNoBodyTest(String post, String check) {
+
+ var exception = given()
+ .contentType(APPLICATION_JSON)
+ .post(post)
+ .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(check);
+
+ }
+
+ @Test
+ void getApplicationPermissionsWrongTongTest() {
+
+ given()
+ .contentType(APPLICATION_JSON)
+ .body(new PermissionRequestDTOV1().token("this-is-not-token"))
+ .post("/application/app1")
+ .then()
+ .statusCode(INTERNAL_SERVER_ERROR.getStatusCode());
+ }
+
+ @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 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/external/v1/PermissionRestControllerTestIT.java b/src/test/java/io/github/onecx/permission/rs/external/v1/PermissionRestControllerTestIT.java
new file mode 100644
index 0000000..0ba63fd
--- /dev/null
+++ b/src/test/java/io/github/onecx/permission/rs/external/v1/PermissionRestControllerTestIT.java
@@ -0,0 +1,8 @@
+package io.github.onecx.permission.rs.external.v1;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+
+@QuarkusIntegrationTest
+class PermissionRestControllerTestIT extends PermissionRestControllerTest {
+
+}
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
new file mode 100644
index 0000000..36d046e
--- /dev/null
+++ b/src/test/java/io/github/onecx/permission/rs/internal/controllers/AssignmentRestControllerTest.java
@@ -0,0 +1,204 @@
+package io.github.onecx.permission.rs.internal.controllers;
+
+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 jakarta.ws.rs.core.HttpHeaders;
+
+import org.junit.jupiter.api.Test;
+import org.tkit.quarkus.test.WithDBData;
+
+import gen.io.github.onecx.permission.rs.internal.model.*;
+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;
+
+@QuarkusTest
+@TestHTTPEndpoint(AssignmentRestController.class)
+@WithDBData(value = "data/test-internal.xml", deleteBeforeInsert = true, deleteAfterTest = true, rinseAndRepeat = true)
+class AssignmentRestControllerTest extends AbstractTest {
+
+ @Test
+ void createAssignment() {
+ // create Assignment
+ var requestDTO = new CreateAssignmentRequestDTO();
+ requestDTO.setPermissionId("p11");
+ requestDTO.setRoleId("r11");
+
+ var uri = given()
+ .when()
+ .contentType(APPLICATION_JSON)
+ .body(requestDTO)
+ .post()
+ .then()
+ .statusCode(CREATED.getStatusCode())
+ .extract().header(HttpHeaders.LOCATION);
+
+ 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));
+
+ // create Role without body
+ var exception = given()
+ .when()
+ .contentType(APPLICATION_JSON)
+ .post()
+ .then()
+ .statusCode(BAD_REQUEST.getStatusCode())
+ .extract().as(ProblemDetailResponseDTO.class);
+
+ assertThat(exception.getErrorCode()).isEqualTo(ExceptionMapper.ErrorKeys.CONSTRAINT_VIOLATIONS.name());
+ assertThat(exception.getDetail()).isEqualTo("createAssignment.createAssignmentRequestDTO: must not be null");
+
+ // create Role with existing name
+ requestDTO = new CreateAssignmentRequestDTO();
+ requestDTO.setPermissionId("p13");
+ requestDTO.setRoleId("r13");
+
+ exception = given().when()
+ .contentType(APPLICATION_JSON)
+ .body(requestDTO)
+ .post()
+ .then()
+ .statusCode(BAD_REQUEST.getStatusCode())
+ .extract().as(ProblemDetailResponseDTO.class);
+
+ assertThat(exception.getErrorCode()).isEqualTo("PERSIST_ENTITY_FAILED");
+ assertThat(exception.getDetail()).isEqualTo(
+ "could not execute statement [ERROR: duplicate key value violates unique constraint 'assignment_key' Detail: Key (tenant_id, role_id, permission_id)=(default, r13, p13) already exists.]");
+
+ }
+
+ @Test
+ void createAssignmentWrong() {
+ // create Assignment
+ var requestDTO = new CreateAssignmentRequestDTO();
+ requestDTO.setPermissionId("does-not-exists");
+ requestDTO.setRoleId("r11");
+
+ given()
+ .when()
+ .contentType(APPLICATION_JSON)
+ .body(requestDTO)
+ .post()
+ .then()
+ .statusCode(NOT_FOUND.getStatusCode());
+
+ requestDTO.setPermissionId("p11");
+ requestDTO.setRoleId("does-not-exists");
+
+ given()
+ .when()
+ .contentType(APPLICATION_JSON)
+ .body(requestDTO)
+ .post()
+ .then()
+ .statusCode(NOT_FOUND.getStatusCode());
+ }
+
+ @Test
+ void getNotFoundAssignment() {
+ given()
+ .contentType(APPLICATION_JSON)
+ .get("does-not-exists")
+ .then()
+ .statusCode(NOT_FOUND.getStatusCode());
+ }
+
+ @Test
+ void searchAssignmentTest() {
+ var criteria = new AssignmentSearchCriteriaDTO();
+
+ var 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(" ");
+
+ 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);
+ }
+
+ @Test
+ void deleteAssignmentTest() {
+
+ // delete Assignment
+ given()
+ .contentType(APPLICATION_JSON)
+ .delete("DELETE_1")
+ .then()
+ .statusCode(NO_CONTENT.getStatusCode());
+
+ // check Assignment
+ given()
+ .contentType(APPLICATION_JSON)
+ .get("a11")
+ .then()
+ .statusCode(OK.getStatusCode());
+
+ // check if Assignment does not exist
+ given()
+ .contentType(APPLICATION_JSON)
+ .delete("a11")
+ .then()
+ .statusCode(NO_CONTENT.getStatusCode());
+
+ // check Assignment
+ given()
+ .contentType(APPLICATION_JSON)
+ .get("a11")
+ .then()
+ .statusCode(NOT_FOUND.getStatusCode());
+
+ }
+}
diff --git a/src/test/java/io/github/onecx/permission/rs/internal/controllers/AssignmentRestControllerTestIT.java b/src/test/java/io/github/onecx/permission/rs/internal/controllers/AssignmentRestControllerTestIT.java
new file mode 100644
index 0000000..86b8ea7
--- /dev/null
+++ b/src/test/java/io/github/onecx/permission/rs/internal/controllers/AssignmentRestControllerTestIT.java
@@ -0,0 +1,8 @@
+package io.github.onecx.permission.rs.internal.controllers;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+
+@QuarkusIntegrationTest
+class AssignmentRestControllerTestIT extends AssignmentRestControllerTest {
+
+}
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 72c5709..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,9 +41,6 @@ void searchTest() {
assertThat(data.getStream()).isNotNull().hasSize(7);
criteria.setAppId(" ");
- criteria.setName(" ");
- criteria.setResource(" ");
- criteria.setAction(" ");
data = given()
.contentType(APPLICATION_JSON)
@@ -64,9 +61,6 @@ void searchTest() {
void searchCriteriaTest() {
var criteria = new PermissionSearchCriteriaDTO();
criteria.setAppId("app1");
- criteria.setName("n1");
- criteria.setResource("o1");
- criteria.setAction("a1");
var data = given()
.contentType(APPLICATION_JSON)
@@ -79,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/RoleRestControllerTest.java b/src/test/java/io/github/onecx/permission/rs/internal/controllers/RoleRestControllerTest.java
new file mode 100644
index 0000000..332bac0
--- /dev/null
+++ b/src/test/java/io/github/onecx/permission/rs/internal/controllers/RoleRestControllerTest.java
@@ -0,0 +1,277 @@
+package io.github.onecx.permission.rs.internal.controllers;
+
+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 jakarta.ws.rs.core.HttpHeaders;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.tkit.quarkus.test.WithDBData;
+
+import gen.io.github.onecx.permission.rs.internal.model.*;
+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;
+
+@QuarkusTest
+@TestHTTPEndpoint(RoleRestController.class)
+@WithDBData(value = "data/test-internal.xml", deleteBeforeInsert = true, deleteAfterTest = true, rinseAndRepeat = true)
+class RoleRestControllerTest extends AbstractTest {
+
+ @Test
+ void createNewRoleTest() {
+
+ // create Role
+ var requestDTO = new CreateRoleRequestDTO();
+ requestDTO.setName("test01");
+ requestDTO.setDescription("description");
+
+ var uri = given()
+ .when()
+ .contentType(APPLICATION_JSON)
+ .body(requestDTO)
+ .post()
+ .then().statusCode(CREATED.getStatusCode())
+ .extract().header(HttpHeaders.LOCATION);
+
+ var dto = given()
+ .contentType(APPLICATION_JSON)
+ .get(uri)
+ .then()
+ .statusCode(OK.getStatusCode())
+ .extract()
+ .body().as(RoleDTO.class);
+
+ assertThat(dto).isNotNull()
+ .returns(requestDTO.getName(), from(RoleDTO::getName))
+ .returns(requestDTO.getDescription(), from(RoleDTO::getDescription));
+
+ // create Role without body
+ var exception = given()
+ .when()
+ .contentType(APPLICATION_JSON)
+ .post()
+ .then()
+ .statusCode(BAD_REQUEST.getStatusCode())
+ .extract().as(ProblemDetailResponseDTO.class);
+
+ assertThat(exception.getErrorCode()).isEqualTo(ExceptionMapper.ErrorKeys.CONSTRAINT_VIOLATIONS.name());
+ assertThat(exception.getDetail()).isEqualTo("createRole.createRoleRequestDTO: must not be null");
+
+ // create Role with existing name
+ requestDTO = new CreateRoleRequestDTO();
+ requestDTO.setName("n1");
+
+ exception = given().when()
+ .contentType(APPLICATION_JSON)
+ .body(requestDTO)
+ .post()
+ .then()
+ .statusCode(BAD_REQUEST.getStatusCode())
+ .extract().as(ProblemDetailResponseDTO.class);
+
+ assertThat(exception.getErrorCode()).isEqualTo("PERSIST_ENTITY_FAILED");
+ assertThat(exception.getDetail()).isEqualTo(
+ "could not execute statement [ERROR: duplicate key value violates unique constraint 'role_name' Detail: Key (tenant_id, name)=(default, n1) already exists.]");
+ }
+
+ @Test
+ void deleteRoleTest() {
+
+ // delete Role
+ given()
+ .contentType(APPLICATION_JSON)
+ .delete("DELETE_1")
+ .then().statusCode(NO_CONTENT.getStatusCode());
+
+ // check if Role exists
+ given()
+ .contentType(APPLICATION_JSON)
+ .get("DELETE_1")
+ .then().statusCode(NOT_FOUND.getStatusCode());
+
+ // delete Role in portal
+ given()
+ .contentType(APPLICATION_JSON)
+ .delete("r11")
+ .then()
+ .statusCode(NO_CONTENT.getStatusCode());
+
+ }
+
+ @Test
+ void getRoleByIdTest() {
+
+ var dto = given()
+ .contentType(APPLICATION_JSON)
+ .get("r12")
+ .then().statusCode(OK.getStatusCode())
+ .contentType(APPLICATION_JSON)
+ .extract()
+ .body().as(RoleDTO.class);
+
+ assertThat(dto).isNotNull();
+ assertThat(dto.getName()).isEqualTo("n2");
+ assertThat(dto.getId()).isEqualTo("r12");
+
+ given()
+ .contentType(APPLICATION_JSON)
+ .get("___")
+ .then().statusCode(NOT_FOUND.getStatusCode());
+
+ dto = given()
+ .contentType(APPLICATION_JSON)
+ .get("r11")
+ .then().statusCode(OK.getStatusCode())
+ .contentType(APPLICATION_JSON)
+ .extract()
+ .body().as(RoleDTO.class);
+
+ assertThat(dto).isNotNull();
+ assertThat(dto.getName()).isEqualTo("n1");
+ assertThat(dto.getId()).isEqualTo("r11");
+
+ }
+
+ @Test
+ void searchRolesTest() {
+ var criteria = new RoleSearchCriteriaDTO();
+
+ var data = given()
+ .contentType(APPLICATION_JSON)
+ .body(criteria)
+ .post("/search")
+ .then()
+ .statusCode(OK.getStatusCode())
+ .contentType(APPLICATION_JSON)
+ .extract()
+ .as(RolePageResultDTO.class);
+
+ assertThat(data).isNotNull();
+ assertThat(data.getTotalElements()).isEqualTo(4);
+ assertThat(data.getStream()).isNotNull().hasSize(4);
+
+ criteria.setName(" ");
+ criteria.setDescription(" ");
+ data = given()
+ .contentType(APPLICATION_JSON)
+ .body(criteria)
+ .post("/search")
+ .then()
+ .statusCode(OK.getStatusCode())
+ .contentType(APPLICATION_JSON)
+ .extract()
+ .as(RolePageResultDTO.class);
+
+ assertThat(data).isNotNull();
+ assertThat(data.getTotalElements()).isEqualTo(4);
+ assertThat(data.getStream()).isNotNull().hasSize(4);
+
+ criteria.setName("n3");
+ criteria.setDescription("d1");
+
+ data = given()
+ .contentType(APPLICATION_JSON)
+ .body(criteria)
+ .post("/search")
+ .then()
+ .statusCode(OK.getStatusCode())
+ .contentType(APPLICATION_JSON)
+ .extract()
+ .as(RolePageResultDTO.class);
+
+ assertThat(data).isNotNull();
+ assertThat(data.getTotalElements()).isEqualTo(1);
+ assertThat(data.getStream()).isNotNull().hasSize(1);
+
+ }
+
+ @Test
+ void updateRoleTest() {
+
+ // update none existing Role
+ var requestDto = new UpdateRoleRequestDTO();
+ requestDto.setName("test01");
+ requestDto.setDescription("description-update");
+
+ given()
+ .contentType(APPLICATION_JSON)
+ .body(requestDto)
+ .when()
+ .put("does-not-exists")
+ .then().statusCode(NOT_FOUND.getStatusCode());
+
+ // update Role
+ given()
+ .contentType(APPLICATION_JSON)
+ .body(requestDto)
+ .when()
+ .put("r11")
+ .then()
+ .statusCode(OK.getStatusCode());
+
+ // download Role
+ var dto = given().contentType(APPLICATION_JSON)
+ .body(requestDto)
+ .when()
+ .get("r11")
+ .then().statusCode(OK.getStatusCode())
+ .contentType(APPLICATION_JSON)
+ .extract()
+ .body().as(RoleDTO.class);
+
+ assertThat(dto).isNotNull();
+ assertThat(dto.getDescription()).isEqualTo(requestDto.getDescription());
+
+ }
+
+ @Test
+ void updateRoleWithExistingNameTest() {
+
+ var dto = new UpdateRoleRequestDTO();
+ dto.setName("n3");
+ dto.setDescription("description");
+
+ var exception = given()
+ .contentType(APPLICATION_JSON)
+ .when()
+ .body(dto)
+ .put("r11")
+ .then()
+ .statusCode(BAD_REQUEST.getStatusCode())
+ .extract().as(ProblemDetailResponseDTO.class);
+
+ Assertions.assertNotNull(exception);
+ Assertions.assertEquals("MERGE_ENTITY_FAILED", exception.getErrorCode());
+ Assertions.assertEquals(
+ "could not execute statement [ERROR: duplicate key value violates unique constraint 'role_name' Detail: Key (tenant_id, name)=(default, n3) already exists.]",
+ exception.getDetail());
+ Assertions.assertNull(exception.getInvalidParams());
+
+ }
+
+ @Test
+ void updateRoleWithoutBodyTest() {
+
+ var exception = given()
+ .contentType(APPLICATION_JSON)
+ .when()
+ .pathParam("id", "update_create_new")
+ .put("{id}")
+ .then()
+ .statusCode(BAD_REQUEST.getStatusCode())
+ .extract().as(ProblemDetailResponseDTO.class);
+
+ Assertions.assertNotNull(exception);
+ Assertions.assertEquals(ExceptionMapper.ErrorKeys.CONSTRAINT_VIOLATIONS.name(), exception.getErrorCode());
+ Assertions.assertEquals("updateRole.updateRoleRequestDTO: must not be null",
+ exception.getDetail());
+ Assertions.assertNotNull(exception.getInvalidParams());
+ Assertions.assertEquals(1, exception.getInvalidParams().size());
+ }
+}
diff --git a/src/test/java/io/github/onecx/permission/rs/internal/controllers/RoleRestControllerTestIT.java b/src/test/java/io/github/onecx/permission/rs/internal/controllers/RoleRestControllerTestIT.java
new file mode 100644
index 0000000..71ba0be
--- /dev/null
+++ b/src/test/java/io/github/onecx/permission/rs/internal/controllers/RoleRestControllerTestIT.java
@@ -0,0 +1,8 @@
+package io.github.onecx.permission.rs.internal.controllers;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+
+@QuarkusIntegrationTest
+class RoleRestControllerTestIT extends RoleRestControllerTest {
+
+}
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
new file mode 100644
index 0000000..ccc910d
--- /dev/null
+++ b/src/test/java/io/github/onecx/permission/rs/internal/controllers/WorkspaceAssignmentRestControllerTest.java
@@ -0,0 +1,203 @@
+package io.github.onecx.permission.rs.internal.controllers;
+
+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 jakarta.ws.rs.core.HttpHeaders;
+
+import org.junit.jupiter.api.Test;
+import org.tkit.quarkus.test.WithDBData;
+
+import gen.io.github.onecx.permission.rs.internal.model.*;
+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;
+
+@QuarkusTest
+@TestHTTPEndpoint(WorkspaceAssignmentRestController.class)
+@WithDBData(value = "data/test-internal.xml", deleteBeforeInsert = true, deleteAfterTest = true, rinseAndRepeat = true)
+class WorkspaceAssignmentRestControllerTest extends AbstractTest {
+
+ @Test
+ void createWorkspaceAssignment() {
+ // create Assignment
+ var requestDTO = new CreateWorkspaceAssignmentRequestDTO();
+ requestDTO.setPermissionId("wp11");
+ requestDTO.setRoleId("r11");
+
+ var uri = given()
+ .when()
+ .contentType(APPLICATION_JSON)
+ .body(requestDTO)
+ .post()
+ .then()
+ .statusCode(CREATED.getStatusCode())
+ .extract().header(HttpHeaders.LOCATION);
+
+ var dto = given()
+ .contentType(APPLICATION_JSON)
+ .get(uri)
+ .then()
+ .statusCode(OK.getStatusCode())
+ .extract()
+ .body().as(WorkspaceAssignmentDTO.class);
+
+ assertThat(dto).isNotNull()
+ .returns(requestDTO.getRoleId(), from(WorkspaceAssignmentDTO::getRoleId))
+ .returns(requestDTO.getPermissionId(), from(WorkspaceAssignmentDTO::getPermissionId));
+
+ // create Role without body
+ var exception = given()
+ .when()
+ .contentType(APPLICATION_JSON)
+ .post()
+ .then()
+ .statusCode(BAD_REQUEST.getStatusCode())
+ .extract().as(ProblemDetailResponseDTO.class);
+
+ assertThat(exception.getErrorCode()).isEqualTo(ExceptionMapper.ErrorKeys.CONSTRAINT_VIOLATIONS.name());
+ assertThat(exception.getDetail())
+ .isEqualTo("createWorkspaceAssignment.createWorkspaceAssignmentRequestDTO: must not be null");
+
+ // create Role with existing name
+ requestDTO = new CreateWorkspaceAssignmentRequestDTO();
+ requestDTO.setPermissionId("wp13");
+ requestDTO.setRoleId("r13");
+
+ exception = given().when()
+ .contentType(APPLICATION_JSON)
+ .body(requestDTO)
+ .post()
+ .then()
+ .statusCode(BAD_REQUEST.getStatusCode())
+ .extract().as(ProblemDetailResponseDTO.class);
+
+ assertThat(exception.getErrorCode()).isEqualTo("PERSIST_ENTITY_FAILED");
+ assertThat(exception.getDetail()).isEqualTo(
+ "could not execute statement [ERROR: duplicate key value violates unique constraint 'workspace_assignment_key' Detail: Key (tenant_id, role_id, permission_id)=(default, r13, wp13) already exists.]");
+
+ }
+
+ @Test
+ void createWorkspaceAssignmentWrong() {
+ // create Assignment
+ var requestDTO = new CreateWorkspaceAssignmentRequestDTO();
+ requestDTO.setPermissionId("does-not-exists");
+ requestDTO.setRoleId("r11");
+
+ given()
+ .when()
+ .contentType(APPLICATION_JSON)
+ .body(requestDTO)
+ .post()
+ .then()
+ .statusCode(NOT_FOUND.getStatusCode());
+
+ requestDTO.setPermissionId("wp11");
+ requestDTO.setRoleId("does-not-exists");
+
+ given()
+ .when()
+ .contentType(APPLICATION_JSON)
+ .body(requestDTO)
+ .post()
+ .then()
+ .statusCode(NOT_FOUND.getStatusCode());
+ }
+
+ @Test
+ void getNotFoundWorkspaceAssignment() {
+ given()
+ .contentType(APPLICATION_JSON)
+ .get("does-not-exists")
+ .then()
+ .statusCode(NOT_FOUND.getStatusCode());
+ }
+
+ @Test
+ void searchWorkspaceAssignmentTest() {
+ var criteria = new WorkspaceAssignmentSearchCriteriaDTO();
+
+ var 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(" ");
+ 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);
+ }
+
+ @Test
+ void deleteWorkspaceAssignmentTest() {
+
+ // delete Assignment
+ given()
+ .contentType(APPLICATION_JSON)
+ .delete("DELETE_1")
+ .then()
+ .statusCode(NO_CONTENT.getStatusCode());
+
+ // check Assignment
+ given()
+ .contentType(APPLICATION_JSON)
+ .get("wa11")
+ .then()
+ .statusCode(OK.getStatusCode());
+
+ // check if Assignment does not exist
+ given()
+ .contentType(APPLICATION_JSON)
+ .delete("wa11")
+ .then()
+ .statusCode(NO_CONTENT.getStatusCode());
+
+ // check Assignment
+ given()
+ .contentType(APPLICATION_JSON)
+ .get("wa11")
+ .then()
+ .statusCode(NOT_FOUND.getStatusCode());
+
+ }
+}
diff --git a/src/test/java/io/github/onecx/permission/rs/internal/controllers/WorkspaceAssignmentRestControllerTestIT.java b/src/test/java/io/github/onecx/permission/rs/internal/controllers/WorkspaceAssignmentRestControllerTestIT.java
new file mode 100644
index 0000000..217f1ce
--- /dev/null
+++ b/src/test/java/io/github/onecx/permission/rs/internal/controllers/WorkspaceAssignmentRestControllerTestIT.java
@@ -0,0 +1,7 @@
+package io.github.onecx.permission.rs.internal.controllers;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+
+@QuarkusIntegrationTest
+class WorkspaceAssignmentRestControllerTestIT extends WorkspaceAssignmentRestControllerTest {
+}
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
new file mode 100644
index 0000000..cb29d7c
--- /dev/null
+++ b/src/test/java/io/github/onecx/permission/rs/internal/controllers/WorkspacePermissionRestControllerTest.java
@@ -0,0 +1,293 @@
+package io.github.onecx.permission.rs.internal.controllers;
+
+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 jakarta.ws.rs.core.HttpHeaders;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.tkit.quarkus.test.WithDBData;
+
+import gen.io.github.onecx.permission.rs.internal.model.*;
+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;
+
+@QuarkusTest
+@TestHTTPEndpoint(WorkspacePermissionRestController.class)
+@WithDBData(value = "data/test-internal.xml", deleteBeforeInsert = true, deleteAfterTest = true, rinseAndRepeat = true)
+class WorkspacePermissionRestControllerTest extends AbstractTest {
+
+ @Test
+ void searchTest() {
+ var criteria = new WorkspacePermissionSearchCriteriaDTO();
+
+ var data = given()
+ .contentType(APPLICATION_JSON)
+ .body(criteria)
+ .post("search")
+ .then()
+ .statusCode(OK.getStatusCode())
+ .contentType(APPLICATION_JSON)
+ .extract()
+ .as(WorkspacePermissionPageResultDTO.class);
+
+ assertThat(data).isNotNull();
+ assertThat(data.getTotalElements()).isEqualTo(7);
+ assertThat(data.getStream()).isNotNull().hasSize(7);
+
+ criteria.setWorkspaceId(" ");
+
+ data = given()
+ .contentType(APPLICATION_JSON)
+ .body(criteria)
+ .post("search")
+ .then()
+ .statusCode(OK.getStatusCode())
+ .contentType(APPLICATION_JSON)
+ .extract()
+ .as(WorkspacePermissionPageResultDTO.class);
+
+ assertThat(data).isNotNull();
+ assertThat(data.getTotalElements()).isEqualTo(7);
+ assertThat(data.getStream()).isNotNull().hasSize(7);
+ }
+
+ @Test
+ void searchCriteriaTest() {
+ var criteria = new WorkspacePermissionSearchCriteriaDTO();
+ criteria.setWorkspaceId("wapp1");
+
+ var data = given()
+ .contentType(APPLICATION_JSON)
+ .body(criteria)
+ .post("search")
+ .then()
+ .statusCode(OK.getStatusCode())
+ .contentType(APPLICATION_JSON)
+ .extract()
+ .as(WorkspacePermissionPageResultDTO.class);
+
+ assertThat(data).isNotNull();
+ assertThat(data.getTotalElements()).isEqualTo(5);
+ assertThat(data.getStream()).isNotNull().hasSize(5);
+ }
+
+ @Test
+ void searchNoBodyTest() {
+ var exception = given()
+ .contentType(APPLICATION_JSON)
+ .post("search")
+ .then()
+ .statusCode(BAD_REQUEST.getStatusCode())
+ .contentType(APPLICATION_JSON)
+ .extract()
+ .as(ProblemDetailResponseDTO.class);
+
+ assertThat(exception).isNotNull();
+ assertThat(exception.getErrorCode()).isEqualTo(ExceptionMapper.ErrorKeys.CONSTRAINT_VIOLATIONS.name());
+ assertThat(exception.getDetail())
+ .isEqualTo("searchWorkspacePermissions.workspacePermissionSearchCriteriaDTO: must not be null");
+ }
+
+ @Test
+ void deleteWorkspacePermissionTest() {
+
+ // delete Role
+ given()
+ .contentType(APPLICATION_JSON)
+ .delete("DELETE_1")
+ .then().statusCode(NO_CONTENT.getStatusCode());
+
+ // check if Role exists
+ given()
+ .contentType(APPLICATION_JSON)
+ .get("DELETE_1")
+ .then().statusCode(NOT_FOUND.getStatusCode());
+
+ // delete Role in portal
+ given()
+ .contentType(APPLICATION_JSON)
+ .delete("wp21")
+ .then()
+ .statusCode(NO_CONTENT.getStatusCode());
+
+ }
+
+ @Test
+ void getWorkspacePermissionTest() {
+
+ var dto = given()
+ .contentType(APPLICATION_JSON)
+ .get("wp21")
+ .then().statusCode(OK.getStatusCode())
+ .contentType(APPLICATION_JSON)
+ .extract()
+ .body().as(WorkspacePermissionDTO.class);
+
+ assertThat(dto).isNotNull();
+ assertThat(dto.getWorkspaceId()).isEqualTo("wapp2");
+ assertThat(dto.getId()).isEqualTo("wp21");
+
+ given()
+ .contentType(APPLICATION_JSON)
+ .get("___")
+ .then().statusCode(NOT_FOUND.getStatusCode());
+
+ dto = given()
+ .contentType(APPLICATION_JSON)
+ .get("wp11")
+ .then().statusCode(OK.getStatusCode())
+ .contentType(APPLICATION_JSON)
+ .extract()
+ .body().as(WorkspacePermissionDTO.class);
+
+ assertThat(dto).isNotNull();
+ assertThat(dto.getWorkspaceId()).isEqualTo("wapp1");
+ assertThat(dto.getId()).isEqualTo("wp11");
+
+ }
+
+ @Test
+ void createNewWorkspacePermissionTest() {
+
+ // create Role
+ var requestDTO = new CreateWorkspacePermissionRequestDTO();
+ requestDTO.setWorkspaceId("w1");
+ requestDTO.setAction("a1");
+ requestDTO.setResource("r1");
+ requestDTO.setDescription("d1");
+
+ var uri = given()
+ .when()
+ .contentType(APPLICATION_JSON)
+ .body(requestDTO)
+ .post()
+ .then().statusCode(CREATED.getStatusCode())
+ .extract().header(HttpHeaders.LOCATION);
+
+ var dto = given()
+ .contentType(APPLICATION_JSON)
+ .get(uri)
+ .then()
+ .statusCode(OK.getStatusCode())
+ .extract()
+ .body().as(WorkspacePermissionDTO.class);
+
+ assertThat(dto).isNotNull()
+ .returns(requestDTO.getAction(), from(WorkspacePermissionDTO::getAction))
+ .returns(requestDTO.getResource(), from(WorkspacePermissionDTO::getResource))
+ .returns(requestDTO.getWorkspaceId(), from(WorkspacePermissionDTO::getWorkspaceId))
+ .returns(requestDTO.getDescription(), from(WorkspacePermissionDTO::getDescription));
+
+ // create Role without body
+ var exception = given()
+ .when()
+ .contentType(APPLICATION_JSON)
+ .post()
+ .then()
+ .statusCode(BAD_REQUEST.getStatusCode())
+ .extract().as(ProblemDetailResponseDTO.class);
+
+ assertThat(exception.getErrorCode()).isEqualTo(ExceptionMapper.ErrorKeys.CONSTRAINT_VIOLATIONS.name());
+ assertThat(exception.getDetail())
+ .isEqualTo("createWorkspacePermission.createWorkspacePermissionRequestDTO: must not be null");
+
+ // create WorkspacePermission with existing data
+ requestDTO = new CreateWorkspacePermissionRequestDTO();
+ requestDTO.setWorkspaceId("wapp1");
+ requestDTO.setAction("a1");
+ requestDTO.setResource("o1");
+ requestDTO.setDescription("d1");
+
+ exception = given().when()
+ .contentType(APPLICATION_JSON)
+ .body(requestDTO)
+ .post()
+ .then()
+ .statusCode(BAD_REQUEST.getStatusCode())
+ .extract().as(ProblemDetailResponseDTO.class);
+
+ assertThat(exception.getErrorCode()).isEqualTo("PERSIST_ENTITY_FAILED");
+ assertThat(exception.getDetail()).isEqualTo(
+ "could not execute statement [ERROR: duplicate key value violates unique constraint 'workspace_permission_key' Detail: Key (tenant_id, workspace_id, resource, action)=(default, wapp1, o1, a1) already exists.]");
+ }
+
+ @Test
+ void updateWorkspacePermissionWithoutBodyTest() {
+
+ var exception = given()
+ .contentType(APPLICATION_JSON)
+ .when()
+ .put("update_create_new")
+ .then()
+ .statusCode(BAD_REQUEST.getStatusCode())
+ .extract().as(ProblemDetailResponseDTO.class);
+
+ Assertions.assertNotNull(exception);
+ Assertions.assertEquals(ExceptionMapper.ErrorKeys.CONSTRAINT_VIOLATIONS.name(), exception.getErrorCode());
+ Assertions.assertEquals("updateWorkspacePermission.updateWorkspacePermissionRequestDTO: must not be null",
+ exception.getDetail());
+ Assertions.assertNotNull(exception.getInvalidParams());
+ Assertions.assertEquals(1, exception.getInvalidParams().size());
+ }
+
+ @Test
+ void updateWorkspacePermissionTest() {
+
+ var dto = given()
+ .contentType(APPLICATION_JSON)
+ .get("wp11")
+ .then().statusCode(OK.getStatusCode())
+ .contentType(APPLICATION_JSON)
+ .extract()
+ .body().as(WorkspacePermissionDTO.class);
+
+ var request = new UpdateWorkspacePermissionRequestDTO();
+ request.setModificationCount(dto.getModificationCount());
+ request.setDescription("description123");
+
+ given()
+ .contentType(APPLICATION_JSON)
+ .when()
+ .body(request)
+ .put("wp11")
+ .then()
+ .statusCode(OK.getStatusCode());
+
+ dto = given()
+ .contentType(APPLICATION_JSON)
+ .get("wp11")
+ .then().statusCode(OK.getStatusCode())
+ .contentType(APPLICATION_JSON)
+ .extract()
+ .body().as(WorkspacePermissionDTO.class);
+
+ assertThat(dto).isNotNull();
+ assertThat(dto.getDescription()).isEqualTo(request.getDescription());
+ assertThat(dto.getId()).isEqualTo("wp11");
+
+ }
+
+ @Test
+ void updateWorkspacePermissionNotFoundTest() {
+
+ var request = new UpdateWorkspacePermissionRequestDTO();
+ request.setModificationCount(1);
+ request.setDescription("description123");
+
+ given()
+ .contentType(APPLICATION_JSON)
+ .when()
+ .body(request)
+ .put("does-not-exists")
+ .then()
+ .statusCode(NOT_FOUND.getStatusCode());
+
+ }
+}
diff --git a/src/test/java/io/github/onecx/permission/rs/internal/controllers/WorkspacePermissionRestControllerTestIT.java b/src/test/java/io/github/onecx/permission/rs/internal/controllers/WorkspacePermissionRestControllerTestIT.java
new file mode 100644
index 0000000..63866fd
--- /dev/null
+++ b/src/test/java/io/github/onecx/permission/rs/internal/controllers/WorkspacePermissionRestControllerTestIT.java
@@ -0,0 +1,7 @@
+package io.github.onecx.permission.rs.internal.controllers;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+
+@QuarkusIntegrationTest
+class WorkspacePermissionRestControllerTestIT extends WorkspacePermissionRestControllerTest {
+}
diff --git a/src/test/java/io/github/onecx/permission/rs/operator/v1/controllers/OperatorRestControllerTest.java b/src/test/java/io/github/onecx/permission/rs/operator/v1/controllers/OperatorRestControllerTest.java
index 0724d28..6f45a86 100644
--- a/src/test/java/io/github/onecx/permission/rs/operator/v1/controllers/OperatorRestControllerTest.java
+++ b/src/test/java/io/github/onecx/permission/rs/operator/v1/controllers/OperatorRestControllerTest.java
@@ -59,7 +59,6 @@ void requestEmptyListTest() {
@Test
void requestWrongPermissionTest() {
var per = new PermissionDTOV1();
- per.setName("name");
per.setDescription("description");
var request = new PermissionRequestDTOV1();
@@ -84,8 +83,8 @@ void requestWrongPermissionTest() {
@Test
void requestPermissionTest() {
- var per1 = new PermissionDTOV1().action("a1").resource("o1").name("name").description("description");
- var per2 = new PermissionDTOV1().action("new1").resource("o1").name("name1").description("description1");
+ var per1 = new PermissionDTOV1().action("a1").resource("o1").description("description");
+ var per2 = new PermissionDTOV1().action("new1").resource("o1").description("description1");
var request = new PermissionRequestDTOV1();
request.setPermissions(List.of(per1, per2));
@@ -102,8 +101,8 @@ void requestPermissionTest() {
@Test
void requestDuplicatePermissionTest() {
- var per1 = new PermissionDTOV1().action("a1").resource("o1").name("name").description("description");
- var per2 = new PermissionDTOV1().action("a1").resource("o1").name("name1").description("description1");
+ var per1 = new PermissionDTOV1().action("a1").resource("o1").description("description");
+ var per2 = new PermissionDTOV1().action("a1").resource("o1").description("description1");
var request = new PermissionRequestDTOV1();
request.setPermissions(List.of(per1, per2));
diff --git a/src/test/java/io/github/onecx/permission/test/AbstractTest.java b/src/test/java/io/github/onecx/permission/test/AbstractTest.java
index 43defa9..cbc2070 100644
--- a/src/test/java/io/github/onecx/permission/test/AbstractTest.java
+++ b/src/test/java/io/github/onecx/permission/test/AbstractTest.java
@@ -4,14 +4,37 @@
import static io.restassured.RestAssured.config;
import static io.restassured.config.ObjectMapperConfig.objectMapperConfig;
+import java.security.PrivateKey;
+import java.util.List;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.inject.Produces;
+import jakarta.inject.Inject;
+import jakarta.json.Json;
+import jakarta.json.JsonObjectBuilder;
+
+import org.eclipse.microprofile.config.Config;
+import org.eclipse.microprofile.config.ConfigProvider;
+import org.eclipse.microprofile.jwt.Claims;
+
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import io.github.onecx.permission.common.models.TokenConfig;
+import io.quarkus.test.Mock;
import io.restassured.config.RestAssuredConfig;
+import io.smallrye.config.SmallRyeConfig;
+import io.smallrye.jwt.build.Jwt;
+import io.smallrye.jwt.util.KeyUtils;
@SuppressWarnings("java:S2187")
public class AbstractTest {
+ protected static final String APM_HEADER_PARAM = ConfigProvider.getConfig()
+ .getValue("%test.tkit.rs.context.tenant-id.mock.token-header-param", String.class);
+ protected static final String CLAIMS_ORG_ID = ConfigProvider.getConfig()
+ .getValue("%test.tkit.rs.context.tenant-id.mock.claim-org-id", String.class);
+
static {
config = RestAssuredConfig.config().objectMapperConfig(
objectMapperConfig().jackson2ObjectMapperFactory(
@@ -22,4 +45,47 @@ public class AbstractTest {
return objectMapper;
}));
}
+
+ protected static String createToken(String organizationId) {
+ return createToken(organizationId, null);
+ }
+
+ protected static String createToken(List roles) {
+ return createToken(null, roles);
+ }
+
+ protected static String createToken(String organizationId, List roles) {
+ try {
+
+ String userName = "test-user";
+ JsonObjectBuilder claims = Json.createObjectBuilder();
+ claims.add(Claims.preferred_username.name(), userName);
+ claims.add(Claims.sub.name(), userName);
+ if (organizationId != null) {
+ claims.add(CLAIMS_ORG_ID, organizationId);
+ }
+ if (roles != null && !roles.isEmpty()) {
+ JsonObjectBuilder r = Json.createObjectBuilder();
+ r.add("roles", Json.createArrayBuilder(roles));
+ claims.add("realm_access", r);
+ }
+ PrivateKey privateKey = KeyUtils.generateKeyPair(2048).getPrivate();
+ return Jwt.claims(claims.build()).sign(privateKey);
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ public static class ConfigProducer {
+
+ @Inject
+ Config config;
+
+ @Produces
+ @ApplicationScoped
+ @Mock
+ TokenConfig config() {
+ return config.unwrap(SmallRyeConfig.class).getConfigMapping(TokenConfig.class);
+ }
+ }
}
diff --git a/src/test/resources/data/test-internal.xml b/src/test/resources/data/test-internal.xml
index 45766b8..8f99e13 100644
--- a/src/test/resources/data/test-internal.xml
+++ b/src/test/resources/data/test-internal.xml
@@ -2,13 +2,43 @@
-
-
-
+
+
+
-
-
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/data/test-operator-v1.xml b/src/test/resources/data/test-operator-v1.xml
index 45766b8..c391560 100644
--- a/src/test/resources/data/test-operator-v1.xml
+++ b/src/test/resources/data/test-operator-v1.xml
@@ -2,13 +2,13 @@
-
-
-
+
+
+
-
-
+
+
-
-
+
+
\ No newline at end of file
diff --git a/src/test/resources/data/test-v1.xml b/src/test/resources/data/test-v1.xml
new file mode 100644
index 0000000..fadfb01
--- /dev/null
+++ b/src/test/resources/data/test-v1.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/import/permission-import.json b/src/test/resources/import/permission-import.json
new file mode 100644
index 0000000..4943fa4
--- /dev/null
+++ b/src/test/resources/import/permission-import.json
@@ -0,0 +1,82 @@
+{
+ "permissions": {
+ "application1" : {
+ "resource1": {
+ "action1": "description11",
+ "action2": "description12"
+ },
+ "resource2": {
+ "action1": "description21",
+ "action2": "description22"
+ }
+ },
+ "application2" : {
+ "r3": {
+ "a1": "d31",
+ "a2": "d31"
+ },
+ "r4": {
+ "a1": "d41",
+ "a2": "d41"
+ }
+ }
+ },
+ "tenants": {
+ "i100": {
+ "workspaces-permissions": {
+ "workspace1": {
+ "menu1": {
+ "action1": "action-description11",
+ "action2": "action-description12"
+ }
+ },
+ "workspace2": {
+ "menu2": {
+ "action1": "action-description21"
+ }
+ }
+ },
+ "roles": {
+ "role1": {
+ "description": "description1",
+ "assignments": {
+ "application1": {
+ "resource1": ["action1","action2"]
+ },
+ "application2": {
+ "r4": ["a1"]
+ }
+ },
+ "workspaces-assignments": {
+ "workspace1": {
+ "menu1": ["action1"]
+ },
+ "workspace2": {
+ "menu2": ["action1"]
+ }
+ }
+ },
+ "role2": {
+ "description": "description1",
+ "assignments": {
+ "application1": {
+ "resource1": ["action1","action2"]
+ },
+ "application2": {
+ "r4": ["a1"]
+ }
+ },
+ "workspaces-assignments": {
+ "workspace1": {
+ "menu1": ["action1"]
+ },
+ "workspace2": {
+ "menu2": ["action1"]
+ }
+ }
+ }
+ }
+ }
+ }
+
+}
\ No newline at end of file