Monday, February 22, 2010

Oracle Entitlements Server (OES) Advanced Rules Engine Like Capabilities

Over the past week, I've had a couple of customers looking to use Oracle Entitlements Server (OES) to replace existing home grown authorization solutions that make heavy use of general purpose rules engines. As part of their investigation, I've gotten a few questions which basically boil down to "How to replicate the functionality that I have today in OES?". Before I get into the specific scenarios and how they can be achieved with OES, I want to make a some-what obvious point. OES is a rules-engine but its optimized to answer the "What Roles does this user have?" and "Can this user perform this action on this resource?" very quickly and efficiently. This means that getting OES to function exactly the same way as a generic rules engine functions can be done, but it will take custom code and probably complicate the policy model. The more fidelity required with the existing model the more complex the OES deployment. At some limit, it might just make sense to have OES call the rules engine via a custom eval function. This is an extreme case, and I mention it only to illustrate the point that when purchasing a product which replaces another product or custom solution, you'll get the best ROI by embracing the new products capabilities. Its in this context that I want to explore two specific scenarios.

Named Rules


The concept here is that a policy is just a combination of named rules. For example:

Rule A: user has title="Manager"
Rule B: user has department="12345"
GRANT Role Manager_12345 if A and B

There are a couple of different approaches for how to model this type of scenario in OES.

Model Named Rules as Dynamic Attributes


In this approach, the logic of the specific rules is "hard coded" into the attribute retriever. One nice thing about the AttributeRetrieverV2 interface is that you can get access to attributes via other AttributeRetrievers. Below is psuedocode for Rule A and Rule B

package namedrules;

import com.bea.security.providers.authorization.asi.*;
import com.bea.security.providers.authorization.asi.ARME.evaluator.RequestHandle;

import com.bea.security.providers.authorization.asi.ARME.exceptions.ArmeRuntimeException;
import com.bea.security.providers.authorization.asi.ARME.exceptions.CredvarException;
import com.bea.security.providers.authorization.asi.ARME.exceptions.NotReadyException;
import com.bea.security.quark.exceptions.ParserException;

import com.wles.BadParameterException;
import com.wles.util.AttributeElement;

import java.util.List;
import java.util.Map;

import javax.security.auth.Subject;

import weblogic.security.service.ContextHandler;
import weblogic.security.spi.Resource;

public class NamedRuleAttributeRetriever implements AttributeRetrieverV2 {

public String[] getHandledAttributeNames() {
return new String [] {"RuleA","RuleB"};
}

public Object getAttributeValue(String name, RequestHandle requestHandle,
Subject subject, Map map,
Resource resource,
ContextHandler contextHandler) {

if (name.equals("RuleA")) {
try {
AttributeElement title = requestHandle.getAttribute("title",true);
List titleValues = (List)title.getValueAs(List.class);
return titleValues.contains("Manager");
} catch (Exception e) {
e.printStackTrace();
return Boolean.FALSE;
}
} else if (name.equals("RuleB")) {
try {
AttributeElement dept = requestHandle.getAttribute("department",true);
List deptValues = (List)dept.getValueAs(List.class);
return deptValues.contains("12345");
} catch (Exception e) {
e.printStackTrace();
return Boolean.FALSE;
}

}

}
}

The biggest downside to this approach is that the rules themselves are hard coded inside of the logic of the attribute retriever. If this logic changes, you need to recode the retriever and re-deploy. It would better to have something a little more generic.

Model Named Rules as Resources


The idea is that the actual logic of the rule membership is still modeled in OES policy, instead of in the code. To do this create a separate node in the same SM called "namedrules". Model the rules like this:

GRANT (//priv/RuleA, //app/policy/namedrules, //sgrp/users/allusers) if sys_defined(title) and title="manager"
GRANT (//priv/RuleB, //app/policy/namedrules, //sgrp/users/allusers) if
sys_defined(dept) and dept="12345"

Now, the trick part. How do I get these policies evaluated? You'll need to make a call back into the AuthorizationService. This is pretty powerful, yet some what risky stuff. The issue is that you don't want to wind up with circular dependencies that will cause StackOverflow. The best way to avoid this is to not have named rules referencing named rules. If you need to be more fancy, then the example below could be extended to include a ThreadLocal variable with a counter to make sure that you're not putting more than say 5 requests on the stack. Below is the basics for this type of approach.

package namedrules;

import com.bea.security.AccessResult;
import com.bea.security.AppContext;
import com.bea.security.AppContextElement;
import com.bea.security.AuthenticIdentity;
import com.bea.security.AuthenticationService;
import com.bea.security.AuthorizationService;
import com.bea.security.ParameterException;
import com.bea.security.PolicyDomain;
import com.bea.security.RuntimeAction;
import com.bea.security.RuntimeResource;
import com.bea.security.SecurityRuntime;
import com.bea.security.ServiceNotAvailableException;
import com.bea.security.ServiceType;
import com.bea.security.SimpleContextElement;
import com.bea.security.providers.authorization.asi.ARME.evaluator.RequestHandle;
import com.bea.security.providers.authorization.asi.AttributeRetrieverV2;

import java.util.Map;

import javax.security.auth.Subject;

import weblogic.security.service.ContextElement;
import weblogic.security.service.ContextHandler;
import weblogic.security.spi.Resource;

public class PolicyBasedAttributeRetriever implements AttributeRetrieverV2{

private AuthorizationService atz;
private AuthenticationService auth;
private PolicyDomain pd;

public PolicyBasedAttributeRetriever() {
super();
}

public String[] getHandledAttributeNames() {
return new String[]{};
}

public Object getAttributeValue(String ruleName, RequestHandle requestHandle,
Subject subject, Map map,
Resource resource,
final ContextHandler contextHandler) {


if (atz==null) {
try {
pd = SecurityRuntime.getInstance().getPolicyDomain(System.getProperty("wles.realm"));
this.auth = (AuthenticationService)pd.getService(ServiceType.AUTHENTICATION);
this.atz = (AuthorizationService)pd.getService(ServiceType.AUTHORIZATION);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

try {
AuthenticIdentity ai = this.auth.assertIdentity("WLS.Subject",subject);

RuntimeResource runtimeResource = new RuntimeResource("namedrules","exampleResource");
RuntimeAction runtimeAction = new RuntimeAction(ruleName, "exampleAction");

//This is just a simple wrapper around the ContextHandler
AppContext contextHandlerWrapper = new AppContext() {

public int size() {
return contextHandler.size();
}

public String[] getNames() {
return contextHandler.getNames();
}

public AppContextElement getElement(String name) {
return new SimpleContextElement(name,contextHandler.getValue(name));
}

public AppContextElement[] getElements(String[] strings) {

ContextElement [] ctxElements = contextHandler.getValues(strings);

AppContextElement [] appCtxElements = new AppContextElement[ctxElements.length];

for (int i=0; i<ctxElements.length; i++) {

appCtxElements[i] = new SimpleContextElement(ctxElements[i].getName(),ctxElements[i].getValue());

}

return appCtxElements;
}
};

AccessResult result =
this.atz.isAccessAllowed(ai,runtimeResource,runtimeAction,contextHandlerWrapper);

return Boolean.valueOf(result.isAllowed());

} catch (Exception e) {

return Boolean.FALSE;
}

}
}

A few key things in the example. You'll need to use a lazy initialization of the PolicyDomain. If you try to load it in the constructor of the AttributeRetriever, bad things happen. Also, using the built in WLS.Subject token type for the IdentityAsserter is an easy way to convert from a JAAS Subject to an AuthenticIdentity.

Rule Debug Information


Another scenario is that the caller needs to understand very detailed information about the rules evaluation. Some examples

  • Why did the user did not get access?
  • What role cause the user to get access?

OES has a number of ways of addressing these types of queries

Add report_as to your policies


The simplest thing to do is to add report_as constraint to a given policy. Note: This only works for authorization policies. An example:

DENY (//priv/read, //app/policy/someresource, //role/authors) if NOT owner = sys_user and report_as("error","You are not the owner of this document")

This works for a few policies, but may not be practical if you have a very large number of policies.

Use DebugInfo


If you are using the Java SM, you can use the DebugInfo to get very detailed information about which roles and rules applied. The instructions for using this Debug API are here. One observation that might make things easier. Through the entitlementsadministration ui you can add notes to policies. Those notes are available through the API. You can even get down to specific attributes and their values.

Custom Evaluation Function


This approach works for SMs other than the Java SM or if you want to report back additional information about policy evaluation and attributes. Take the example where a user is granted access based upon attributes.

GRANT (//priv/read, //app/policy/r1, //sgrp/users/allusers/) if assertAttribute("state","=","CA",true,"You don't live in California") AND assertAttribute("txn_amount","<",500,true,"Transaction is over 500")

The idea with this example is that I want to be able to communicate why the user is not granted access. Below is the psuedocode for the assertAttribute function.

package namedrules;

import com.bea.security.providers.authorization.asi.ARME.evaluator.RequestHandle;

import com.wles.util.AttributeElement;

import java.util.List;

public class AttributeEvalFunction {


public boolean assertAttribute (RequestHandle requestHandle, java.lang.Object[] args, javax.security.auth.Subject subject, java.util.Map roles, weblogic.security.spi.Resource resource, weblogic.security.service.ContextHandler contextHandler) {

try {
String attributeName = (String)args[0];
String operator = (String)args[1];

Boolean assertionResult = (Boolean)args[3];

AttributeElement element = requestHandle.getAttribute(attributeName,true);

boolean result = false;

if (element.getASIType().equals("string")) {

String value = (String)args[2];

if (operator.equals("=")) {
List values = (List)element.getValueAs(List.class);
result = values.contains(value);
}


}

if (assertionResult.booleanValue() == result) {
System.out.println("Assertion Passed: "+attributeName+" "+operator+" "+args[2]+" "+assertionResult.booleanValue());
return true;
} else {
//Return the message
System.out.println("Assertion Failed: "+attributeName+" "+operator+" "+args[2]+" "+assertionResult.booleanValue());
requestHandle.appendReturnData(attributeName,args[4]);
return false;
}

} catch (Exception e) {
e.printStackTrace();
return false;
}

}

}

Conclusion


This post outlines a number of ways to use the OES engine to emulate some common capabilities of generic rules engines. Its obviously some bit of work to select the right approach to fit specific requirements. Hopefully what I've show is helpful in bootstrapping some thinking in this area. I look forward to your feedback and some of your own patterns in this area.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.