blob: 9c21d86a63f7286e690320762f06997d67858a7a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.directory.fortress.core.impl;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.sf.ehcache.search.Attribute;
import net.sf.ehcache.search.Query;
import net.sf.ehcache.search.Result;
import net.sf.ehcache.search.Results;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.directory.api.ldap.model.constants.SchemaConstants;
import org.apache.directory.fortress.core.*;
import org.apache.directory.fortress.core.SecurityException;
import org.apache.directory.fortress.core.model.*;
import org.apache.directory.fortress.core.util.Config;
import org.apache.directory.fortress.core.util.cache.Cache;
import org.apache.directory.fortress.core.util.cache.CacheMgr;
import org.apache.directory.fortress.core.util.cache.DsdCacheEntry;
/**
* This utilty provides functionality necessary for SSD and DSD processing and cannot be called by components outside fortress.
* This class also contains utility functions for maintaining the SSD and DSD cache.
* <p>
* This class is thread safe.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
* @created September 3, 2010
*/
final class SDUtil
{
private Cache m_dsdCache;
private static final String FORTRESS_DSDS = "fortress.dsd";
private Cache m_ssdCache;
private static final String FORTRESS_SSDS = "fortress.ssd";
private SdP sp;
private static final String IS_DSD_CACHE_DISABLED_PARM = "enable.dsd.cache";
private static final String DSD_NAME = "name";
private static final String EMPTY_ELEMENT = "empty";
private static final String CONTEXT_ID = "contextId";
private static volatile SDUtil sINSTANCE = null;
static SDUtil getInstance()
{
if(sINSTANCE == null)
{
synchronized (SDUtil.class)
{
if(sINSTANCE == null)
{
sINSTANCE = new SDUtil();
}
}
}
return sINSTANCE;
}
private void init()
{
sp = new SdP();
// Get a reference to the CacheManager Singleton object:
CacheMgr cacheMgr = CacheMgr.getInstance();
// This cache contains a wrapper entry for DSD and is searchable by both DSD and Role name:
m_dsdCache = cacheMgr.getCache(FORTRESS_DSDS);
// This cache is not searchable and contains Lists of SSD objects by Role:
m_ssdCache = cacheMgr.getCache(FORTRESS_SSDS);
}
/**
* Private constructor
*
*/
private SDUtil()
{
init();
}
/**
* This method is called by AdminMgr.assignUser and is used to validate Static Separation of Duty
* constraints when assigning a role to user.
*
* @param uRole
* @throws org.apache.directory.fortress.core.SecurityException
*
*/
void validateSSD(UserRole uRole)
throws SecurityException
{
validateSSD(new User(uRole.getUserId()), new Role(uRole.getName()));
}
/**
* This method is called by AdminMgr.assignUser and is used to validate Static Separation of Duty
* constraints when assigning a role to user.
*
* @param user
* @param role
* @throws org.apache.directory.fortress.core.SecurityException
*
*/
void validateSSD(User user, Role role)
throws SecurityException
{
// get all authorized roles for user
String contextId = user.getContextId();
ReviewMgr rMgr = ReviewMgrFactory.createInstance( contextId );
Set<String> rls = rMgr.authorizedRoles( user );
checkSSD( role, rls, contextId);
}
/**
* This method is called by GroupMgr.assign and is used to validate Static Separation of Duty
* constraints when assigning a role to group.
*
* @param group
* @param role
* @throws org.apache.directory.fortress.core.SecurityException
*
*/
void validateSSD( Group group, Role role ) throws SecurityException
{
// get all authorized roles for this group
String contextId = group.getContextId();
GroupMgr groupMgr = GroupMgrFactory.createInstance(contextId);
List<UserRole> roles = groupMgr.groupRoles( group );
Set<String> rls = RoleUtil.getInstance().getInheritedRoles( roles, contextId);
// check SSD constraints
checkSSD( role, rls, contextId);
}
private void checkSSD( Role role, Set<String> authorizedRls, String contextId ) throws SecurityException
{
int matchCount;
// Need to proceed?
if (CollectionUtils.isEmpty( authorizedRls ))
{
return;
}
// get all SSD sets that contain the new role
List<SDSet> ssdSets = getSsdCache( role.getName(), contextId );
for ( SDSet ssd : ssdSets )
{
matchCount = 0;
Set<String> map = ssd.getMembers();
// iterate over every authorized role for user/group:
for ( String authRole : authorizedRls )
{
// is there a match found between authorized role and SSD set's members?
if ( map.contains( authRole ) )
{
matchCount++;
// does the match count exceed the cardinality allowed for this particular SSD set?
if ( matchCount >= ssd.getCardinality() - 1 )
{
String error = "validateSSD new role [" + role.getName() + "] validates SSD Set Name:"
+ ssd.getName() + " Cardinality:" + ssd.getCardinality();
throw new SecurityException( GlobalErrIds.SSD_VALIDATION_FAILED, error );
}
}
}
}
}
/**
* This method is called by AccessMgr.addActiveRole and is used to validate Dynamic Separation of Duty
* constraints when activating a role one at a time. For activation of multiple roles simultaneously use
* the DSD.validate API which is used during createSession sequence.
*
* @param session
* @param role
* @throws org.apache.directory.fortress.core.SecurityException
*
*/
void validateDSD(Session session, Constraint role)
throws SecurityException
{
// get all activated roles from user's session:
List<UserRole> rls = session.getRoles();
if (CollectionUtils.isEmpty( rls ))
{
// An empty list of roles was passed in the session variable.
// No need to continue.
return;
}
// get all DSD sets that contain the target role
Set<SDSet> dsdSets = getDsdCache(role.getName(), session.getContextId());
for (SDSet dsd : dsdSets)
{
// Keeps the number of matched roles to a particular DSD set.
int matchCount = 0;
// Contains the list of roles assigned to a particular DSD set.
Set<String> map = dsd.getMembers();
// iterate over every role active in session for match wth DSD members:
for (UserRole actRole : rls)
{
// is there a match found between active role in session and DSD set members?
if (map.contains(actRole.getName()))
{
// Yes, we found a match, increment the count.
matchCount++;
// Does the match count exceed the cardinality allowed for this particular DSD set?
if (matchCount >= dsd.getCardinality() - 1)
{
// Yes, the target role violates DSD cardinality rule.
String error = "validateDSD failed for role [" + role.getName() + "] DSD Set Name:" + dsd.getName() + " Cardinality:" + dsd.getCardinality();
throw new SecurityException(GlobalErrIds.DSD_VALIDATION_FAILED, error);
}
}
else // Check the parents of activated role for DSD match:
{
// Now pull the activated role's list of parents.
Set<String> parentSet = RoleUtil.getInstance().getAscendants(actRole.getName(), session.getContextId());
// Iterate over the list of parent roles:
for (String parentRole : parentSet)
{
if (map.contains(parentRole)) // is there match between parent and DSD member?
{
matchCount++;
if (matchCount >= dsd.getCardinality() - 1) // Does the counter exceed max per cardinality on this DSD set?
{
String error = "validateDSD failed for role [" + role.getName() + "] parent role [" + parentRole + "] DSD Set Name:" + dsd.getName() + " Cardinality:" + dsd.getCardinality();
throw new SecurityException(GlobalErrIds.DSD_VALIDATION_FAILED, error);
}
// Breaking out of the loop here means the DSD algorithm will only match one
// role per parent of active role candidate.
break;
}
}
}
}
}
}
/**
* Given DSD entry name, clear its corresponding object values from the cache.
*
* @param name contains the name of object to be cleared.
* @param contextId maps to sub-tree in DIT, e.g. ou=contextId, dc=example, dc=com. *
* @throws SecurityException in the event of system or rule violation.
*/
void clearDsdCacheEntry(String name, String contextId)
{
Attribute<String> context = m_dsdCache.getSearchAttribute(CONTEXT_ID);
Attribute<String> dsdName = m_dsdCache.getSearchAttribute(DSD_NAME);
Query query = m_dsdCache.createQuery();
query.includeKeys();
query.includeValues();
query.addCriteria(dsdName.eq(name).and(context.eq(contextId)));
Results results = query.execute();
for (Result result : results.all())
{
m_dsdCache.clear(result.getKey());
}
}
/**
* Given a role name, return the set of DSD's that have a matching member.
*
* @param name contains name of authorized Role used to search the cache.
* @param contextId maps to sub-tree in DIT, e.g. ou=contextId, dc=example, dc=com.
* @return un-ordered set of matching DSD's.
* @throws SecurityException in the event of system or rule violation.
*/
private Set<SDSet> getDsdCache(String name, String contextId)
throws SecurityException
{
contextId = getContextId(contextId);
Set<SDSet> finalSet = new HashSet<>();
Attribute<String> context = m_dsdCache.getSearchAttribute(CONTEXT_ID);
Attribute<String> member = m_dsdCache.getSearchAttribute(SchemaConstants.MEMBER_AT);
Query query = m_dsdCache.createQuery();
query.includeKeys();
query.includeValues();
query.addCriteria(member.eq(name).and(context.eq(contextId)));
Results results = query.execute();
boolean empty = false;
for (Result result : results.all())
{
DsdCacheEntry entry = (DsdCacheEntry) result.getValue();
if (!entry.isEmpty())
{
finalSet.add(entry.getSdSet());
finalSet = putDsdCache(name, contextId);
}
else
{
empty = true;
}
finalSet.add(entry.getSdSet());
}
// If nothing was found in the cache, determine if it needs to be seeded:
if (finalSet.size() == 0 && !empty)
{
finalSet = putDsdCache(name, contextId);
}
return finalSet;
}
/**
* Given a Set of authorized Roles, return the set of DSD's that have matching members.
*
* @param authorizedRoleSet contains an un-order Set of authorized Roles.
* @param contextId maps to sub-tree in DIT, e.g. ou=contextId, dc=example, dc=com.
* @return un-ordered set of matching DSD's.
* @throws SecurityException in the event of system or rule violation.
*/
Set<SDSet> getDsdCache(Set<String> authorizedRoleSet, String contextId)
throws SecurityException
{
contextId = getContextId(contextId);
Set<SDSet> dsdRetSets = new HashSet<>();
// Need to proceed?
if (!CollectionUtils.isNotEmpty( authorizedRoleSet ))
{
return dsdRetSets;
}
// Was the DSD Cache switched off?
boolean isCacheDisabled = Config.getInstance().getBoolean(IS_DSD_CACHE_DISABLED_PARM, false);
// If so, get DSD's from LDAP:
if (isCacheDisabled)
{
SDSet sdSet = new SDSet();
sdSet.setType(SDSet.SDType.DYNAMIC);
sdSet.setContextId(contextId);
dsdRetSets = sp.search(authorizedRoleSet, sdSet);
}
// Search the DSD cache for matching Role members:
else
{
// Search on roleName attribute which maps to 'member' attr on the cache record:
Attribute<String> member = m_dsdCache.getSearchAttribute(SchemaConstants.MEMBER_AT);
Attribute<String> context = m_dsdCache.getSearchAttribute(CONTEXT_ID);
Query query = m_dsdCache.createQuery();
query.includeKeys();
query.includeValues();
// Add the passed in authorized Role names to this cache query:
Set<String> roles = new HashSet<>(authorizedRoleSet);
query.addCriteria(member.in(roles).and(context.eq(contextId)));
// Return all DSD cache entries that match roleName to the 'member' attribute in cache entry:
Results results = query.execute();
for (Result result : results.all())
{
DsdCacheEntry entry = (DsdCacheEntry) result.getValue();
// Do not add dummy DSD sets to the final list:
if (!entry.isEmpty())
{
dsdRetSets.add(entry.getSdSet());
}
// Remove role member from authorizedRoleSet to preclude from upcoming DSD search:
//authorizedRoleSet.remove(entry.getMember());
}
// Authorized roles remaining in this set correspond to missed cache hits from above:
if (authorizedRoleSet.size() > 0)
{
dsdRetSets = putDsdCache(authorizedRoleSet, contextId);
}
}
return dsdRetSets;
}
/**
* Get the matching DSD's from directory and add to the cache (if found). If matching DSD not found,
* add dummy entry to cache to prevent repeated searches.
*
* @param authorizedRoleSet contains set of Roles used to search directory for matching DSD's.
* @param contextId maps to sub-tree in DIT, e.g. ou=contextId, dc=example, dc=com.
* @return List of DSD's who have matching Role members.
* @throws SecurityException in the event of system or rule violation.
*/
private Set<SDSet> putDsdCache(Set<String> authorizedRoleSet, String contextId)
throws SecurityException
{
contextId = getContextId(contextId);
Set<SDSet> dsdSets = new HashSet<>();
// Search the DSD's iteratively to seed the DSD cache by Role name:
for (String roleName : authorizedRoleSet)
{
Role role = new Role(roleName);
role.setContextId(contextId);
List<SDSet> dsdList = sp.search(role, SDSet.SDType.DYNAMIC);
if (CollectionUtils.isNotEmpty( dsdList ))
{
for (SDSet dsd : dsdList)
{
dsd.setContextId(contextId);
Set<String> members = dsd.getMembers();
if (members != null)
{
// Seed the cache with DSD objects mapped to role name:
for (String member : members)
{
String key = buildKey(dsd.getName(), member);
DsdCacheEntry entry = new DsdCacheEntry(member, dsd, false);
entry.setName(dsd.getName());
m_dsdCache.put(getKey(key, contextId), entry);
}
}
}
// Maintain the set of DSD's to be returned to the caller:
dsdSets.addAll(dsdList);
}
else
{
// Seed the cache with dummy entry for a Role that is not referenced by DSD:
String key = buildKey(EMPTY_ELEMENT, roleName);
SDSet sdSet = new SDSet();
sdSet.setType(SDSet.SDType.DYNAMIC);
sdSet.setName(key);
sdSet.setMember(roleName);
sdSet.setContextId(contextId);
DsdCacheEntry entry = new DsdCacheEntry(roleName, sdSet, true);
entry.setName(key);
m_dsdCache.put(getKey(sdSet.getName(), contextId), entry);
}
}
return dsdSets;
}
/**
* Get the matching DSD's from directory and add to the cache (if found). If matching DSD not found,
* add dummy entry to cache to prevent repeated searches.
*
* @param roleName of Role is used to search directory for matching DSD's.
* @param contextId maps to sub-tree in DIT, e.g. ou=contextId, dc=example, dc=com.
* @return Set of DSD's who have matching Role member.
* @throws SecurityException in the event of system or rule violation.
*/
private Set<SDSet> putDsdCache(String roleName, String contextId)
throws SecurityException
{
contextId = getContextId(contextId);
Role role = new Role(roleName);
role.setContextId(contextId);
List<SDSet> dsdList = sp.search(role, SDSet.SDType.DYNAMIC);
Set<SDSet> finalSet = new HashSet<>(dsdList);
if ( CollectionUtils.isNotEmpty( dsdList ))
{
for (SDSet dsd : dsdList)
{
dsd.setContextId(contextId);
Set<String> members = dsd.getMembers();
if (members != null)
{
// Seed the cache with DSD objects mapped to role name:
for (String member : members)
{
String key = buildKey(dsd.getName(), member);
DsdCacheEntry entry = new DsdCacheEntry(member, dsd, false);
entry.setName(dsd.getName());
m_dsdCache.put(getKey(key, contextId), entry);
}
}
}
}
else
{
// Seed the cache with dummy entry for Role that does not have DSD:
String key = buildKey(EMPTY_ELEMENT, roleName);
SDSet sdSet = new SDSet();
sdSet.setType(SDSet.SDType.DYNAMIC);
sdSet.setName(key);
sdSet.setMember(roleName);
sdSet.setContextId(contextId);
DsdCacheEntry entry = new DsdCacheEntry(roleName, sdSet, true);
entry.setName(key);
m_dsdCache.put(getKey(sdSet.getName(), contextId), entry);
}
return finalSet;
}
/**
* Given entry name, clear its corresponding object value from the cache.
*
* @param name contains the name of object to be cleared.
* @param contextId maps to sub-tree in DIT, e.g. ou=contextId, dc=example, dc=com.
* @throws SecurityException in the event of system or rule violation.
*/
void clearSsdCacheEntry(String name, String contextId)
{
contextId = getContextId(contextId);
m_ssdCache.clear(getKey(name, contextId));
}
/**
* Get the matching SSD's from directory and add to the cache (if found).
*
* @param name of Role is used to search directory for matching SSD's.
* @param contextId maps to sub-tree in DIT, e.g. ou=contextId, dc=example, dc=com.
* @return List of SSD's who have matching Role member.
* @throws SecurityException in the event of system or rule violation.
*/
private List<SDSet> putSsdCache(String name, String contextId)
throws SecurityException
{
Role role = new Role(name);
role.setContextId(contextId);
List<SDSet> ssdSets = sp.search(role, SDSet.SDType.STATIC);
m_ssdCache.put(getKey(name, contextId), ssdSets);
return ssdSets;
}
/**
* Look in cache for matching List of SSD's.
*
* @param name of Role is used to search directory for matching SSD's.
* @param contextId maps to sub-tree in DIT, e.g. ou=contextId, dc=example, dc=com.
* @return List of SSD's who have matching Role member.
* @throws SecurityException in the event of system or rule violation.
*/
private List<SDSet> getSsdCache(String name, String contextId)
throws SecurityException
{
List<SDSet> ssdSets = (List<SDSet>) m_ssdCache.get(getKey(name, contextId));
if (ssdSets == null)
{
ssdSets = putSsdCache(name, contextId);
}
return ssdSets;
}
/**
*
* @param parm1
* @param parm2
* @return
*/
private static String buildKey(String parm1, String parm2)
{
return parm1 + ":" + parm2;
}
/**
*
* @param name
* @param contextId
* @return
*/
private static String getKey(String name, String contextId)
{
contextId = getContextId(contextId);
return name += ":" + contextId;
}
/**
*
* @param contextId
* @return
*/
private static String getContextId(String contextId)
{
String szContextId = GlobalIds.HOME;
if( StringUtils.isNotEmpty( contextId ) && !contextId.equals(GlobalIds.NULL))
{
szContextId = contextId;
}
return szContextId;
}
}