From 794d30bcfe62ce78d0ed15cfd50dc44aea874928 Mon Sep 17 00:00:00 2001 From: gideonsmila Date: Thu, 19 Dec 2024 16:20:29 +0200 Subject: [PATCH] Add new ability to get user permissions via opa directly --- .gitignore | 1 + build.gradle | 1 + src/main/java/io/permit/sdk/Permit.java | 5 ++ src/main/java/io/permit/sdk/PermitConfig.java | 23 ++++++ .../io/permit/sdk/enforcement/Enforcer.java | 80 ++++++++++++++++--- .../permit/sdk/enforcement/IEnforcerApi.java | 10 +++ .../sdk/enforcement/UserPermissionsOpa.java | 28 +++++++ .../java/io/permit/sdk/PermitE2ETestBase.java | 20 +++-- .../java/io/permit/sdk/e2e/RbacE2ETest.java | 10 ++- 9 files changed, 161 insertions(+), 17 deletions(-) create mode 100644 src/main/java/io/permit/sdk/enforcement/UserPermissionsOpa.java diff --git a/.gitignore b/.gitignore index b671201..9b21127 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ build # Ignore stg schemas stg-schemas/ +bin diff --git a/build.gradle b/build.gradle index 69a8ca8..b473bd7 100644 --- a/build.gradle +++ b/build.gradle @@ -37,6 +37,7 @@ repositories { java { toolchain { languageVersion = JavaLanguageVersion.of(8) + // languageVersion = JavaLanguageVersion.of(17) } // sources are required by maven central in order to accept the package withSourcesJar() diff --git a/src/main/java/io/permit/sdk/Permit.java b/src/main/java/io/permit/sdk/Permit.java index 248708f..4c7ef98 100644 --- a/src/main/java/io/permit/sdk/Permit.java +++ b/src/main/java/io/permit/sdk/Permit.java @@ -159,6 +159,11 @@ public UserPermissions getUserPermissions(GetUserPermissionsQuery input) throws return this.enforcer.getUserPermissions(input); } + @Override + public UserPermissions getUserPermissionsWithOPA(GetUserPermissionsQuery input) throws IOException, PermitApiError { + return this.enforcer.getUserPermissionsWithOPA(input); + } + @Override public List getUserTenants(User user, Context context) throws IOException, PermitApiError { return this.enforcer.getUserTenants(user, context); diff --git a/src/main/java/io/permit/sdk/PermitConfig.java b/src/main/java/io/permit/sdk/PermitConfig.java index 8cd7999..4f1afc1 100644 --- a/src/main/java/io/permit/sdk/PermitConfig.java +++ b/src/main/java/io/permit/sdk/PermitConfig.java @@ -9,6 +9,7 @@ public class PermitConfig { // main config vars private final String token; private final String pdp; + private final String opa; private final String apiUrl; private final Boolean debugMode; @@ -32,6 +33,7 @@ public class PermitConfig { private PermitConfig(Builder builder) { this.token = builder.token; this.pdp = builder.pdp; + this.opa = builder.opa; this.apiUrl = builder.apiUrl; this.debugMode = builder.debugMode; this.logLevel = builder.logLevel; @@ -75,6 +77,15 @@ public String getPdpAddress() { return pdp; } + /** + * Returns the URL of the OPA Inside Policy Decision Point (PDP) used to evaluate authorization queries (i.e: permission checks). + * + * @return The OPA URL. + */ + public String getOpaAddress() { + return opa; + } + /** * Returns whether the Permit SDK is in debug mode. * @@ -194,6 +205,7 @@ public static class Builder { // main config vars private String token; private String pdp = "http://localhost:7766"; + private String opa = "http://localhost:8181/v1/data/permit"; private String apiUrl = "https://api.permit.io"; private Boolean debugMode = false; @@ -234,6 +246,17 @@ public Builder withPdpAddress(String pdp) { return this; } + /** + * Configures the Policy Decision Point (PDP) address. + * + * @param opa The PDP address to be set. + * @return The updated {@code Builder} object. + */ + public Builder withOpaAddress(String opa) { + this.opa = opa; + return this; + } + /** * Configures the URL of the Permit REST API. * diff --git a/src/main/java/io/permit/sdk/enforcement/Enforcer.java b/src/main/java/io/permit/sdk/enforcement/Enforcer.java index 2bd123f..0b3e90b 100644 --- a/src/main/java/io/permit/sdk/enforcement/Enforcer.java +++ b/src/main/java/io/permit/sdk/enforcement/Enforcer.java @@ -1,29 +1,30 @@ package io.permit.sdk.enforcement; -import com.google.common.primitives.Booleans; -import com.google.gson.Gson; -import io.permit.sdk.PermitConfig; -import io.permit.sdk.api.HttpLoggingInterceptor; -import io.permit.sdk.api.PermitApiError; -import io.permit.sdk.openapi.models.ConditionSetRuleRead; -import io.permit.sdk.util.Context; -import io.permit.sdk.util.ContextStore; - import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.primitives.Booleans; +import com.google.gson.Gson; + +import io.permit.sdk.PermitConfig; +import io.permit.sdk.api.HttpLoggingInterceptor; +import io.permit.sdk.api.PermitApiError; +import io.permit.sdk.util.Context; +import io.permit.sdk.util.ContextStore; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import okhttp3.ResponseBody; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * The {@code EnforcerInput} class represents the input data for the Permit PDP enforcement API. @@ -448,6 +449,63 @@ public UserPermissions getUserPermissions(GetUserPermissionsQuery input) throws return result; } + + @Override + public UserPermissions getUserPermissionsWithOPA(GetUserPermissionsQuery input) throws IOException, PermitApiError { + // request body + Gson gson = new Gson(); + + // Inner map for the nested JSON + Map innerMap = new HashMap<>(); + innerMap.put("user", input.user); + innerMap.put("tenants", input.tenants); + innerMap.put("resource_types", input.resource_types); + innerMap.put("resources", input.resources); + innerMap.put("context", input.context); + + // Outer map wrapping the inner map + Map outerMap = new HashMap<>(); + outerMap.put("input", innerMap); + + // Serialize to JSON + String requestBody = gson.toJson(outerMap); + + RequestBody body = RequestBody.create(requestBody, MediaType.parse("application/json")); + + String url = String.format("%s/user_permissions/permissions", this.config.getOpaAddress()); + Request request = new Request.Builder() + .url(url) + .post(body) + .addHeader("Content-Type", "application/json") + .addHeader("Authorization", String.format("Bearer %s", this.config.getToken())) + .addHeader("X-Permit-SDK-Version", String.format("java:%s", this.config.version)) + .build(); + + String requestRepr = String.format( + "permit.getUserPermissions(%s, %s, %s, %s)", + input.user.toString(), + input.tenants != null ? input.tenants.toString() : "null", + input.resource_types != null ? input.resource_types.toString() : "null", + input.resources != null ? input.resources.toString() : "null" + ); + + UserPermissionsOpa result = this.callApiAndParseJson(request, requestRepr, UserPermissionsOpa.class); + + UserPermissions userPermissions = new UserPermissions(); + userPermissions.putAll(result.getResult()); + + if (this.config.isDebugMode()) { + logger.info(String.format( + "%s => returned %d permissions on %d objects", + requestRepr, + userPermissions.values().stream().map(obj -> obj.permissions.size()).reduce(0, Integer::sum), + userPermissions.keySet().size() + )); + } + + return userPermissions; + } + private List getUserTenants(GetUserTenantsQuery input) throws IOException, PermitApiError { // request body Gson gson = new Gson(); diff --git a/src/main/java/io/permit/sdk/enforcement/IEnforcerApi.java b/src/main/java/io/permit/sdk/enforcement/IEnforcerApi.java index d59a1f1..79b7b3c 100644 --- a/src/main/java/io/permit/sdk/enforcement/IEnforcerApi.java +++ b/src/main/java/io/permit/sdk/enforcement/IEnforcerApi.java @@ -112,6 +112,16 @@ public interface IEnforcerApi { */ UserPermissions getUserPermissions(GetUserPermissionsQuery input) throws IOException, PermitApiError; + /** + * list all the permissions granted to a user (by default in all tenants and for all objects). + * + * @param input input to get user permissions api + * @return A UserPermissions object, that contains all the permissions granted to the user. + * @throws PermitApiError if an error occurs while sending the authorization request to the PDP. + * @throws IOException if could not read the content of the returned http response. + */ + UserPermissions getUserPermissionsWithOPA(GetUserPermissionsQuery input) throws IOException, PermitApiError; + /** * list all the tenants the user is associated with. * diff --git a/src/main/java/io/permit/sdk/enforcement/UserPermissionsOpa.java b/src/main/java/io/permit/sdk/enforcement/UserPermissionsOpa.java new file mode 100644 index 0000000..39d0a81 --- /dev/null +++ b/src/main/java/io/permit/sdk/enforcement/UserPermissionsOpa.java @@ -0,0 +1,28 @@ +package io.permit.sdk.enforcement; + +import java.util.HashMap; + +/** + * The {@code UserPermissionsOPA} class represents all the objects a user can access. + */ +final public class UserPermissionsOpa { + private String decisionId; + private HashMap result; + + // Getters and setters + public String getDecisionId() { + return decisionId; + } + + public void setDecisionId(String decisionId) { + this.decisionId = decisionId; + } + + public HashMap getResult() { + return result; + } + + public void setResult(HashMap result) { + this.result = result; + } +} diff --git a/src/test/java/io/permit/sdk/PermitE2ETestBase.java b/src/test/java/io/permit/sdk/PermitE2ETestBase.java index 709093b..46e7e05 100644 --- a/src/test/java/io/permit/sdk/PermitE2ETestBase.java +++ b/src/test/java/io/permit/sdk/PermitE2ETestBase.java @@ -1,23 +1,26 @@ package io.permit.sdk; -import okhttp3.HttpUrl; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import okhttp3.HttpUrl; + public abstract class PermitE2ETestBase { protected final static Logger logger = LoggerFactory.getLogger(PermitE2ETestBase.class); protected final PermitConfig config; + protected final PermitConfig opaConfig; protected boolean skipTests = false; private final static int connectionTimeout = 10; // 3 seconds to give up on sidecar / API public PermitE2ETestBase() { final String token = System.getenv().getOrDefault("PDP_API_KEY", ""); final String pdpAddress = System.getenv().getOrDefault("PDP_URL", "http://localhost:7766"); - final String pdpControlPlane = System.getenv().getOrDefault("PDP_CONTROL_PLANE", "http://localhost:8000"); + final String opaAddress = System.getenv().getOrDefault("OPA_URL", "http://localhost:8181/v1/data/permit"); + final String pdpControlPlane = System.getenv().getOrDefault("PDP_CONTROL_PLANE", "https://api.permit.io"); this.config = new PermitConfig.Builder(token) .withApiUrl(pdpControlPlane) @@ -25,6 +28,13 @@ public PermitE2ETestBase() { .withDebugMode(true) .build(); + + this.opaConfig = new PermitConfig.Builder(token) + .withApiUrl(pdpControlPlane) + .withOpaAddress(opaAddress) + .withDebugMode(true) + .build(); + HttpUrl apiUrl = HttpUrl.parse(config.getApiUrl()); HttpUrl pdpUrl = HttpUrl.parse(config.getPdpAddress()); diff --git a/src/test/java/io/permit/sdk/e2e/RbacE2ETest.java b/src/test/java/io/permit/sdk/e2e/RbacE2ETest.java index aa0d1ec..15865f2 100644 --- a/src/test/java/io/permit/sdk/e2e/RbacE2ETest.java +++ b/src/test/java/io/permit/sdk/e2e/RbacE2ETest.java @@ -36,7 +36,7 @@ public class RbacE2ETest extends PermitE2ETestBase { void testPermissionCheckRBAC() { // init the client Permit permit = new Permit(this.config); - + Permit permitOpa = new Permit(this.opaConfig); try { // resource actions HashMap actions = new HashMap<>(); @@ -250,7 +250,15 @@ void testPermissionCheckRBAC() { User.fromString("auth0|elon") ) ); + + UserPermissions permissions_2 = permitOpa.getUserPermissionsWithOPA( + new GetUserPermissionsQuery( + User.fromString("auth0|elon") + ) + ); + assertEquals(permissions.keySet().size(), 2); // elon has access to 2 tenants + assertEquals(permissions_2.keySet().size(), 2); // elon has access to 2 tenants String tenantObjectKey = String.format("%s:%s", TENANT_RESOURCE_KEY, tenant.key); String tenant2ObjectKey = String.format("%s:%s", TENANT_RESOURCE_KEY, tenant2.key); assertTrue(permissions.containsKey(tenantObjectKey));