Call out to a XACML Policy Decision Point (PDP) from PHP

Today, I have the pleasure to invite a fellow colleague, Patrick McDowell, to post on my blog. Today’s topic is around reaching out to other languages other than just Java and C# for XACML-based authorization. Today’s choice? PHP, naturellement as both Patrick and I are huge WordPress fans.

If you have been programming in PHP it is very likely that you have interacted with an external authentication service. For example Google and Facebook provide external authentication services that people can use to allow other providers to authenticate users for you using standard protocols such as SAML, OAuth, and OpenID.
Once a user has been authenticated, we then need to determine what that user is authorized to do inside of an application or service. This authorization logic can be externalized and defined using a standard language called XACML.
XACML stands for eXtensible Access Control Markup Language. The standard defines a declarative access control policy language implemented in XML and a processing model describing how to evaluate authorization requests according to the rules defined in policies.
Unlike the typical role-based security methodology used in many applications XACML authorization decisions are made in real time, and are made based on attributes about the user, the resource, and the context. For example a bank teller might have access to an account but should not be permitted to access his or her own account; the teller should not have access to accounts outside of his or her bank. XACML policies can be evaluated in real time to determine whether the teller should be permitted to access an account.
Additionally authorization decisions are made externally from the application, therefore security policy is not hardcoded into the application, which makes it easier for security policies to be changed. and Security administrators gain easier access to the authorization logic.
XACML requests include:

  • Subject
  • Action
  • Resource
  • Environment

And the XACML Policy Server (PDP, aka Policy Decision Point), will return:

  • Decision (Deny, Permit, Indeterminate, Not Applicable)
  • Status
  • Obligations or Advice

To keep this simple, we are simply going to ask the XACML Authorization server to confirm that:
Users with Teller Role (Subject), can View (Action), Bank Accounts (Resource)
This request in raw XACML looks like this:

<xacml-ctx:Request ReturnPolicyIdList="true" CombinedDecision="false" xmlns:xacml-ctx="urn:oasis:names:tc:xacml:3.0:core:schema:wd-17">
   <xacml-ctx:Attributes Category="urn:oasis:names:tc:xacml:1.0:subject-category:access-subject" >
      <xacml-ctx:Attribute AttributeId="http://www.axiomatics.com/acs/role" IncludeInResult="true">
         <xacml-ctx:AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">teller</xacml-ctx:AttributeValue>
      </xacml-ctx:Attribute>
   </xacml-ctx:Attributes>
   <xacml-ctx:Attributes Category="urn:oasis:names:tc:xacml:3.0:attribute-category:action" >
      <xacml-ctx:Attribute AttributeId="urn:oasis:names:tc:xacml:1.0:action:action-id" IncludeInResult="true">
         <xacml-ctx:AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">view</xacml-ctx:AttributeValue>
      </xacml-ctx:Attribute>
   </xacml-ctx:Attributes>
   <xacml-ctx:Attributes Category="urn:oasis:names:tc:xacml:3.0:attribute-category:environment" >
   </xacml-ctx:Attributes>
   <xacml-ctx:Attributes Category="urn:oasis:names:tc:xacml:3.0:attribute-category:resource" >
      <xacml-ctx:Attribute AttributeId="urn:oasis:names:tc:xacml:1.0:resource:resource-id" IncludeInResult="true">
         <xacml-ctx:AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">bank account</xacml-ctx:AttributeValue>
      </xacml-ctx:Attribute>
   </xacml-ctx:Attributes>
</xacml-ctx:Request>

The same request can be produced in PHP with SOAP like this:

$regReq = new stdClass();

$regReq->Version = "3.0";
$regReq->ID = "abcde1234";
$regReq->ReturnContext = true;
$regReq->Request = new stdClass();
$regReq->Request->ReturnPolicyIdList="true";
$regReq->Request->CombinedDecision="false";

$regReq->Request->Attributes = array();

$regReq->Request->Attributes[0]= new stdClass();
$regReq->Request->Attributes[0]->Category="urn:oasis:names:tc:xacml:1.0:subject-category:access-subject";
$regReq->Request->Attributes[0]->Attribute = new stdClass();
$regReq->Request->Attributes[0]->Attribute->AttributeId = "http://www.axiomatics.com/acs/role";
$regReq->Request->Attributes[0]->Attribute->AttributeValue = new stdClass();
$regReq->Request->Attributes[0]->Attribute->AttributeValue->DataType="http://www.w3.org/2001/XMLSchema#string";
$regReq->Request->Attributes[0]->Attribute->AttributeValue->any="teller";

$regReq->Request->Attributes[1]= new stdClass();
$regReq->Request->Attributes[1]->Category="urn:oasis:names:tc:xacml:3.0:attribute-category:action"; 
$regReq->Request->Attributes[1]->Attribute = new stdClass();
$regReq->Request->Attributes[1]->Attribute->AttributeId = "urn:oasis:names:tc:xacml:1.0:action:action-id";
$regReq->Request->Attributes[1]->Attribute->AttributeValue = new stdClass();
$regReq->Request->Attributes[1]->Attribute->AttributeValue->DataType="http://www.w3.org/2001/XMLSchema#string";
$regReq->Request->Attributes[1]->Attribute->AttributeValue->any="view";

$regReq->Request->Attributes[2]= new stdClass();
$regReq->Request->Attributes[2]->Category="urn:oasis:names:tc:xacml:3.0:attribute-category:resource"; 
$regReq->Request->Attributes[2]->Attribute = new stdClass();
$regReq->Request->Attributes[2]->Attribute->AttributeId = "urn:oasis:names:tc:xacml:1.0:resource:resource-id";
$regReq->Request->Attributes[2]->Attribute->AttributeValue = new stdClass();
$regReq->Request->Attributes[2]->Attribute->AttributeValue->DataType="http://www.w3.org/2001/XMLSchema#string";
$regReq->Request->Attributes[2]->Attribute->AttributeValue->any="bank account";
 
$regReq->Request->Attributes[3]= new stdClass();
$regReq->Request->Attributes[3]->Category="urn:oasis:names:tc:xacml:3.0:attribute-category:environment";

The following PHP code creates the WS client and sends the request to the PDP.

ini_set('soap.wsdl_cache_enabled', 0); // Turn off the cache if you like
$client = new SoapClient("http://axiomatics:8080/asm-pdp/pdp?WSDL", Array("trace" => 1));
$result = $client->AccessQuery3($regReq);

The result returns a stdClass like this:

stdClass Object ( [Response] => stdClass Object ( [Result] => stdClass Object ( [Decision] => Permit [Status] => stdClass Object ( [StatusCode] => stdClass Object ( [Value] => urn:oasis:names:tc:xacml:1.0:status:ok ) ) ) ) )

The response to this request will return an object called Result with a field labeled Decision set to Permit, as well as other information about the request such as Obligations / Advice and information about the request.
To handle the response in PHP, all you need is the following code snippet:

if ( $result->Response->Result->Decision == "Permit") { 
 echo "Permit.. Tellers can view bank accounts";
}  

The response does depend on the policy, and if the request did not find an appropriate target it is possible the response could be NotApplicable, which essentially means the Policy Decision Point (PDP), did not have enough information to make a decision.
The XACML Policy is also written in XML, and look like this in Raw XML:

<xacml3:Policy xmlns:xacml3="urn:oasis:names:tc:xacml:3.0:core:schema:wd-17" PolicyId="http://www.axiomatics.com/automatic-unique-id/5ec8c01d-456c-4800-b28b-ad3d289ecba3" Version="1.0" RuleCombiningAlgId="urn:oasis:names:tc:xacml:3.0:rule-combining-algorithm:deny-unless-permit">
<xacml3:PolicyDefaults><xacml3:XPathVersion>http://www.w3.org/TR/1999/REC-xpath-19991116</xacml3:XPathVersion></xacml3:PolicyDefaults>
  <xacml3:Target>
    <xacml3:AnyOf>
      <xacml3:AllOf>
        <xacml3:Match MatchId="urn:oasis:names:tc:xacml:1.0:function:string-equal">
          <xacml3:AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">bank account</xacml3:AttributeValue>
          <xacml3:AttributeDesignator Category="urn:oasis:names:tc:xacml:3.0:attribute-category:resource"  AttributeId="urn:oasis:names:tc:xacml:1.0:resource:resource-id" DataType="http://www.w3.org/2001/XMLSchema#string" MustBePresent="false"/>
        </xacml3:Match>
        <xacml3:Match MatchId="urn:oasis:names:tc:xacml:1.0:function:string-equal">
          <xacml3:AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">view</xacml3:AttributeValue>
          <xacml3:AttributeDesignator Category="urn:oasis:names:tc:xacml:3.0:attribute-category:action"  AttributeId="urn:oasis:names:tc:xacml:1.0:action:action-id" DataType="http://www.w3.org/2001/XMLSchema#string" MustBePresent="false"/>
        </xacml3:Match>
        <xacml3:Match MatchId="urn:oasis:names:tc:xacml:1.0:function:string-equal">
          <xacml3:AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">teller</xacml3:AttributeValue>
          <xacml3:AttributeDesignator Category="urn:oasis:names:tc:xacml:1.0:subject-category:access-subject"  AttributeId="http://www.axiomatics.com/acs/role" DataType="http://www.w3.org/2001/XMLSchema#string" MustBePresent="false"/>
        </xacml3:Match>
      </xacml3:AllOf>
    </xacml3:AnyOf>
  </xacml3:Target>
  <xacml3:Rule RuleId="51179cb3-5319-4a35-a13e-ac1db27b5d93" Effect="Permit">
    <xacml3:Description>doctor can view medicalrecord</xacml3:Description>
    <xacml3:Target/>
  </xacml3:Rule>
  <xacml3:Rule RuleId="a2af1648-41c0-4843-ba4f-c1284d367957" Effect="Deny">
    <xacml3:Target/>
  </xacml3:Rule>
</xacml3:Policy>

If you don’t like working with raw XML, Axiomatics offers an Eclipse Plug-in which uses a “Pseudocode” called ALFA (Axiomatics Language for Authorization) which makes writing these policies much simpler. It can be downloaded for free here.