Skip to content

Commit

Permalink
support xacml json 1.0 (#124)
Browse files Browse the repository at this point in the history
  • Loading branch information
azahnen authored Feb 7, 2023
1 parent 2b77bbf commit 7b1ab9e
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ public AuthFilter<String, User> getAuthFilter() {
if (authConfig.getExternalDynamicAuthorizationEndpoint().isPresent()) {
return new ExternalDynamicAuthFilter<>(
authConfig.getExternalDynamicAuthorizationEndpoint().get(),
authConfig.getXacmlJsonVersion(),
authConfig.getXacmlJsonMediaType(),
authConfig.getPostProcessingEndpoint().orElse(""),
httpClient,
authFilter);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/
package de.ii.xtraplatform.auth.app.external;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Joiner;
Expand All @@ -15,6 +16,7 @@
import de.ii.xtraplatform.auth.domain.ImmutableUser;
import de.ii.xtraplatform.auth.domain.User;
import de.ii.xtraplatform.auth.domain.User.PolicyDecision;
import de.ii.xtraplatform.base.domain.LogContext;
import de.ii.xtraplatform.web.domain.HttpClient;
import io.dropwizard.auth.AuthFilter;
import io.dropwizard.auth.DefaultUnauthorizedHandler;
Expand All @@ -26,6 +28,7 @@
import java.nio.charset.Charset;
import java.security.Principal;
import java.util.List;
import java.util.Map;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.PreMatching;
Expand All @@ -42,29 +45,49 @@
public class ExternalDynamicAuthFilter<P extends Principal> extends AuthFilter<String, P> {
private static final Logger LOGGER = LoggerFactory.getLogger(ExternalDynamicAuthFilter.class);

private static final MediaType XACML = new MediaType("application", "xacml+json", "utf-8");
private static final MediaType GEOJSON = new MediaType("application", "geo+json", "utf-8");
private static final MediaType XACML = new MediaType("application", "xacml+json");
private static final MediaType GEOJSON = new MediaType("application", "geo+json");
private static final ObjectMapper JSON =
new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

private final String edaUrl;
private final boolean xacmlJson10;
private final MediaType mediaType;
private final MediaType mediaTypeAccept;
private final String ppUrl;
private final HttpClient httpClient;
private final OAuthCredentialAuthFilter<P> delegate;

ExternalDynamicAuthFilter(
String edaUrl, String ppUrl, HttpClient httpClient, OAuthCredentialAuthFilter<P> delegate) {
String edaUrl,
String xacmlJsonVersion,
String xacmlJsonMediaType,
String ppUrl,
HttpClient httpClient,
OAuthCredentialAuthFilter<P> delegate) {
super();
this.realm = "ldproxy";
this.prefix = "Bearer";
this.unauthorizedHandler = new DefaultUnauthorizedHandler();

this.edaUrl = edaUrl;
this.xacmlJson10 = "1.0".equals(xacmlJsonVersion);
this.mediaType = parse(xacmlJsonMediaType);
this.mediaTypeAccept = new MediaType(mediaType.getType(), mediaType.getSubtype());
this.ppUrl = ppUrl;
this.httpClient = httpClient;
this.delegate = delegate;
}

private MediaType parse(String xacmlJsonMediaType) {
try {
return MediaType.valueOf(xacmlJsonMediaType);
} catch (Throwable e) {
LOGGER.error("Could not parse xacmlJsonMediaType: {}", xacmlJsonMediaType);
return XACML.withCharset("utf-8");
}
}

// TODO
static List<String> METHODS = ImmutableList.of("GET", "POST", "PUT", "DELETE");

Expand Down Expand Up @@ -134,7 +157,9 @@ private void postProcess(ContainerRequestContext requestContext, byte[] body) {
if (requestContext.getMethod().equals("POST") || requestContext.getMethod().equals("PUT")) {
try {

InputStream processedBody = httpClient.postAsInputStream(ppUrl, body, GEOJSON);
InputStream processedBody =
httpClient.postAsInputStream(
ppUrl, body, GEOJSON.withCharset("utf-8"), Map.of("Accept", GEOJSON.toString()));

putEntityBody(requestContext, processedBody);

Expand All @@ -150,32 +175,48 @@ private PolicyDecision askPDP(String user, String method, String path, byte[] bo
LOGGER.debug("EDA {} {} {} {}", user, method, path, new String(body, Charset.forName("utf-8")));

try {

XacmlRequest xacmlRequest1 = new XacmlRequest(user, method, path, body);
byte[] xacmlRequest = JSON.writeValueAsBytes(xacmlRequest1);

LOGGER.debug(
"XACML {}", JSON.writerWithDefaultPrettyPrinter().writeValueAsString(xacmlRequest1));
byte[] xacmlRequest = getXacmlRequest(user, method, path, body);

InputStream response =
httpClient.postAsInputStream(edaUrl, xacmlRequest, MediaType.APPLICATION_JSON_TYPE);

XacmlResponse xacmlResponse = JSON.readValue(response, XacmlResponse.class);
httpClient.postAsInputStream(
edaUrl, xacmlRequest, mediaType, Map.of("Accept", mediaTypeAccept.toString()));

LOGGER.debug(
"XACML R {}", JSON.writerWithDefaultPrettyPrinter().writeValueAsString(xacmlResponse));
XacmlResponse xacmlResponse = getXacmlResponse(response);

return xacmlResponse.isAllowed()
? PolicyDecision.PERMIT
: xacmlResponse.isNotApplicable() ? PolicyDecision.NOT_APPLICABLE : PolicyDecision.DENY;

} catch (Throwable e) {
// ignore
LogContext.errorAsDebug(LOGGER, e, "Error requesting a policy decision");
}

return PolicyDecision.DENY;
}

private byte[] getXacmlRequest(String user, String method, String path, byte[] body)
throws JsonProcessingException {
Object xacmlRequest =
xacmlJson10
? new XacmlRequest10(user, method, path, body)
: new XacmlRequest(user, method, path, body);

LOGGER.debug(
"XACML {}", JSON.writerWithDefaultPrettyPrinter().writeValueAsString(xacmlRequest));

return JSON.writeValueAsBytes(xacmlRequest);
}

private XacmlResponse getXacmlResponse(InputStream response) throws IOException {
XacmlResponse xacmlResponse = JSON.readValue(response, XacmlResponse.class);

LOGGER.debug(
"XACML R {}", JSON.writerWithDefaultPrettyPrinter().writeValueAsString(xacmlResponse));

return xacmlResponse;
}

private byte[] getEntityBody(ContainerRequestContext requestContext) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
InputStream in = requestContext.getEntityStream();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright 2018-2020 interactive instruments GmbH
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package de.ii.xtraplatform.auth.app.external;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

/**
* @author zahnen
*/
public class XacmlRequest10 {
public final Map<String, Object> Request;

public XacmlRequest10(String user, String method, String path, byte[] body) {
List<Attribute> subject =
ImmutableList.of(new Attribute("urn:oasis:names:tc:xacml:1.0:subject:subject-id", user));
List<Attribute> resource =
ImmutableList.of(new Attribute("urn:oasis:names:tc:xacml:1.0:resource:resource-id", path));
ImmutableList.Builder<Attribute> action =
ImmutableList.<Attribute>builder()
.add(new Attribute("urn:oasis:names:tc:xacml:1.0:action:action-id", method));
if (method.equals("POST") || method.equals("PUT")) {
action.add(new Attribute("payload", new String(body, StandardCharsets.UTF_8)));
}

Request =
ImmutableMap.of(
"Category",
ImmutableList.of(
ImmutableMap.of(
"CategoryId",
"urn:oasis:names:tc:xacml:1.0:subject-category:access-subject",
"Attribute",
subject),
ImmutableMap.of(
"CategoryId",
"urn:oasis:names:tc:xacml:3.0:attribute-category:resource",
"Attribute",
resource),
ImmutableMap.of(
"CategoryId",
"urn:oasis:names:tc:xacml:3.0:attribute-category:action",
"Attribute",
action.build())));
}

static class Attribute {
public final String AttributeId;
public final String Value;
public final String DataType;

Attribute(String attributeId, String value) {
AttributeId = attributeId;
Value = value;
DataType = "http://www.w3.org/2001/XMLSchema#string";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,14 @@ default String getUserScopesKey() {
Optional<String> getExternalDynamicAuthorizationEndpoint();

Optional<String> getPostProcessingEndpoint();

@Value.Default
default String getXacmlJsonVersion() {
return "1.1";
}

@Value.Default
default String getXacmlJsonMediaType() {
return "application/xacml+json;charset=UTF-8";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,10 @@ public InputStream getAsInputStream(String url, Map<String, String> headers) {
}

@Override
public InputStream postAsInputStream(String url, byte[] body, MediaType mediaType) {
public InputStream postAsInputStream(
String url, byte[] body, MediaType mediaType, Map<String, String> headers) {
HttpPost httpPost = new HttpPost(url);
headers.forEach(httpPost::addHeader);
httpPost.setEntity(new ByteArrayEntity(body, ContentType.parse(mediaType.toString())));

if (Objects.nonNull(httpPost.getURI().getUserInfo())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ public interface HttpClient {

InputStream getAsInputStream(String url, Map<String, String> headers);

InputStream postAsInputStream(String url, byte[] body, MediaType mediaType);
InputStream postAsInputStream(
String url, byte[] body, MediaType mediaType, Map<String, String> headers);
}

0 comments on commit 7b1ab9e

Please sign in to comment.