Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1512,7 +1512,7 @@ try {
// Load the current authorization schema
try {
FGASchema schema = fs.loadSchema();
// Do something with schema.getDsl()
// Do something with schema.getDsl(), schema.getVersion() and schema.getConditions()
} catch (DescopeException de) {
// Handle the error
}
Expand All @@ -1533,12 +1533,34 @@ try {
try {
List<FGACheckResult> results = fs.check(relations);
for (FGACheckResult result : results) {
// Do something with result.isAllowed()
// Do something with result.isAllowed(), result.getRelation() and result.getInfo()
}
} catch (DescopeException de) {
// Handle the error
}

// Check relations against a schema that uses conditions (ABAC), passing the values
// the conditions are evaluated with
Map<String, Object> context = new HashMap<>();
context.put("role", "admin");

try {
List<FGACheckResult> results = fs.check(relations, context);
for (FGACheckResult result : results) {
FGACheckInfo info = result.getInfo();
// info.isConditional() - the result was decided by a condition
// info.getMissingContext() - context variables the conditions needed but did not get
// info.getConditionalErr() - the condition could not be evaluated
}
} catch (DescopeException de) {
// Handle the error
}

// The same context can be passed to the authz queries that evaluate conditions
AuthzService authz = descopeClient.getManagementServices().getAuthzService();
List<String> targets = authz.whoCanAccess("doc1", "viewer", "document", context);
List<Relation> relationsForTarget = authz.whatCanTargetAccess("user123", context);

// Delete relations
try {
fs.deleteRelations(relations);
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/com/descope/model/fga/FGACheckInfo.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.descope.model.fga;

import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
Expand All @@ -11,4 +12,8 @@
@AllArgsConstructor
public class FGACheckInfo {
private boolean direct;
private boolean conditional;
private List<String> missingContext;
private String conditionalErr;
private boolean factUsed;
}
25 changes: 25 additions & 0 deletions src/main/java/com/descope/model/fga/FGACheckResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.descope.model.fga;

import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FGACheckResponse {
private List<FGACheckResponseTuple> tuples;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class FGACheckResponseTuple {
private boolean allowed;
private FGARelation tuple;
private FGACheckInfo info;
}
}
17 changes: 17 additions & 0 deletions src/main/java/com/descope/model/fga/FGACondition.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.descope.model.fga;

import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FGACondition {
private String name;
private List<FGAConditionParam> params;
private String expression;
}
15 changes: 15 additions & 0 deletions src/main/java/com/descope/model/fga/FGAConditionParam.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.descope.model.fga;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FGAConditionParam {
private String name;
private String type;
}
25 changes: 25 additions & 0 deletions src/main/java/com/descope/model/fga/FGALoadSchemaResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.descope.model.fga;

import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FGALoadSchemaResponse {
private String dsl;
private String version;
private FGALoadSchemaConditions schema;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class FGALoadSchemaConditions {
private List<FGACondition> conditions;
}
}
7 changes: 7 additions & 0 deletions src/main/java/com/descope/model/fga/FGASchema.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.descope.model.fga;

import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
Expand All @@ -11,4 +12,10 @@
@AllArgsConstructor
public class FGASchema {
private String dsl;
private List<FGACondition> conditions;
private String version;

public FGASchema(String dsl) {
this.dsl = dsl;
}
}
32 changes: 32 additions & 0 deletions src/main/java/com/descope/sdk/mgmt/AuthzService.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.descope.model.authz.Schema;
import java.time.Instant;
import java.util.List;
import java.util.Map;

/** Provides ReBAC authorization service APIs. */
public interface AuthzService {
Expand Down Expand Up @@ -126,6 +127,23 @@ void saveRelationDefinition(RelationDefinition relationDefinition, String namesp
*/
List<String> whoCanAccess(String resource, String relationDefinition, String namespace) throws DescopeException;

/**
* List all the users that have the given relation definition to the given resource, evaluating
* schema conditions against the given context.
*
* <p>Context keys become variables available to the CEL conditions defined in the schema.
* Values must be JSON serializable.
*
* @param resource The resource we are checking
* @param relationDefinition The relation definition we are querying
* @param namespace The namespace for the relation definition
* @param context Extra context for condition evaluation, may be null or empty
* @return {@link List} of users who have the given relation definition
* @throws DescopeException If there occurs any exception, a subtype of this exception will be thrown.
*/
List<String> whoCanAccess(String resource, String relationDefinition, String namespace,
Map<String, Object> context) throws DescopeException;

/**
* Return the list of all defined relations (not recursive) on the given resource.
*
Expand Down Expand Up @@ -153,6 +171,20 @@ void saveRelationDefinition(RelationDefinition relationDefinition, String namesp
*/
List<Relation> whatCanTargetAccess(String target) throws DescopeException;

/**
* Return the list of all relations for the given target including derived relations from the
* schema tree, evaluating schema conditions against the given context.
*
* <p>Context keys become variables available to the CEL conditions defined in the schema.
* Values must be JSON serializable.
*
* @param target The target to check relations for
* @param context Extra context for condition evaluation, may be null or empty
* @return {@link List} of {@link Relation} that exist for the given target
* @throws DescopeException If there occurs any exception, a subtype of this exception will be thrown.
*/
List<Relation> whatCanTargetAccess(String target, Map<String, Object> context) throws DescopeException;

/**
* Return the list of all resources for the given target and a given relation definition
* including derived resources from the schema tree.
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/com/descope/sdk/mgmt/FGAService.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.descope.model.fga.FGASchema;
import com.descope.model.fga.FGASchemaDryRunResponse;
import java.util.List;
import java.util.Map;

/**
* Provides functions for managing Fine-Grained Authorization (FGA) in a project.
Expand Down Expand Up @@ -68,6 +69,20 @@ public interface FGAService {
*/
List<FGACheckResult> check(List<FGARelation> relations) throws DescopeException;

/**
* Checks if the given FGA relations are satisfied, evaluating schema conditions against
* the given context.
*
* <p>Context keys become variables available to the CEL conditions defined in the schema,
* on top of any attributes the backend already holds. Values must be JSON serializable.
*
* @param relations list of relations to check
* @param context extra context for condition evaluation, may be null or empty
* @return list of check results indicating whether each relation is allowed
* @throws DescopeException if the operation fails
*/
List<FGACheckResult> check(List<FGARelation> relations, Map<String, Object> context) throws DescopeException;

/**
* Loads detailed information for the given resource identifiers.
*
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/com/descope/sdk/mgmt/impl/AuthzServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,12 @@ public List<RelationQuery> hasRelations(List<RelationQuery> relationQueries) thr
@Override
public List<String> whoCanAccess(String resource, String relationDefinition, String namespace)
throws DescopeException {
return whoCanAccess(resource, relationDefinition, namespace, null);
}

@Override
public List<String> whoCanAccess(String resource, String relationDefinition, String namespace,
Map<String, Object> context) throws DescopeException {
if (StringUtils.isBlank(resource)) {
throw ServerCommonException.invalidArgument("resource");
}
Expand All @@ -202,6 +208,9 @@ public List<String> whoCanAccess(String resource, String relationDefinition, Str
ApiProxy apiProxy = getApiProxy();
Map<String, Object> request =
mapOf("resource", resource, "relationDefinition", relationDefinition, "namespace", namespace);
if (context != null && !context.isEmpty()) {
request.put("context", context);
}
WhoCanAccessResponse resp = apiProxy.post(getUri(MANAGEMENT_AUTHZ_RE_WHO), request, WhoCanAccessResponse.class);
return resp.getTargets();
}
Expand Down Expand Up @@ -230,11 +239,19 @@ public List<Relation> targetsRelations(List<String> targets) throws DescopeExcep

@Override
public List<Relation> whatCanTargetAccess(String target) throws DescopeException {
return whatCanTargetAccess(target, null);
}

@Override
public List<Relation> whatCanTargetAccess(String target, Map<String, Object> context) throws DescopeException {
if (StringUtils.isBlank(target)) {
throw ServerCommonException.invalidArgument("target");
}
ApiProxy apiProxy = getApiProxy();
Map<String, Object> request = mapOf("target", target);
if (context != null && !context.isEmpty()) {
request.put("context", context);
}
RelationsResponse resp = apiProxy.post(getUri(MANAGEMENT_AUTHZ_RE_TARGET_ALL), request, RelationsResponse.class);
return resp.getRelations();
}
Expand Down
52 changes: 33 additions & 19 deletions src/main/java/com/descope/sdk/mgmt/impl/FGAServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@
import com.descope.exception.DescopeException;
import com.descope.exception.ServerCommonException;
import com.descope.model.client.Client;
import com.descope.model.fga.FGACheckInfo;
import com.descope.model.fga.FGACheckResponse;
import com.descope.model.fga.FGACheckResponse.FGACheckResponseTuple;
import com.descope.model.fga.FGACheckResult;
import com.descope.model.fga.FGALoadSchemaResponse;
import com.descope.model.fga.FGARelation;
import com.descope.model.fga.FGAResourceDetails;
import com.descope.model.fga.FGAResourceIdentifier;
Expand Down Expand Up @@ -62,12 +66,17 @@ public FGASchemaDryRunResponse dryRunSchema(FGASchema schema) throws DescopeExce
@Override
public FGASchema loadSchema() throws DescopeException {
ApiProxy apiProxy = getApiProxy();
Map<String, Object> response = apiProxy.getArray(getUri(MANAGEMENT_FGA_LOAD_SCHEMA),
new TypeReference<Map<String, Object>>() {});
FGALoadSchemaResponse response = apiProxy.get(getUri(MANAGEMENT_FGA_LOAD_SCHEMA), FGALoadSchemaResponse.class);

FGASchema schema = new FGASchema();
if (response.containsKey("dsl")) {
schema.setDsl((String) response.get("dsl"));
schema.setConditions(new ArrayList<>());
if (response == null) {
return schema;
}
schema.setDsl(response.getDsl());
schema.setVersion(response.getVersion());
if (response.getSchema() != null && response.getSchema().getConditions() != null) {
schema.setConditions(response.getSchema().getConditions());
Comment thread
yosiharan marked this conversation as resolved.
}
return schema;
}
Expand Down Expand Up @@ -100,32 +109,37 @@ public void deleteRelations(List<FGARelation> relations) throws DescopeException

@Override
public List<FGACheckResult> check(List<FGARelation> relations) throws DescopeException {
return check(relations, null);
}

@Override
public List<FGACheckResult> check(List<FGARelation> relations, Map<String, Object> context)
throws DescopeException {
if (relations == null || relations.isEmpty()) {
throw ServerCommonException.invalidArgument("relations list");
}

Map<String, Object> requestBody = new HashMap<>();
requestBody.put("tuples", relations);
if (context != null && !context.isEmpty()) {
requestBody.put("context", context);
}

ApiProxy apiProxy = getApiProxy();
Map<String, Object> response = apiProxy.postAndGetArray(getUri(MANAGEMENT_FGA_CHECK),
requestBody, new TypeReference<Map<String, Object>>() {});
FGACheckResponse response = apiProxy.post(getUri(MANAGEMENT_FGA_CHECK), requestBody, FGACheckResponse.class);

if (response.containsKey("tuples")) {
// Convert the response tuples to FGACheckResult objects
@SuppressWarnings("unchecked")
List<Map<String, Object>> tuples = (List<Map<String, Object>>) response.get("tuples");
List<FGACheckResult> results = new ArrayList<>();
for (Map<String, Object> tuple : tuples) {
FGACheckResult result = new FGACheckResult();
if (tuple.containsKey("allowed")) {
result.setAllowed((Boolean) tuple.get("allowed"));
}
results.add(result);
}
List<FGACheckResult> results = new ArrayList<>();
if (response == null || response.getTuples() == null) {
return results;
}
return new ArrayList<>();
for (FGACheckResponseTuple tuple : response.getTuples()) {
FGACheckInfo info = tuple.getInfo() == null ? new FGACheckInfo() : tuple.getInfo();
Comment thread
yosiharan marked this conversation as resolved.
if (info.getMissingContext() == null) {
info.setMissingContext(new ArrayList<>());
}
results.add(new FGACheckResult(tuple.isAllowed(), tuple.getTuple(), info));
}
return results;
}

@Override
Expand Down
Loading