mirror of
https://github.com/keycloak/keycloak.git
synced 2026-05-28 04:13:22 -04:00
AuthZen Evaluations API
Closes #47825 Signed-off-by: Ryan Emerson <remerson@ibm.com>
This commit is contained in:
parent
a9d523b0cd
commit
5811348cbc
10 changed files with 1003 additions and 91 deletions
|
|
@ -16,18 +16,21 @@
|
|||
*/
|
||||
package org.keycloak.authorization.authzen;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonNaming;
|
||||
|
||||
public final class AuthZen {
|
||||
|
||||
public static final String AUTHZEN_ACCESS_PATH = "access/v1";
|
||||
public static final String EVALUATION_PATH = AUTHZEN_ACCESS_PATH + "/evaluation";
|
||||
public static final String EVALUATIONS_PATH = AUTHZEN_ACCESS_PATH + "/evaluations";
|
||||
|
||||
private AuthZen() {
|
||||
}
|
||||
|
|
@ -80,6 +83,43 @@ public final class AuthZen {
|
|||
@JsonProperty(required = true) Action action,
|
||||
Map<String, Object> context) {}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record EvaluationResponse(boolean decision) {}
|
||||
public record EvaluationResponse(boolean decision, Map<String, Object> context) {
|
||||
public EvaluationResponse(boolean decision) {
|
||||
this(decision, null);
|
||||
}
|
||||
}
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public record EvaluationItem(
|
||||
Subject subject,
|
||||
Resource resource,
|
||||
Action action,
|
||||
Map<String, Object> context) {}
|
||||
|
||||
public enum EvaluationsSemantic {
|
||||
@JsonProperty("execute_all")
|
||||
EXECUTE_ALL,
|
||||
|
||||
@JsonProperty("deny_on_first_deny")
|
||||
DENY_ON_FIRST_DENY,
|
||||
|
||||
@JsonProperty("permit_on_first_permit")
|
||||
PERMIT_ON_FIRST_PERMIT;
|
||||
}
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public record Options(EvaluationsSemantic evaluationsSemantic) {}
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public record EvaluationsRequest(
|
||||
Subject subject,
|
||||
Resource resource,
|
||||
Action action,
|
||||
Map<String, Object> context,
|
||||
Options options,
|
||||
List<EvaluationItem> evaluations) {}
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public record EvaluationsResponse(List<EvaluationResponse> evaluations) {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
package org.keycloak.authorization.authzen;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
|
@ -56,7 +57,7 @@ import org.keycloak.util.JsonSerialization;
|
|||
|
||||
public class AuthZenResource {
|
||||
|
||||
private static final Response DECISION_FALSE = Response.ok(new AuthZen.EvaluationResponse(false)).build();
|
||||
private static final AuthZen.EvaluationResponse DECISION_FALSE = new AuthZen.EvaluationResponse(false);
|
||||
private static final Pattern UUID_PATTERN = Pattern.compile("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$");
|
||||
|
||||
private static final String NAMESPACE_ID = "id:";
|
||||
|
|
@ -78,6 +79,64 @@ public class AuthZenResource {
|
|||
if (token == null) {
|
||||
throw new NotAuthorizedException("Bearer");
|
||||
}
|
||||
return Response.ok(evaluateSingle(request, token)).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path(AuthZen.EVALUATIONS_PATH)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response evaluations(AuthZen.EvaluationsRequest request) {
|
||||
AccessToken token = Tokens.getAccessToken(session);
|
||||
if (token == null) {
|
||||
throw new NotAuthorizedException("Bearer");
|
||||
}
|
||||
|
||||
if (request.evaluations() == null || request.evaluations().isEmpty()) {
|
||||
// AuthZen 1.0 Section 7.1
|
||||
// "If an evaluations array is NOT present or is empty, the Access Evaluations Request behaves in a
|
||||
// backwards-compatible manner with the (single) Access Evaluation API Request (Section 6.1)."
|
||||
AuthZen.EvaluationRequest single = new AuthZen.EvaluationRequest(
|
||||
request.subject(), request.resource(), request.action(), request.context());
|
||||
return Response.ok(evaluateSingle(single, token)).build();
|
||||
}
|
||||
|
||||
AuthZen.EvaluationsSemantic semantic = AuthZen.EvaluationsSemantic.EXECUTE_ALL;
|
||||
if (request.options() != null && request.options().evaluationsSemantic() != null) {
|
||||
semantic = request.options().evaluationsSemantic();
|
||||
}
|
||||
|
||||
List<AuthZen.EvaluationResponse> results = new ArrayList<>(request.evaluations().size());
|
||||
for (AuthZen.EvaluationItem item : request.evaluations()) {
|
||||
AuthZen.EvaluationRequest merged = mergeDefaults(request, item);
|
||||
AuthZen.EvaluationResponse itemResponse = evaluateSingle(merged, token);
|
||||
|
||||
if (semantic == AuthZen.EvaluationsSemantic.DENY_ON_FIRST_DENY && !itemResponse.decision()) {
|
||||
results.add(new AuthZen.EvaluationResponse(false, Map.of("reason", "deny_on_first_deny")));
|
||||
break;
|
||||
}
|
||||
|
||||
results.add(itemResponse);
|
||||
|
||||
if (semantic == AuthZen.EvaluationsSemantic.PERMIT_ON_FIRST_PERMIT && itemResponse.decision()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Response.ok(new AuthZen.EvaluationsResponse(results)).build();
|
||||
}
|
||||
|
||||
private static AuthZen.EvaluationRequest mergeDefaults(AuthZen.EvaluationsRequest defaults, AuthZen.EvaluationItem item) {
|
||||
AuthZen.Subject subject = item.subject() != null ? item.subject() : defaults.subject();
|
||||
AuthZen.Resource resource = item.resource() != null ? item.resource() : defaults.resource();
|
||||
AuthZen.Action action = item.action() != null ? item.action() : defaults.action();
|
||||
Map<String, Object> context = item.context() != null ? item.context() : defaults.context();
|
||||
return new AuthZen.EvaluationRequest(subject, resource, action, context);
|
||||
}
|
||||
|
||||
private AuthZen.EvaluationResponse evaluateSingle(AuthZen.EvaluationRequest request, AccessToken token) {
|
||||
if (request.subject() == null || request.resource() == null || request.action() == null) {
|
||||
return new AuthZen.EvaluationResponse(false);
|
||||
}
|
||||
|
||||
RealmModel realm = session.getContext().getRealm();
|
||||
AuthorizationProvider authorization = session.getProvider(AuthorizationProvider.class);
|
||||
|
|
@ -143,8 +202,7 @@ public class AuthZenResource {
|
|||
.from(List.of(permission), context)
|
||||
.evaluate(resourceServer, null);
|
||||
|
||||
boolean decision = !granted.isEmpty();
|
||||
return Response.ok(new AuthZen.EvaluationResponse(decision)).build();
|
||||
return new AuthZen.EvaluationResponse(!granted.isEmpty());
|
||||
}
|
||||
|
||||
private Identity resolveSubjectIdentity(RealmModel realm, AuthZen.EvaluationRequest request) {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,8 @@ public class AuthZenWellKnownProvider implements WellKnownProvider {
|
|||
|
||||
return Map.of(
|
||||
"policy_decision_point", realmUri,
|
||||
"access_evaluation_endpoint", accessEvaluationEndpoint(realmUri)
|
||||
"access_evaluation_endpoint", accessEvaluationEndpoint(realmUri),
|
||||
"access_evaluations_endpoint", accessEvaluationsEndpoint(realmUri)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -47,6 +48,10 @@ public class AuthZenWellKnownProvider implements WellKnownProvider {
|
|||
return String.format("%s/%s/%s", realmUri, AuthZenRealmResourceProviderFactory.PROVIDER_ID, AuthZen.EVALUATION_PATH);
|
||||
}
|
||||
|
||||
public static String accessEvaluationsEndpoint(String realmUri) {
|
||||
return String.format("%s/%s/%s", realmUri, AuthZenRealmResourceProviderFactory.PROVIDER_ID, AuthZen.EVALUATIONS_PATH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@
|
|||
package org.keycloak.testframework.authzen.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.ws.rs.core.HttpHeaders;
|
||||
|
|
@ -68,7 +70,9 @@ public class AuthZenClient {
|
|||
}
|
||||
try (SimpleHttpResponse response = req(postReq)) {
|
||||
int status = response.getStatus();
|
||||
AuthZen.EvaluationResponse body = response.asJson(AuthZen.EvaluationResponse.class);
|
||||
AuthZen.EvaluationResponse body = status == 200
|
||||
? response.asJson(AuthZen.EvaluationResponse.class)
|
||||
: null;
|
||||
Map<String, String> responseHeaders = new HashMap<>();
|
||||
for (Header h : response.getAllHeaders()) {
|
||||
responseHeaders.putIfAbsent(h.getName(), h.getValue());
|
||||
|
|
@ -77,6 +81,38 @@ public class AuthZenClient {
|
|||
}
|
||||
}
|
||||
|
||||
public EvaluationsResult evaluations(AuthZen.EvaluationsRequest request) throws IOException {
|
||||
return evaluations((Object) request, null);
|
||||
}
|
||||
|
||||
public EvaluationsResult evaluations(AuthZen.EvaluationsRequest request, Map<String, String> headers) throws IOException {
|
||||
return evaluations((Object) request, headers);
|
||||
}
|
||||
|
||||
public EvaluationsResult evaluations(JsonNode request) throws IOException {
|
||||
return evaluations((Object) request, null);
|
||||
}
|
||||
|
||||
private EvaluationsResult evaluations(Object req, Map<String, String> headers) throws IOException {
|
||||
String url = realmUrl + "/authzen/access/v1/evaluations";
|
||||
|
||||
SimpleHttpRequest postReq = simpleHttp.doPost(url).json(req);
|
||||
if (headers != null) {
|
||||
headers.forEach(postReq::header);
|
||||
}
|
||||
try (SimpleHttpResponse response = req(postReq)) {
|
||||
int status = response.getStatus();
|
||||
AuthZen.EvaluationsResponse body = status == 200
|
||||
? response.asJson(AuthZen.EvaluationsResponse.class)
|
||||
: null;
|
||||
Map<String, String> responseHeaders = new HashMap<>();
|
||||
for (Header h : response.getAllHeaders()) {
|
||||
responseHeaders.putIfAbsent(h.getName(), h.getValue());
|
||||
}
|
||||
return new EvaluationsResult(status, body, responseHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
public WellKnownResponse fetchWellKnownConfiguration() throws IOException {
|
||||
String url = realmUrl + "/.well-known/authzen-configuration";
|
||||
try (SimpleHttpResponse rsp = req(simpleHttp.doGet(url))) {
|
||||
|
|
@ -124,10 +160,29 @@ public class AuthZenClient {
|
|||
}
|
||||
}
|
||||
|
||||
public record EvaluationsResult(int statusCode, AuthZen.EvaluationsResponse response, Map<String, String> headers) {
|
||||
|
||||
public List<AuthZen.EvaluationResponse> evaluations() {
|
||||
return response.evaluations();
|
||||
}
|
||||
|
||||
public String header(String name) {
|
||||
return headers != null ? headers.get(name) : null;
|
||||
}
|
||||
}
|
||||
|
||||
public static EvaluationRequestBuilder evaluationRequest() {
|
||||
return new EvaluationRequestBuilder();
|
||||
}
|
||||
|
||||
public static EvaluationsRequestBuilder evaluationsRequest() {
|
||||
return new EvaluationsRequestBuilder();
|
||||
}
|
||||
|
||||
public static EvaluationItemBuilder evaluationItem() {
|
||||
return new EvaluationItemBuilder();
|
||||
}
|
||||
|
||||
public static final class EvaluationRequestBuilder {
|
||||
private AuthZen.SubjectType subjectType;
|
||||
private String subjectId;
|
||||
|
|
@ -189,4 +244,114 @@ public class AuthZenClient {
|
|||
return new AuthZen.EvaluationRequest(subject, resource, action, contextProperties);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class EvaluationItemBuilder {
|
||||
private AuthZen.SubjectType subjectType;
|
||||
private String subjectId;
|
||||
private boolean subjectSet;
|
||||
private String resourceType;
|
||||
private String resourceId;
|
||||
private Map<String, Object> resourceProperties;
|
||||
private boolean resourceSet;
|
||||
private AuthZen.Action action;
|
||||
private Map<String, Object> contextProperties;
|
||||
|
||||
public EvaluationItemBuilder subject(AuthZen.SubjectType type, String id) {
|
||||
this.subjectType = type;
|
||||
this.subjectId = id;
|
||||
this.subjectSet = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EvaluationItemBuilder resource(String type, String id) {
|
||||
this.resourceType = type;
|
||||
this.resourceId = id;
|
||||
this.resourceSet = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EvaluationItemBuilder resourceProperty(String key, Object value) {
|
||||
if (resourceProperties == null) {
|
||||
resourceProperties = new HashMap<>();
|
||||
}
|
||||
resourceProperties.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EvaluationItemBuilder action(String name) {
|
||||
this.action = new AuthZen.Action(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EvaluationItemBuilder contextProperty(String key, Object value) {
|
||||
if (contextProperties == null) {
|
||||
contextProperties = new HashMap<>();
|
||||
}
|
||||
contextProperties.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AuthZen.EvaluationItem build() {
|
||||
AuthZen.Subject subject = subjectSet ? new AuthZen.Subject(subjectType, subjectId, null) : null;
|
||||
AuthZen.Resource resource = resourceSet ? new AuthZen.Resource(resourceType, resourceId, resourceProperties) : null;
|
||||
return new AuthZen.EvaluationItem(subject, resource, action, contextProperties);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class EvaluationsRequestBuilder {
|
||||
private AuthZen.SubjectType subjectType;
|
||||
private String subjectId;
|
||||
private boolean subjectSet;
|
||||
private String resourceType;
|
||||
private String resourceId;
|
||||
private boolean resourceSet;
|
||||
private AuthZen.Action action;
|
||||
private Map<String, Object> contextProperties;
|
||||
private AuthZen.EvaluationsSemantic evaluationsSemantic;
|
||||
private final List<AuthZen.EvaluationItem> evaluations = new ArrayList<>();
|
||||
|
||||
public EvaluationsRequestBuilder subject(AuthZen.SubjectType type, String id) {
|
||||
this.subjectType = type;
|
||||
this.subjectId = id;
|
||||
this.subjectSet = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EvaluationsRequestBuilder resource(String type, String id) {
|
||||
this.resourceType = type;
|
||||
this.resourceId = id;
|
||||
this.resourceSet = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EvaluationsRequestBuilder action(String name) {
|
||||
this.action = new AuthZen.Action(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EvaluationsRequestBuilder contextProperty(String key, Object value) {
|
||||
if (contextProperties == null) {
|
||||
contextProperties = new HashMap<>();
|
||||
}
|
||||
contextProperties.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EvaluationsRequestBuilder evaluationsSemantic(AuthZen.EvaluationsSemantic semantic) {
|
||||
this.evaluationsSemantic = semantic;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EvaluationsRequestBuilder addEvaluation(AuthZen.EvaluationItem evaluation) {
|
||||
evaluations.add(evaluation);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AuthZen.EvaluationsRequest build() {
|
||||
AuthZen.Subject subject = subjectSet ? new AuthZen.Subject(subjectType, subjectId, null) : null;
|
||||
AuthZen.Resource resource = resourceSet ? new AuthZen.Resource(resourceType, resourceId, null) : null;
|
||||
AuthZen.Options options = evaluationsSemantic != null ? new AuthZen.Options(evaluationsSemantic) : null;
|
||||
return new AuthZen.EvaluationsRequest(subject, resource, action, contextProperties, options, evaluations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,15 +23,16 @@ import java.util.Map;
|
|||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.keycloak.authorization.authzen.AuthZen;
|
||||
import org.keycloak.testframework.annotations.InjectRealm;
|
||||
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
|
||||
import org.keycloak.testframework.authzen.client.AuthZenClient;
|
||||
import org.keycloak.testframework.authzen.client.AuthZenClient.EvaluationResult;
|
||||
import org.keycloak.testframework.authzen.client.AuthZenClient.EvaluationsResult;
|
||||
import org.keycloak.testframework.authzen.client.annotations.InjectAuthZenClient;
|
||||
import org.keycloak.testframework.oauth.OAuthClient;
|
||||
import org.keycloak.testframework.oauth.annotations.InjectOAuthClient;
|
||||
import org.keycloak.testframework.realm.ManagedRealm;
|
||||
import org.keycloak.testsuite.util.oauth.AccessTokenResponse;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
|
@ -42,10 +43,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||
|
||||
/**
|
||||
* Runs the AuthZen interop evaluation test suite defined in the OpenID AuthZen interop project.
|
||||
* Test scenarios are loaded from the decisions-authorization-api-1_0-01.json resource file.
|
||||
* Each entry in the JSON defines an AuthZen evaluation request and its expected decision.
|
||||
* Test scenarios are loaded from the decisions-authorization-api-1_0-02.json resource file.
|
||||
* The JSON file defines AuthZen evaluation and evaluations requests, as well as the expected decision.
|
||||
* <p>
|
||||
* All required Realm configuration including users, roles, client, and authorization settings is loaded from interop-realm.json.
|
||||
* All required Realm configuration including users, roles, client, and authorization settings are loaded from interop-realm.json.
|
||||
*/
|
||||
@KeycloakIntegrationTest(config = AuthZenServerConfig.class)
|
||||
public class AuthZenEvaluationInteropTest {
|
||||
|
|
@ -73,44 +74,83 @@ public class AuthZenEvaluationInteropTest {
|
|||
|
||||
@TestFactory
|
||||
Stream<DynamicTest> interopEvaluationTests() throws IOException {
|
||||
List<InteropDecision> decisions = loadDecisions();
|
||||
JsonNode root = loadDecisions();
|
||||
AuthZenClient.Authenticated client = authenticatedClient();
|
||||
|
||||
AccessTokenResponse tokenResponse = oauth
|
||||
.client(PDP_CLIENT_ID, PDP_CLIENT_SECRET)
|
||||
.doClientCredentialsGrantAccessTokenRequest();
|
||||
AuthZenClient.Authenticated client = authZenClient.withAccessToken(tokenResponse.getAccessToken());
|
||||
|
||||
return decisions.stream().map(decision -> {
|
||||
String testName = buildTestName(decision);
|
||||
return DynamicTest.dynamicTest(testName, () -> {
|
||||
EvaluationResult result = client.evaluate(decision.request());
|
||||
assertEquals(200, result.statusCode(), "Expected 200 OK for: " + testName);
|
||||
assertEquals(decision.expected(), result.decision(), "Expected decision=" + decision.expected() + " for: " + testName);
|
||||
});
|
||||
});
|
||||
return StreamSupport.stream(root.path("evaluation").spliterator(), false)
|
||||
.map(node -> {
|
||||
JsonNode request = node.get("request");
|
||||
boolean expected = node.get("expected").asBoolean();
|
||||
String testName = buildEvaluationTestName(request, expected);
|
||||
return DynamicTest.dynamicTest(testName, () -> {
|
||||
EvaluationResult result = client.evaluate(request);
|
||||
assertEquals(200, result.statusCode(), "Expected 200 OK for: " + testName);
|
||||
assertEquals(expected, result.decision(), "Expected decision=" + expected + " for: " + testName);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static String buildTestName(InteropDecision decision) {
|
||||
JsonNode req = decision.request();
|
||||
String subjectId = req.path("subject").path("id").asText();
|
||||
@TestFactory
|
||||
Stream<DynamicTest> interopEvaluationsTests() throws IOException {
|
||||
JsonNode root = loadDecisions();
|
||||
JsonNode evaluationsNode = root.path("evaluations");
|
||||
|
||||
if (evaluationsNode.isMissingNode() || !evaluationsNode.isArray() || evaluationsNode.isEmpty()) {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
AuthZenClient.Authenticated client = authenticatedClient();
|
||||
return StreamSupport.stream(evaluationsNode.spliterator(), false)
|
||||
.map(node -> {
|
||||
JsonNode request = node.get("request");
|
||||
JsonNode expectedArray = node.get("expected");
|
||||
String testName = buildEvaluationsTestName(request);
|
||||
return DynamicTest.dynamicTest(testName, () -> {
|
||||
EvaluationsResult result = client.evaluations(request);
|
||||
assertEquals(200, result.statusCode(), "Expected 200 OK for: " + testName);
|
||||
|
||||
List<AuthZen.EvaluationResponse> actualEvaluations = result.evaluations();
|
||||
assertEquals(expectedArray.size(), actualEvaluations.size(), "Evaluations count mismatch for: " + testName);
|
||||
|
||||
for (int i = 0; i < expectedArray.size(); i++) {
|
||||
boolean expectedDecision = expectedArray.get(i).get("decision").asBoolean();
|
||||
assertEquals(expectedDecision, actualEvaluations.get(i).decision(),
|
||||
"Decision mismatch at index " + i + " for: " + testName);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private AuthZenClient.Authenticated authenticatedClient() {
|
||||
return authZenClient.withAccessToken(
|
||||
oauth.client(PDP_CLIENT_ID, PDP_CLIENT_SECRET)
|
||||
.doClientCredentialsGrantAccessTokenRequest()
|
||||
.getAccessToken()
|
||||
);
|
||||
}
|
||||
|
||||
private static String buildEvaluationTestName(JsonNode json, boolean expected) {
|
||||
String subjectId = json.path("subject").path("id").asText();
|
||||
String userName = USER_NAMES.getOrDefault(subjectId, subjectId);
|
||||
String action = req.path("action").path("name").asText();
|
||||
String resourceType = req.path("resource").path("type").asText();
|
||||
String resourceId = req.path("resource").path("id").asText();
|
||||
String expected = decision.expected() ? "ALLOW" : "DENY";
|
||||
return String.format("%s | %s %s:%s => %s", userName, action, resourceType, resourceId, expected);
|
||||
String action = json.path("action").path("name").asText();
|
||||
String resourceType = json.path("resource").path("type").asText();
|
||||
String resourceId = json.path("resource").path("id").asText();
|
||||
String expectedStr = expected ? "ALLOW" : "DENY";
|
||||
return String.format("%s | %s %s:%s => %s", userName, action, resourceType, resourceId, expectedStr);
|
||||
}
|
||||
|
||||
private static List<InteropDecision> loadDecisions() throws IOException {
|
||||
private static String buildEvaluationsTestName(JsonNode json) {
|
||||
String subjectId = json.path("subject").path("id").asText();
|
||||
String userName = USER_NAMES.getOrDefault(subjectId, subjectId);
|
||||
String action = json.path("action").path("name").asText();
|
||||
int count = json.path("evaluations").size();
|
||||
return String.format("batch | %s | %s x%d", userName, action, count);
|
||||
}
|
||||
|
||||
private static JsonNode loadDecisions() throws IOException {
|
||||
try (InputStream is = AuthZenEvaluationInteropTest.class.getResourceAsStream(
|
||||
"decisions-authorization-api-1_0-01.json")) {
|
||||
JsonNode root = JsonSerialization.mapper.readTree(is);
|
||||
return StreamSupport.stream(root.path("evaluation").spliterator(), false)
|
||||
.map(node -> new InteropDecision(node.get("request"), node.get("expected").asBoolean()))
|
||||
.toList();
|
||||
"decisions-authorization-api-1_0-02.json")) {
|
||||
return JsonSerialization.mapper.readTree(is);
|
||||
}
|
||||
}
|
||||
|
||||
public record InteropDecision(JsonNode request, boolean expected) {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -629,7 +629,6 @@ public class AuthZenEvaluationTest {
|
|||
);
|
||||
|
||||
assertEquals(401, result.statusCode());
|
||||
assertFalse(result.decision());
|
||||
assertEquals(requestId, result.header(X_REQUEST_ID));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,506 @@
|
|||
/*
|
||||
* Copyright 2025 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.tests.authzen;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import jakarta.ws.rs.core.Response;
|
||||
|
||||
import org.keycloak.admin.client.resource.AuthorizationResource;
|
||||
import org.keycloak.authorization.authzen.AuthZen;
|
||||
import org.keycloak.http.simple.SimpleHttp;
|
||||
import org.keycloak.http.simple.SimpleHttpResponse;
|
||||
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.RolePolicyRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
|
||||
import org.keycloak.testframework.annotations.InjectClient;
|
||||
import org.keycloak.testframework.annotations.InjectKeycloakUrls;
|
||||
import org.keycloak.testframework.annotations.InjectRealm;
|
||||
import org.keycloak.testframework.annotations.InjectSimpleHttp;
|
||||
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
|
||||
import org.keycloak.testframework.annotations.TestSetup;
|
||||
import org.keycloak.testframework.authzen.client.AuthZenClient;
|
||||
import org.keycloak.testframework.authzen.client.AuthZenClient.EvaluationsResult;
|
||||
import org.keycloak.testframework.authzen.client.annotations.InjectAuthZenClient;
|
||||
import org.keycloak.testframework.oauth.OAuthClient;
|
||||
import org.keycloak.testframework.oauth.annotations.InjectOAuthClient;
|
||||
import org.keycloak.testframework.realm.ClientBuilder;
|
||||
import org.keycloak.testframework.realm.ClientConfig;
|
||||
import org.keycloak.testframework.realm.ManagedClient;
|
||||
import org.keycloak.testframework.realm.ManagedRealm;
|
||||
import org.keycloak.testframework.realm.RealmBuilder;
|
||||
import org.keycloak.testframework.realm.RealmConfig;
|
||||
import org.keycloak.testframework.realm.UserBuilder;
|
||||
import org.keycloak.testframework.server.KeycloakUrls;
|
||||
import org.keycloak.testsuite.util.oauth.AccessTokenResponse;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.keycloak.authorization.authzen.AuthZen.SubjectType.USER;
|
||||
import static org.keycloak.authorization.authzen.AuthZenRequestIdFilter.X_REQUEST_ID;
|
||||
import static org.keycloak.authorization.authzen.AuthZenWellKnownProvider.accessEvaluationsEndpoint;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* Tests for the AuthZen Evaluations (batch) endpoint.
|
||||
*/
|
||||
@KeycloakIntegrationTest(config = AuthZenServerConfig.class)
|
||||
public class AuthZenEvaluationsTest {
|
||||
|
||||
private static final String ADMIN_USER = "admin-user";
|
||||
private static final String REGULAR_USER = "regular-user";
|
||||
|
||||
@InjectRealm(config = TestRealmConfig.class)
|
||||
ManagedRealm realm;
|
||||
|
||||
@InjectClient(ref = "authzen-client", config = AuthzClientConfig.class)
|
||||
ManagedClient client;
|
||||
|
||||
@InjectOAuthClient
|
||||
OAuthClient oauth;
|
||||
|
||||
@InjectAuthZenClient
|
||||
AuthZenClient authZenClient;
|
||||
|
||||
@InjectSimpleHttp
|
||||
SimpleHttp simpleHttp;
|
||||
|
||||
@InjectKeycloakUrls
|
||||
KeycloakUrls keycloakUrls;
|
||||
|
||||
@TestSetup
|
||||
public void setup() {
|
||||
AuthorizationResource authz = client.admin().authorization();
|
||||
String adminRoleId = realm.admin().roles().get("admin").toRepresentation().getId();
|
||||
|
||||
createScope(authz, "read");
|
||||
createScope(authz, "write");
|
||||
|
||||
String adminPolicyId = createRolePolicy(authz, "Require Admin Role", adminRoleId);
|
||||
String alwaysGrantId = createAlwaysGrantPolicy(authz);
|
||||
|
||||
createResource(authz, "/admin", "endpoint", "read");
|
||||
createResourcePermission(authz, "Admin Resource Permission", "/admin", adminPolicyId);
|
||||
|
||||
createResource(authz, "/users", "endpoint", "read");
|
||||
createResourcePermission(authz, "Users Resource Permission", "/users", alwaysGrantId);
|
||||
|
||||
createResource(authz, "/scope-limited", "endpoint", "read", "write");
|
||||
createScopePermission(authz, "Scope Limited Read Permission", "/scope-limited", "read", "Always Grant");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEvaluationsBatchAllGranted() throws IOException {
|
||||
EvaluationsResult result = authzenClient("admin-user", "password")
|
||||
.evaluations(AuthZenClient.evaluationsRequest()
|
||||
.subject(USER, ADMIN_USER)
|
||||
.action("read")
|
||||
.addEvaluation(AuthZenClient.evaluationItem()
|
||||
.resource("endpoint", "/admin")
|
||||
.build())
|
||||
.addEvaluation(AuthZenClient.evaluationItem()
|
||||
.resource("endpoint", "/users")
|
||||
.build())
|
||||
.build());
|
||||
|
||||
assertEquals(200, result.statusCode());
|
||||
assertEquals(2, result.evaluations().size());
|
||||
assertTrue(result.evaluations().get(0).decision());
|
||||
assertTrue(result.evaluations().get(1).decision());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEvaluationsBatchMixedDecisions() throws IOException {
|
||||
EvaluationsResult result = authzenClient("regular-user", "password")
|
||||
.evaluations(AuthZenClient.evaluationsRequest()
|
||||
.subject(USER, REGULAR_USER)
|
||||
.action("read")
|
||||
.addEvaluation(AuthZenClient.evaluationItem()
|
||||
.resource("endpoint", "/admin")
|
||||
.build())
|
||||
.addEvaluation(AuthZenClient.evaluationItem()
|
||||
.resource("endpoint", "/users")
|
||||
.build())
|
||||
.build());
|
||||
|
||||
assertEquals(200, result.statusCode());
|
||||
assertEquals(2, result.evaluations().size());
|
||||
assertFalse(result.evaluations().get(0).decision());
|
||||
assertTrue(result.evaluations().get(1).decision());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEvaluationsItemOverridesDefaults() throws IOException {
|
||||
EvaluationsResult result = authzenClient("admin-user", "password")
|
||||
.evaluations(AuthZenClient.evaluationsRequest()
|
||||
.subject(USER, ADMIN_USER)
|
||||
.action("read")
|
||||
.resource("endpoint", "/admin")
|
||||
.addEvaluation(AuthZenClient.evaluationItem()
|
||||
.build())
|
||||
.addEvaluation(AuthZenClient.evaluationItem()
|
||||
.action("write")
|
||||
.resource("endpoint", "/scope-limited")
|
||||
.build())
|
||||
.build());
|
||||
|
||||
assertEquals(200, result.statusCode());
|
||||
assertEquals(2, result.evaluations().size());
|
||||
assertTrue(result.evaluations().get(0).decision());
|
||||
assertFalse(result.evaluations().get(1).decision());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEvaluationsUnauthenticatedReturnsUnauthorized() throws IOException {
|
||||
EvaluationsResult result = authZenClient.evaluations(AuthZenClient.evaluationsRequest()
|
||||
.subject(USER, ADMIN_USER)
|
||||
.action("read")
|
||||
.addEvaluation(AuthZenClient.evaluationItem()
|
||||
.resource("endpoint", "/admin")
|
||||
.build())
|
||||
.build());
|
||||
|
||||
assertEquals(401, result.statusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEvaluationsEmptyArrayFallsBackToSingleEvaluation() throws IOException {
|
||||
String json = """
|
||||
{"subject":{"type":"user","id":"%s"},\
|
||||
"resource":{"type":"endpoint","id":"/admin"},\
|
||||
"action":{"name":"read"},\
|
||||
"evaluations":[]}""".formatted(ADMIN_USER);
|
||||
|
||||
JsonNode body = postEvaluations("admin-user", "password", json);
|
||||
assertTrue(body.get("decision").asBoolean());
|
||||
assertNull(body.get("evaluations"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEvaluationsAbsentFallsBackToSingleEvaluation() throws IOException {
|
||||
String json = """
|
||||
{"subject":{"type":"user","id":"%s"},\
|
||||
"resource":{"type":"endpoint","id":"/admin"},\
|
||||
"action":{"name":"read"}}""".formatted(ADMIN_USER);
|
||||
|
||||
JsonNode body = postEvaluations(ADMIN_USER, "password", json);
|
||||
assertTrue(body.get("decision").asBoolean());
|
||||
assertNull(body.get("evaluations"));
|
||||
}
|
||||
|
||||
private JsonNode postEvaluations(String username, String password, String json) throws IOException {
|
||||
String url = accessEvaluationsEndpoint(keycloakUrls.getBase() + "/realms/" + realm.getName());
|
||||
AccessTokenResponse tokenResponse = oauth
|
||||
.client(client.getClientId(), client.getSecret())
|
||||
.doPasswordGrantRequest(username, password);
|
||||
|
||||
try (SimpleHttpResponse response = simpleHttp.doPost(url)
|
||||
.auth(tokenResponse.getAccessToken())
|
||||
.header("Content-Type", "application/json")
|
||||
.entity(new StringEntity(json))
|
||||
.asResponse()) {
|
||||
assertEquals(200, response.getStatus());
|
||||
return response.asJson();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteAllSemantic() throws IOException {
|
||||
EvaluationsResult result = authzenClient("regular-user", "password")
|
||||
.evaluations(AuthZenClient.evaluationsRequest()
|
||||
.subject(USER, REGULAR_USER)
|
||||
.action("read")
|
||||
.evaluationsSemantic(AuthZen.EvaluationsSemantic.EXECUTE_ALL)
|
||||
.addEvaluation(AuthZenClient.evaluationItem()
|
||||
.resource("endpoint", "/admin")
|
||||
.build())
|
||||
.addEvaluation(AuthZenClient.evaluationItem()
|
||||
.resource("endpoint", "/users")
|
||||
.build())
|
||||
.build());
|
||||
|
||||
assertEquals(200, result.statusCode());
|
||||
assertEquals(2, result.evaluations().size());
|
||||
assertFalse(result.evaluations().get(0).decision());
|
||||
assertTrue(result.evaluations().get(1).decision());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDenyOnFirstDenyStopsOnDenial() throws IOException {
|
||||
EvaluationsResult result = authzenClient("regular-user", "password")
|
||||
.evaluations(AuthZenClient.evaluationsRequest()
|
||||
.subject(USER, REGULAR_USER)
|
||||
.action("read")
|
||||
.evaluationsSemantic(AuthZen.EvaluationsSemantic.DENY_ON_FIRST_DENY)
|
||||
.addEvaluation(AuthZenClient.evaluationItem()
|
||||
.resource("endpoint", "/admin")
|
||||
.build())
|
||||
.addEvaluation(AuthZenClient.evaluationItem()
|
||||
.resource("endpoint", "/users")
|
||||
.build())
|
||||
.build());
|
||||
|
||||
assertEquals(200, result.statusCode());
|
||||
assertEquals(1, result.evaluations().size());
|
||||
assertFalse(result.evaluations().get(0).decision());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDenyOnFirstDenyReturnsAllWhenAllPermitted() throws IOException {
|
||||
EvaluationsResult result = authzenClient("admin-user", "password")
|
||||
.evaluations(AuthZenClient.evaluationsRequest()
|
||||
.subject(USER, ADMIN_USER)
|
||||
.action("read")
|
||||
.evaluationsSemantic(AuthZen.EvaluationsSemantic.DENY_ON_FIRST_DENY)
|
||||
.addEvaluation(AuthZenClient.evaluationItem()
|
||||
.resource("endpoint", "/admin")
|
||||
.build())
|
||||
.addEvaluation(AuthZenClient.evaluationItem()
|
||||
.resource("endpoint", "/users")
|
||||
.build())
|
||||
.build());
|
||||
|
||||
assertEquals(200, result.statusCode());
|
||||
assertEquals(2, result.evaluations().size());
|
||||
assertTrue(result.evaluations().get(0).decision());
|
||||
assertTrue(result.evaluations().get(1).decision());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPermitOnFirstPermitStopsOnPermit() throws IOException {
|
||||
EvaluationsResult result = authzenClient("regular-user", "password")
|
||||
.evaluations(AuthZenClient.evaluationsRequest()
|
||||
.subject(USER, REGULAR_USER)
|
||||
.action("read")
|
||||
.evaluationsSemantic(AuthZen.EvaluationsSemantic.PERMIT_ON_FIRST_PERMIT)
|
||||
.addEvaluation(AuthZenClient.evaluationItem()
|
||||
.resource("endpoint", "/admin")
|
||||
.build())
|
||||
.addEvaluation(AuthZenClient.evaluationItem()
|
||||
.resource("endpoint", "/users")
|
||||
.build())
|
||||
.build());
|
||||
|
||||
assertEquals(200, result.statusCode());
|
||||
assertEquals(2, result.evaluations().size());
|
||||
assertFalse(result.evaluations().get(0).decision());
|
||||
assertTrue(result.evaluations().get(1).decision());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPermitOnFirstPermitStopsImmediatelyOnFirstPermit() throws IOException {
|
||||
EvaluationsResult result = authzenClient("admin-user", "password")
|
||||
.evaluations(AuthZenClient.evaluationsRequest()
|
||||
.subject(USER, ADMIN_USER)
|
||||
.action("read")
|
||||
.evaluationsSemantic(AuthZen.EvaluationsSemantic.PERMIT_ON_FIRST_PERMIT)
|
||||
.addEvaluation(AuthZenClient.evaluationItem()
|
||||
.resource("endpoint", "/admin")
|
||||
.build())
|
||||
.addEvaluation(AuthZenClient.evaluationItem()
|
||||
.resource("endpoint", "/users")
|
||||
.build())
|
||||
.build());
|
||||
|
||||
assertEquals(200, result.statusCode());
|
||||
assertEquals(1, result.evaluations().size());
|
||||
assertTrue(result.evaluations().get(0).decision());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testXRequestIdEchoedInResponse() throws IOException {
|
||||
String requestId = "test-request-id-12345";
|
||||
|
||||
EvaluationsResult result = authzenClient("admin-user", "password")
|
||||
.evaluations(AuthZenClient.evaluationsRequest()
|
||||
.subject(USER, ADMIN_USER)
|
||||
.action("read")
|
||||
.addEvaluation(AuthZenClient.evaluationItem()
|
||||
.resource("endpoint", "/admin")
|
||||
.build())
|
||||
.build(),
|
||||
Map.of(X_REQUEST_ID, requestId));
|
||||
|
||||
assertEquals(200, result.statusCode());
|
||||
assertTrue(result.evaluations().get(0).decision());
|
||||
assertEquals(requestId, result.header(X_REQUEST_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testXRequestIdEchoedOnUnauthorizedResponse() throws IOException {
|
||||
String requestId = "unauth-request-id";
|
||||
|
||||
EvaluationsResult result = new AuthZenClient(simpleHttp,
|
||||
keycloakUrls.getBase() + "/realms/" + realm.getName())
|
||||
.evaluations(AuthZenClient.evaluationsRequest()
|
||||
.subject(USER, ADMIN_USER)
|
||||
.action("read")
|
||||
.addEvaluation(AuthZenClient.evaluationItem()
|
||||
.resource("endpoint", "/admin")
|
||||
.build())
|
||||
.build(),
|
||||
Map.of(X_REQUEST_ID, requestId));
|
||||
|
||||
assertEquals(401, result.statusCode());
|
||||
assertEquals(requestId, result.header(X_REQUEST_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testXRequestIdEchoedOnBadRequestResponse() throws IOException {
|
||||
String requestId = "bad-request-id";
|
||||
|
||||
String url = accessEvaluationsEndpoint(keycloakUrls.getBase() + "/realms/" + realm.getName());
|
||||
AccessTokenResponse tokenResponse = oauth
|
||||
.client(client.getClientId(), client.getSecret())
|
||||
.doPasswordGrantRequest("admin-user", "password");
|
||||
|
||||
try (SimpleHttpResponse response = simpleHttp.doPost(url)
|
||||
.auth(tokenResponse.getAccessToken())
|
||||
.header("Content-Type", "application/json")
|
||||
.header(X_REQUEST_ID, requestId)
|
||||
.entity(new StringEntity("{invalid json"))
|
||||
.asResponse()) {
|
||||
assertEquals(400, response.getStatus());
|
||||
assertEquals(requestId, response.getFirstHeader(X_REQUEST_ID));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultSemanticIsExecuteAll() throws IOException {
|
||||
EvaluationsResult result = authzenClient("regular-user", "password")
|
||||
.evaluations(AuthZenClient.evaluationsRequest()
|
||||
.subject(USER, REGULAR_USER)
|
||||
.action("read")
|
||||
.addEvaluation(AuthZenClient.evaluationItem()
|
||||
.resource("endpoint", "/admin")
|
||||
.build())
|
||||
.addEvaluation(AuthZenClient.evaluationItem()
|
||||
.resource("endpoint", "/users")
|
||||
.build())
|
||||
.build());
|
||||
|
||||
assertEquals(200, result.statusCode());
|
||||
assertEquals(2, result.evaluations().size());
|
||||
}
|
||||
|
||||
private AuthZenClient.Authenticated authzenClient(String username, String password) {
|
||||
AccessTokenResponse tokenResponse = oauth
|
||||
.client(client.getClientId(), client.getSecret())
|
||||
.doPasswordGrantRequest(username, password);
|
||||
return authZenClient.withAccessToken(tokenResponse.getAccessToken());
|
||||
}
|
||||
|
||||
private static void createScope(AuthorizationResource authz, String name) {
|
||||
try (Response response = authz.scopes().create(new ScopeRepresentation(name))) {
|
||||
assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
private static void createResource(AuthorizationResource authz, String name, String type, String... scopes) {
|
||||
ResourceRepresentation resource = new ResourceRepresentation();
|
||||
resource.setName(name);
|
||||
resource.setType(type);
|
||||
resource.addScope(scopes);
|
||||
try (Response response = authz.resources().create(resource)) {
|
||||
assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
private static String createRolePolicy(AuthorizationResource authz, String name, String roleId) {
|
||||
RolePolicyRepresentation policy = new RolePolicyRepresentation();
|
||||
policy.setName(name);
|
||||
policy.addRole(roleId);
|
||||
try (Response response = authz.policies().role().create(policy)) {
|
||||
assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus());
|
||||
}
|
||||
return authz.policies().role().findByName(name).getId();
|
||||
}
|
||||
|
||||
private static String createAlwaysGrantPolicy(AuthorizationResource authz) {
|
||||
PolicyRepresentation policy = new PolicyRepresentation();
|
||||
policy.setName("Always Grant");
|
||||
policy.setType("always-grant");
|
||||
try (Response response = authz.policies().create(policy)) {
|
||||
assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus());
|
||||
}
|
||||
return authz.policies().findByName("Always Grant").getId();
|
||||
}
|
||||
|
||||
private static void createResourcePermission(AuthorizationResource authz, String name,
|
||||
String resourceName, String policyId) {
|
||||
ResourcePermissionRepresentation permission = ResourcePermissionRepresentation.create()
|
||||
.name(name)
|
||||
.resources(Set.of(authz.resources().findByName(resourceName).get(0).getId()))
|
||||
.policies(Set.of(policyId))
|
||||
.build();
|
||||
try (Response response = authz.permissions().resource().create(permission)) {
|
||||
assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
private static void createScopePermission(AuthorizationResource authz, String name,
|
||||
String resourceName, String scopeName, String policyName) {
|
||||
ScopePermissionRepresentation permission = new ScopePermissionRepresentation();
|
||||
permission.setName(name);
|
||||
permission.setResources(Set.of(authz.resources().findByName(resourceName).get(0).getId()));
|
||||
permission.setScopes(Set.of(authz.scopes().findByName(scopeName).getId()));
|
||||
permission.addPolicy(policyName);
|
||||
try (Response response = authz.permissions().scope().create(permission)) {
|
||||
assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestRealmConfig implements RealmConfig {
|
||||
@Override
|
||||
public RealmBuilder configure(RealmBuilder realm) {
|
||||
return realm.realmRoles("admin")
|
||||
.users(
|
||||
UserBuilder.create("admin-user")
|
||||
.name("Admin", "User")
|
||||
.email("admin@localhost")
|
||||
.password("password")
|
||||
.realmRoles("admin"),
|
||||
|
||||
UserBuilder.create("regular-user")
|
||||
.name("Regular", "User")
|
||||
.email("regular@localhost")
|
||||
.password("password")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static class AuthzClientConfig implements ClientConfig {
|
||||
@Override
|
||||
public ClientBuilder configure(ClientBuilder client) {
|
||||
return client
|
||||
.secret("secret")
|
||||
.directAccessGrantsEnabled(true)
|
||||
.authorizationServicesEnabled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -70,6 +70,7 @@ public class AuthZenWellKnownTest {
|
|||
assertNotNull(response);
|
||||
assertEquals(expectedRealmUrl, response.policyDecisionPoint());
|
||||
assertEquals(expectedRealmUrl + "/authzen/access/v1/evaluation", response.accessEvaluationEndpoint());
|
||||
assertEquals(expectedRealmUrl + "/authzen/access/v1/evaluations", response.accessEvaluationsEndpoint());
|
||||
}
|
||||
|
||||
private String realmUrl() {
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"name": "240d0db-8ff0-41ec-98b2-34a096273b95",
|
||||
"name": "7240d0db-8ff0-41ec-98b2-34a096273b95",
|
||||
"type": "todo",
|
||||
"scopes": [
|
||||
{ "name": "can_read_todos" },
|
||||
|
|
@ -254,7 +254,7 @@
|
|||
"logic": "POSITIVE",
|
||||
"decisionStrategy": "UNANIMOUS",
|
||||
"config": {
|
||||
"resources": "[\"todo-1\",\"7240d0db-8ff0-41ec-98b2-34a096273b92\",\"7240d0db-8ff0-41ec-98b2-34a096273b91\",\"7240d0db-8ff0-41ec-98b2-34a096273b93\",\"7240d0db-8ff0-41ec-98b2-34a096273b94\",\"240d0db-8ff0-41ec-98b2-34a096273b95\"]",
|
||||
"resources": "[\"todo-1\",\"7240d0db-8ff0-41ec-98b2-34a096273b92\",\"7240d0db-8ff0-41ec-98b2-34a096273b91\",\"7240d0db-8ff0-41ec-98b2-34a096273b93\",\"7240d0db-8ff0-41ec-98b2-34a096273b94\",\"7240d0db-8ff0-41ec-98b2-34a096273b95\"]",
|
||||
"scopes": "[\"can_read_todos\"]",
|
||||
"applyPolicies": "[\"any-role-policy\"]"
|
||||
}
|
||||
|
|
@ -265,7 +265,7 @@
|
|||
"logic": "POSITIVE",
|
||||
"decisionStrategy": "UNANIMOUS",
|
||||
"config": {
|
||||
"resources": "[\"todo-1\",\"7240d0db-8ff0-41ec-98b2-34a096273b92\",\"7240d0db-8ff0-41ec-98b2-34a096273b91\",\"7240d0db-8ff0-41ec-98b2-34a096273b93\",\"7240d0db-8ff0-41ec-98b2-34a096273b94\",\"240d0db-8ff0-41ec-98b2-34a096273b95\"]",
|
||||
"resources": "[\"todo-1\",\"7240d0db-8ff0-41ec-98b2-34a096273b92\",\"7240d0db-8ff0-41ec-98b2-34a096273b91\",\"7240d0db-8ff0-41ec-98b2-34a096273b93\",\"7240d0db-8ff0-41ec-98b2-34a096273b94\",\"7240d0db-8ff0-41ec-98b2-34a096273b95\"]",
|
||||
"scopes": "[\"can_create_todo\"]",
|
||||
"applyPolicies": "[\"admin-or-editor-policy\"]"
|
||||
}
|
||||
|
|
@ -276,7 +276,7 @@
|
|||
"logic": "POSITIVE",
|
||||
"decisionStrategy": "UNANIMOUS",
|
||||
"config": {
|
||||
"resources": "[\"todo-1\",\"7240d0db-8ff0-41ec-98b2-34a096273b92\",\"7240d0db-8ff0-41ec-98b2-34a096273b91\",\"7240d0db-8ff0-41ec-98b2-34a096273b93\",\"7240d0db-8ff0-41ec-98b2-34a096273b94\",\"240d0db-8ff0-41ec-98b2-34a096273b95\"]",
|
||||
"resources": "[\"todo-1\",\"7240d0db-8ff0-41ec-98b2-34a096273b92\",\"7240d0db-8ff0-41ec-98b2-34a096273b91\",\"7240d0db-8ff0-41ec-98b2-34a096273b93\",\"7240d0db-8ff0-41ec-98b2-34a096273b94\",\"7240d0db-8ff0-41ec-98b2-34a096273b95\"]",
|
||||
"scopes": "[\"can_update_todo\"]",
|
||||
"applyPolicies": "[\"admin-or-editor-owner-policy\"]"
|
||||
}
|
||||
|
|
@ -287,7 +287,7 @@
|
|||
"logic": "POSITIVE",
|
||||
"decisionStrategy": "UNANIMOUS",
|
||||
"config": {
|
||||
"resources": "[\"todo-1\",\"7240d0db-8ff0-41ec-98b2-34a096273b92\",\"7240d0db-8ff0-41ec-98b2-34a096273b91\",\"7240d0db-8ff0-41ec-98b2-34a096273b93\",\"7240d0db-8ff0-41ec-98b2-34a096273b94\",\"240d0db-8ff0-41ec-98b2-34a096273b95\"]",
|
||||
"resources": "[\"todo-1\",\"7240d0db-8ff0-41ec-98b2-34a096273b92\",\"7240d0db-8ff0-41ec-98b2-34a096273b91\",\"7240d0db-8ff0-41ec-98b2-34a096273b93\",\"7240d0db-8ff0-41ec-98b2-34a096273b94\",\"7240d0db-8ff0-41ec-98b2-34a096273b95\"]",
|
||||
"scopes": "[\"can_delete_todo\"]",
|
||||
"applyPolicies": "[\"admin-or-editor-owner-policy\"]"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDA2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDA2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_read_user"
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDA2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDA2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_read_user"
|
||||
|
|
@ -36,7 +36,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDA2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDA2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_read_todos"
|
||||
|
|
@ -52,7 +52,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDA2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDA2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_create_todo"
|
||||
|
|
@ -68,7 +68,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDA2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDA2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_update_todo"
|
||||
|
|
@ -87,7 +87,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDA2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDA2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_update_todo"
|
||||
|
|
@ -106,7 +106,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDA2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDA2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_delete_todo"
|
||||
|
|
@ -125,7 +125,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDA2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDA2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_delete_todo"
|
||||
|
|
@ -145,7 +145,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDE2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDE2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_read_user"
|
||||
|
|
@ -161,7 +161,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDE2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDE2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_read_user"
|
||||
|
|
@ -177,7 +177,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDE2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDE2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_read_todos"
|
||||
|
|
@ -193,7 +193,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDE2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDE2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_create_todo"
|
||||
|
|
@ -209,7 +209,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDE2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDE2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_update_todo"
|
||||
|
|
@ -228,7 +228,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDE2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDE2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_update_todo"
|
||||
|
|
@ -247,7 +247,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDE2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDE2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_delete_todo"
|
||||
|
|
@ -266,7 +266,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDE2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDE2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_delete_todo"
|
||||
|
|
@ -286,7 +286,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDI2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDI2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_read_user"
|
||||
|
|
@ -302,7 +302,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDI2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDI2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_read_user"
|
||||
|
|
@ -318,7 +318,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDI2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDI2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_read_todos"
|
||||
|
|
@ -334,7 +334,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDI2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDI2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_create_todo"
|
||||
|
|
@ -350,7 +350,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDI2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDI2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_update_todo"
|
||||
|
|
@ -369,7 +369,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDI2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDI2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_update_todo"
|
||||
|
|
@ -388,7 +388,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDI2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDI2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_delete_todo"
|
||||
|
|
@ -407,7 +407,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDI2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDI2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_delete_todo"
|
||||
|
|
@ -426,7 +426,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDM2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDM2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_read_user"
|
||||
|
|
@ -442,7 +442,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDM2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDM2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_read_user"
|
||||
|
|
@ -458,7 +458,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDM2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDM2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_read_todos"
|
||||
|
|
@ -474,7 +474,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDM2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDM2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_create_todo"
|
||||
|
|
@ -490,7 +490,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDM2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDM2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_update_todo"
|
||||
|
|
@ -509,7 +509,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDM2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDM2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_update_todo"
|
||||
|
|
@ -528,7 +528,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDM2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDM2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_delete_todo"
|
||||
|
|
@ -547,7 +547,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDM2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDM2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_delete_todo"
|
||||
|
|
@ -566,7 +566,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDQ2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDQ2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_read_user"
|
||||
|
|
@ -582,7 +582,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDQ2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDQ2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_read_user"
|
||||
|
|
@ -598,7 +598,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDQ2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDQ2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_read_todos"
|
||||
|
|
@ -614,7 +614,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDQ2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDQ2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_create_todo"
|
||||
|
|
@ -630,7 +630,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDQ2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDQ2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_update_todo"
|
||||
|
|
@ -649,14 +649,14 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDQ2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDQ2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_update_todo"
|
||||
},
|
||||
"resource": {
|
||||
"type": "todo",
|
||||
"id": "240d0db-8ff0-41ec-98b2-34a096273b95",
|
||||
"id": "7240d0db-8ff0-41ec-98b2-34a096273b95",
|
||||
"properties": {
|
||||
"ownerID": "jerry@the-smiths.com"
|
||||
}
|
||||
|
|
@ -668,7 +668,7 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDQ2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDQ2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_delete_todo"
|
||||
|
|
@ -687,14 +687,14 @@
|
|||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id":"CiRmZDQ2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
"id": "CiRmZDQ2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_delete_todo"
|
||||
},
|
||||
"resource": {
|
||||
"type": "todo",
|
||||
"id": "240d0db-8ff0-41ec-98b2-34a096273b95",
|
||||
"id": "7240d0db-8ff0-41ec-98b2-34a096273b95",
|
||||
"properties": {
|
||||
"ownerID": "jerry@the-smiths.com"
|
||||
}
|
||||
|
|
@ -702,5 +702,103 @@
|
|||
},
|
||||
"expected": false
|
||||
}
|
||||
],
|
||||
"evaluations": [
|
||||
{
|
||||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id": "CiRmZDA2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_update_todo"
|
||||
},
|
||||
"evaluations": [
|
||||
{
|
||||
"resource": {
|
||||
"type": "todo",
|
||||
"id": "7240d0db-8ff0-41ec-98b2-34a096273b92",
|
||||
"properties": {
|
||||
"ownerID": "rick@the-citadel.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"resource": {
|
||||
"type": "todo",
|
||||
"id": "7240d0db-8ff0-41ec-98b2-34a096273b95",
|
||||
"properties": {
|
||||
"ownerID": "jerry@the-smiths.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"expected": [ { "decision": true }, { "decision": true } ]
|
||||
},
|
||||
{
|
||||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id": "CiRmZDE2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_update_todo"
|
||||
},
|
||||
"evaluations": [
|
||||
{
|
||||
"resource": {
|
||||
"type": "todo",
|
||||
"id": "7240d0db-8ff0-41ec-98b2-34a096273b92",
|
||||
"properties": {
|
||||
"ownerID": "rick@the-citadel.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"resource": {
|
||||
"type": "todo",
|
||||
"id": "7240d0db-8ff0-41ec-98b2-34a096273b91",
|
||||
"properties": {
|
||||
"ownerID": "morty@the-citadel.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"expected": [ { "decision": false }, { "decision": true } ]
|
||||
},
|
||||
{
|
||||
"request": {
|
||||
"subject": {
|
||||
"type": "user",
|
||||
"id": "CiRmZDQ2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs"
|
||||
},
|
||||
"action": {
|
||||
"name": "can_update_todo"
|
||||
},
|
||||
"evaluations": [
|
||||
{
|
||||
"resource": {
|
||||
"type": "todo",
|
||||
"id": "7240d0db-8ff0-41ec-98b2-34a096273b92",
|
||||
"properties": {
|
||||
"ownerID": "rick@the-citadel.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"resource": {
|
||||
"type": "todo",
|
||||
"id": "7240d0db-8ff0-41ec-98b2-34a096273b95",
|
||||
"properties": {
|
||||
"ownerID": "jerry@the-smiths.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"expected": [ { "decision": false }, { "decision": false } ]
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Reference in a new issue