| /* |
| * 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; |
| } |
| } |