Skip to content
Open
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
70 changes: 70 additions & 0 deletions CedarJava/src/main/java/com/cedarpolicy/AuthorizationEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@
import com.cedarpolicy.model.EntityValidationRequest;
import com.cedarpolicy.model.PartialAuthorizationRequest;
import com.cedarpolicy.model.PartialAuthorizationResponse;
import com.cedarpolicy.model.TypeAwarePartialAuthorizationRequest;
import com.cedarpolicy.model.TypeAwarePartialAuthorizationResponse;
import com.cedarpolicy.model.ValidationRequest;
import com.cedarpolicy.model.ValidationResponse;
import com.cedarpolicy.model.entity.Entities;
import com.cedarpolicy.model.entity.PartialEntities;
import com.cedarpolicy.model.entity.Entity;
import com.cedarpolicy.model.exception.AuthException;
import com.cedarpolicy.model.exception.BadRequestException;
Expand Down Expand Up @@ -109,6 +112,73 @@ PartialAuthorizationResponse isAuthorizedPartial(PartialAuthorizationRequest req
PartialAuthorizationResponse isAuthorizedPartial(PartialAuthorizationRequest request,
PolicySet policySet, Entities entities) throws AuthException;

/**
* Asks what the given {@link TypeAwarePartialAuthorizationRequest} evaluates to against the {@code policySet} and
* the partially known {@code entities}, returning residual policies for whatever could not be decided.
*
* <p>The <em>policy set</em> is validated against the request's schema, which plain authorization does not do, so a
* policy set that authorizes fine can fail here — an unconstrained scope means every condition must typecheck for
* every entity type the action accepts, and an optional attribute needs a {@code has} guard.
*
* @param request The request to evaluate.
* @param policySet The policy set to evaluate against.
* @param entities The partially known entities to evaluate against.
* @return Every residual, plus a concrete decision if one could be reached.
* @throws AuthException On failure to make the authorization request.
* @throws UnsupportedOperationException If the implementation does not support type-aware partial evaluation.
*/
@Experimental(ExperimentalFeature.TYPE_AWARE_PARTIAL_EVALUATION)
default TypeAwarePartialAuthorizationResponse isAuthorizedTypeAwarePartial(
TypeAwarePartialAuthorizationRequest request, PolicySet policySet,
PartialEntities entities) throws AuthException {
throw new UnsupportedOperationException(
"isAuthorizedTypeAwarePartial is not implemented by " + getClass().getName());
}

/**
* Overload for entities that are all fully known; they are lifted using the schema the request already carries.
*
* @param request The request to evaluate.
* @param policySet The policy set to evaluate against.
* @param entities The concrete entities to evaluate against.
* @return Every residual, plus a concrete decision if one could be reached.
* @throws AuthException On failure to make the authorization request, or if the entities do not check out against
* the request's schema.
*/
@Experimental(ExperimentalFeature.TYPE_AWARE_PARTIAL_EVALUATION)
default TypeAwarePartialAuthorizationResponse isAuthorizedTypeAwarePartial(
TypeAwarePartialAuthorizationRequest request, PolicySet policySet,
Entities entities) throws AuthException {
return isAuthorizedTypeAwarePartial(request, policySet,
new PartialEntities(entities, request.schema));
}

/**
* Completes a type-aware partial authorization now that the previously unknown parts of the request and entities
* are known, returning the decision the original policies would have reached.
*
* <p>Prefer {@link com.cedarpolicy.model.TypeAwarePartialAuthorizationSuccessResponse#reauthorize}, which supplies
* the first three arguments from the response itself so they cannot disagree with the residuals you inspected. This
* form exists for that method to delegate to, and for a caller who has the partial inputs but not the response.
*
* @param request The partial request the residuals were computed from.
* @param policySet The policy set the residuals were computed from.
* @param partialEntities The partially known entities the residuals were computed from.
* @param concreteRequest The same request with its unknowns filled in.
* @param entities The same entities with their unknowns filled in. Cedar validates both against the partial
* inputs and rejects a mismatch; see
* {@link com.cedarpolicy.model.TypeAwarePartialAuthorizationSuccessResponse#reauthorize} for what it compares.
* @return The authorization decision, or a failure response if the completed inputs are inconsistent with the
* partial ones.
* @throws AuthException On failure to make the authorization request.
*/
@Experimental(ExperimentalFeature.TYPE_AWARE_PARTIAL_EVALUATION)
default AuthorizationResponse reauthorize(TypeAwarePartialAuthorizationRequest request, PolicySet policySet,
PartialEntities partialEntities, AuthorizationRequest concreteRequest, Entities entities)
throws AuthException {
throw new UnsupportedOperationException("reauthorize is not implemented by " + getClass().getName());
}

/**
* Asks whether the policies in the given {@link ValidationRequest} <code>q</code> are correct
* when validated against the schema it describes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,18 @@
import com.cedarpolicy.model.EntityValidationRequest;
import com.cedarpolicy.model.LevelValidationRequest;
import com.cedarpolicy.model.PartialAuthorizationResponse;
import com.cedarpolicy.model.TypeAwarePartialAuthorizationResponse;
import com.cedarpolicy.model.ValidationRequest;
import com.cedarpolicy.model.ValidationResponse;
import com.cedarpolicy.model.entity.Entities;
import com.cedarpolicy.model.entity.Entity;
import com.cedarpolicy.model.entity.PartialEntities;
import com.cedarpolicy.model.entity.PartialEntity;
import com.cedarpolicy.model.exception.AuthException;
import com.cedarpolicy.model.exception.BadRequestException;
import com.cedarpolicy.model.exception.InternalException;
import com.cedarpolicy.model.exception.MissingExperimentalFeatureException;
import com.cedarpolicy.model.DetailedError;
import com.cedarpolicy.model.policy.PolicySet;
import com.cedarpolicy.value.Value;
import com.fasterxml.jackson.annotation.JsonCreator;
Expand Down Expand Up @@ -123,6 +128,44 @@ public PartialAuthorizationResponse isAuthorizedPartial(com.cedarpolicy.model.Pa
return isAuthorizedPartial(q, policySet, entities.getEntities());
}

@Experimental(ExperimentalFeature.TYPE_AWARE_PARTIAL_EVALUATION)
@Override
public TypeAwarePartialAuthorizationResponse isAuthorizedTypeAwarePartial(
com.cedarpolicy.model.TypeAwarePartialAuthorizationRequest q, PolicySet policySet,
PartialEntities entities) throws AuthException {
var response = call("AuthorizationTypeAwarePartialOperation",
TypeAwarePartialAuthorizationResponse.class,
new TypeAwarePartialAuthorizationRequest(q, policySet, entities));
throwIfFeatureDisabled(response.getErrors().orElse(List.of()));
return response.withInputs(q, policySet, entities, this);
}

@Experimental(ExperimentalFeature.TYPE_AWARE_PARTIAL_EVALUATION)
@Override
public AuthorizationResponse reauthorize(com.cedarpolicy.model.TypeAwarePartialAuthorizationRequest request,
PolicySet policySet, PartialEntities partialEntities,
com.cedarpolicy.model.AuthorizationRequest concreteRequest, Entities entities) throws AuthException {
var response = call("TypeAwarePartialReauthorizeOperation", AuthorizationResponse.class,
new TypeAwarePartialReauthorizeRequest(request, policySet, partialEntities, concreteRequest, entities));
throwIfFeatureDisabled(response.errors.map(e -> (List<DetailedError>) e).orElse(List.of()));
return response;
}

/**
* The native library reports a disabled experimental feature as a failure answer rather than a thrown exception,
* because {@link #call} converts every native error into an {@link AuthException}. Recognising the token here is
* what turns it back into {@link MissingExperimentalFeatureException}.
*/
private static void throwIfFeatureDisabled(List<DetailedError> errors) throws AuthException {
for (var error : errors) {
var translated = ExperimentalFeature.TYPE_AWARE_PARTIAL_EVALUATION
.translateIfDisabled(new InternalException(error.message));
if (translated instanceof MissingExperimentalFeatureException) {
throw translated;
}
}
}

@Override
public ValidationResponse validate(ValidationRequest q) throws AuthException {
return call("ValidateOperation", ValidationResponse.class, q);
Expand Down Expand Up @@ -213,6 +256,40 @@ private static class AuthorizationRequest extends com.cedarpolicy.model.Authoriz
}
}

@JsonInclude(JsonInclude.Include.NON_ABSENT)
private static final class TypeAwarePartialAuthorizationRequest
extends com.cedarpolicy.model.TypeAwarePartialAuthorizationRequest {
@JsonProperty private final PolicySet policies;
@JsonProperty private final Set<PartialEntity> entities;

TypeAwarePartialAuthorizationRequest(com.cedarpolicy.model.TypeAwarePartialAuthorizationRequest request,
PolicySet policySet, PartialEntities entities) {
super(request);
this.policies = policySet;
this.entities = entities.getEntities();
}
}

/** The partial payload plus the completed request and entities Cedar validates it against. */
@JsonInclude(JsonInclude.Include.NON_ABSENT)
private static final class TypeAwarePartialReauthorizeRequest
extends com.cedarpolicy.model.TypeAwarePartialAuthorizationRequest {
@JsonProperty private final PolicySet policies;
@JsonProperty private final Set<PartialEntity> entities;
@JsonProperty private final com.cedarpolicy.model.AuthorizationRequest concreteRequest;
@JsonProperty private final Set<Entity> concreteEntities;

TypeAwarePartialReauthorizeRequest(com.cedarpolicy.model.TypeAwarePartialAuthorizationRequest request,
PolicySet policySet, PartialEntities partialEntities,
com.cedarpolicy.model.AuthorizationRequest concreteRequest, Entities concreteEntities) {
super(request);
this.policies = policySet;
this.entities = partialEntities.getEntities();
this.concreteRequest = concreteRequest;
this.concreteEntities = concreteEntities.getEntities();
}
}

@JsonInclude(JsonInclude.Include.NON_ABSENT)
private static final class PartialAuthorizationRequest extends com.cedarpolicy.model.PartialAuthorizationRequest {
@JsonProperty private final PolicySet policies;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,41 @@ public final class TypeAwarePartialAuthorizationResponse {
this.warnings = warnings == null ? List.of() : List.copyOf(warnings);
}

private TypeAwarePartialAuthorizationResponse(TypeAwarePartialAuthorizationResponse response,
TypeAwarePartialAuthorizationRequest request, com.cedarpolicy.model.policy.PolicySet policies,
com.cedarpolicy.model.entity.PartialEntities entities, com.cedarpolicy.AuthorizationEngine engine) {
this.type = response.type;
this.success = response.success.map(
s -> new TypeAwarePartialAuthorizationSuccessResponse(s, request, policies, entities, engine));
this.errors = response.errors;
this.warnings = response.warnings;
}

/**
* Returns a copy whose success response, if any, remembers what the residuals were computed from, so that
* {@link TypeAwarePartialAuthorizationSuccessResponse#reauthorize} does not need them passed again.
*
* <p>Public only because the engine that calls it is in another package; <b>callers should not use it.</b> Pass
* inputs other than the ones the residuals were computed from and the residuals describe one authorization while
* {@code reauthorize} answers a different one — as a success, because Cedar can only check the completed request
* against the partial inputs it is handed.
*
* @param request the partial request the residuals were computed from
* @param policies the policy set the residuals were computed from
* @param entities the partially known entities the residuals were computed from
* @param engine the engine to reauthorize through
* @return a copy carrying those inputs
*/
public TypeAwarePartialAuthorizationResponse withInputs(TypeAwarePartialAuthorizationRequest request,
com.cedarpolicy.model.policy.PolicySet policies,
com.cedarpolicy.model.entity.PartialEntities entities, com.cedarpolicy.AuthorizationEngine engine) {
java.util.Objects.requireNonNull(request, "request");
java.util.Objects.requireNonNull(policies, "policies");
java.util.Objects.requireNonNull(entities, "entities");
java.util.Objects.requireNonNull(engine, "engine");
return new TypeAwarePartialAuthorizationResponse(this, request, policies, entities, engine);
}

/**
* Whether this is a success or a failure response.
*
Expand Down Expand Up @@ -83,7 +118,7 @@ public Optional<List<DetailedError>> getErrors() {
}

/**
* Warnings, which either kind of response may carry.
* Warnings, always empty for type-aware partial evaluation: Cedar's {@code TpeResponse} exposes none.
*
* @return the warnings, empty if there were none
*/
Expand Down
Loading
Loading