Skip to content

Commit

Permalink
Merge pull request #29 from permitio/gidi/per-11143-add-check-against…
Browse files Browse the repository at this point in the history
…-opa-directly-to-java-sdk

Add new ability to get user permissions via opa directly
  • Loading branch information
gideonsmila authored Dec 22, 2024
2 parents 48b4ed2 + a66bf1b commit 941b584
Show file tree
Hide file tree
Showing 9 changed files with 179 additions and 28 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ build

# Ignore stg schemas
stg-schemas/
bin
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ repositories {

java {
toolchain {
languageVersion = JavaLanguageVersion.of(8)
languageVersion = JavaLanguageVersion.of(8)
}
// sources are required by maven central in order to accept the package
withSourcesJar()
Expand Down
27 changes: 20 additions & 7 deletions src/main/java/io/permit/sdk/Permit.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
package io.permit.sdk;

import java.io.IOException;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import io.permit.sdk.api.ApiClient;
import io.permit.sdk.api.ElementsApi;
import io.permit.sdk.api.PermitApiError;
import io.permit.sdk.enforcement.*;
import io.permit.sdk.enforcement.CheckQuery;
import io.permit.sdk.enforcement.Enforcer;
import io.permit.sdk.enforcement.GetUserPermissionsQuery;
import io.permit.sdk.enforcement.IEnforcerApi;
import io.permit.sdk.enforcement.Resource;
import io.permit.sdk.enforcement.TenantDetails;
import io.permit.sdk.enforcement.User;
import io.permit.sdk.enforcement.UserPermissions;
import io.permit.sdk.util.Context;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.List;

/**
* The {@code Permit} class represents the main entry point for interacting with the Permit.io SDK.
*
Expand Down Expand Up @@ -159,6 +167,11 @@ public UserPermissions getUserPermissions(GetUserPermissionsQuery input) throws
return this.enforcer.getUserPermissions(input);
}

@Override
public UserPermissions getUserPermissionsFromOPA(GetUserPermissionsQuery input) throws IOException, PermitApiError {
return this.enforcer.getUserPermissionsFromOPA(input);
}

@Override
public List<TenantDetails> getUserTenants(User user, Context context) throws IOException, PermitApiError {
return this.enforcer.getUserTenants(user, context);
Expand Down
23 changes: 23 additions & 0 deletions src/main/java/io/permit/sdk/PermitConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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";
private String apiUrl = "https://api.permit.io";
private Boolean debugMode = false;

Expand Down Expand Up @@ -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.
*
Expand Down
80 changes: 69 additions & 11 deletions src/main/java/io/permit/sdk/enforcement/Enforcer.java
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -448,6 +449,63 @@ public UserPermissions getUserPermissions(GetUserPermissionsQuery input) throws
return result;
}


@Override
public UserPermissions getUserPermissionsFromOPA(GetUserPermissionsQuery input) throws IOException, PermitApiError {
// request body
Gson gson = new Gson();

// Inner map for the nested JSON
Map<String, Object> 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<String, Object> 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 PERMISSIONS_PATH = "permit/user_permissions/permissions";
String url = String.format("%s/v1/data/%s", this.config.getOpaAddress(), PERMISSIONS_PATH);
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<TenantDetails> getUserTenants(GetUserTenantsQuery input) throws IOException, PermitApiError {
// request body
Gson gson = new Gson();
Expand Down
16 changes: 13 additions & 3 deletions src/main/java/io/permit/sdk/enforcement/IEnforcerApi.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package io.permit.sdk.enforcement;

import io.permit.sdk.api.PermitApiError;
import io.permit.sdk.util.Context;

import java.io.IOException;
import java.util.List;

import io.permit.sdk.api.PermitApiError;
import io.permit.sdk.util.Context;

public interface IEnforcerApi {
/**
* Checks if a `user` is authorized to perform an `action` on a `resource` within the specified context.
Expand Down Expand Up @@ -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 getUserPermissionsFromOPA(GetUserPermissionsQuery input) throws IOException, PermitApiError;

/**
* list all the tenants the user is associated with.
*
Expand Down
28 changes: 28 additions & 0 deletions src/main/java/io/permit/sdk/enforcement/UserPermissionsOpa.java
Original file line number Diff line number Diff line change
@@ -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<String, ObjectPermissions> result;

// Getters and setters
public String getDecisionId() {
return decisionId;
}

public void setDecisionId(String decisionId) {
this.decisionId = decisionId;
}

public HashMap<String, ObjectPermissions> getResult() {
return result;
}

public void setResult(HashMap<String, ObjectPermissions> result) {
this.result = result;
}
}
20 changes: 15 additions & 5 deletions src/test/java/io/permit/sdk/PermitE2ETestBase.java
Original file line number Diff line number Diff line change
@@ -1,30 +1,40 @@
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");
final String pdpControlPlane = System.getenv().getOrDefault("PDP_CONTROL_PLANE", "https://api.permit.io");

this.config = new PermitConfig.Builder(token)
.withApiUrl(pdpControlPlane)
.withPdpAddress(pdpAddress)
.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());

Expand Down
10 changes: 9 additions & 1 deletion src/test/java/io/permit/sdk/e2e/RbacE2ETest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, ActionBlockEditable> actions = new HashMap<>();
Expand Down Expand Up @@ -250,7 +250,15 @@ void testPermissionCheckRBAC() {
User.fromString("auth0|elon")
)
);

UserPermissions permissions_2 = permitOpa.getUserPermissionsFromOPA(
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));
Expand Down

0 comments on commit 941b584

Please sign in to comment.