/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2005 Sun Microsystems Inc. All Rights Reserved * * The contents of this file are subject to the terms * of the Common Development and Distribution License * (the License). You may not use this file except in * compliance with the License. * * You can obtain a copy of the License at * https://opensso.dev.java.net/public/CDDLv1.0.html or * opensso/legal/CDDLv1.0.txt * See the License for the specific language governing * permission and limitations under the License. * * When distributing Covered Code, include this CDDL * Header Notice in each file and include the License file * at opensso/legal/CDDLv1.0.txt. * If applicable, add the following below the CDDL Header, * with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * $Id: SMSEntry.java,v 1.53 2009/12/07 19:46:47 veiming Exp $ * * Portions Copyrighted 2010-2015 ForgeRock AS. */ package com.sun.identity.sm; import com.iplanet.am.util.Cache; import com.iplanet.am.util.SystemProperties; import com.iplanet.sso.SSOException; import com.iplanet.sso.SSOToken; import com.iplanet.sso.SSOTokenManager; import com.iplanet.ums.IUMSConstants; import com.sun.identity.common.CaseInsensitiveHashMap; import com.sun.identity.common.CaseInsensitiveHashSet; import com.sun.identity.delegation.DelegationEvaluator; import com.sun.identity.delegation.DelegationEvaluatorImpl; import com.sun.identity.delegation.DelegationException; import com.sun.identity.delegation.DelegationPermission; import com.sun.identity.security.AdminTokenAction; import com.sun.identity.shared.Constants; import com.sun.identity.shared.datastruct.OrderedSet; import com.sun.identity.shared.debug.Debug; import com.sun.identity.shared.locale.AMResourceBundleCache; import com.sun.identity.sm.jaxrpc.SMSJAXRPCObject; import java.security.AccessController; import java.security.Principal; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; import java.util.StringTokenizer; import javax.naming.directory.BasicAttribute; import javax.naming.directory.DirContext; import javax.naming.directory.ModificationItem; import org.forgerock.openam.ldap.LDAPUtils; import org.forgerock.opendj.ldap.DN; import org.forgerock.opendj.ldap.LdapException; import org.forgerock.opendj.ldap.ResultCode; /** * This object represents a SMS entry in datastore, similar to UMS's equivalent * class called PersistentObject. *
* This class is used both to read and write information into the datastore.
*/
public class SMSEntry implements Cloneable {
// Name of place holder nodes
public static final String ORGANIZATION_RDN = "o";
public static final String EQUALS = "=";
static final String ORG_PLACEHOLDER_RDN = ORGANIZATION_RDN + EQUALS;
public static final String SERVICES_NODE = "services";
public static final String PLACEHOLDER_RDN = "ou";
public static final String SERVICES_RDN = PLACEHOLDER_RDN + "="
+ SERVICES_NODE;
public static final String COMMA = ",";
// Debug instance & SSOTokenManager
public static Debug debug = Debug.getInstance("amSMS");
public static Debug eventDebug = Debug.getInstance("amSMSEvent");
public static SSOTokenManager tm;
// Variable for caching parse organization names
private static Cache cache = new Cache(500);
/**
* Flat File Configuration Data Store
*/
public static String DATASTORE_FLAT_FILE = "flatfile";
/**
* Sun Directory Server Configuration Data Store
*/
public static String DATASTORE_SUN_DIR = "dirServer";
/**
* Active Directory Configuration Data Store
*/
public static String DATASTORE_ACTIVE_DIR = "activeDir";
static boolean cacheSMSEntries;
public static ResourceBundle bundle;
static String baseDN;
static String servicesDN;
static String dataStore;
static String amsdkbaseDN;
static int baseDNCount;
static SMSException initializationException;
// DataStore Implementation Object
static final String SMS_OBJECT_PROPERTY =
"com.sun.identity.sm.sms_object_class_name";
static final String DEFAULT_SMS_CLASS_NAME =
"com.sun.identity.sm.ldap.SMSLdapObject";
static final String JAXRPC_SMS_CLASS_NAME =
"com.sun.identity.sm.jaxrpc.SMSJAXRPCObject";
static final String FLATFILE_SMS_CLASS_NAME =
"com.sun.identity.sm.flatfile.SMSEnhancedFlatFileObject";
// Flag to enable LDAP's proxy support
public static final String DB_PROXY_ENABLE =
"com.sun.identity.sm.ldap.enableProxy";
static SMSObject smsObject;
// Variable for import/export
static final String SLASH_STR = "/";
static final String DOT_STR = ".";
public static final String EXPORTEDARGS = "exportedTo";
public static final String IMPORTEDARGS = "importedFrom";
// Variables for checking delegation permission
static final String AUTH_SUPER_USER =
"com.sun.identity.authentication.super.user";
static final String READ = "READ";
static final String MODIFY = "MODIFY";
static final Setsave()
must be called to make
* the changes persistant
*/
public void setAttribute(String attrName, String[] attrValues) {
// Attribute Values to be Set and BasicAttribute
Set attrs = new HashSet();
BasicAttribute ba = new BasicAttribute(attrName);
for (int i = 0; attrValues != null && i < attrValues.length; i++) {
attrs.add(attrValues[i]);
ba.add(attrValues[i]);
}
// Check if attrSet, modSet is present, if not create
attrSet = (attrSet == null) ? (new CaseInsensitiveHashMap()) : attrSet;
modSet = (modSet == null) ? (new HashSet()) : modSet;
// Check if the attribute exists, if not present add, else replace
if (!attrSet.containsKey(attrName)) {
// Not present: add it, update modset
modSet.add(new ModificationItem(DirContext.ADD_ATTRIBUTE, ba));
} else {
// Remove old attrbute and add the new attribute, update modset
modSet.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE, ba));
}
// Update attrset
attrSet.put(attrName, attrs);
}
/**
* Modify the attribute values. save()
must be called to make
* the changes persistant. This does not affect the existing attributes
* already read.
*/
public void modifyAttributes(ModificationItem[] modItems) {
if (modSet == null) {
modSet = new HashSet();
}
for (int i = 0; (modItems != null) && (i < modItems.length); i++) {
modSet.add(modItems[i]);
}
}
/**
* Set the attributes. save()
must be called to make
* the changes persistant
*/
public void setAttributes(Map attributes) {
// Obtain attribute names and values and set them
if (!(attributes == null) && !attributes.isEmpty()) {
Iterator attrNames = attributes.keySet().iterator();
while (attrNames.hasNext()) {
String attrName = (String) attrNames.next();
Set values = (Set) attributes.get(attrName);
String[] attrValues = null;
if ((values != null) && !values.isEmpty()) {
attrValues = new String[values.size()];
attrValues = (String[]) values.toArray(attrValues);
}
setAttribute(attrName, attrValues);
}
}
}
/**
* Removes the attribute value from the attribute.
*
* @param attrName Name of attribute.
* @param value Value to be removed.
* @throws SMSException if value cannot be removed.
*/
public void removeAttribute(String attrName, String value)
throws SMSException {
Set attr = null;
if ((attrSet == null) || ((attr = (Set) attrSet.get(attrName)) == null)
|| (!attr.contains(value))) {
throw (new SMSException(LdapException.newLdapException(ResultCode.ATTRIBUTE_OR_VALUE_EXISTS,
getBundleString(IUMSConstants.SMS_ATTR_OR_VAL_EXISTS)),
"sms-ATTR_OR_VAL_EXISTS"));
}
// Update attr and attrSet --> will not be null
attr.remove(value);
attrSet.put(attrName, attr);
// Update modification set
if (modSet == null) {
modSet = new HashSet();
}
modSet.add(new ModificationItem(DirContext.REMOVE_ATTRIBUTE,
new BasicAttribute(attrName, value)));
}
/**
* Remove the attribute from the entry.
*/
public void removeAttribute(String attrName) throws SMSException {
Set attribute = (Set) attrSet.get(attrName);
if (attribute == null) {
throw (new SMSException(LdapException.newLdapException(ResultCode.ATTRIBUTE_OR_VALUE_EXISTS,
getBundleString(IUMSConstants.SMS_ATTR_OR_VAL_EXISTS)),
"sms-ATTR_OR_VAL_EXISTS"));
}
attrSet.remove(attrName);
if (modSet == null) {
modSet = new HashSet();
}
BasicAttribute ba = new BasicAttribute(attrName, attribute);
for (Iterator items = attribute.iterator(); items.hasNext();)
ba.add(items.next());
modSet.add(new ModificationItem(DirContext.REMOVE_ATTRIBUTE, ba));
}
private CharSequence getBundleString(String key) {
return bundle.getString(key);
}
/**
* Checks if the attribute value exists in the attribute
*/
public boolean containsAttrValue(String attrName, String attrValue) {
if (attrSet != null) {
Set attr = (Set) attrSet.get(attrName);
if (attr != null) {
return (attr.contains(attrValue));
}
}
return (false);
}
/**
* Reads in the object from persistent store, assuming that the guid and the
* principal are valid
*/
void read() throws SSOException, SMSException {
read(ssoToken);
}
/**
* Reads in the object from persistent store using the given ssoToken
*/
void read(SSOToken token) throws SSOException, SMSException {
// If backend has proxy enabled, check for delegation
// permissions and use admin token
if (backendProxyEnabled) {
if (isAllowed(token, normalizedDN, readActionSet)) {
if (adminSSOToken == null) {
adminSSOToken = (SSOToken) AccessController.doPrivileged(
com.sun.identity.security.AdminTokenAction
.getInstance());
}
token = adminSSOToken;
}
} else {
// Check for delegation permission throws exception if
// permission is denied
getDelegationPermission(token, normalizedDN, readActionSet);
}
attrSet = smsObject.read(token, dn);
if (attrSet == null) {
newEntry = true;
} else {
newEntry = false;
}
}
/**
* Save the modification(s) to the object. Save the changes made so far to
* the datastore.
*/
public void save() throws SSOException, SMSException {
if (readOnly) {
if (debug.warningEnabled()) {
debug.warning("SMSEntry: Attempted to save an entry that "
+ "is marked as read-only: " + dn);
}
throw (new SMSException(SMSException.STATUS_NO_PERMISSION,
"sms-INSUFFICIENT_ACCESS_RIGHTS"));
}
save(ssoToken);
}
/**
* Save the modification(s) to the object. Save the changes made so far to
* the datastore using the given SSOToken
*/
void save(SSOToken token) throws SSOException, SMSException {
// If backend has proxy enabled, check for delegation
// permissions and use admin token
if (backendProxyEnabled) {
if (isAllowed(token, normalizedDN, modifyActionSet)) {
if (adminSSOToken == null) {
adminSSOToken = (SSOToken) AccessController.doPrivileged(
com.sun.identity.security.AdminTokenAction
.getInstance());
}
token = adminSSOToken;
}
} else {
// Check for delegation permission throws exception if
// permission is denied
getDelegationPermission(token, normalizedDN, modifyActionSet);
}
if (newEntry && attrSet != null) {
smsObject.create(token, dn, attrSet);
// send object change notification
SMSNotificationManager.getInstance().localObjectChanged(dn,
SMSObjectListener.ADD);
} else if (modSet != null) {
smsObject.modify(token, dn, (ModificationItem[]) (modSet
.toArray(new ModificationItem[modSet.size()])));
// send object change notification
SMSNotificationManager.getInstance().localObjectChanged(dn,
SMSObjectListener.MODIFY);
} else {
// %%% throw an exception, since nothing has changed
}
newEntry = false;
}
/**
* Delete the entry in the datastore. This will delete sub-entries also!
*/
public void delete() throws SMSException, SSOException {
if (readOnly) {
if (debug.warningEnabled()) {
debug.warning("SMSEntry: Attempted to delete an entry that "
+ "is marked as read-only: " + dn);
}
throw (new SMSException(SMSException.STATUS_NO_PERMISSION,
"sms-INSUFFICIENT_ACCESS_RIGHTS"));
}
delete(ssoToken);
}
/**
* Delete the entry in the datastore. This will delete sub-entries also!
*
* TODO: There is no way to set read-only to false, we should see what we can
* about this.
*/
public void forceDelete(SSOToken adminToken) throws SMSException, SSOException {
delete(adminToken);
}
/**
* Delete the entry in the directory. This will delete sub-entries also!
*/
void delete(SSOToken token) throws SMSException, SSOException {
if (!newEntry) {
// If backend has proxy enabled, check for delegation
// permissions and use admin token
if (backendProxyEnabled) {
if (isAllowed(token, normalizedDN, modifyActionSet)) {
if (adminSSOToken == null) {
adminSSOToken = (SSOToken)
AccessController.doPrivileged(
com.sun.identity.security.AdminTokenAction
.getInstance());
}
token = adminSSOToken;
}
} else {
// Check for delegation permission throws exception if
// permission is denied
getDelegationPermission(token, normalizedDN, modifyActionSet);
}
smsObject.delete(token, dn);
newEntry = true;
attrSet = null; // new HashMap();
modSet = null;
// send object change notification
SMSNotificationManager.getInstance().localObjectChanged(dn,
SMSObjectListener.DELETE);
} else {
if (debug.warningEnabled()) {
debug.warning("SMSEntry: Attempted to delete an entry that "
+ "does not exist: " + dn);
}
}
}
/**
* Returns the subOrgNames. Returns a set of suborganization names (rdns).
* The paramter numOfEntries
identifies the number of entries
* to return, if 0
returns all the entries. The paramter
* recursive
determines if to return one level of entries
* beneath the entryDN or all the entries till the leaf node.
*/
Set searchSubOrgNames(SSOToken token, String filter, int numOfEntries,
boolean sortResults, boolean ascendingOrder, boolean recursive)
throws SMSException, SSOException {
// If backend has proxy enabled, check for delegation
// permissions and use admin token. Also if JAXRPC is used,
// permission is checked at the server.
if (backendProxyEnabled && !SMSJAXRPCObjectFlg) {
if (isAllowed(token, normalizedDN, readActionSet)) {
if (adminSSOToken == null) {
adminSSOToken = (SSOToken)
AccessController.doPrivileged(
com.sun.identity.security.AdminTokenAction
.getInstance());
}
token = adminSSOToken;
}
} else if (!SMSJAXRPCObjectFlg) {
// Check for delegation permission throws exception if
// permission is denied
getDelegationPermission(token, normalizedDN, readActionSet);
}
Set resultSet = smsObject.searchSubOrgNames(token, dn, filter,
numOfEntries, sortResults, ascendingOrder, recursive);
// Check for remote client using JAX-RPC
if (SMSJAXRPCObjectFlg) {
// Since this is a JAX-RPC call, the permission checking and
// parsing would be done at the server
return (resultSet);
}
// Server side. Check for read permissions
Set allowedSet = new OrderedSet();
for (Iterator items = resultSet.iterator(); items.hasNext();) {
String item = (String) items.next();
if (hasReadPermission(token, item)) {
allowedSet.add(item);
}
}
Set answer = parseResult(allowedSet, normalizedDN);
if (debug.messageEnabled()) {
debug.message("SMSEntry: Successfully obtained "
+ "suborganization names for : " + dn);
}
return (answer);
}
SetnumOfEntries
identifies the number of entries to
* return, if 0
returns all the entries. The paramter
* recursive
determines if to return one level of entries
* beneath the entryDN or all the entries till the leaf node.
*/
Set searchOrganizationNames(SSOToken token, int numOfEntries,
boolean sortResults, boolean ascendingOrder, String serviceName,
String attrName, Set values) throws SMSException, SSOException {
// If backend has proxy enabled, check for delegation
// permissions and use admin token. Also if JAXRPC is used,
// permission is checked at the server.
if (backendProxyEnabled && !SMSJAXRPCObjectFlg) {
if (isAllowed(token, normalizedDN, readActionSet)) {
if (adminSSOToken == null) {
adminSSOToken = (SSOToken)
AccessController.doPrivileged(
com.sun.identity.security.AdminTokenAction
.getInstance());
}
token = adminSSOToken;
}
} else if (!SMSJAXRPCObjectFlg) {
// Check for delegation permission throws exception if
// permission is denied
getDelegationPermission(token, normalizedDN, readActionSet);
}
Set resultSet = smsObject.searchOrganizationNames(token, dn,
numOfEntries, sortResults, ascendingOrder, serviceName,
attrName, values);
// Check for remote client using JAX-RPC
if (SMSJAXRPCObjectFlg) {
// Since this is a JAX-RPC call, the permission checking and
// parsing would be done at the server
return (resultSet);
}
// Check for read permissions
Set allowedSet = new OrderedSet();
for (Iterator items = resultSet.iterator(); items.hasNext();) {
String item = (String) items.next();
if (hasReadPermission(token, item)) {
allowedSet.add(item);
}
}
if (attrName.equalsIgnoreCase(EXPORTEDARGS))
return allowedSet;
Set answer = parseResult(allowedSet, normalizedDN, true);
if (debug.messageEnabled()) {
debug.message("SMSEntry: Successfully obtained "
+ "organization names for : " + dn);
}
return answer;
}
/**
* Returns the DNs that match the filter. The search is performed from the
* root suffix ie., DN. It searchs for SMS objects only.
*
* @param token Single-Sign On token.
* @param dn Base DN
* @param filter Search Filter.
* @param numOfEntries number of max entries, 0 means unlimited
* @param timeLimit maximum number of seconds for the search to spend, 0
* means unlimited
* @param sortResults true
to have result sorted.
* @param ascendingOrder true
to have result sorted in
* ascending order.
*/
public static Settrue
to have result sorted.
* @param ascendingOrder true
to have result sorted in
* ascending order.
* @param exclude List of DN to exclude.
*/
public static Iterator search(SSOToken token, String dn, String filter,
int numOfEntries, int timeLimit, boolean sortResults,
boolean ascendingOrder, Set exclude)
throws SMSException {
try {
return smsObject.search(token, dn, filter, numOfEntries, timeLimit,
sortResults, ascendingOrder, exclude);
} catch (SSOException ssoe) {
debug.error("SMSEntry: Search ERROR: " + filter, ssoe);
throw new SMSException(bundle.getString("sms-error-in-searching"),
ssoe, "sms-error-in-searching");
}
}
/**
* Returns the DNs that match the filter. The search is performed from the
* root suffix ie., DN. It searchs for SMS objects only.
*/
static Set search(String filter) throws SMSException {
try {
return (smsObject.search(null, baseDN, filter, 0, 0, false, false));
} catch (SSOException ssoe) {
debug.error("SMSEntry: Search ERROR: " + filter, ssoe);
throw new SMSException(bundle.getString("sms-error-in-searching"),
ssoe, "sms-error-in-searching");
}
}
/**
* Updates the attribute set from the provided SMSEntry
*/
void refresh(SMSEntry e) {
if (e.attrSet != null) {
attrSet = SMSUtils.copyAttributes(e.attrSet);
} else {
attrSet = null;
}
newEntry = e.newEntry;
modSet = null;
}
/**
* Checks if the provided DN exists. Used by PolicyManager.
*/
public static boolean checkIfEntryExists(String dn, SSOToken token) {
try {
return (smsObject.entryExists(token, dn));
} catch (Exception e) {
debug.error("SMSEntry: Error in checking if entry exists: "
+ dn, e);
}
return (false);
}
/**
* Returns the DN of the entity
*/
String getDN() {
return (dn);
}
/**
* Returns the principal used to access the entry
*/
Principal getPrincipal() {
try {
return (ssoToken.getPrincipal());
} catch (SSOException ssoe) {
return (null);
}
}
/**
* Returns the ssoToken used to access the entry
*/
SSOToken getSSOToken() {
return (ssoToken);
}
/**
* Sets the SMSEntry to be read only. Can be changed only by explicitly
* providing the principal name.
*/
void setReadOnly() {
readOnly = true;
}
/**
* Returns true
if the entry does not exist in the data store
*/
public boolean isNewEntry() {
return (newEntry);
}
/**
* Returns the SMSObject
*/
static SMSObject getSMSObject() {
return (smsObject);
}
public static void validateToken(SSOToken token) throws SMSException {
try {
tm.validateToken(token);
} catch (SSOException ssoe) {
throw (new SMSException(ssoe, "sms-INVALID_SSO_TOKEN"));
}
}
public Object clone() throws CloneNotSupportedException {
SMSEntry answer = (SMSEntry) super.clone();
answer.ssoToken = ssoToken;
answer.dn = dn;
answer.newEntry = newEntry;
answer.modSet = null;
if (attrSet != null) {
answer.attrSet = SMSUtils.copyAttributes(attrSet);
} else {
answer.attrSet = null;
}
if (debug.messageEnabled()) {
debug.message("SMSEntry being cloned: " + dn);
}
return (answer);
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("DN\t\t: ").append(dn).append("\n");
if (newEntry) {
sb.append("\t(NEW Entry)");
}
sb.append("Attribute Set\t: ").append(attrSet).append("\n");
sb.append("Modifcation Set\t: ").append(modSet).append("\n");
return (sb.toString());
}
public static String getRootSuffix() {
return baseDN;
}
public static String getAMSdkBaseDN() {
return amsdkbaseDN;
}
public static String getDataStore(SSOToken token) {
if (dataStore == null) {
// This has to be called only if the backend datastore is
// based on LDAP protocol. Should not be for JDBC.
String smsClassName = smsObject.getClass().getName();
if (smsClassName.equals(DEFAULT_SMS_CLASS_NAME) ||
smsClassName.equals(JAXRPC_SMS_CLASS_NAME)) {
dataStore = GetBackendDataStore.getDataStore(token);
} else {
dataStore = "flatfile";
}
if (debug.messageEnabled()) {
debug.message("SMSEntry:getDataStore.dataStore "+dataStore);
}
}
return dataStore;
}
/**
* @return true if the given attribute's value is case sensitive.
*/
public static boolean isAttributeCaseSensitive(String attrName) {
return (mCaseSensitiveAttributes.contains(attrName));
}
/**
* @return the service filter pattern string
*/
public static String getFilterPatternService() {
return SMSEntry.FILTER_PATTERN_SERVICE;
}
// Parse the DNs and return as "/" seperated, but does not
// include the current DN
protected static Set parseResult(Set resultSet, String dn) {
return (parseResult(resultSet, dn, false));
}
// Parse the DNs and return as "/" seperated
protected static Set parseResult(Set resultSet, String dn,
boolean includeThisDN) {
/*
* Search results based on scope 'SCOPE_SUB' returns all entries beneath
* the entry DN, and adding to the set. This piece of code iterates
* through the list, gets the suborganization names, replaces the
* namingattribute 'o' with '/' and reverses the result string to be
* displayed in the slash '/' seperated format. eg., Search for suborgs
* from 'o=coke' in o=fanta,o=pepsi,o=coke,ou=services,dc=iplanet,dc=com
* will return the following set: [pepsi, pepsi/fanta]
*/
Set answer = new OrderedSet();
if (resultSet != null) {
Iterator Iter = resultSet.iterator();
while (Iter.hasNext()) {
DN sdn = DN.valueOf((String) Iter.next());
String rfcDN = sdn.toString();
String rfcDNlc = sdn.toString().toLowerCase();
if (!rfcDNlc.equals(baseDN)
&& !rfcDNlc.startsWith(SUN_INTERNAL_REALM_PREFIX)) {
/*
* Need to include current DN for search operations.
* Required by AuthN to login to root organization
*/
if (rfcDNlc.equals(dn)) {
if (includeThisDN) {
answer.add(SLASH_STR);
}
continue;
}
/*
* To handle such a case. eg., ou=policy1,
* ou=Policies,ou=default,ou=OrganizationConfig,
* ou=1.0,ou=iPlanetAMPolicyService,ou=services,
* o=engg,o=coke,ou=services,dc=iplanet,dc=com in which case
* we return "/Coke/engg"
*/
String orgAttr = ServiceManager.isRealmEnabled() ?
ORG_PLACEHOLDER_RDN :
OrgConfigViaAMSDK.getNamingAttrForOrg()
+ EQUALS;
if (debug.messageEnabled()) {
debug.message("SMSEntry:parseResult:orgAttr "
+ orgAttr);
}
int i = rfcDNlc.indexOf(orgAttr.toLowerCase());
if (i > 0) {
rfcDN = rfcDN.substring(i);
}
if (debug.messageEnabled()) {
debug.message("SMSEntry:parseResult:DNName " + dn);
debug.message("SMSEntry:parseResult:RFCDN " + rfcDN);
}
int indx = rfcDNlc.indexOf(dn);
if (indx < 0) {
indx = rfcDNlc.lastIndexOf(baseDN);
}
String origStr = rfcDN.substring(0, indx - 1);
if (!ServiceManager.isRealmEnabled()) {
// Continue in the case of Containers in the node in
// legacy install.
// eg., o=coke,ou=ContainerOne,dc=planet,dc=com
ArrayList rdns = new ArrayList();
StringTokenizer strtok = new StringTokenizer(origStr,
COMMA);
while (strtok.hasMoreElements()) {
String token = (String) strtok.nextToken().trim();
if (debug.messageEnabled()) {
debug.message("SMSEntry:parseResult().token "
+ token);
}
if (token != null && token.length() != 0) {
rdns.add(token);
}
}
int size = rdns.size();
Set dnKeyset = new HashSet();
for (int is = 0; is < size; is++) {
String[] strArr = DNMapper
.splitString((String) rdns.get(is));
dnKeyset.add(strArr[0]);
}
String orgUnitAttr = OrgConfigViaAMSDK
.getNamingAttrForOrgUnit();
if (dnKeyset.contains(orgUnitAttr)) {
if (debug.messageEnabled()) {
debug.message("SMSEntry.parseResult(): "
+ "Container node: " + origStr);
}
continue;
}
}
// If orgAttr is not null,replace with the org naming
// attribute which is defined for legacy mode.
// Replace orgAttr= to '/' and ',' to "" (or)
// Replace 'o=' to '/' and ',' to ""
origStr = DNMapper.replaceString(origStr, orgAttr,
SLASH_STR);
if (debug.messageEnabled()) {
debug.message("SMSEntry:parseResult:origStr1 "
+ origStr);
}
origStr = DNMapper.replaceString(origStr, SMSEntry.COMMA,
"");
if (debug.messageEnabled()) {
debug.message("SMSEntry:parseResult:origStr2 "
+ origStr);
}
// Logic here is to reverse the string from dn format to
// slash format.
String tmpStr = "";
StringBuilder sb = new StringBuilder();
while (origStr.length() != 0) {
int id = origStr.lastIndexOf(SLASH_STR);
if (id >= 0) {
sb.append(origStr.substring(id + 1)).append(
SLASH_STR);
origStr = origStr.substring(0, id);
}
}
tmpStr = sb.toString();
/*
* To remove the ending slash '/'. eg., pepsi/fanta/ to be
* added as pepsi/fanta
*/
if (tmpStr != null && tmpStr.length() > 0) {
answer.add(tmpStr.substring(0, tmpStr.length() - 1));
}
}
}
}
return answer;
}
static String[] parseOrgDN(String dnName) {
// Check in cache first.
String[] answer = (String[]) cache.get(dnName);
if (answer != null) {
return (answer);
}
// Not in cache, parse the DN
debug.message("SMSEntry:parseOrgDN:DNName {}", dnName);
answer = new String[5];
if (dnName == null || dnName.length() == 0) {
// This is an invalid DN, return "*"s
answer[0] = baseDN;
answer[1] = "*";
answer[2] = "*";
answer[3] = "*";
answer[4] = "*";
return (answer);
}
/*
* We assume "ou=services" seperate the organization name from the rest
* of the service components Hence if the full DN is: ou=subconfig1,
* ou=default,ou=OrganizationConfig,
* ou=1.0,ou=iPlanetAMPolicyService,ou=services,
* o=engg,o=coke,ou=services,dc=iplanet,dc=com the orgDN would be:
* o=engg,o=coke,ou=services,dc=iplanet,dc=com And if the full DN is:
* ou=subconfig1, ou=default, ou=OrganizationConfig, ou=1.0,
* ou=iPlanetAMPolicyService,ou=services, dc=iplanet, dc=com the orgDN
* would be: dc=iplanet,dc=com
*/
DN sdn = DN.valueOf(dnName);
String rfcDN = sdn.toString().toLowerCase();
String restOfDN = null;
// Get the index to the first occurance of ",ou=services,"
int oldStrIndex = rfcDN.indexOf(DELEGATION_SERVICES_RDN_WITH_COMMA);
if (oldStrIndex == -1 || rfcDN.equals(servicesDN)) {
// The DN must be for root suffix for realm mode
// and orgname in the case of legacy mode.
answer[0] = rfcDN;
restOfDN = "";
} else if (ServiceManager.isRealmEnabled()) {
// In realm mode there will be 2 ou=services, except
// for the root org and in Legacy mode, there will be
// only one "ou=services" for the root org.
// Hence remove baseDN and check if rfcDN contains realm name
int baseDNIndex = rfcDN.indexOf(servicesDN);
if (baseDNIndex == -1 || baseDNIndex == 0) {
// Invalid DN or base DN, return base DN as org
answer[0] = baseDN;
restOfDN = "";
} else {
String dn1 = rfcDN.substring(0, baseDNIndex - 1);
if ((dn1.indexOf(DELEGATION_SERVICES_RDN) == -1) &&
(!dn1.startsWith(ORGANIZATION_RDN + EQUALS))) {
// Since services node is not present, it
// must be root realm.
answer[0] = baseDN;
restOfDN = dn1;
} else if (dn1.startsWith(DELEGATION_SERVICES_RDN)) {
answer[0] =
rfcDN.substring(DELEGATION_SERVICES_RDN.length());
restOfDN = "";
} else if (dn1.startsWith(ORGANIZATION_RDN + EQUALS)) {
// In case of subrealms say,
// o=a3,o=a2,o=a1,o=etat-de-vaud,ou=services,o=smsnode
answer[0] = rfcDN;
restOfDN = "";
} else {
// In realm mode, "ou=services" seperates service name
// from realm name.
answer[0] = rfcDN.substring(oldStrIndex
+ DELEGATION_SERVICES_RDN_WITH_COMMA_LEN);
restOfDN = rfcDN.substring(0, oldStrIndex);
}
}
} else {
// In Legacy mode, there will be only one "ou=services"
answer[0] = rfcDN.substring(oldStrIndex
+ DELEGATION_SERVICES_RDN_WITH_COMMA_LEN);
restOfDN = rfcDN.substring(0, oldStrIndex);
}
debug.message("SMSEntry:parseOrgDN: orgDN: {} restOfDN: {}", answer[0], restOfDN);
// Parse restOfDN to get servicename, version, type and subconfig
DN remainingDN = DN.valueOf(restOfDN);
int size = remainingDN.size();
// store serviceName,version,configType,subConfigNames
// from restOfDN which will be of the format:
// ou=default,ou=globalconfig,ou=1.0,ou=iPlanetAMAdminConsoleService
answer[4] = (size < 1) ? REALM_SERVICE : LDAPUtils.rdnValueFromDn(remainingDN.parent(size - 1));
answer[3] = (size < 2) ? "*" : LDAPUtils.rdnValueFromDn(remainingDN.parent(size - 2));
answer[2] = (size < 3) ? "*" : LDAPUtils.rdnValueFromDn(remainingDN.parent(size - 3));
// The subconfig names should be "/" separated and left to right
if (size >= 4) {
StringBuilder sbr = new StringBuilder();
for (int i = size - 4; i >= 0; i--) {
sbr.append('/').append(LDAPUtils.rdnValueFromDn(remainingDN.parent(i)));
}
answer[1] = sbr.toString();
} else {
answer[1] = "*";
}
// Add to cache
cache.put(dnName, answer);
return answer;
}
static boolean hasReadPermission(SSOToken token, String dn) {
try {
getDelegationPermission(token, dn, readActionSet);
} catch (SMSException smse) {
if (debug.messageEnabled()) {
try {
debug
.message("SMSEntry::hasReadPermission Denied user: "
+ token.getPrincipal().getName()
+ " for dn: " + dn);
} catch (SSOException ssoe) {
debug.message("SMSEntry::hasReadPermission Denied access"
+ " for dn: " + dn + " Got SSOException", ssoe);
}
}
return (false);
}
if (debug.messageEnabled()) {
try {
debug.message("SMSEntry::hasReadPermission Allowed user: "
+ token.getPrincipal().getName() + " for dn: " + dn);
} catch (SSOException ssoe) {
// This should not happen
debug.message("SMSEntry::hasReadPermission Allowed access"
+ " for dn: " + dn + " Got SSOException", ssoe);
}
}
return (true);
}
static boolean getDelegationPermission(SSOToken token, String dnName,
Set actions) throws SMSException {
// call this API in SMSEntry::write,read,delete... methods.
// If true proceed writing and reading.
boolean delPermFlag = true;
/*
* No delegation checked for the following : Allow them for all
* operations. 1) Since client SDK uses JAXRPC, bypass delegation
* permission check in client SDK. Else it would done twice once on the
* client and another at the server. if SMSJAXRPCObject is used then
* bypass delegation check.
*
* 2) Allow the users in the special users set.This is configurable
* using the following 2 properties in AMConfig.properties.
* 'com.sun.identity.authentication.super.user'
* 'com.sun.identity.authentication.special.users'
* 3) Since service config/data resides only under the group node ie.,
* ou=default node, bypass the delegation check for the nodes above
* that. This is to avoid unnecessary parsing and delegation checking.
* bypass dnNames : ou=services,dc=iplanet,dc=com dc=iplanet,dc=com
* ou=GlobalConfig ou=OrganizationConfig ou=PluginConfig
*/
if (SMSJAXRPCObjectFlg || backendProxyEnabled ||
dnName.equals(baseDN) ||
(dnName.equals(servicesDN) && !actions.contains(MODIFY))) {
/*
if (debug.messageEnabled()) {
debug.message("SMSEntry:getDelegationPermission :"
+ "No delegation check needed for client sdk, "
+ "db proxy enabled and for baseDNs: " + baseDN);
}
*/
return delPermFlag;
}
// Check for special and admin users
try {
// Normalize the user name from the token and compare it
// against the special user set and if equal returns true
// allowing the user to perform all operations.
// eg., if root_suffix is dn=iplanet, dn=com then the normalized
// dn is dc=iplanet,dc=com.
String tokenName = token.getPrincipal().getName();
if (LDAPUtils.isDN(tokenName)) {
DN tokendn = DN.valueOf(tokenName);
if (specialUserSet.contains(tokendn.toString())) {
/*
if (debug.messageEnabled()) {
debug.message("SMSEntry.getDelegationPermission: No "
+ "delegation check needed for special users."
+ normTok);
}
*/
return delPermFlag;
}
}
} catch (SSOException se) {
debug.error("SMSEntry.isAllowed : " + "Invalid Token: ", se);
throw (new SMSException(bundle.getString("sms-INVALID_SSO_TOKEN"),
"sms-INVALID_SSO_TOKEN"));
}
// If running in co-existence mode, return true if backend
// has proxy enabled
if (!ServiceManager.isConfigMigratedTo70()) {
// Make sure Backend Datastore Proxy is enabled
if (!backendProxyEnabled) {
debug.error("SMSEntry::getDelegationPermission "
+ "Must enable LDAP proxy support if configuration "
+ "(DIT) is not migrated to AM 7.0");
// Throw permission denied exception
throw (new SMSException(SMSException.STATUS_NO_PERMISSION,
"sms-INSUFFICIENT_ACCESS_RIGHTS"));
}
// Since backend would check permissions, return true
return (delPermFlag);
}
// Perform delgation check
if (debug.messageEnabled()) {
debug.message("SMSEntry:getDelegationPermission :"
+ "Calling delegation service for dnName: " + dnName
+ " for permissions: " + actions);
}
if (!isAllowedByDelegation(token, dnName, actions)) {
throw (new SMSException(SMSException.STATUS_NO_PERMISSION,
"sms-INSUFFICIENT_ACCESS_RIGHTS"));
}
return (delPermFlag);
}
private static boolean isAllowed(SSOToken token, String dnName, Set actions)
throws SMSException {
// If JAXRPC return false
if (SMSJAXRPCObjectFlg) {
return (false);
}
// Return true for base DN and base services node
if (dnName.equals(baseDN) || dnName.equals(servicesDN)) {
return (true);
}
// Check for special and admin users
try {
// Normalize the user name from the token and compare it
// against the special user set and if equal returns true
// allowing the user to perform all operations.
// eg., if root_suffix is dn=iplanet, dn=com then the normalized
// dn is dc=iplanet,dc=com.
String tokenName = token.getPrincipal().getName();
if (LDAPUtils.isDN(tokenName)) {
DN tokendn = DN.valueOf(tokenName);
if (specialUserSet.contains(tokendn.toString())) {
/*
if (debug.messageEnabled()) {
debug.message("SMSEntry.isAllowed : No delegation "
+ "check needed for special users." + normTok);
}
*/
return true;
}
}
} catch (SSOException se) {
debug.error("SMSEntry.isAllowed : " + "Invalid Token: ", se);
throw (new SMSException(bundle.getString("sms-INVALID_SSO_TOKEN"),
"sms-INVALID_SSO_TOKEN"));
}
// If running in co-existence mode, return false since
// delegation service would not have been initialized
if (!ServiceManager.isConfigMigratedTo70()) {
return false;
}
return isAllowedByDelegation(token, dnName, actions);
}
private static boolean isAllowedByDelegation(SSOToken token, String dnName,
Set actions) throws SMSException {
boolean delPermFlag = true;
// Parse the DN
String[] parseTokens = parseOrgDN(dnName);
String orgName = parseTokens[0];
String subConfigName = parseTokens[1];
String configType = parseTokens[2];
String version = parseTokens[3];
String serviceName = parseTokens[4];
// Ignore permission checks for DN that donot have config type
// and subConfigName, except for sunAMRealmService and for read only
if (!serviceName.equals(REALM_SERVICE) &&
(configType.equalsIgnoreCase("*") ||
subConfigName.equalsIgnoreCase("*")) &&
(actions.size() == 1) && actions.contains(READ)) {
return (delPermFlag);
}
try {
// get orgName,serviceName,subConfigName from the parsed result.
// Call DelegatedPermission's constructor
DelegationPermission dlgPerm = new DelegationPermission(orgName,
serviceName, version, configType, subConfigName, actions,
Collections.EMPTY_MAP);
// Perform delegation check
delPermFlag = DelegationEvaluatorHolder.dlgEval.isAllowed(token, dlgPerm,
Collections.EMPTY_MAP);
if (!delPermFlag) {
// Debug the message
if (debug.warningEnabled()) {
try {
debug.warning("SMSEntry: Attempt by: "
+ token.getPrincipal().getName()
+ " to read/modify entry: " + dnName
+ " has no permissions");
} catch (SSOException ssoe) {
debug.warning("SMSEntry: Attempted to: "
+ "read/modify an entry that has invalid "
+ "delegation privilege: " + dnName, ssoe);
}
}
}
} catch (SSOException se) {
debug.error("SMSEntry.isAllowed : " + "Invalid Token: ", se);
throw (new SMSException(bundle.getString("sms-INVALID_SSO_TOKEN"),
"sms-INVALID_SSO_TOKEN"));
} catch (DelegationException de) {
debug.error("SMSEntry.isAllowed : "
+ "Invalid DelegationPermission: ", de);
throw (new SMSException(bundle
.getString("sms-invalid_delegation_privilege"),
"sms-invalid_delegation_privilege"));
}
return delPermFlag;
}
// Instance variables
private SSOToken ssoToken;
protected String dn;
protected String normalizedDN;
private boolean newEntry;
private boolean readOnly;
private Map attrSet;
private Set modSet;
// Static global variables
// Attributes with defined positions
public static final String DC_RDN = "dc";
// Constructs for placeholder nodes
public static final String DEFAULT_RDN = PLACEHOLDER_RDN + EQUALS
+ SMSUtils.DEFAULT;
static final String DELEGATION_SERVICES_RDN = PLACEHOLDER_RDN + EQUALS
+ SERVICES_NODE + COMMA;
static final String DELEGATION_SERVICES_RDN_WITH_COMMA = COMMA
+ PLACEHOLDER_RDN + EQUALS + SERVICES_NODE + COMMA;
static final int DELEGATION_SERVICES_RDN_WITH_COMMA_LEN =
DELEGATION_SERVICES_RDN_WITH_COMMA.length();
// Types of SMS objects
static final int ORG_UNIT_OBJECT = 1;
static final int SERVICE_OBJECT = 2;
static final int SERVICE_COMP_OBJECT = 3;
// Pre-defined SMS ATTRIBUTE names
public static final String ATTR_SCHEMA = "sunServiceSchema";
public static final String ATTR_PLUGIN_SCHEMA = "sunPluginSchema";
public static final String ATTR_KEYVAL = "sunKeyValue";
public static final String ATTR_XML_KEYVAL = "sunxmlKeyValue";
public static final String ATTR_OBJECTCLASS = "objectclass";
public static final String ATTR_PRIORITY = "sunsmspriority";
public static final String ATTR_SERVICE_ID = "sunserviceID";
public static final String ATTR_LABELED_URI = "labeledURI";
public static final String ATTR_MODIFY_TIMESTAMP = "modifytimestamp";
public static final String[] SMS_ATTRIBUTES = { PLACEHOLDER_RDN,
ATTR_SCHEMA, ATTR_PLUGIN_SCHEMA, ATTR_KEYVAL, ATTR_XML_KEYVAL,
ATTR_OBJECTCLASS, ATTR_PRIORITY, ATTR_SERVICE_ID, ATTR_LABELED_URI,
ATTR_MODIFY_TIMESTAMP };
// Object classes to identify the objects
public static final String OC_TOP = "top";
public static final String OC_ORG_UNIT = "organizationalunit";
public static final String OC_SERVICE = "sunService";
public static final String OC_REALM_SERVICE = "sunRealmService";
public static final String OC_SERVICE_COMP = "sunServiceComponent";
public static final String SMS_SERVER_GROUP = "sms";
// Internal hidden realms used for storing delegation policies
public static final String SUN_INTERNAL_REALM_NAME = "sunamhiddenrealm";
public static final String SUN_INTERNAL_REALM_PREFIX = ORGANIZATION_RDN
+ EQUALS + SUN_INTERNAL_REALM_NAME;
public static final String SUN_INTERNAL_REALM_PREFIX2 = "/" +
SUN_INTERNAL_REALM_NAME;
// Service name for Realm management used for delegation
public static final String REALM_SERVICE = "sunAMRealmService";
// Pre-defined filters
protected static final String FILTER_PATTERN_ALL = "(&(&(objectclass="
+ OC_TOP + ")(" + PLACEHOLDER_RDN + "={0}))" + "(&(objectclass="
+ OC_TOP + ")(" + ATTR_SERVICE_ID + "={1})))";
protected static final String FILTER_PATTERN = "(&(objectclass=" + OC_TOP
+ ")(" + PLACEHOLDER_RDN + "={0}))";
protected static final String FILTER_PATTERN_SERVICE = "(&(objectclass="
+ OC_SERVICE + ")(" + PLACEHOLDER_RDN + "={0})(" + PLACEHOLDER_RDN
+ "={1}))";
public static final String FILTER_SERVICE_COMPONENTS = "(|(objectclass="
+ OC_SERVICE + ")(objectclass=" + OC_SERVICE_COMP + "))";
}