Fine-grained access control using XACML in C# applications and the .Net framework

Background

When I talk to customers, I see an increase in interest in XACML for the .Net framework and the C# language. Historically, all XACML implementations have been in Java, most of them stemming from SunXACML. It is the case for instance of WSO2’s offering, SICSACML, and of course Axiomatics’ Policy Server.

Support for XACML in C#

Axiomatics and other companies have since released .Net PDPs and cover the space fairly well. In addition, it is possible to develop WS clients to SOAP-based PDPs regardless of the technology used.

A simple tutorial

The tutorial hereafter focuses on a C# example built using svcutil and the Axiomatics Policy Server’s SOAP-based PDP. With this code, it is therefore possible to invoke the Java-based SOAP-based web service from any .Net application.

Pre-requisites

Before you get started, you will need the .Net framework 3+ as well as the Axiomatics Policy Server. You can download them here:

Generating the .Net client stub

The .Net framework comes with a little utility called svcutil. This utility can generate client stubs from a WSDL such as the one that comes with the Axiomatics Policy Server. Using svcutil (or the add service reference… option in Visual Studio), let’s generate the client stub.

Add Service Reference in C# .Net - MS Visual Studio
Use the Add Service Reference to generate a C# client stub for the Axiomatics Policy Decision Point.

This will bring up the following dialog where one can type in the path to a WSDL that describes any service for which one wants to create a client stub. In our case we can browse to our PDP service. In this example, the Axiomatics PDP is running locally inside an Apache Tomcat 7 web server. It is exposed as a SOAP web service.
The Add Service Reference… will scan the WSDL and display the available operations. We can inspect the different operations and eventually click on OK (after possibly changing the namespace for the newly generated client). Clicking on OK will lead to:

  • the generation of a C# file called Reference.cs which contains the client stub and serialization code.
  • the creation of a default configuration file, App.config, which contains the configuration for the endpoint such as the URL, the type of security and binding as well the message formatting. You can find more information on these files at Microsoft.
Add a service reference and generate a client stub from a WSDL in Visual Studio 2010
Use the Add Service Reference to browse to the PDP's WSDL and add a C# client stub.

We can now start writing a few lines of code to see how the client behaves. The code svcutil automatically generates is a direct mirror of the WSDL. This means the objects we can work with are objects defined in the WSDL. Let’s get started with a simple example. Let’s first create a standard C# class as below

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace com.axiomatics.sample.pep
{
    class PepC
    {
    }
}

What we now want to do is add a main method and an instance of the service reference we just added to our project. We can also call the GetVersion() method of the PDP which returns the product version (e.g. Axiomatics Policy Server 4.0.6).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ConsoleApplication1.DelegentPDPNoSec;

namespace com.axiomatics.sample.pep
{
    class PepC
    {
        static DelegentPDPPortTypeClient client = new DelegentPDPPortTypeClient();
        static void Main(String[] args)
        {
            client = new DelegentPDPPortTypeClient();
            GetVersion gv = new GetVersion();
            client.GetVersion(gv);
        }
    }
}

Let’s now focus on the actual authorization methods. The Axiomatics Policy Server includes a XACML 3.0 PDP which expects a XACML request and sends a XACML response back. The method we need to use to send a XACML request is InstanceAccessQuery3(…). We can simply add the line client.InstanceAccessQuery3();. However this method needs a parameter, namely the SOAP-wrapped XACML request. Let’s start from the beginning then and start creating a XACML request, bundle it inside the SOAP message and pass to our WCF SOAP client stub.

A XACML request is made of any number of attributes grouped into categories, typically the subject, resource, action, and environment categories. A request is simply the XACML representation of a plain old English question e.g. can Alice view the document? In this example:

  • Alice would be a subject identifier and belong to the subject category.
  • View would be an action identifier and belong to the action category
  • Document would be a resource type and belong to the resource category

An attribute in XACML is in fact made up of a bag of values (attributes can be multi-valued). Each value has a datatype (e.g. String, integer… Usually XML datatypes) and 1 or more values. Now, since attribute values can be any content as per the XACML 3.0 schema (see below), this means that svcutil generates classes that will require XML nodes to be built to set the value of an attribute. This is not particularly friendly and we will factor that away in a later part of this tutorial.

	<xs:complexType name="AttributeValueType" mixed="true">
		<xs:complexContent mixed="true">
			<xs:extension base="xacml:ExpressionType">
				<xs:sequence>
					<xs:any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
				</xs:sequence>

				<xs:attribute name="DataType" type="xs:anyURI" use="required"/>
				<xs:anyAttribute namespace="##any" processContents="lax"/>
			</xs:extension>
		</xs:complexContent>
	</xs:complexType>

First off, let’s create the attribute that will represent the subject id Alice. We will start from creating a string that contains the value Alice. In a real scenario, this value would be read from an authentication mechanism.

            // 1. let's create a subject attribute
            String subjectId = "Alice";

We now want to store that value inside an AttributeValue. To do that we must use the objects generated automatically by svcutil.

            // 2. let's store the value inside an array of XmlNode[] preparing it for the SOAP call
            XmlDocument dom = new XmlDocument();
            XmlNode node = dom.CreateNode(XmlNodeType.Text, subjectId, null);
            XmlNode[] nodes = new XmlNode[1];
            // 3. let's store the XML any nodes inside an array of attribute values
            AttributeValueType1 aValue = new AttributeValueType1();
            aValue.Any = nodes;
            aValue.DataType = "http://www.w3.org/2001/XMLSchema#string";
            // we use an array below since attributes in XACML can be multi-valued
            AttributeValueType1[] values = new AttributeValueType1[] { aValue };

In the code above, note that we specify the data type of Alice. Most of the XACML datatypes are directly taken from XML itself. The list is defined in the XACML 3.0 specification. Also note we use an array of AttributeValueType1 since there can be several attribute values per attribute in XACML (the multi-valued nature of XACML attributes).
Let’s now wrap the values inside a XACML attribute (see the specification for details):

            // 4. create the containing attribute
            AttributeType1 attrContent = new AttributeType1();
            attrContent.Issuer = "some issuer"; // usually leave that blank
            attrContent.AttributeId = "subject-id";
            attrContent.AttributeValue = values;
            attrContent.IncludeInResult = true;
            AttributeType1[] attrContents = new AttributeType1[] { attrContent };

In the above code, note the use of IncludeInResult. This relates to the ability of a PDP to return the attributes initially sent in the request along with the decision. This is useful when using the Multiple Decision Profile. Also note that attributes can have an issuer. This is used in the matching process of the evaluation of a XACML request against a XACML policy.
Now that we have attributes and their values, in this case subject-id and Alice, we can group them (it) inside a given category. This is what the code hereafter does.

            // 5. create the category containing the attr and return
            AttributesType subjectAttr = new AttributesType();
            subjectAttr.Category = "urn:oasis:names:tc:xacml:1.0:subject-category:access-subject";
            subjectAttr.Attribute = attrContents;
            // repeat the code above for any attribute e.g. actionAttr, resourceAttr...

Category identifiers are defined in the XACML 3.0 specification(section 10.2.6). In this case we use the subject category identifier, urn:oasis:names:tc:xacml:1.0:subject-category:access-subject.
We can now group together our different categories into an array of categories as below. We then build a XACML request from which we derive a SOAP request (merely the wrapping of the XACML request inside a SOAP message).

            // 6. bundle the attributes inside the set of attributes for the XACML request
            AttributesType[] attrs = new AttributesType[] { subjectAttr, actionAttr, resourceAttr };
            // 7. build the XACML request
            RequestType1 xacmlRequest = new RequestType1();
            xacmlRequest.Attributes = attrs;
            InstanceAccessQuery3 soapRequest = new InstanceAccessQuery3();
            soapRequest.InstanceId = "my-pdp-instance";
            soapRequest.Request = xacmlRequest;        

The InstanceId attribute in the code above refers to which PDP instance the request should be sent to. The Axiomatics Policy Server PDP has a multi-tenancy model and the PDP instance id helps identify to which particular instance the XACML request should go.

Hands on the plumbing & Handling the response

In the following code, we simply pass the XACML request to the client stub method to invoke the PDP service. Notice how the answer received can contain multiple results, each of which boasts a single decision. This, again, is due to the Multiple Decision Profile which supports sending and receiving multiple XACML requests in a single network exchange (e.g. SOAP request/response).

            // 8. send the XACML request and print the response
            InstanceAccessQuery3Response response = client.InstanceAccessQuery3(soapRequest);
            foreach(ResultType1 result in response.Response){
                Console.WriteLine(result.Decision);
            }

Refactoring the generated code

The code to create a single XACML attribute is fairly lengthy and repetitive due to the automatic code generation done with svcutil. To get around that, let’s create a small utility method which creates the right type of attribute object from the simple building blocks of an attribute: its id, value, type, and issuer.

        public static AttributesType createAttribute(String id, Uri category, String value, Uri datatype, String issuer)
        {
            // 1. create the value
            AttributeValueType1 aValue = new AttributeValueType1();
            aValue.Any = createXMLBlob(value);
            aValue.DataType = datatype.ToString();
            AttributeValueType1[] values = new AttributeValueType1[]{aValue};
            // 2. create the containing attribute
            AttributeType1 attrContent = new AttributeType1();
            attrContent.Issuer = issuer;
            attrContent.AttributeId = id;
            attrContent.AttributeValue = values;
            attrContent.IncludeInResult = true;
            AttributeType1[] attrContents = new AttributeType1[] { attrContent };
            // 3. create the category containing the attr and return
            AttributesType attr = new AttributesType();
            attr.Category = category.ToString();
            attr.Attribute = attrContents;
            return attr;
        }

.
With this small utility in hand, we can rewrite our example in a more efficient way. Below, I have included an entire simple SamplePEP class which you can use with the Axiomatics PDP.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using com.axiomatics.xacml;
using System.Xml;
using ConsoleApplication1.DelegentPDPNoSec;


namespace com.axiomatics.sample.pep
{
    class SamplePEP
    {
        static DelegentPDPPortTypeClient client = new DelegentPDPPortTypeClient();
        private InstanceAccessQuery3 soapRequest;
        private RequestType1 xacmlRequest;
        static void Main2(string[] args)
        {
            
            SamplePEP pep = new SamplePEP();
            pep.callPDP();
            
        }

        public static XmlNode[] createXMLBlob(String content)
        {
            XmlDocument dom = new XmlDocument();
            XmlNode node = dom.CreateNode(XmlNodeType.Text, content, null);
            XmlNode[] nodes = new XmlNode[1];
            return nodes;
        }

        public static AttributesType createAttribute(String id, Uri category, String value, Uri datatype, String issuer)
        {
            // 1. create the value
            AttributeValueType1 aValue = new AttributeValueType1();
            aValue.Any = createXMLBlob(value);
            aValue.DataType = datatype.ToString();
            AttributeValueType1[] values = new AttributeValueType1[]{aValue};
            // 2. create the containing attribute
            AttributeType1 attrContent = new AttributeType1();
            attrContent.Issuer = issuer;
            attrContent.AttributeId = id;
            attrContent.AttributeValue = values;
            attrContent.IncludeInResult = true;
            AttributeType1[] attrContents = new AttributeType1[] { attrContent };
            // 3. create the category containing the attr and return
            AttributesType attr = new AttributesType();
            attr.Category = category.ToString();
            attr.Attribute = attrContents;
            return attr;
        }

        public void callPDP()
        {
            // 1. collect attributes
            Console.WriteLine("// 1. create attributes");
            AttributesType subjectAttr = createAttribute("subject-id", Constants.SUBJECT_CAT, "Alice", new Uri("http://www.w3.org/2001/XMLSchema#string"), "");
            AttributesType actionAttr = createAttribute("action-id", Constants.ACTION_CAT, "view", new Uri("http://www.w3.org/2001/XMLSchema#string"), "");
            AttributesType resourceAttr = createAttribute("resource-id", Constants.RESOURCE_CAT, "Document", new Uri("http://www.w3.org/2001/XMLSchema#string"), "");
            AttributesType[] attrs = new AttributesType[] { subjectAttr, actionAttr, resourceAttr };
            // 2. create XACML request
            Console.WriteLine("// 2. create XACML request");
            xacmlRequest = new RequestType1();
            xacmlRequest.Attributes = attrs;
            // 3. create wrapping SOAP request
            Console.WriteLine("// 3. create wrapping SOAP request");
            soapRequest = new InstanceAccessQuery3();
            soapRequest.InstanceId = "my-pdp-instance";
            soapRequest.Request = xacmlRequest;

            try
            {
                InstanceAccessQuery3Response resp = client.InstanceAccessQuery3(soapRequest);
                ResultType1[] results = resp.Response;
                foreach (ResultType1 res in results)
                {
                    DecisionType1 decision = res.Decision;
                    Console.WriteLine(decision.ToString());
                }
            }
            catch (Exception err)
            {
                Console.WriteLine(err.Message);
            }
        }
    }
}

Enjoy!