A Brief Introduction to XACML
Top Level Constructs: Policy and PolicySet
Targets and Rules
Attributes, Attribute Values, and Functions
Requests and Responses
Putting it Together: An Example
A Note About Time Ranges
Using The XACML Implementation
Overview Of The APIs
Building a Basic PDP
Building a Basic PEP
Creating and Encoding Policies
Validating Policies and Requests
Supporting AttributeSelectors
Getting and Building the Implementation
Testing the Implementation
Extending The XACML Implementation
Working With The Factories
Adding New Attribute Types
Adding New Functions
Adding New Combining Algorithms
Adding New Finder Modules
In a nutshell, XACML is a general-purpose access control policy language. This means that it provides a syntax (defined in XML) for managing authorization decisions. Sun's XACML Implementation is a set of JavaTM classes that understand the XACML language, as well as the rules about how to process requests and how to manage attributes and other related data. With a relatively small amount of code, you can write applications that use XACML to manage their own policy or that hook into existing infrastructure components like LDAP or SAML. This implementation requires a 1.4.0 or later JDK to build or run.
The typical setup is that someone wants to take some action on a resource. They will make a request to whatever actually protects that resource (like a filesystem or a web server), which is called a Policy Enforcement Point (PEP). The PEP will form a request based on the requester's attributes, the resource in question, the action, and other information pertaining to the request. The PEP will then send this request to a Policy Decision Point (PDP), which will look at the request, find some policy that applies to the request, and come up with an answer about whether access should be granted. That answer is returned to the PEP, which can then allow or deny access to the requester. Note that the PEP and PDP might both be contained within a single application, or might be distributed across several servers. In addition to providing request/response and policy languages, XACML also provides the other pieces of this relationship, namely finding a policy that applies to a given request and evaluating the request against that policy to come up with a yes or no answer.
There are many existing proprietary and application-specific languages for doing this kind of thing but XACML has several points in its favor:
Because a Policy or PolicySet may contain multiple policies or Rules, each of which may evaluate to different access control decisions, XACML needs some way of reconciling the decisions each makes. This is done through a collection of Combining Algorithms. Each algorithm represents a different way of combining multiple decisions into a single decision. There are Policy Combining Algorithms (used by PolicySet) and Rule Combining Algorithms (used by Policy). An example of these is the Deny Overrides Algorithm, which says that no matter what, if any evaluation returns Deny, or no evaluation permits, then the final result is also Deny. These Combining Algorithms are used to build up increasingly complex policies, and they are what allow XACML policies to be distributed and decentralized. While there are several standard algorithms, you can build your own to suit your needs.
Once a Policy is found, and verified as applicable to a request, its Rules are evaluated. A policy can have any number of Rules which contain the core logic of an XACML policy. The heart of most Rules is a Condition, which is a boolean function. If the Condition evaluates to true, then the Rule's Effect (Permit or Deny) is returned. If the Condition evaluates to false, the the Condition doesn't apply (NotApplicable). Evaluation of a Condition can also result in an error (Indeterminate). Conditions can be quite complex, built from an arbitrary nesting of functions and attributes branching from the top-level boolean function.
A Policy resolves attribute values from a request or from some other source through two mechanisms: the AttributeDesignator and the AttributeSelector. An AttributeDesignator lets the policy specify an attribute with a given name and type, and optionally an issuer as well. The PDP looks for that value in the request, and failing that, can look in any other location (like a an LDAP service). There are four kinds of designators, one for each of the types of attributes in a request: Subject, Resource, Action, and Environment. Subject attributes can be broken into different categories to support the notion of multiple subjects making a request (eg, the user, the user's workstation, the user's network, etc.), so SubjectAttributeDesignators can also specify a category to look in. AttributeSelectors allow a policy to look for attribute values through an XPath query. A data type and an XPath expression are provided, and these can be used to resolve some set of values either in the request document or elsewhere (just as Designators do).
Both the AttributeDesignator and the AttributeSelector can return multiple values (since there might be multiple matches in a request or elsewhere), so XACML provides a special attribute type called a Bag. Bags are unordered collections that allow duplicates, and are always what designators and selectors return, even if only one value was matched. In the case that no matches were made, an empty bag is returned, although a designator or selector may set a flag that causes an error instead in this case. Bags are never encoded in XML or included in a policy or Request/Response.
Once some Bag of attribute values is retrieved, the values need to be compared to the expected values to make access decisions. This is done though a powerful system of functions. Functions can work on any combination of attribute values, and can return any kind of attribute value supported in the system. Functions can also be nested, so you can have functions that operate on the output of other functions, and this hierarchy can be arbitrarily complex. Custom functions can be written to provide an ever richer language for expressing access conditions.
One thing to note when building these hierarchies of functions is that most of the standard functions are defined to work on specific types (like strings or integers) while designators and selectors always return Bags of values. To handle this mis-match, XACML defines a collection of standard functions of the form [type]-one-and-only, which accept a bag of values of the specified type and return the single value if there is exactly one item in the bag, or an error if there are zero or multiple values in the bag. These are among the most common functions used in a Condition. Functions of the form [type]-one-and-only functions are not needed in Targets, however, since the PDP automatically applies the matching function to each element of a bag.
A Response consists of one or more Results, each of which represents the result of an evaluation. Multiple Results can only be caused by evaluation of a hierarchical resource (explained later), so typically there will only be one Result in a Response. Each Result contains a Decision (Permit, Deny, NotApplicable, or Indeterminate), some Status information (for instance, why evaluation failed), and optionally one or more Obligations (things that the PEP is obligated to do before granting or denying access). The Request and the Response provide a standard format for interacting with a PDP.
The scenario shows a user trying to access a web page. The request includes the Subject's identity as well as a group they belong to, the resource being accessed, and the action being taken. The policy applies to anyone taking any action on the resource, and contains a rule that applies to a specific action. The Condition in the Rule requires certain group membership to access the resource.
The Request:
<Request> <Subject> <Attribute AttributeId="urn:oasis:names:tc:xacml:1.0:subject:subject-id" DataType="urn:oasis:names:tc:xacml:1.0:data-type:rfc822Name"> <AttributeValue>seth@users.example.com</AttributeValue> </Attribute> <Attribute AttributeId="group" DataType="http://www.w3.org/2001/XMLSchema#string" Issuer="admin@users.example.com"> <AttributeValue>developers</AttributeValue> </Attribute> </Subject> <Resource> <Attribute AttributeId="urn:oasis:names:tc:xacml:1.0:resource:resource-id" DataType="http://www.w3.org/2001/XMLSchema#anyURI"> <AttributeValue>http://server.example.com/code/docs/developer-guide.html</AttributeValue> </Attribute> </Resource> <Action> <Attribute AttributeId="urn:oasis:names:tc:xacml:1.0:action:action-id" DataType="http://www.w3.org/2001/XMLSchema#string"> <AttributeValue>read</AttributeValue> </Attribute> </Action> </Request>The Policy:
<Policy PolicyId="ExamplePolicy" RuleCombiningAlgId="urn:oasis:names:tc:xacml:1.0:rule-combining-algorithm:permit-overrides"> <Target> <Subjects> <AnySubject/> </Subjects> <Resources> <Resource> <ResourceMatch MatchId="urn:oasis:names:tc:xacml:1.0:function:anyURI-equal"> <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#anyURI">http://server.example.com/code/docs/developer-guide.html</AttributeValue> <ResourceAttributeDesignator DataType="http://www.w3.org/2001/XMLSchema#anyURI" AttributeId="urn:oasis:names:tc:xacml:1.0:resource:resource-id"/> </ResourceMatch> </Resource> </Resources> <Actions> <AnyAction/> </Actions> </Target> <Rule RuleId="ReadRule" Effect="Permit"> <Target> <Subjects> <AnySubject/> </Subjects> <Resources> <AnyResource/> </Resources> <Actions> <Action> <ActionMatch MatchId="urn:oasis:names:tc:xacml:1.0:function:string-equal"> <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">read</AttributeValue> <ActionAttributeDesignator DataType="http://www.w3.org/2001/XMLSchema#string" AttributeId="urn:oasis:names:tc:xacml:1.0:action:action-id"/> </ActionMatch> </Action> </Actions> </Target> <Condition FunctionId="urn:oasis:names:tc:xacml:1.0:function:string-equal"> <Apply FunctionId="urn:oasis:names:tc:xacml:1.0:function:string-one-and-only"> <SubjectAttributeDesignator DataType="http://www.w3.org/2001/XMLSchema#string" AttributeId="group"/> </Apply> <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">developers</AttributeValue> </Condition> </Rule> </Policy>The Response:
<Response> <Result> <Decision>Permit</Decision> <Status> <StatusCode Value="urn:oasis:names:tc:xacml:1.0:status:ok"/> </Status> </Result> </Response>
Because supporting time ranges was an original goal in XACML, a quick solution has been proposed: a time-in-range function. This new function takes three time values (the moment, the lower-bound on the range, and the upper-bound on the range), and returns true if the moment falls between the two bounds. The semantics are such that the range can describe any period of time less than 24 hours in duration, and it handles time zones and normalization correctly. This problem was not identified in time for inclusion in the 1.1 specification, however, so it won't be included until the 2.0 specification (though it has been proposed as an errata against 1.1, so it should be standardized sooner than XACML 2.0).
As a temporary measure, an implementation of this new function has been implemented, but not included in the core packages. Instead, it is included in the samples package. When this function is standardized, it will be moved into the core code. Because this is not a standard function yet, it is also not in the standard XACML namespace for functions. It is in a temporary namespace which also will be changed when the function is standardized. The bottom line is that if you need to express time ranges in your policies then you should use this new time-in-range function, even keeping in mind that it's not standard functionality yet, since it gives the proper behavior in all time zones whereas the mechanisms in the current specification do not.
As a general rule of thumb, you should take care with using the time comparison functions. All time AttributeValues that don't include a time-zone will be shifted to GMT. That may cause you to cross the midnight boundary and therefore fail the comparison test. Note that the included module implementation for supplying the current-time always includes the local time-zone, which makes things a little easier.
com.sun.xacml
is the core package. It contains the
logic for target matching, rule evaluation, policy and policy set
handling, and other related features. It also contains the PDP
class, which is the entry point for most code.com.sun.xacml.attr
is the package that supports all
the standard XACML attribute data types, as well as designators,
selectors, and the factories used to create new attribute
values. Standard interfaces and abstract classes are provided to
define new attributes types.com.sun.xacml.combine
contains all of the standard
combining algorithms as well as the factory for accessing those
algs. Standard interfaces are provided for creating new combining
algorithms.com.sun.xacml.cond
provides all of the condition
and function logic, supporting all the standard functions (except
the XPath functions which, for reasons explained in the
SelectorModule section, are not included). Standard interfaces and
classes are provided for defining new functions.com.sun.xacml.ctx
represents all of the types
defined in the context schema, ie. the request and response
formats. All of these classes are both encodable and parsable,
making it easy to build a PEP based on these classes.com.sun.xacml.finder
provides support for
retrieving things that the PDP needs. This is used for things like
finding policies, retrieving attributes not provided in the request,
resolving resource identifiers, or generating real-time values. A set
of standard classes lets the application writer use different kinds of
finder modules to suit their tasks.com.sun.xacml.finder.impl
has a few simple finder
modules that provide some basic functionality. Any of these can be
pulled out and replaced with better or more fully featured
implementations, but they provide enough functionality to get your
PDP working without having to write any custom finder code.com.sun.xacml.attr.proxy
and
com.sun.xacml.cond.cluster
which are really just
convenience classes used by the configuration code. In practice
these are rarely used by programmers.
Note that the core classes all do some logging using the standard
java.util.logging
interfaces. All classes that log
messages register themselves under the name of their package and
class, so (for example) if you want to see messages from from only the
core classes, you can look for messages registered in the
com.sun.xacml
namespace. Logging is done using the
standard logging levels, and is categorized as follows:
In essence, a PDP is defined by the finder modules it contains. These modules are used to access policies, retrieve attribute values, and resolve resource hierarchies. A PDP may have any number of modules available to it, and this will often dictate exactly how the PDP functions. The rest of this section uses a few common modules that will get you started. In practice, you will probably need to write at least a few custom modules for your system based on factors like how you manage your policies, where attribute values are available, etc. For more on the finder module system, and how to use it to extend a PDP, see the final section of this guide.
To create and configure a PDP programatically, first you need
to instantiate a set of finder modules for that PDP to use. Use of
finder modules is optional, but because they provide access to policies
(among other things), you always want to include at least one. A
sample module (FilePolicyModule
) is provided to access
policies as files, but you can write more complex modules as needed
(explained later). Note that this module is provided as a sample and
as a quick way to get started. You should not use it any real system,
and you should not count on it always being available in future
releases. It is a convenient starting point, however, and you can add
as many files to the provided module as you like.
FilePolicyModule policyModule = new FilePolicyModule(); policyModule.addPolicy("/path/to/policy.xml");Note that this module will reject any policies that it can recognize as invalid. If schema validation is being used it will reject all invalid policies; otherwise it will catch some syntax errors, all cases where invalid function combinations are being used, and any data type mis-matches. Any policies that are recognized as invalid will not be available to the PDP.
A finder module you should add is the
CurrentEnvModule
, since it provides values for the
current time, date, and dateTime. The XACML specification requires
that this information always be available regardless of whether or not
it's provided in the request. Of course, you can always use your own
module to provide this information, but this functionality must always
be available. This module will optionally ensure that current
date/time values remain constant during a single evaluation (this is
the default behavior).
CurrentEnvModule envModule = new CurrentEnvModule();Note that a third module is provided in the
com.sun.xacml.finder.impl
package. It is the
SelectorModule
, and is provided for supporting
AttributeSelectors (though only minimally). This is described in
greater detail in the next section.
The two modules created above are provided to the PDP through finders:
PolicyFinder policyFinder = new PolicyFinder(); Set policyModules = new HashSet(); policyModules.add(policyModule); policyFinder.setModules(policyModules); AttributeFinder attrFinder = new AttributeFinder(); List attrModules = new ArrayList(); attrModules.add(envModule); attrFinder.setModules(attrModules);With these finders defined, we can create a new
PDP
and
configure it with the modules using the PDPConfig
class.
Note that in this case we're not providing a
ResourceFinder
, so there will be no support for
hierarchical resources (explained later), but this will be the common
behavior for most applications.
PDP pdp = new PDP(new PDPConfig(attrFinder, policyFinder, null));That's it. Now you can start processing requests, and as long as you provided the right policy/policies to the
FilePolicyModule
,
the PDP
will do the rest. The PDP can be queried through
a RequstCtx
(which represents an XACML Request), or by
using an EvaluationCtx
. The latter is what all the
PDP-internal classes use, and effectively is a more efficient
representation of both the Request and the evaluation context (ie, the
Conext Handler described in the XACML specification). There is a
default implementation, but you are free to write your own
EvaluationCtx
, if you like, and query the PDP with that.
The samples package contains SimplePDP
, a ready to run PDP
example application, that shows these interfaces in action.
This distribution does not provide any kind of support for sending requests and responses over a network (e.g., to support an online PDP service), but it should be relatively clear how to do this. First, use the above code (or the method described in the run-time configuration guide) to build a PDP. Next, setup some kind of interface like a socket, for accepting Requests and returning Responses (note you could also invent your own wire format if you like, but it's probably easier to work with the standard XACML formats). Now, write a PEP (see below) that generates the Requests, and parses back in the Responses. Finally, plug that PEP code into your applications, and talk to an online PDP. This can be extended further by actually sending the data in some enveloping technology like SAML. There has been some experimental work done on this, and the SAML 2.0 release is planning to include a standard format for exactly this kind of interaction. For more information, send mail to the project team.
Most of the code that you need for building a PEP is in the com.sun.xacml.ctx package (which represents the context schema). To start out, create a RequestCtx instance, which represents an XACML Request (note that the the samples package contains a full example of how to use these APIs). The Request constructor takes four inputs, which represent the four categories of Attributes in a Request (Subject, Resource, Action, Environment).
RequestCtx request = new RequestCtx(subjects, resourceAttrs, actionAttrs, environmentAttrs);You now have a valid XACML Request that you can pass to your PDP. You can pass that
RequestCtx
instance directly to a
PDP
like the one created in the previous section
ResponseCtx response = pdp.evaluate(request);or you can encode your Request and pass it to something else for evaluation
OutputStream ouput; // output to whatever uses the Request request.encode(output);Note the return type from the call to
evaluate
. At some
point the Request gets evaluated by a PDP, and this results in a
ResponseCtx
, which represents an XACML Response. A
Response is a collection of Results, though in practice you'll usually
only have one Result. A Result contains the Decision, a status code,
and optionally a message or Obligations.
The SampleRequestBuilder
class in the samples package has
a complete example of how to use these APIs, and the
SimplePDP
class shows a Request being evaluated. Between
these examples and the JavaDocs, it should be pretty easy to build a
simple PEP.
getInstance
methods using the DOM node that defines the corresponding type. This
lets a program build whole policies or just parts of policies as
needed. Creating a Policy or PolicySet is somewhat more complex than
creating a Request, so rather than walk through the example code here,
a complete example is provided as the SamplePolicyBuilder
class in the samples package. Given that, there are a few useful
features to note.
One useful set of features are the encode
methods, which
can be found on any class that represents XML-encodable data. Like
Requests and Responses, you can use the encode
methods to
encode a Policy or PolicySet to its XML form. This is useful if you're
writing policy authoring tools, code that generates policy in real
time, or any number of related systems.
Another useful feature set is the accessor methods on all policy-related classes. This lets you parse a policy and then inspect it from within your code. This kind of functionality is especially useful for debuggers, visualizers, and other systems that require knowledge of how policies are structured (note that you may never need to use these interfaces if you're only loading policies into a PDP and then evaluating Requests). These accessors are also useful if you write code that does dynamic policy generation based on existing policies. For instance, if you want to write code that creates a PolicySet that should have the same Target as some other Policy you've already loaded, that's easy using the existing APIs. This example is actually quite common when you have PolicyFinderModules (explained in more detail later) that need to combine policies under a single PolicySet in real-time.
One final thing to note is the PolicyTreeElement
interface. This is a general interface that lets you work with the
various elements of a policy tree (Policy, PolicySet, PolicyReference,
and Rule). It makes it easy to traverse the tree, doing applicability
checking and evaluation. Programs that want to build policy sub-sets,
test them, and then continue building the policies will find this
interface particularly helpful.
FilePolicyModule
finder module
validate by default. This is intentional, since documents may
be coming from a source that always generates valid documents (as in
the above explanation of policy generation), or from a source that
has already validated the documents. While some basic errors will be
caught by the parsing routines even without schema checking, strict
checking is not done (in the interest of processing time, allowing
applications to build the XML in pieces, and for the reasons listed
previously). So, if you are accepting unchecked requests or policies
in your PDP, you should enable schema validation.
The first step in doing this is to make sure you have an XML parser
package that implements the standard jaxax.xml.parsers
interfaces, and that it supports validation. A popular example of this
is the Xerces package from Apache. Once this is in your classpath, the
standard way to set it as the XML parser to use is through the
javax.xml.parsers.DocumentBuilderFactory
property. In the
case of Xerces, you would say:
javax.xml.parsers.DocumentBuilderFactory=org.apache.xerces.jaxp.DocumentBuilderFactoryImplNow that this is in place, the only thing left to do is tell the PDP to use these validating features. The easiest way to do this is by using the
com.sun.xacml.PolicySchema
(if you're using the
sample FilePolicyModule
module) and
com.sun.xacml.ContextSchema
properties to set the schema
file locations for the policy schema and the context schema
respectively. If you want just context or just policy validation, then
only set that one property.
Putting all of that together, your Java invocation might look like:
java -Djavax.xml.parsers.DocumentBuilderFactory=org.apache.xerces.jaxp.DocumentBuilderFactoryImpl -Dcom.sun.xacml.PolicySchema=cs-xacml-schema-policy-01.xsd -Dcom.sun.xacml.ContextSchema=cs-xacml-schema-context-01.xsd ...Now if any of the policy or request/response documents parsed are invalid, an error will be reported, and the document will be rejected. This does make parsing more expensive, but it also makes the PDP safer, and guarantees that you're working with valid inputs.
SelectorModule
is provided for supporting
the optional AttributeSelector type. Because AttributeSelectors are
an optional part of the XACML language, you do not need to include
this module to build a conformant PDP. If you do want to support
selectors, however, you have two choices: include this module, or
write your own.
The SelectorModule
uses Xalan from Apache for XPath
support, so if you're going to use this module, you'll have to
download the Xalan package or use a JDK that supports these routines.
Specifically, the module uses the
org.apache.xpath.XPathAPI
class. At present, there is no
standard Java API for XPath queries, but when one becomes available,
this module will be changed to use that interface.
A note about the provided SelectorModule
: as the JavaDoc
says, this class is provided as a separate module rather than core
functionality (like the AttributeDesignator
) expressly so
that it's easy to pull out of a PDP. Because it's optional
functionality, and because XPath queries are expensive operations, a
PDP may want to exclude this functionality. However, this module is
not particularly efficient, and it is tied to a particular XPath
library, so for these reasons also you may want to exclude this
module. The module will work perfectly well for simple cases, but for
a more robust PDP you should consider writing your own module to
support AttributeSelectors.
One final issue to consider is namespace handling. The
SelectorModule
tries to use the namespace definitions
from the root of the policy document that contains the selector
invoking the module. If namespaces are used in the XPath expression,
then they will be mapped from the policy and used in the query. If no
namespaces are used in the Request, then there should be no namespace
mappings in the XPath expression. Beyond that, the implementation
doesn't try to do anything fancier (like add default namespaces if
none are used, or walk the tree looking for namespace mappings in
nodes other than the root). In practice you shouldn't need this kind
of functionality, but if you find that you do, then you should
consider implementing your own module to support AttributeSelectors.
The distribution packages also contain built jar files, but to keep up
with the latest changes you may want to build the code yourself.
Building the code included in the releases and building the code from
the CVS tree works the same way, and is fairly simple. The build
process uses Ant, a cross-platform
build system. The default target builds the code into a
build
directory. There is also a doc
target to build the JavaDocs, and a jar
target to build a
jar file of the code. Note that the code is built with debugging on,
which is what most people who are building the code themselves
want.
Note that there are currently no regression tests, input/output tests, or other formal tests available as part of this project. Mostly this is because no one has had the time to build these into the testing framework. If you're interested in helping to remedy this situation, please let us know!
Because the 1.0/1.1 factory system supported only a single factory instance for each kind of factory, there was no way to support different factories for different uses, and modifying a factory in one section of code affected all other code. In the 1.2 release this has improved, since you can now work with multiple factory instances. You stil define a single, global default factory that is used by the core code, however, so only half the problem is solved. The reason for this is that it will require changes to many interfaces to support the "right" functionality. Since the 2.0 release will require exactly these kinds of changes (because it will be supporting multiple versions of the XACML schema), the interfaces will be changed then to pair identifiable factories with PDPs. This will give the system full safety with the factories. Until then, care should still be taken when modifying the default factories.
A related problem in the 1.0 and 1.1 releases was, because there
was only one instance of each kind of factory, you could modify the
standard functionality and create non-standard factories. This has
been fixed in the 1.2 release. Specifically, standard functionality is
now supported by a specific instance of each kind of factory (for
instance, the com.sun.xacml.attr.StandardAttributeFactory
)
and these standard factories are immutable. You cannot add to or
remove from these factories (doing so will throw an exception). This
ensures that you can ask for a factory supporting only standard
functionality, and that it won't at some future time also support
custom functionality.
Note that the system starts with standard factories as the default. This means that you cannot simply ask for the default factory and then add to it. If you want the standard functionality, but want to augment it with some new datatype, function, or algorithm, then you should first create a new factory instance supporting the same standard functionality. This is easy to do. For datatypes
StandardAttributeFactory standardFactory = StandardAttributeFactory.getFactory(); final BaseAttributeFactory newFactory = new BaseAttributeFactory(standardFactory.getStandardDatatypes()); ... // modify the contents of the new factory ... AttributeFactory.setDefaultFactory(new AttributeFactoryProxy() { public AttributeFactory getFactory() { return newFactory; } });and combining algorthms
StandardCombiningAlgFactory standardFactory = StandardCombiningAlgFactory.getFactory(); final BaseCombiningAlgFactory newFactory = new BaseCombiningAlgFactory(standardFactory.getStandardAlgorithms()); ... // modify the contents of the new factory ... CombiningAlgFactory.setDefaultFactory(new CombiningAlgFactoryProxy() { public CombiningAlgFactory getFactory() { return newFactory; } });it looks pretty much the same. Functions are handled differently, since they're more complex, so a convenience method is provided to help you out
FunctionFactoryProxy proxy = StandardFunctionFactory.getNewFactoryProxy(); ... // modify the contents of the new factory through the proxy ... FunctionFactory.setDefaultFactory(proxy);In the following sections, the assumption is that the factories being modified have been setup in this manner.
With the addition of a generic interface for creating new factory
implementations, new base implementations were added (along the lines
of the FunctionBase
class). These are basically
convenience classes that implement the basic functionality that most
people need for these factories. If you are implementing a new
factory, consider using these base implementations as a starting
point.
One final note about the factory interfaces: in the interest of
keeping the interfaces as stable as possible from past releases to
this release, all of the original static interfaces on
AttributeFactory
, FunctionFactory
, and
CombiningAlgFactory
have been left intact and
functional. They will still operate on the default factories. Many of
these methods, however, have been deprecated, and they will be removed
with the 2.0 release. They are also much slower in their new form. It
is strongly recommended, therefore, that if you are currently using
any of these interfaces you switch to using the new methods. Mostly,
this involes asking for a factory instance (see the examples in
the following three sections) and operating on it, rather than simply
using the static intarfaces on the base factory classes. For more
details, look at the javadocs for information about what has been
deprecated and what methods are now recommended.
All attribute types are represented as subclasses of the abstract
AttributeValue
. To create a new type, therefore, you
start by creating a new class that extends
AttributeValue
, you provide the type's identifier, and
then implement the encode method as well as equals and hashCode (from
Object
). The other methods all have default behavior that
will be correct for most types. Here is an example of an attribute
that represents a simple string value:
public class NewAttribute extends AttributeValue { public static final String TYPE = "newType"; private String value; public NewAttribute(String value) throws URISyntaxException { super(new URI(TYPE)); this.value = value; } public String encode() { return value; } public boolean equals(Object o) { return value.equals(o); } public int hashCode() { return value.hashCode(); } }Now that there is a class that supports the new type, it needs to be added to the
AttributeFactory
using an
AttributeProxy
. The proxy is called each time a new value
is created. Typically you just use an anonymous class to support the
new type, and then have that class call constructors or methods to get
new instances of the new type:
AttributeFactory factory = AttributeFactory.getInstance(); factory.addDatatype("newType", new AttributeProxy() { public AttributeValue getInstance(Node root) throws Exception { // this method is not implemented in the previous example, // but would simply pull the data out of the node, validate // the data, and then create a new NewAttribute return NewAttribute.getInstance(root); } public AttributeValue getInstance(String value) throws Exception { return new NewAttribute(value); } });Of course, if you wanted, you could make a more complex proxy, but this is really all that a proxy needs to do. At this point, if the
AttributeFactory
is asked to create a value of type
"newType", it will call one of the two proxy methods (based on how the
factory was invoked).
All functions implement the Function
interface, though a
FunctionBase
helper class, which implements some common
functionality, is provided if you don't need to do anything too
complex in your new function. The JavaDocs in Function
spell out what is expected of any new function. Basically, a function
can be evaluated against a set of inputs, can be asked if a set of
inputs is acceptable, and provides information about its return
type and identity.
The FunctionBase
helper class may be useful if you're
writing a function that has predefined input and output types and/or
if it doesn't need to do any special checking when validating
parameters (see the JavaDocs for more specifics). The
FunctionBase
implements most of the required methods,
leaving a custom function needing to implement only the evaluation
method. For instance, suppose you want to write a new function that
takes a boolean and a string, and compares them to see if text of the
string is equal to the value of the boolean. Using the
FunctionBase
helper class, this is a pretty easy task.
public class BoolTextCompare extends FunctionBase { // the name of the function, which will be used publicly public static final String NAME = "bool-text-compare"; // the parameter types, in order, and whether or not they're bags private static final String params [] = { BooleanAttribute.identifier, StringAttribute.identifier }; private static final boolean bagParams [] = { false, false }; public BoolTextCompare() { // use the constructor that handles mixed argument types super(NAME, 0, params, bagParams, BooleanAttribute.identifier, false); } public EvaluationResult evaluate(List inputs, EvaluationCtx context) { // Evaluate the arguments using the helper method...this will // catch any errors, and return values that can be compared AttributeValue [] argValues = new AttributeValue[inputs.size()]; EvaluationResult result = evalArgs(inputs, context, argValues); if (result != null) return result; // cast the resolved values into specific types BooleanAttribute bool = (BooleanAttribute)(argValues[0]); StringAttribute str = (StringAttribute)(argValues[1]); boolean evalResult; // now compare the values if (bool.getValue()) { // see if the string is "true" evalResult = str.getValue().equals("true"); } else { // see if the string is "false" evalResult = str.getValue().equals("false"); } // boolean returns are common, so there's a getInstance() for that return EvaluationResult.getInstance(evalResult); } }Now you just need to add this function into the factory. The
FunctionFactory
is split into three groups, providing
different categories of functions (explained in detail in the
JavaDocs). In this case, we've written a boolean function that should
be available to any part of the system, so it gets added to the
factory as a Target function (since it can be used in a Target, a
Condition, or in any general situation). Most functions are singletons
(since most don't need state so it saves time and memory), so to add
the function, you instantiate it and pass it into the factory:
FunctionFactory factory = FunctionFactory.getTargetInstance(); factory.addFunction(new BoolTextCompare());In addition to writing new functions from scratch, certain functions can be added when new attribute types are introduced. The standard Bag and Set functions, as well as the Equal function are all functions that can work on any datatype. When a new attribute type is created (as described in the previous section), it can be used with these standard functions quite easily. A name has to be chosen, and then the function is created and added to the factory. For instance, if you wanted to be able to do equality checking in a policy on the NewAttribute type created in the previous example, you would configure your factory:
FunctionFactory factory = FunctionFactory.getTargetInstance(); factory.addFunction(new EqualFunction("newAttribute-equal", NewAttribute.TYPE));Now you can use the "newAttribute-equal" function in a policy. Likewise, the
BagFunction
and
SetFunction
classes provide several methods for creating
new instances that use new attribute types. The [type]-one-and-only
function is almost always a function that you want to include for your
new type. To do this, you configure the factory the same way:
FunctionFactory factory = FunctionFactory.getGeneralInstance(); factory.addFunction( BagFunction.getOneAndOnlyInstance("newAttribute-one-and-only", NewAttribute.TYPE));Now the PDP knows about the "newAttribute-one-and-only" function which can be used as a wrapper around an AttributeDesignator to get single values of the newAttribute type. Note that the higher order bag functions will also be able to use new attribute types, but because of how they work, you don't need to explicitly install new instances for new attribute types.
One final kind of function is an abstract function. These are functions
that keep some state and therefore can't be completely constructed
until they are provided some information from a policy. An example
from the specification is the map function, which derives its return
type from its parameters. Unlike regular functions, these are not
singletons, and they are created as needed within a policy. The
FunctionBase
may be useful for abstract functions, but if
the functions don't know what types they accept or return (often the
case for these functions), they're better off being defined directly
from the Function
interface.
Like attributes, abstract functions are used by giving the
FunctionFactory
a proxy that is called each time a new
instance is needed. The proxy's getInstance
method is
passed a DOM node that contains the given function (typically an
ApplyType), and returns a Function
based on the data in
that node. Because this is a somewhat expensive operation, these
functions should be used only when a regular function cannot be
used. The only standard function provided as an abstract function is
the map function. For more on how to install and create these
functions, see the JavaDocs for FunctionFactory
.
combine
method, the two types of
algorithms are expected to do somewhat different things.
To create a new Rule Combining Algorithm you need to extend
RuleCombiningAlgorithm
and implement its one method. The
combine
method takes the context and a list of rules (in
order), and returns a single Result. All that a Rule Combining
Algorithm is expected to do with the rules is evaluate each one,
interpreting the result as appropriate. For example, you might write a
"PermitOrDeny" rule that returns Permit if any Rule returns Permit,
and otherwise always returns Deny. The code will evaluate each Rule,
looking at the result, and then decide whether or not to keep going.
public class PermitOrDenyRuleAlg extends RuleCombiningAlgorithm { public PermitOrDenyRuleAlg() throws URISyntaxException { super(new URI("rule-combining-alg:permit-or-deny")); } public Result combine(EvaluationCtx context, List rules) { Iterator it = rules.iterator(); while (it.hasNext()) { // get the next Rule, and evaluate it Rule rule = (Rule)(it.next()); Result result = rule.evaluate(context); // if it returns Permit, then the alg returns Permit if (result.getDecision() == Result.DECISION_PERMIT) return result; } // if nothing returned Permit, then the alg returns Deny return new Result(Result.DECISION_DENY); } }To create a new Policy Combining Algorithm, you need to do a little more work. Start by extending
PolicyCombiningAlgorithm
,
and again implement the single combine
method. Now, in
addition to evaluating each policy, you must first check that the
policy applies (Rules check applicability when evaluated, but policies
do not for various performance reasons). The algorithm must also
handle Obligations correctly, ensuring that the correct obligations
are returned. This issue is discussed in great detail in the XACML
specification, and to a lesser degree, in the JavaDocs. The general
rule is that you always return any Obligations that were considered
during evaluation and that have the same Effect that the combining
algorithm is returning. Taking the same "PermitOrDeny" example used
above, the Policy Combining Algorithm will look similar with the
additional steps described here.
public class PermitOrDenyPolicyAlg extends PolicyCombiningAlgorithm { public PermitOrDenyPolicyAlg() throws URISyntaxException { super(new URI("policy-combining-alg:permit-or-deny")); } public Result combine(EvaluationCtx context, List policies) { Set denyObligations = new HashSet(); Iterator it = policies.iterator(); while (it.hasNext()) { // get the next AbstractPolicy, and match it AbstractPolicy policy = (AbstractPolicy)(it.next()); MatchResult match = policy.match(context); // see if the AbstractPolicy policy applies... if (match.getResult() == MatchResult.MATCH) { // ...and if it does, evaluate it Result result = policy.evaluate(context); // if it returns Permit, then the alg returns Permit if (result.getDecision() == Result.DECISION_PERMIT) return result; // if it returns Deny, save the Obligations for later if (result.getDecision() == Result.DECISION_DENY) denyObligations.addAll(result.getObligations()); } } // if nothing matched and returned Permit, then the alg // returns Deny, including all Deny Obligations return new Result(Result.DECISION_DENY, denyObligations); } }Note that both of these examples are fairly simple, and don't look for Indeterminate returns from matching or evaluating. Often a Combining Algorithm will want to take special actions when these errors occur, either at the moment they happen, or if the algorithm makes it all the way to the end of the list of rules or policies. For instance, if an error occurred when trying to match a policy, then you can't really trust a final result of Deny, since had the matching operation succeeded, it might have a found a policy that returned Permit on evaluation, which would change the final result. These algorithms can be somewhat tricky, so before you try to write your own, you should probably look at the source code for the standard algorithms in the
combine
package for guidance.
The last step is to include the new algorithms in the
CombiningAlgFactory
:
CombiningAlgFactory factory = CombiningAlgFactory.getInstance(); factory.addAlgorithm(new PermitOrDenyRuleAlg()); factory.addAlgorithm(new PermitOrDenyPolicyAlg());Like with Functions, there is only one instance of each Combining Algorithm since they don't keep any state.
Attribute finder modules are used when an attribute value can't be found
in the request. They are called from a designator or selector, and so
there are two methods for finding values, one of which takes the fields
used in a designator and the other which accepts an XPath
expression. There are also methods used to specify which of those two
retrieval methods (or both) are supported by a given module, and what
types of attributes can be found using a designator. This information
is used by the AttributeFinder
to decide which modules to
use for any given query.
Policy finder modules are used both to find Policies for a given request and to find Policies referenced from PolicySets. Like attribute modules, there are different methods for the different types of retrieval, and there are also methods that let the module specify which kinds of retrieval are supported. There are several rules about policy retrieval, most important of which is that a policy module may never find more than one matching policy. If it does, this is an error and must be reported. Decisions like whether or not to cache policies and whether to add support for signed policies are up to the implementor.
Resource finder modules are used by a feature of XACML called hierarchical
resources. A request must name a resource, but that resource might be
specified as the root of a hierarchy of other resources, like a
filesystem or XML document. If the resource is specified as
hierarchical, the ResourceFinder
is used to get the
complete list of resource identifiers associated with the root
resource. Because of the potential for abuse of this feature, and
because the specification doesn't include any specific requirements
for what kinds of hierarchies must be supported, there are no resource
modules included in this distribution.
Resource hierarchies are, perhaps, one of the most mis-understood
features of XACML. Basically, they let you request access to more than
one resource, as long as the resources are in some simple
hierarchy. The resulting Response will contain a separate Result for
each of the resources in the hierarchy. To make this happen, specify
the root of the hierarchy in the standard resource-id attribute, and
then include the standard scope attribute with a value of either
"Children" or "Descendants" (for details, look at the javadocs for
ResourceFinder
and EvaluationCtx
). If you
supply a ResourceFinderModule
that can handle the
hierarchy, then it will be queried for all the resources under the
root resource, and each resource will result in a separate Request to
the PDP. This guide doesn't go into more detail on this topic since
few people actually need this feature, and since XACML 2.0 will
introduce a much clearer and more powerful system for handling this
kind of fucntionality.
Finally, here is a simple example of how to write a custom
AttributeFinderModule
(writing other modules looks very
similar, and the com.sun.xacml.finder.impl package provides more
examples, as does the test code, which relies on custom finders to
pass all the tests). In this example, a module is provided that can get
the current processor load (a measure of how busy the CPU is right now),
which might be useful for determining whether or not a new application
is allowed to run on this host, or even if another intensive session
(like an encrypted channel) should be opened. Note that this value
isn't being returned to the PEP, so there's not much security risk in
providing this module. It's simply providing another real-time piece
of data that policies can use to make informed decisions. The module
itself is pretty simple: it announces its capabilities, and when
called to find a value it checks first to see that it's being called
for the attribute it knows about.
public class LoadEnvModule extends AttributeFinderModule { // always return true, since this is a feature we always support public boolean isDesignatorSupported() { return true; } // return a single identifier that shows support for environment attrs public Set getSupportedDesignatorTypes() { Set set = new HashSet(); set.add(new Integer(AttributeDesignator.ENVIRONMENT_TARGET)); return set; } public EvaluationResult findAttribute(URI attributeType, URI attributeId, URI issuer, URI subjectCategory, EvaluationCtx context, int designatorType) { // make sure this is an Environment attribute if (designatorType != AttributeDesignator.ENVIRONMENT_TARGET) return new EvaluationResult(BagAttribute. createEmptyBag(attributeType)); // make sure they're asking for our identifier if (! attributeId.toString().equals("processor-load")) return new EvaluationResult(BagAttribute. createEmptyBag(attributeType)); // make sure they're asking for an integer return value if (! attributeType.toString().equals(IntegerAttribute.identifier)) return new EvaluationResult(BagAttribute. createEmptyBag(attributeType)); // now that everything checks out, get the load... long procLoad = someMethodToCalculateProcessorLoad(); // ... and return that value Set set = new HashSet(); set.add(new IntegerAttribute(procLoad)); return new EvaluationResult(new BagAttribute(attributeType, set)); } }The final step is to install this module as was explained back in the section on building a PDP. With this module in place, a policy can now include:
<EnvironmentAttributeSelector DataType="http://www.w3.org/2001/XMLSchema#integer" AttributeId="processor-load"/>
Copyright 2003-2004 Sun Microsystems, Inc. All rights reserved. Use is subject to license terms.
Sun, Sun Microsystems, the Sun Logo, and Java are trademarks or registered trademarks of Sun Microsystems, Inc. in the US and other countries.