Sun OpenSSO Enterprise 8.0 Developer's Guide

Sample Code for Custom Subjects, Conditions, Referrals, and Response Providers

OpenSSO Enterprise provides subject, condition, referral, and response provider interfaces that enable you to develop your own custom subjects, conditions, referrals, and response providers. The following samples illustrate how to implement these custom objects:

SampleSubject.java

Implements the Subject interface. This subject applies to all the authenticated users who have valid SSOTokens.


Example 2–2 SampleSubject.java

package com.sun.identity.samples.policy;

import java.util.*;
//import java.security.Principal;

import com.iplanet.sso.*;
import com.sun.identity.policy.*;
import com.sun.identity.policy.interfaces.Subject;

/**
 * The class <code>Subject</code> defines a collection
 * of users (or subject) to whom the specified policy is applied.
 * A complete implementation of this interface can have complex
 * boolean operations to determine if the given user identified
 * by the <code>SSOToken</code> belongs to this collection.
 * <p>
 * The interfaces are seperated into administrative
 * interfaces and evaluation interfaces. The administrative interfaces
 * will be used by GUI/CLI component to create a <code>Subject</code>
 * object and the evaluation interfaces will be used by the policy evaluator.
 * 
 * This sample inplementation defines the collection of all users who have 
 * been authenticated (a user with a valid SSOToken.).
 */
public class SampleSubject implements Subject {

    /**
     * Constructor with no parameter
     */
    public SampleSubject() {
	// do nothing
    }

    /**
     * Initialize (or configure) the <code>Subject</code>
     * object. Usually it will be initialized with the environment
     * paramaters set by the system administrator via SMS.
     * For example in a Role implementation, the configuration
     * parameters could specify the directory server name, port, etc.
     *
     * @param configParams configuration parameters as a map.
     * The values in the map is <code>java.util.Set</code>,
     * which contains one or more configuration paramaters.
     *
     * @exception PolicyException if an error occured during
     * initialization of <code>Subject</code> instance
     */

    public void initialize(Map configParams) 
        throws PolicyException {
	// do nothing
    }

    /**
     * Returns the syntax of the values the
     * <code>Subject</code> implementation can have.
     * @see com.sun.identity.policy.Syntax
     *
     * @param token the <code>SSOToken</code> that will be used
     * to determine the syntax
     *
     * @return set of of valid names for the user collection.
     *
     * @exception SSOException if SSO token is not valid
     * @exception PolicyException if unable to get the list of valid
     * names.
     *
     * @return syntax of the values for the <code>Subject</code>
     */

    public Syntax getValueSyntax(SSOToken token) {
	return (Syntax.CONSTANT);
    }


    /**
     * Returns the syntax of the values the
     * <code>Subject</code> implementation can have.
     * @see com.sun.identity.policy.Syntax
     *
     * @param token the <code>SSOToken</code> that will be used
     * to determine the syntax
     *
     * @return set of of valid names for the user collection.
     *
     * @exception SSOException if SSO token is not valid
     * @exception PolicyException if unable to get the list of valid
     * names.
     *
     * @return syntax of the values for the <code>Subject</code>
     */

    public ValidValues getValidValues(SSOToken token) {
	return (new ValidValues(ValidValues.SUCCESS, 
					Collections.EMPTY_SET));
    }


    /**
     * Returns a list of possible values for the <code>Subject
     * </code>. The implementation must use the <code>SSOToken
     * </code> <i>token</i> provided to determine the possible
     * values. For example, in a Role implementation
     * this method will return all the roles defined
     * in the organization.
     *
     * @param token the <code>SSOToken</code> that will be used
     * to determine the possible values
     *
     * @return <code>ValidValues</code> object
     *
     * @exception SSOException if SSO token is not valid
     * @exception PolicyException if unable to get the list of valid
     * names.
     */

    public ValidValues getValidValues(SSOToken token, String pattern) {
	return (new ValidValues(ValidValues.SUCCESS, 
					Collections.EMPTY_SET));
    }


    /**
     * Returns the display name for the value for the given locale.
     * For all the valid values obtained through the methods
     * <code>getValidValues</code> this method must be called
     * by GUI and CLI to get the corresponding display name.
     * The <code>locale</code> variable could be used by the
     * plugin to customize
     * the display name for the given locale.
     * The <code>locale</code> variable
     * could be <code>null</code>, in which case the plugin must
     * use the default locale (most probabily en_US).
     * This method returns only the display name and should not
     * be used for the method <code>setValues</code>.
     * Alternatively, if the plugin does not have to localize
     * the value, it can just return the <code>value</code> as is.
     *
     * @param value one of the valid value for the plugin
     * @param locale locale for which the display name must be customized
     *
     * @exception NameNotFoundException if the given <code>value</code>
     * is not one of the valid values for the plugin
     */

    public String getDisplayNameForValue(String value, Locale locale)
	throws NameNotFoundException {
        return value;
    }


    /**
     * Returns the values that was set using the
     * method <code>setValues</code>.
     *
     * @return values that have been set for the user collection
     */

    public Set getValues() {
	return (Collections.EMPTY_SET);
    }


    /**
     * Sets the names for the instance of the <code>Subject</code>
     * object. The names are obtained from the policy object,
     * usually configured when a policy is created. For example
     * in a Role implementation, this would be name of the role.
     *
     * @param names names selected for the instance of
     * the user collection object.
     *
     * @exception InvalidNameException if the given names are not valid
     */

    public void setValues(Set names) throws InvalidNameException {
    }


    /**
     * Determines if the user belongs to this instance
     * of the <code>Subject</code> object.
     * For example, a Role implemenation
     * would return <code>true</code> if the user belongs
     * the specified role; <code>false</code> otherwise.
     *
     * @param token single-sign-on token of the user
     *
     * @return <code>true</code> if the user is memeber of the
     * given subject; <code>false</code> otherwise.
     *
     * @exception SSOException if SSO token is not valid
     * @exception PolicyException if an error occured while
     * checking if the user is a member of this subject
     */

    public boolean isMember(SSOToken token) 
	throws SSOException {
	return (SSOTokenManager.getInstance().isValidToken(token));
    }

    /**
     * Indicates whether some other object is "equal to" this one.
     *
     * @param o another object that will be compared with this one
     *
     * @return <code>true</code> if eqaul; <code>false</code>
     * otherwise
     */

    public boolean equals(Object o) {
	if (o instanceof SampleSubject) {
	    return (true);
	}
	return (false);
    }


    /**
     * Creates and returns a copy of this object.
     *
     * @return a copy of this object
     */

    public Object clone() {
	return (new SampleSubject());
    }
}

SampleCondition.java

Implements the Condition interface. This condition makes the policy applicable to those users whose user name length is greater than or equal to the length specified in the condition.


Example 2–3 SampleCondition.java

package com.sun.identity.samples.policy;

import java.util.*;

import com.sun.identity.policy.interfaces.Condition;
import com.sun.identity.policy.ConditionDecision;
import com.sun.identity.policy.PolicyException;
import com.sun.identity.policy.PolicyManager;
import com.sun.identity.policy.Syntax;
import com.iplanet.sso.SSOException;
import com.iplanet.sso.SSOToken;
import com.iplanet.sso.SSOTokenManager;


/**
 * The class <code>SampleCondition</code> is a plugin 
 * implementation of <code>Condition</code> interface.
 * This condition object provides the policy framework with the 
 * condition decision based on the length of the user's name.
 */

public class SampleCondition implements Condition {

    /** Key that is used to define the minimum of the user name length  
     *  for which the policy would apply.  The value should be
     *  a Set with only one element. The element should be a 
     *  String, parsable as an integer.
     */

    public static final String USER_NAME_LENGTH = "userNameLength";

    private List propertyNames;
    private Map properties;
    private int nameLength;

    /** No argument constructor 
     */
    public SampleCondition() {
         propertyNames = new ArrayList();
         propertyNames.add(USER_NAME_LENGTH);
    }

     /**
      * Returns a set of property names for the condition.
      *
      * @return set of property names
      */

     public List getPropertyNames()
     {
         return propertyNames;
     }
 
     /**
      * Returns the syntax for a property name
      * @see com.sun.identity.policy.Syntax
      *
      * @param String property name
      *
      * @return <code>Syntax<code> for the property name
      */
     public Syntax getPropertySyntax(String property)
     {
         return (Syntax.ANY);
     }
      
     /**
      * Gets the display name for the property name.
      * The <code>locale</code> variable could be used by the
      * plugin to customize the display name for the given locale.
      * The <code>locale</code> variable could be <code>null</code>, in which 
      * case the plugin must use the default locale.
      *
      * @param String property name
      * @param Locale locale for which the property name must be customized
      * @return display name for the property name
      */
     public String getDisplayName(String property, Locale locale) 
       throws PolicyException
     {
         return property;
     }
 
     /**
      * Returns a set of valid values given the property name. This method
      * is called if the property Syntax is either the SINGLE_CHOICE or 
      * MULTIPLE_CHOICE.
      *
      * @param String property name
      * @return Set of valid values for the property.
      * @exception PolicyException if unable to get the Syntax.
      */
     public Set getValidValues(String property) throws PolicyException
     {
         return (Collections.EMPTY_SET);
     }


    /** Sets the properties of the condition.
     *  Evaluation of ConditionDecision is influenced by these properties.
     *  @param properties the properties of the condition that governs
     *         whether a policy applies. The properties should
     *         define value for the key USER_NAME_LENGTH. The value should
     *         be a Set with only one element. The element should be
     *         a String, parsable as an integer. Please note that
     *         properties is not cloned by the method.
     *
     *  @throws PolicyException if properties is null or does not contain
     *          value for the key USER_NAME_LENGTH or the value of the key is
     *          not a Set with one String element that is parsable as
     *          an integer.
     */

    public void setProperties(Map properties) throws PolicyException {
        this.properties = (Map)((HashMap) properties);
        if ( (properties == null) || ( properties.keySet() == null) ) {
            throw new PolicyException("properties can not be null or empty");
        }

        //Check if the key is valid
        Set keySet = properties.keySet();
        Iterator keys = keySet.iterator();
        String key = (String) keys.next();
        if ( !USER_NAME_LENGTH.equals(key) ) {
            throw new PolicyException(
                "property " + USER_NAME_LENGTH + " is not defined");
        }

        // check if the value is valid
        Set nameLengthSet = (Set) properties.get(USER_NAME_LENGTH);
        if (( nameLengthSet == null ) || nameLengthSet.isEmpty() 
            || ( nameLengthSet.size() > 1 )) {
            throw new PolicyException(
                "property value is not defined or invalid");
        }

        Iterator nameLengths = nameLengthSet.iterator();
        String nameLengthString = null;
        nameLengthString = (String) nameLengths.next();
        try {
            nameLength = Integer.parseInt(nameLengthString);
        } catch (Exception e) {
            throw new PolicyException("name length value is not an integer");
        }
    }


    /** Get properties of this condition.
     */
    public Map getProperties() {
        return properties;
    } 


    /**
     * Gets the decision computed by this condition object.
     *
     * @param token single sign on token of the user
     *
     * @param env request specific environment map of key/value pairs.
     *        SampleCondition doesn't use this parameter.
     *
     * @return the condition decision. The condition decision 
     *         encapsulates whether a policy applies for the request. 
     *
     * Policy framework continues evaluating a policy only if it 
     * applies to the request as indicated by the CondtionDecision. 
     * Otherwise, further evaluation of the policy is skipped. 
     *
     * @throws SSOException if the token is invalid
     */

    public ConditionDecision getConditionDecision(SSOToken token, Map env) 
            throws PolicyException, SSOException {
	boolean allowed = false;

        String userDN = token.getPrincipal().getName();
        // user DN is in the format like "uid=username,ou=people,dc=example,dc=com"
        int beginIndex = userDN.indexOf("=");
        int endIndex = userDN.indexOf(",");
        if (beginIndex >= endIndex) {
            throw (new PolicyException("invalid user DN"));
        }

        String userName = userDN.substring(beginIndex+1, endIndex);
        if (userName.length() >= nameLength) {
            allowed = true;
        }

	return new ConditionDecision(allowed);
    }


    public Object clone() {
	Object theClone = null;
	try {
	    theClone = super.clone();
	} catch (CloneNotSupportedException e) {
            throw new InternalError();
	}
	return theClone;
    }

}

SampleReferral.java

Implements the Referral interface. SampleReferral.java gets the referral policy decision from a text file SampleReferral.properties located in the /samples directory.


Example 2–4 SampleReferral.java

package com.sun.identity.samples.policy;

import java.io.*;
import java.util.*;

import com.sun.identity.policy.*;
import com.sun.identity.policy.interfaces.Referral;
import com.iplanet.sso.SSOToken;
import com.iplanet.sso.SSOException;
import com.iplanet.am.util.SystemProperties;

public class SampleReferral implements Referral {

    static final String SEPARATOR = ":";
    static String PROPERTIES = "samples/policy/SampleReferral.properties";
    static String INSTALL_DIR = SystemProperties.get("com.iplanet.am.installdir");
    static Properties properties = new Properties();
    private String _name;
    private Set _values;

    /** No argument constructor */
    public SampleReferral() {
    }

    /**Initializes the referral with a map of Configuration parameters
     * @param configurationMap a map containing configuration 
     *        information. Each key of the map is a configuration
     *        parameter. Each value of the key would be a set of values
     *        for the parameter. The map is cloned and a reference to the 
     *        clone is stored in the referral
     */
    public void initialize(Map configurationMap) {
    }

    /**Sets the name of this referral 
     * @param name name of this referral
     */
    private void setName(String name) {
        _name = name;
    }

    /**Gets the name of this referral 
     * @return the name of this referral
     */
    private String getName() {
        return _name;
    }            

    /**Sets the values of this referral.
     * @param values a set of values for this referral
     *        Each element of the set has to be a String
     * @throws InvalidNameException if any value passed in the 
     * values is invalid
     */
    public void setValues(Set values) throws InvalidNameException {
        _values = values;
    }

    /**Gets the values of this referral 
     * @return the values of this referral
     *                Each element of the set would be a String
     */
    public Set getValues() {
        return _values;
    }

    /**
     * Returns the display name for the value for the given locale.
     * For all the valid values obtained through the methods
     * <code>getValidValues</code> this method must be called
     * by GUI and CLI to get the corresponding display name.
     * The <code>locale</code> variable could be used by the
     * plugin to customize
     * the display name for the given locale.
     * The <code>locale</code> variable
     * could be <code>null</code>, in which case the plugin must
     * use the default locale (most probabily en_US).
     * This method returns only the display name and should not
     * be used for the method <code>setValues</code>.
     * Alternatively, if the plugin does not have to localize
     * the value, it can just return the <code>value</code> as is.
     *
     * @param value one of the valid value for the plugin
     * @param locale locale for which the display name must be customized
     *
     * @exception NameNotFoundException if the given <code>value</code>
     * is not one of the valid values for the plugin
     */
    public String getDisplayNameForValue(String value, Locale locale)
	throws NameNotFoundException {
	return value;
    }

    /**Gets the valid values for this referral 
     * @param token SSOToken
     * @return <code>ValidValues</code> object
     * @throws SSOException, PolicyException
     */
    public ValidValues getValidValues(SSOToken token) 
            throws SSOException, PolicyException {
        return getValidValues(token, "*");
    }

    /**Gets the valid values for this referral 
     * matching a pattern
     * @param token SSOToken
     * @param pattern a pattern to match against the value
     * @return </code>ValidValues</code> object
     * @throws SSOException, PolicyException
     */
    public ValidValues getValidValues(SSOToken token, String pattern)
            throws SSOException, PolicyException {
        Set values = new HashSet();
        values.add(PROPERTIES);
        return (new ValidValues(ValidValues.SUCCESS,
                                values));
    }

    /**Gets the syntax for the value 
     * @param token SSOToken
     * @see com.sun.identity.policy.Syntax
     */
    public Syntax getValueSyntax(SSOToken token)
            throws SSOException, PolicyException {
        return (Syntax.SINGLE_CHOICE);
    }

    /**Gets the name of the ReferralType 
     * @return name of the ReferralType representing this referral
     */
    public String getReferralTypeName() 
    {
        return "SampleReferral";
    }

    /**Gets policy results 
     * @param token SSOToken
     * @param resourceType resource type
     * @param resourceName name of the resource 
     * @param actionNames a set of action names
     * @param envParameters a map of enivronment parameters.
     *        Each key is an environment parameter name.
     *        Each value is a set of values for the parameter.
     * @return policy decision
     * @throws SSOException
         * @throws PolicyException
     */
    public PolicyDecision getPolicyDecision(SSOToken token, String resourceType, 
	    String resourceName, Set actionNames, Map envParameters) 
            throws SSOException, PolicyException {

        PolicyDecision pd = new PolicyDecision();
        Iterator elements = _values.iterator();
        if (!elements.hasNext()) {
            return pd;
        }

        String fileName = (String)elements.next(); 
        fileName = INSTALL_DIR + "/" + fileName;
        try {
            InputStream is = new FileInputStream(fileName);
            if (is == null) {
                return pd;
            }
            properties.load(is);
        } catch (Exception e) {
            return pd;
        }

        String serviceName = getProperty("servicename");
        if (!serviceName.equals(resourceType)) {
            return pd;
        }

        String resName = getProperty("resourcename");
        if (!resName.equals(resourceName)) {
            return pd;
        }
       
        List actionNameList = getPropertyValues("actionnames");
        List actionValueList = getPropertyValues("actionvalues");

        int numOfActions = actionNameList.size();
        int numOfValues = actionValueList.size();

        if ((numOfActions == 0 || (numOfValues == 0) 
                               || numOfActions != numOfValues)) {
            return pd;
        } 

        Iterator namesIter = actionNameList.iterator();
        Iterator valuesIter = actionValueList.iterator();

        for (int i = 0; i < numOfActions; i++) {
            String actionName = (String)namesIter.next();
            String actionValue = (String)valuesIter.next();
            if (actionNames.contains(actionName)) {
                Set values = new HashSet();
                values.add(actionValue);
                ActionDecision ad = new ActionDecision(
                    actionName, values, null, Long.MAX_VALUE);
                pd.addActionDecision(ad);            
            }
        }
        return pd;
    }


    private String getProperty(String key)
    {
        return properties.getProperty(key);
    }


    private List getPropertyValues(String name) {
        List values = new ArrayList();
        String value = getProperty(name);
        if ( value != null ) {
            StringTokenizer st = new StringTokenizer(value, SEPARATOR);
            while ( st.hasMoreTokens() ) {
               values.add(st.nextToken());
            }
        }
        return values;
    }

    /** Gets resource names rooted at the given resource name for the given
     *  serviceType that could be governed by this referral 
     * @param token ssoToken sso token
     * @param serviceTypeName service type name
     * @param rsourceName resource name
     * @return names of sub resources for the given resourceName.
     *         The return value also includes the resourceName.
     *
     * @throws PolicyException
     * @throws SSOException
     */
    public Set getResourceNames(SSOToken token, String serviceTypeName, 
            String resourceName) throws PolicyException, SSOException {
        return null;
    }

}

SampleResponseProvider.java

Implements the ResponseProvider interface. SampleResponseProvider.java takes as input the attribute for which values are retrieved from OpenSSO Enterprise and sent back in the Policy Decision. If the attribute does not exist in the user profile, no value is sent back in the response. SampleResponseProvider.java relies on the underlying Identity Repository service to retrieve the attribute values for the Subject(s) defined in the policy.


Example 2–5 SampleResponseProvider.java

package com.sun.identity.samples.policy;

import com.sun.identity.policy.PolicyException;
import com.sun.identity.policy.PolicyUtils;
import com.sun.identity.policy.PolicyConfig;
import com.sun.identity.policy.PolicyManager;
import com.sun.identity.policy.interfaces.ResponseProvider;
import com.sun.identity.policy.Syntax;

import com.iplanet.sso.SSOToken;
import com.iplanet.sso.SSOException;

import com.sun.identity.idm.AMIdentity;
import com.sun.identity.idm.IdUtils;
import com.sun.identity.idm.IdRepoException;

import java.util.List;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.Locale;
import java.util.Map;
import java.util.HashSet;
import java.util.HashMap;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.Collections;

/**
 * This class is an implementation of <code>ResponseProvider</code> interface. 
 * It takes as input the attribute for which values are to be fetched from
 * the access manager and sent back in the Policy Decision.
 * if the attribute does not exist in the use profile no value is sent
 * back in the response.
 * It relies on underlying Identity repository service to 
 * fetch the attribute values for the Subject(s) defined in the policy.
 * It computes a <code>Map</code> of response attributes
 * based on the <code>SSOToken</code>, resource name and  env map passed 
 * in the method call <code>getResponseDecision()</code>.
 *
 * Policy framework would make a call to the ResponseProvider in a 
 * policy only if the policy is applicable to a request as determined by 
 * <code>SSOToken</code>, resource name, <code>Subjects</code> and <code>Conditions
 * </code>.
 *
 */
public class SampleResponseProvider implements ResponseProvider {

    public static final String ATTRIBUTE_NAME = "AttributeName";

    private Map properties;
    private static List propertyNames = new ArrayList(1);

    private boolean initialized=false;
    private String orgName = null;

    static {
        propertyNames.add(ATTRIBUTE_NAME);
    }

    /**
     * No argument constructor.
     */
    public SampleResponseProvider () {

    }


    /** 
     * Initialize the SampleResponseProvider object by using the configuration
     * information passed by the Policy Framework.
     * @param configParams the configuration information
     * @exception PolicyException if an error occured during 
     * initialization of the instance
     */

    public void initialize(Map configParams) throws PolicyException {
        // get the organization name
        Set orgNameSet = (Set) configParams.get(
                                     PolicyManager.ORGANIZATION_NAME);
        if ((orgNameSet != null) && (orgNameSet.size() != 0)) {
            Iterator items = orgNameSet.iterator();
            orgName = (String) items.next();
        }
	/** 
         * Organization name is not used in this sample, but this is code
    	 * to illustrate how any other custom response provider can get data
         * out from the policy configuration service and use it in 
         * getResponseDecision() as necessary.
	 */
	initialized = true;
    }


    /**
     * Returns a list of property names for the responseprovider.
     *
     * @return <code>List</code> of property names
     */
    public List getPropertyNames()  {
         return propertyNames;
    }

    /**
     * Returns the syntax for a property name
     * @see com.sun.identity.policy.Syntax
     *
     * @param property property name
     *
     * @return <code>Syntax<code> for the property name
     */
    public Syntax getPropertySyntax(String property) {
        return (Syntax.LIST);
    }
	
    /**
     * Gets the display name for the property name.
     * The <code>locale</code> variable could be used by the plugin to
     * customize the display name for the given locale.
     * The <code>locale</code> variable could be <code>null</code>, in which
     * case the plugin must use the default locale.
     *
     * @param property property name
     * @param locale locale for which the property name must be customized
     * @return display name for the property name.
     * @throws PolicyException
     */
    public String getDisplayName(String property, Locale locale)
            throws PolicyException {
	return property;
    }

    /**
     * Returns a set of valid values given the property name. 
     *
     * @param property property name
     * from the PolicyConfig Service configured for the specified realm.
     * @return Set of valid values for the property.
     * @exception PolicyException if unable to get the Syntax.
     */
    public Set getValidValues(String property) throws PolicyException {
	if (!initialized) {
	    throw (new PolicyException("idrepo response provider not yet "
		+"initialized"));
	}
        return Collections.EMPTY_SET;
    }

    /** Sets the properties of the responseProvider plugin.
     *  This influences the response attribute-value Map that would be
     *  computed by a call to method <code>getResponseDecision(Map)</code>
     *  These attribute-value pairs are encapsulated in 
     *  <code>ResponseAttribute</code> element tag which is a child of the 
     *  <code>PolicyDecision</code> element in the PolicyResponse xml
     *  if the policy is applicable to the user for the resource, subject and
     *  conditions defined.
     *  @param properties the properties of the responseProvider
     *         Keys of the properties have to be String.
     *         Value corresponding to each key have to be a Set of String
     *         elements. Each implementation of ResponseProvider could add 
     *         further restrictions on the keys and values of this map.
     *  @throws PolicyException for any abnormal condition
     */
    public void setProperties(Map properties) throws PolicyException {
        if ( (properties == null) || ( properties.isEmpty()) ) {
            throw new PolicyException("Properties cannot be null or empty");
        }
        this.properties = properties;

        //Check if the keys needed for this provider are present namely
 	// ATTRIBUTE_NAME
	if (!properties.containsKey(ATTRIBUTE_NAME)) {
            throw new PolicyException("Missing required property");
	}
	/** 
         * Addtional validation on property name and values can be done
         * as per the individual use case
         */
    }

    /** Gets the properties of the responseprovider
     *  @return properties of the responseprovider
     *  @see #setProperties
     */
    public Map getProperties() {
	return (properties == null) 
		? null : Collections.unmodifiableMap(properties);
    }

    /**
     * Gets the response attributes computed by this ResponseProvider object,
     * based on the sso token and map of environment parameters
     *
     * @param token single-sign-on token of the user
     *
     * @param env specific environment map of key/value pairs
     * @return  a Map of response attributes.
     *          Keys of the Map are attribute names ATTRIBUTE_NAME or
     *          Value is a Set of Strings representing response attribute 
     *          values.
     *
     * @throws PolicyException if the decision could not be computed
     * @throws SSOException if SSO token is not valid
     *
     */
    public Map getResponseDecision(SSOToken token, 
            Map env) throws PolicyException, SSOException { 

	Map respMap = new HashMap();
	Set attrs = (Set)properties.get(ATTRIBUTE_NAME);
	Set values = null;
	if ((attrs != null) && !(attrs.isEmpty())) {
            try {
                if (token.getPrincipal() != null) {
                    AMIdentity id = IdUtils.getIdentity(token);
                    Map idRepoMap = id.getAttributes(attrs);
                    if (idRepoMap != null) {
                        for (Iterator iter = attrs.iterator(); iter.hasNext(); )
			{
                            String attrName = (String)iter.next();
                            values = new HashSet();
                            Set subValues = (Set)idRepoMap.get(attrName);
                            if (subValues != null) {
                                values.addAll(subValues);
                            }
			    respMap.put(attrName, values);
                        }
                    }
                } else {
                    throw (new PolicyException("SSOToken principal is null"));
                }
            } catch (IdRepoException ide) {
                throw new PolicyException(ide);
  	    }	
        }
	return respMap;
    }


    /**
     * Returns a copy of this object.
     *
     * @return a copy of this object
     */
    public Object clone() {
        SampleResponseProvider theClone = null;
        try {
            theClone = (SampleResponseProvider)super.clone();
        } catch (CloneNotSupportedException e) {
            // this should never happen
            throw new InternalError();
        }

        if (properties != null) {
            theClone.properties = new HashMap();
            Iterator iter = properties.keySet().iterator();
            while (iter.hasNext()) {
                Object obj = iter.next();
                Set values = new HashSet();
                values.addAll((Set) properties.get(obj));
                theClone.properties.put(obj, values);
            }
        }
        return theClone;
    }
}