Commit 3019c9fe authored by cdanger's avatar cdanger

- Added new CXF authorization interceptor unit test description

- updated changelog
parent 6c5c4100
...@@ -2,6 +2,24 @@ ...@@ -2,6 +2,24 @@
All notable changes to this project are documented in this file following the [Keep a CHANGELOG](http://keepachangelog.com) conventions. All notable changes to this project are documented in this file following the [Keep a CHANGELOG](http://keepachangelog.com) conventions.
## Unreleased
### Changed
- Changed parent version: 4.1.1 -> 5.0.0
- Changed dependency versions:
- AuthzForce Core PDP API: 8.2.0 -> 9.0.0
- SLF4J: 1.7.6 -> 1.7.22
- Spring: 4.3.5 -> 4.3.6
- Guava: 20.0 -> 21.0
- Renamed `PDPImpl` class to `BasePdpEngine` implements new `PDPEngine` API
### Removed
- Removed/Merged `PdpConfigurationParser` class into new `BasePdpEngine` class (replacing `PDPImpl`)
### Added
- Unit test of CXF authorization interceptor (web service PEP) using AuthForce PDP engine, based on
@coheiga's [XACML 3.0 Authorization Interceptor test](https://github.com/coheigea/testcases/blob/master/apache/cxf/cxf-sts-xacml/src/test/java/org/apache/coheigea/cxf/sts/xacml/authorization/xacml3/XACML3AuthorizationTest.java)
## 6.1.0 ## 6.1.0
### Changed ### Changed
- Parent project version: 4.0.0 -> 4.1.1 => Changed dependency versions: - Parent project version: 4.0.0 -> 4.1.1 => Changed dependency versions:
......
...@@ -75,6 +75,11 @@ Our PDP implementation uses SLF4J for logging so you can use any SLF4J implement ...@@ -75,6 +75,11 @@ Our PDP implementation uses SLF4J for logging so you can use any SLF4J implement
If you are using **Java 8**, make sure the following JVM argument is set before execution: If you are using **Java 8**, make sure the following JVM argument is set before execution:
`-Djavax.xml.accessExternalSchema=http` `-Djavax.xml.accessExternalSchema=http`
## Example of usage and code with a web service authorization module
For an example of using an AuthzForce PDP engine in a real-life use case, please refer to the JUnit test class [LocalPdpAuthorizationTest](src/test/java/org/ow2/authzforce/core/pdp/impl/test/cxf/LocalPdpAuthorizationTest.java) and the Apache CXF authorization interceptor [LocalPdpBasedAuthzInterceptor](src/test/java/org/ow2/authzforce/core/pdp/impl/test/cxf/LocalPdpBasedAuthzInterceptor.java). The test class runs a test similar to @coheiga's [XACML 3.0 Authorization Interceptor test](https://github.com/coheigea/testcases/blob/master/apache/cxf/cxf-sts-xacml/src/test/java/org/apache/coheigea/cxf/sts/xacml/authorization/xacml3/XACML3AuthorizationTest.java) but using AuthzForce as PDP engine instead of OpenAZ. In this test, a web service client requests a Apache-CXF-based web service with a SAML token as credentials (previously issued by a Security Token Service upon successful client authentication) that contains the user ID and roles. Each request is intercepted on the web service side by a [CXF interceptor](src/test/java/org/ow2/authzforce/core/pdp/impl/test/cxf/LocalPdpBasedAuthzInterceptor.java) that plays the role of PEP (Policy Enforcement Point in XACML jargon), i.e. it extracts the various authorization attributes (user ID and roles, web service name, operation...) and requests a decision from a local PDP with these attributes, then enforces the PDP's decision, i.e. forwards the request to the web service implementation if the decision is Permit, else rejects it.
For more information, see the Javadoc of [LocalPdpAuthorizationTest](src/test/java/org/ow2/authzforce/core/pdp/impl/test/cxf/LocalPdpAuthorizationTest.java).
## Support ## Support
If you are experiencing any issue with this project, please report it on the [OW2 Issue Tracker](https://jira.ow2.org/browse/AUTHZFORCE/). If you are experiencing any issue with this project, please report it on the [OW2 Issue Tracker](https://jira.ow2.org/browse/AUTHZFORCE/).
......
...@@ -18,18 +18,20 @@ ...@@ -18,18 +18,20 @@
*/ */
package org.ow2.authzforce.core.pdp.impl.test.cxf; package org.ow2.authzforce.core.pdp.impl.test.cxf;
import static org.ow2.authzforce.core.pdp.api.value.StandardDatatypes.ANYURI_FACTORY;
import static org.ow2.authzforce.core.pdp.api.value.StandardDatatypes.STRING_FACTORY;
import static org.ow2.authzforce.xacml.identifiers.XACMLAttributeCategory.XACML_1_0_ACCESS_SUBJECT;
import static org.ow2.authzforce.xacml.identifiers.XACMLAttributeCategory.XACML_3_0_ACTION;
import static org.ow2.authzforce.xacml.identifiers.XACMLAttributeCategory.XACML_3_0_RESOURCE;
import java.security.Principal; import java.security.Principal;
import java.util.ArrayList; import java.util.Collections;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.namespace.QName; import javax.xml.namespace.QName;
import oasis.names.tc.xacml._3_0.core.schema.wd_17.DecisionType; import oasis.names.tc.xacml._3_0.core.schema.wd_17.DecisionType;
import org.apache.cxf.common.logging.LogUtils;
import org.apache.cxf.interceptor.Fault; import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.interceptor.security.AccessDeniedException; import org.apache.cxf.interceptor.security.AccessDeniedException;
import org.apache.cxf.message.Message; import org.apache.cxf.message.Message;
...@@ -41,18 +43,18 @@ import org.apache.cxf.security.LoginSecurityContext; ...@@ -41,18 +43,18 @@ import org.apache.cxf.security.LoginSecurityContext;
import org.apache.cxf.security.SecurityContext; import org.apache.cxf.security.SecurityContext;
import org.apache.wss4j.common.ext.WSSecurityException; import org.apache.wss4j.common.ext.WSSecurityException;
import org.ow2.authzforce.core.pdp.api.AttributeGUID; import org.ow2.authzforce.core.pdp.api.AttributeGUID;
import org.ow2.authzforce.core.pdp.api.HashCollections;
import org.ow2.authzforce.core.pdp.api.PdpDecisionRequest; import org.ow2.authzforce.core.pdp.api.PdpDecisionRequest;
import org.ow2.authzforce.core.pdp.api.PdpDecisionRequestBuilder; import org.ow2.authzforce.core.pdp.api.PdpDecisionRequestBuilder;
import org.ow2.authzforce.core.pdp.api.PdpDecisionResult; import org.ow2.authzforce.core.pdp.api.PdpDecisionResult;
import org.ow2.authzforce.core.pdp.api.value.AnyURIValue; import org.ow2.authzforce.core.pdp.api.value.AnyURIValue;
import org.ow2.authzforce.core.pdp.api.value.Bag; import org.ow2.authzforce.core.pdp.api.value.Bag;
import org.ow2.authzforce.core.pdp.api.value.Bags; import org.ow2.authzforce.core.pdp.api.value.Bags;
import org.ow2.authzforce.core.pdp.api.value.StandardDatatypes;
import org.ow2.authzforce.core.pdp.api.value.StringValue; import org.ow2.authzforce.core.pdp.api.value.StringValue;
import org.ow2.authzforce.core.pdp.impl.BasePdpEngine; import org.ow2.authzforce.core.pdp.impl.BasePdpEngine;
import org.ow2.authzforce.core.pdp.impl.ImmutablePdpDecisionRequest; import org.ow2.authzforce.core.pdp.impl.ImmutablePdpDecisionRequest;
import org.ow2.authzforce.xacml.identifiers.XACMLAttributeCategory;
import org.ow2.authzforce.xacml.identifiers.XACMLAttributeId; import org.ow2.authzforce.xacml.identifiers.XACMLAttributeId;
import org.slf4j.LoggerFactory;
/** /**
* This class represents a so-called XACML PEP that, for every CXF service request, creates an XACML 3.0 authorization decision Request to a PDP using AuthzForce's native API, given a Principal, list * This class represents a so-called XACML PEP that, for every CXF service request, creates an XACML 3.0 authorization decision Request to a PDP using AuthzForce's native API, given a Principal, list
...@@ -70,7 +72,7 @@ import org.ow2.authzforce.xacml.identifiers.XACMLAttributeId; ...@@ -70,7 +72,7 @@ import org.ow2.authzforce.xacml.identifiers.XACMLAttributeId;
public class LocalPdpBasedAuthzInterceptor extends AbstractPhaseInterceptor<Message> public class LocalPdpBasedAuthzInterceptor extends AbstractPhaseInterceptor<Message>
{ {
private static final Logger LOG = LogUtils.getL7dLogger(LocalPdpBasedAuthzInterceptor.class); private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(LocalPdpBasedAuthzInterceptor.class);
private static final String defaultSOAPAction = "execute"; private static final String defaultSOAPAction = "execute";
...@@ -92,16 +94,19 @@ public class LocalPdpBasedAuthzInterceptor extends AbstractPhaseInterceptor<Mess ...@@ -92,16 +94,19 @@ public class LocalPdpBasedAuthzInterceptor extends AbstractPhaseInterceptor<Mess
public void handleMessage(final Message message) throws Fault public void handleMessage(final Message message) throws Fault
{ {
final SecurityContext sc = message.get(SecurityContext.class); final SecurityContext sc = message.get(SecurityContext.class);
if (sc instanceof LoginSecurityContext) if (sc instanceof LoginSecurityContext)
{ {
final Principal principal = sc.getUserPrincipal(); final Principal principal = sc.getUserPrincipal();
final LoginSecurityContext loginSecurityContext = (LoginSecurityContext) sc; final LoginSecurityContext loginSecurityContext = (LoginSecurityContext) sc;
final Set<Principal> principalRoles = loginSecurityContext.getUserRoles(); final Set<Principal> principalRoles = loginSecurityContext.getUserRoles();
final List<String> roles = new ArrayList<>(); final Set<String> roles;
if (principalRoles != null) if (principalRoles == null)
{ {
roles = Collections.emptySet();
}
else
{
roles = HashCollections.newUpdatableSet(principalRoles.size());
for (final Principal p : principalRoles) for (final Principal p : principalRoles)
{ {
if (p != principal) if (p != principal)
...@@ -120,26 +125,22 @@ public class LocalPdpBasedAuthzInterceptor extends AbstractPhaseInterceptor<Mess ...@@ -120,26 +125,22 @@ public class LocalPdpBasedAuthzInterceptor extends AbstractPhaseInterceptor<Mess
} }
catch (final Exception e) catch (final Exception e)
{ {
LOG.log(Level.FINE, "Unauthorized: " + e.getMessage(), e); LOGGER.debug("Unauthorized", e);
throw new AccessDeniedException("Unauthorized"); throw new AccessDeniedException("Unauthorized");
} }
} }
else else
{ {
LOG.log(Level.FINE, "The SecurityContext was not an instance of LoginSecurityContext. No authorization is possible as a result"); LOGGER.debug("The SecurityContext was not an instance of LoginSecurityContext. No authorization is possible as a result");
} }
throw new AccessDeniedException("Unauthorized"); throw new AccessDeniedException("Unauthorized");
} }
protected boolean authorize(final Principal principal, final List<String> roles, final Message message) throws Exception protected boolean authorize(final Principal principal, final Set<String> roles, final Message message) throws Exception
{ {
final ImmutablePdpDecisionRequest request = createRequest(principal, roles, message); final ImmutablePdpDecisionRequest request = createRequest(principal, roles, message);
// final String jsonRequest = JSONRequest.toString(request); LOGGER.debug("XACML Request: {}", request);
if (LOG.isLoggable(Level.FINE))
{
LOG.fine("XACML Request: " + request);
}
// Evaluate the request // Evaluate the request
final PdpDecisionResult result = pdp.evaluate(request); final PdpDecisionResult result = pdp.evaluate(request);
...@@ -150,15 +151,16 @@ public class LocalPdpBasedAuthzInterceptor extends AbstractPhaseInterceptor<Mess ...@@ -150,15 +151,16 @@ public class LocalPdpBasedAuthzInterceptor extends AbstractPhaseInterceptor<Mess
} }
// Handle any Obligations returned by the PDP // Handle any Obligations returned by the PDP
handleObligations(request, principal, message, result); handleObligationsOrAdvice(request, principal, message, result);
LOG.fine("XACML authorization result: " + result); LOGGER.debug("XACML authorization result: {}", result);
final DecisionType decision = result.getDecision(); return result.getDecision() == DecisionType.PERMIT;
return decision == DecisionType.PERMIT;
} }
private ImmutablePdpDecisionRequest createRequest(final Principal principal, final List<String> roles, final Message message) throws WSSecurityException private ImmutablePdpDecisionRequest createRequest(final Principal principal, final Set<String> roles, final Message message) throws WSSecurityException
{ {
assert roles != null;
final CXFMessageParser messageParser = new CXFMessageParser(message); final CXFMessageParser messageParser = new CXFMessageParser(message);
final String issuer = messageParser.getIssuer(); final String issuer = messageParser.getIssuer();
...@@ -168,20 +170,17 @@ public class LocalPdpBasedAuthzInterceptor extends AbstractPhaseInterceptor<Mess ...@@ -168,20 +170,17 @@ public class LocalPdpBasedAuthzInterceptor extends AbstractPhaseInterceptor<Mess
final PdpDecisionRequestBuilder<ImmutablePdpDecisionRequest> requestBuilder = pdp.newRequestBuilder(3, 7); final PdpDecisionRequestBuilder<ImmutablePdpDecisionRequest> requestBuilder = pdp.newRequestBuilder(3, 7);
// Subject ID // Subject ID
final AttributeGUID subjectIdAttributeId = new AttributeGUID(XACMLAttributeCategory.XACML_1_0_ACCESS_SUBJECT.value(), issuer, XACMLAttributeId.XACML_1_0_SUBJECT_ID.value()); final AttributeGUID subjectIdAttributeId = new AttributeGUID(XACML_1_0_ACCESS_SUBJECT.value(), issuer, XACMLAttributeId.XACML_1_0_SUBJECT_ID.value());
final Bag<?> subjectIdAttributeValues = Bags.singleton(StandardDatatypes.STRING_FACTORY.getDatatype(), new StringValue(principal.getName())); final Bag<?> subjectIdAttributeValues = Bags.singleton(STRING_FACTORY.getDatatype(), new StringValue(principal.getName()));
requestBuilder.putNamedAttributeIfAbsent(subjectIdAttributeId, subjectIdAttributeValues); requestBuilder.putNamedAttributeIfAbsent(subjectIdAttributeId, subjectIdAttributeValues);
// Subject role(s) // Subject role(s)
if (roles != null) final AttributeGUID subjectRoleAttributeId = new AttributeGUID(XACML_1_0_ACCESS_SUBJECT.value(), issuer, XACMLAttributeId.XACML_2_0_SUBJECT_ROLE.value());
{ requestBuilder.putNamedAttributeIfAbsent(subjectRoleAttributeId, stringsToAnyURIBag(roles));
final AttributeGUID subjectRoleAttributeId = new AttributeGUID(XACMLAttributeCategory.XACML_1_0_ACCESS_SUBJECT.value(), issuer, XACMLAttributeId.XACML_2_0_SUBJECT_ROLE.value());
requestBuilder.putNamedAttributeIfAbsent(subjectRoleAttributeId, createSubjectRoleAttributeValues(roles));
}
// Resource ID // Resource ID
final AttributeGUID resourceIdAttributeId = new AttributeGUID(XACMLAttributeCategory.XACML_3_0_RESOURCE.value(), null, XACMLAttributeId.XACML_1_0_RESOURCE_ID.value()); final AttributeGUID resourceIdAttributeId = new AttributeGUID(XACML_3_0_RESOURCE.value(), null, XACMLAttributeId.XACML_1_0_RESOURCE_ID.value());
final Bag<?> resourceIdAttributeValues = Bags.singleton(StandardDatatypes.STRING_FACTORY.getDatatype(), new StringValue(getResourceId(messageParser))); final Bag<?> resourceIdAttributeValues = Bags.singleton(STRING_FACTORY.getDatatype(), new StringValue(getResourceId(messageParser)));
requestBuilder.putNamedAttributeIfAbsent(resourceIdAttributeId, resourceIdAttributeValues); requestBuilder.putNamedAttributeIfAbsent(resourceIdAttributeId, resourceIdAttributeValues);
// Resource - WSDL-defined Service ID / Operation / Endpoint // Resource - WSDL-defined Service ID / Operation / Endpoint
...@@ -191,51 +190,50 @@ public class LocalPdpBasedAuthzInterceptor extends AbstractPhaseInterceptor<Mess ...@@ -191,51 +190,50 @@ public class LocalPdpBasedAuthzInterceptor extends AbstractPhaseInterceptor<Mess
final QName wsdlService = messageParser.getWSDLService(); final QName wsdlService = messageParser.getWSDLService();
if (wsdlService != null) if (wsdlService != null)
{ {
final AttributeGUID resourceServiceIdAttributeId = new AttributeGUID(XACMLAttributeCategory.XACML_3_0_RESOURCE.value(), null, XACMLConstants.RESOURCE_WSDL_SERVICE_ID); final AttributeGUID resourceServiceIdAttributeId = new AttributeGUID(XACML_3_0_RESOURCE.value(), null, XACMLConstants.RESOURCE_WSDL_SERVICE_ID);
final Bag<?> resourceServiceIdAttributeValues = Bags.singleton(StandardDatatypes.STRING_FACTORY.getDatatype(), new StringValue(wsdlService.toString())); final Bag<?> resourceServiceIdAttributeValues = Bags.singleton(STRING_FACTORY.getDatatype(), new StringValue(wsdlService.toString()));
requestBuilder.putNamedAttributeIfAbsent(resourceServiceIdAttributeId, resourceServiceIdAttributeValues); requestBuilder.putNamedAttributeIfAbsent(resourceServiceIdAttributeId, resourceServiceIdAttributeValues);
} }
// WSDL Operation // WSDL Operation
final QName wsdlOperation = messageParser.getWSDLOperation(); final QName wsdlOperation = messageParser.getWSDLOperation();
final AttributeGUID resourceOperationIdAttributeId = new AttributeGUID(XACMLAttributeCategory.XACML_3_0_RESOURCE.value(), null, XACMLConstants.RESOURCE_WSDL_OPERATION_ID); final AttributeGUID resourceOperationIdAttributeId = new AttributeGUID(XACML_3_0_RESOURCE.value(), null, XACMLConstants.RESOURCE_WSDL_OPERATION_ID);
final Bag<?> resourceOperationIddAttributeValues = Bags.singleton(StandardDatatypes.STRING_FACTORY.getDatatype(), new StringValue(wsdlOperation.toString())); final Bag<?> resourceOperationIddAttributeValues = Bags.singleton(STRING_FACTORY.getDatatype(), new StringValue(wsdlOperation.toString()));
requestBuilder.putNamedAttributeIfAbsent(resourceOperationIdAttributeId, resourceOperationIddAttributeValues); requestBuilder.putNamedAttributeIfAbsent(resourceOperationIdAttributeId, resourceOperationIddAttributeValues);
// WSDL Endpoint // WSDL Endpoint
final String endpointURI = messageParser.getResourceURI(false); final String endpointURI = messageParser.getResourceURI(false);
final AttributeGUID resourceWSDLEndpointAttributeId = new AttributeGUID(XACMLAttributeCategory.XACML_3_0_RESOURCE.value(), null, XACMLConstants.RESOURCE_WSDL_ENDPOINT); final AttributeGUID resourceWSDLEndpointAttributeId = new AttributeGUID(XACML_3_0_RESOURCE.value(), null, XACMLConstants.RESOURCE_WSDL_ENDPOINT);
final Bag<?> resourceWSDLEndpointAttributeValues = Bags.singleton(StandardDatatypes.STRING_FACTORY.getDatatype(), new StringValue(endpointURI)); final Bag<?> resourceWSDLEndpointAttributeValues = Bags.singleton(STRING_FACTORY.getDatatype(), new StringValue(endpointURI));
requestBuilder.putNamedAttributeIfAbsent(resourceWSDLEndpointAttributeId, resourceWSDLEndpointAttributeValues); requestBuilder.putNamedAttributeIfAbsent(resourceWSDLEndpointAttributeId, resourceWSDLEndpointAttributeValues);
} }
// Action ID // Action ID
final String actionToUse = messageParser.getAction(defaultSOAPAction); final String actionToUse = messageParser.getAction(defaultSOAPAction);
final AttributeGUID actionIdAttributeId = new AttributeGUID(XACMLAttributeCategory.XACML_3_0_ACTION.value(), null, XACMLAttributeId.XACML_1_0_ACTION_ID.value()); final AttributeGUID actionIdAttributeId = new AttributeGUID(XACML_3_0_ACTION.value(), null, XACMLAttributeId.XACML_1_0_ACTION_ID.value());
final Bag<?> actionIdAttributeValues = Bags.singleton(StandardDatatypes.STRING_FACTORY.getDatatype(), new StringValue(actionToUse)); final Bag<?> actionIdAttributeValues = Bags.singleton(STRING_FACTORY.getDatatype(), new StringValue(actionToUse));
requestBuilder.putNamedAttributeIfAbsent(actionIdAttributeId, actionIdAttributeValues); requestBuilder.putNamedAttributeIfAbsent(actionIdAttributeId, actionIdAttributeValues);
// Environment - current date/time will be set by the PDP // Environment - current date/time will be set by the PDP
return requestBuilder.build(false); return requestBuilder.build(false);
} }
private static Bag<?> createSubjectRoleAttributeValues(final List<String> roles) private static Bag<?> stringsToAnyURIBag(final Set<String> strings)
{ {
assert roles != null; assert strings != null;
final List<AnyURIValue> subjectRoleAttributeValues = new ArrayList<>(roles.size()); final Set<AnyURIValue> anyURIs = HashCollections.newUpdatableSet(strings.size());
for (final String role : roles) for (final String string : strings)
{ {
subjectRoleAttributeValues.add(new AnyURIValue(role)); anyURIs.add(new AnyURIValue(string));
} }
return Bags.getInstance(StandardDatatypes.ANYURI_FACTORY.getDatatype(), subjectRoleAttributeValues); return Bags.getInstance(ANYURI_FACTORY.getDatatype(), anyURIs);
} }
private static String getResourceId(final CXFMessageParser messageParser) private static String getResourceId(final CXFMessageParser messageParser)
{ {
String resourceId = ""; final String resourceId;
if (messageParser.isSOAPService()) if (messageParser.isSOAPService())
{ {
final QName serviceName = messageParser.getWSDLService(); final QName serviceName = messageParser.getWSDLService();
...@@ -243,14 +241,14 @@ public class LocalPdpBasedAuthzInterceptor extends AbstractPhaseInterceptor<Mess ...@@ -243,14 +241,14 @@ public class LocalPdpBasedAuthzInterceptor extends AbstractPhaseInterceptor<Mess
if (serviceName != null) if (serviceName != null)
{ {
resourceId = serviceName.toString() + "#"; final String resourceIdPrefix = serviceName.toString() + "#";
if (serviceName.getNamespaceURI() != null && serviceName.getNamespaceURI().equals(operationName.getNamespaceURI())) if (serviceName.getNamespaceURI() != null && serviceName.getNamespaceURI().equals(operationName.getNamespaceURI()))
{ {
resourceId += operationName.getLocalPart(); resourceId = resourceIdPrefix + operationName.getLocalPart();
} }
else else
{ {
resourceId += operationName.toString(); resourceId = resourceIdPrefix + operationName.toString();
} }
} }
else else
...@@ -267,9 +265,9 @@ public class LocalPdpBasedAuthzInterceptor extends AbstractPhaseInterceptor<Mess ...@@ -267,9 +265,9 @@ public class LocalPdpBasedAuthzInterceptor extends AbstractPhaseInterceptor<Mess
} }
/** /**
* Handle any Obligations returned by the PDP * Handle any Obligations returned by the PDP. Does nothing by default. Override this method if you want to handle Obligations/Advice in a specific way
*/ */
protected void handleObligations(final PdpDecisionRequest request, final Principal principal, final Message message, final PdpDecisionResult result) throws Exception protected void handleObligationsOrAdvice(final PdpDecisionRequest request, final Principal principal, final Message message, final PdpDecisionResult result) throws Exception
{ {
// Do nothing by default // Do nothing by default
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment