Skip to content

Commit

Permalink
added new permission check: check in all tenants
Browse files Browse the repository at this point in the history
  • Loading branch information
asafc committed Oct 15, 2023
1 parent 8df3f5b commit 395a49d
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 3 deletions.
14 changes: 12 additions & 2 deletions src/main/java/io/permit/sdk/Permit.java
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,23 @@ public boolean checkUrl(User user, String httpMethod, String url, String tenant,
return this.enforcer.checkUrl(user, httpMethod, url, tenant, context);
}

@Override
public boolean checkUrl(User user, String httpMethod, String url, String tenant) throws IOException {
return this.enforcer.checkUrl(user, httpMethod, url, tenant);
}

@Override
public boolean[] bulkCheck(List<CheckQuery> checks) throws IOException {
return this.enforcer.bulkCheck(checks);
}

@Override
public boolean checkUrl(User user, String httpMethod, String url, String tenant) throws IOException {
return this.enforcer.checkUrl(user, httpMethod, url, tenant);
public List<TenantDetails> checkInAllTenants(User user, String action, Resource resource, Context context) throws IOException {
return this.enforcer.checkInAllTenants(user, action, resource, context);
}

@Override
public List<TenantDetails> checkInAllTenants(User user, String action, Resource resource) throws IOException {
return this.enforcer.checkInAllTenants(user, action, resource);
}
}
98 changes: 97 additions & 1 deletion src/main/java/io/permit/sdk/enforcement/Enforcer.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import com.google.gson.Gson;
import io.permit.sdk.PermitConfig;
import io.permit.sdk.api.HttpLoggingInterceptor;
import io.permit.sdk.openapi.models.RoleRead;
import io.permit.sdk.util.Context;
import io.permit.sdk.util.ContextStore;

Expand Down Expand Up @@ -82,6 +81,32 @@ class OpaResult {
}
}


/**
* The {@code TenantResult} class represents a single tenant returned by the checkInAllTenants query.
*/
class TenantResult {
public final Boolean allow;

public final TenantDetails tenant;

public TenantResult(Boolean allow, TenantDetails tenant) {
this.allow = allow;
this.tenant = tenant;
}
}

/**
* The {@code AllTenantsResult} class represents the result of the checkInAllTenants query.
*/
class AllTenantsResult {
public final TenantResult[] allowed_tenants;

public AllTenantsResult(TenantResult[] allowed_tenants) {
this.allowed_tenants = allowed_tenants;
}
}

/**
* The {@code OpaBulkResult} class represents the result of a Permit bulk enforcement check returned by the policy agent.
*/
Expand Down Expand Up @@ -343,6 +368,77 @@ public boolean[] bulkCheck(List<CheckQuery> checks) throws IOException {
}
}

@Override
public List<TenantDetails> checkInAllTenants(User user, String action, Resource resource, Context context) throws IOException {
Resource normalizedResource = resource.normalize(this.config);
Context queryContext = this.contextStore.getDerivedContext(context);

EnforcerInput input = new EnforcerInput(
user,
action,
normalizedResource,
queryContext
);

// request body
Gson gson = new Gson();
String requestBody = gson.toJson(input);
RequestBody body = RequestBody.create(requestBody, MediaType.parse("application/json"));

// create the request
String url = String.format("%s/allowed/all-tenants", this.config.getPdpAddress());
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();

try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
String errorMessage = String.format(
"Error in permit.checkInAllTenants(%s, %s, %s): got unexpected status code %d",
user.toString(),
action,
resource,
response.code()
);
logger.error(errorMessage);
throw new IOException(errorMessage);
}
ResponseBody responseBody = response.body();
if (responseBody == null) {
String errorMessage = String.format(
"Error in permit.checkInAllTenants(%s, %s, %s): got empty response",
user,
action,
resource
);
logger.error(errorMessage);
throw new IOException(errorMessage);
}
String responseString = responseBody.string();
AllTenantsResult result = gson.fromJson(responseString, AllTenantsResult.class);
List<TenantDetails> tenants = Arrays.stream(result.allowed_tenants).map(r -> r.tenant).collect(Collectors.toList());
if (this.config.isDebugMode()) {
logger.info(String.format(
"permit.checkInAllTenants(%s, %s, %s) => allowed in: [%s]",
user,
action,
resource,
tenants.stream().map(t -> t.key).collect(Collectors.joining(", "))
));
}
return tenants;
}
}

@Override
public List<TenantDetails> checkInAllTenants(User user, String action, Resource resource) throws IOException {
return checkInAllTenants(user, action, resource, new Context());
}

@Override
public boolean checkUrl(User user, String httpMethod, String url, String tenant) throws IOException {
return this.checkUrl(user, httpMethod, url, tenant, new Context());
Expand Down
26 changes: 26 additions & 0 deletions src/main/java/io/permit/sdk/enforcement/IEnforcerApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,30 @@ public interface IEnforcerApi {
* @throws IOException if an error occurs while sending the authorization request to the PDP.
*/
boolean[] bulkCheck(List<CheckQuery> checks) throws IOException;

/**
* Checks if a `user` is authorized to perform an `action` on a `resource` (with `context`) across all tenants.
* Returns only tenants in which the action is allowed for this user, including the tenant attributes.
*
* @param user The user object representing the user.
* @param action The action to be performed on the resource.
* @param resource The resource object representing the resource.
* @param context The context object representing the context in which the action is performed.
* @return List of TenantDetails objects, representing the tenants in which the action is allowed.
* @throws IOException if an error occurs while sending the authorization request to the PDP.
*/
List<TenantDetails> checkInAllTenants(User user, String action, Resource resource, Context context) throws IOException;

/**
* Checks if a `user` is authorized to perform an `action` on a `resource` across all tenants,
* without additional context. Returns only tenants in which the action is allowed for this user,
* including the tenant attributes.
*
* @param user The user object representing the user.
* @param action The action to be performed on the resource.
* @param resource The resource object representing the resource.
* @return List of TenantDetails objects, representing the tenants in which the action is allowed.
* @throws IOException if an error occurs while sending the authorization request to the PDP.
*/
List<TenantDetails> checkInAllTenants(User user, String action, Resource resource) throws IOException;
}
17 changes: 17 additions & 0 deletions src/main/java/io/permit/sdk/enforcement/TenantDetails.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.permit.sdk.enforcement;

import io.permit.sdk.util.Context;

import java.util.HashMap;

/**
* The {@code TenantDetails} class represents a single tenant information fetched from the PDP (key and attributes).
*/
public final class TenantDetails {
public final String key;
public final HashMap<String, Object> attributes;
public TenantDetails(String key, HashMap<String, Object> attributes) {
this.key = key;
this.attributes = attributes;
}
}
42 changes: 42 additions & 0 deletions src/test/java/io/permit/sdk/e2e/RbacE2ETest.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import io.permit.sdk.api.models.CreateOrUpdateResult;
import io.permit.sdk.enforcement.CheckQuery;
import io.permit.sdk.enforcement.Resource;
import io.permit.sdk.enforcement.TenantDetails;
import io.permit.sdk.enforcement.User;
import io.permit.sdk.openapi.models.*;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -124,11 +125,24 @@ void testPermissionCheckRBAC() {
assertEquals(tenant.description, "The car company");
assertNull(tenant.attributes);

// create another tenant
HashMap<String, Object> tenantAttributes = new HashMap<>();
tenantAttributes.put("tier", "pro");
tenantAttributes.put("unit", "one");

TenantRead tenant2 = permit.api.tenants.create(
new TenantCreate("twitter", "Twitter Inc").withAttributes(tenantAttributes)
);
assertEquals(tenant2.key, "twitter");
assertEquals(((String)tenant2.attributes.get("tier")), "pro");
assertEquals(((String)tenant2.attributes.get("unit")), "one");

// create a user
HashMap<String, Object> userAttributes = new HashMap<>();
userAttributes.put("age", Integer.valueOf(50));
userAttributes.put("fav_color", "red");


User userInput = (new User.Builder("auth0|elon"))
.withEmail("[email protected]")
.withFirstName("Elon")
Expand All @@ -155,6 +169,12 @@ void testPermissionCheckRBAC() {
assertEquals(ra.role, viewer.key);
assertEquals(ra.tenant, tenant.key);

// assign a second role in another tenant
RoleAssignmentRead ra2 = permit.api.users.assignRole("auth0|elon", "admin", "twitter");
assertEquals(ra2.userId, user.id);
assertEquals(ra2.roleId, admin.id);
assertEquals(ra2.tenantId, tenant2.id);

logger.info("sleeping 20 seconds before permit.check() to make sure all writes propagated from cloud to PDP");
Thread.sleep(20000);

Expand Down Expand Up @@ -203,6 +223,27 @@ void testPermissionCheckRBAC() {
assertTrue(checks[0]);
assertFalse(checks[1]);

logger.info("testing 'check in all tenants' on read:document");
List<TenantDetails> allowedTenants = permit.checkInAllTenants(
userInput,
"read",
new Resource.Builder("document").build()
);
assertEquals(allowedTenants.size(), 2);
assertTrue(allowedTenants.get(0).key.equals(tenant.key) || allowedTenants.get(0).key.equals(tenant2.key));
assertTrue(allowedTenants.get(1).key.equals(tenant.key) || allowedTenants.get(1).key.equals(tenant2.key));
assertNotEquals(allowedTenants.get(0).key, allowedTenants.get(1).key);

logger.info("testing 'check in all tenants' on create:document");
List<TenantDetails> allowedTenants2 = permit.checkInAllTenants(
userInput,
"create",
new Resource.Builder("document").build()
);
assertEquals(allowedTenants2.size(), 1);
assertEquals(allowedTenants2.get(0).key, tenant2.key);
assertEquals(((String)allowedTenants2.get(0).attributes.get("unit")), "one");

// change the user role
permit.api.users.assignRole(user.key, admin.key, tenant.key);
permit.api.users.unassignRole(user.key, viewer.key, tenant.key);
Expand Down Expand Up @@ -235,6 +276,7 @@ void testPermissionCheckRBAC() {
permit.api.roles.delete("admin");
permit.api.roles.delete("viewer");
permit.api.tenants.delete("tesla");
permit.api.tenants.delete("twitter");
permit.api.users.delete("auth0|elon");
assertEquals(permit.api.resources.list().length, 0);
assertEquals(permit.api.roles.list().length, 0);
Expand Down

0 comments on commit 395a49d

Please sign in to comment.