| /* |
| * 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.server.core.cache; |
| |
| |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| import javax.naming.directory.SearchControls; |
| |
| import net.sf.ehcache.Cache; |
| import net.sf.ehcache.Element; |
| |
| import org.apache.directory.server.constants.ServerDNConstants; |
| import org.apache.directory.server.core.CoreSession; |
| import org.apache.directory.server.core.DirectoryService; |
| import org.apache.directory.server.core.filtering.EntryFilteringCursor; |
| import org.apache.directory.server.core.interceptor.context.SearchOperationContext; |
| import org.apache.directory.server.core.partition.PartitionNexus; |
| import org.apache.directory.server.i18n.I18n; |
| import org.apache.directory.shared.ldap.constants.SchemaConstants; |
| import org.apache.directory.shared.ldap.entry.Entry; |
| import org.apache.directory.shared.ldap.entry.EntryAttribute; |
| import org.apache.directory.shared.ldap.entry.Modification; |
| import org.apache.directory.shared.ldap.entry.ModificationOperation; |
| import org.apache.directory.shared.ldap.entry.StringValue; |
| import org.apache.directory.shared.ldap.entry.Value; |
| import org.apache.directory.shared.ldap.exception.LdapException; |
| import org.apache.directory.shared.ldap.exception.LdapOperationException; |
| import org.apache.directory.shared.ldap.filter.BranchNode; |
| import org.apache.directory.shared.ldap.filter.EqualityNode; |
| import org.apache.directory.shared.ldap.filter.OrNode; |
| import org.apache.directory.shared.ldap.message.AliasDerefMode; |
| import org.apache.directory.shared.ldap.name.DN; |
| import org.apache.directory.shared.ldap.schema.AttributeType; |
| import org.apache.directory.shared.ldap.schema.SchemaManager; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| |
| /** |
| * A cache for tracking static group membership. |
| * |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| */ |
| public class GroupCache |
| { |
| /** the logger for this class */ |
| private static final Logger LOG = LoggerFactory.getLogger( GroupCache.class ); |
| |
| /** Speedup for logs */ |
| private static final boolean IS_DEBUG = LOG.isDebugEnabled(); |
| |
| /** a handle on the partition nexus */ |
| private final PartitionNexus nexus; |
| |
| /** A storage for the ObjectClass attributeType */ |
| private AttributeType OBJECT_CLASS_AT; |
| |
| /** A storage for the member attributeType */ |
| private AttributeType MEMBER_AT; |
| |
| /** A storage for the uniqueMember attributeType */ |
| private AttributeType UNIQUE_MEMBER_AT; |
| |
| /** |
| * the schema manager |
| */ |
| private SchemaManager schemaManager; |
| |
| /** the normalized dn of the administrators group */ |
| private DN administratorsGroupDn; |
| |
| private static final Set<DN> EMPTY_GROUPS = new HashSet<DN>(); |
| |
| /** String key for the DN of a group to a Set (HashSet) for the Strings of member DNs */ |
| private Cache ehCache; |
| |
| /** |
| * Creates a static group cache. |
| * |
| * @param directoryService the directory service core |
| * @throws LdapException if there are failures on initialization |
| */ |
| public GroupCache( DirectoryService dirService ) throws LdapException |
| { |
| schemaManager = dirService.getSchemaManager(); |
| nexus = dirService.getPartitionNexus(); |
| OBJECT_CLASS_AT = schemaManager.getAttributeType( SchemaConstants.OBJECT_CLASS_AT ); |
| MEMBER_AT = schemaManager.getAttributeType( SchemaConstants.MEMBER_AT ); |
| UNIQUE_MEMBER_AT = schemaManager.getAttributeType( SchemaConstants.UNIQUE_MEMBER_AT ); |
| |
| // stuff for dealing with the admin group |
| administratorsGroupDn = parseNormalized( ServerDNConstants.ADMINISTRATORS_GROUP_DN ); |
| |
| this.ehCache = dirService.getCacheService().getCache( "groupCache" ); |
| |
| initialize( dirService.getAdminSession() ); |
| } |
| |
| |
| private DN parseNormalized( String name ) throws LdapException |
| { |
| DN dn = new DN( name, schemaManager ); |
| return dn; |
| } |
| |
| |
| private void initialize( CoreSession session ) throws LdapException |
| { |
| // search all naming contexts for static groups and generate |
| // normalized sets of members to cache within the map |
| |
| Set<String> suffixes = nexus.listSuffixes(); |
| |
| for ( String suffix:suffixes ) |
| { |
| // moving the filter creation to inside loop to fix DIRSERVER-1121 |
| // didn't use clone() cause it is creating List objects, which IMO is not worth calling |
| // in this initialization phase |
| BranchNode filter = new OrNode(); |
| filter.addNode( new EqualityNode<String>( OBJECT_CLASS_AT, new StringValue( |
| SchemaConstants.GROUP_OF_NAMES_OC ) ) ); |
| filter.addNode( new EqualityNode<String>( OBJECT_CLASS_AT, new StringValue( |
| SchemaConstants.GROUP_OF_UNIQUE_NAMES_OC ) ) ); |
| |
| DN baseDn = new DN( suffix, schemaManager ); |
| SearchControls ctls = new SearchControls(); |
| ctls.setSearchScope( SearchControls.SUBTREE_SCOPE ); |
| |
| SearchOperationContext searchOperationContext = new SearchOperationContext( session, |
| baseDn, filter, ctls ); |
| searchOperationContext.setAliasDerefMode( AliasDerefMode.DEREF_ALWAYS ); |
| EntryFilteringCursor results = nexus.search( searchOperationContext ); |
| |
| try |
| { |
| while ( results.next() ) |
| { |
| Entry result = results.get(); |
| DN groupDn = result.getDn().normalize( schemaManager.getNormalizerMapping() ); |
| EntryAttribute members = getMemberAttribute( result ); |
| |
| if ( members != null ) |
| { |
| Set<String> memberSet = new HashSet<String>( members.size() ); |
| addMembers( memberSet, members ); |
| |
| Element cacheElement = new Element( groupDn.getNormName(), memberSet ); |
| ehCache.put( cacheElement ); |
| } |
| else |
| { |
| LOG.warn( "Found group '{}' without any member or uniqueMember attributes", groupDn.getName() ); |
| } |
| } |
| |
| results.close(); |
| } |
| catch ( Exception e ) |
| { |
| LdapOperationException le = new LdapOperationException( e.getMessage() ); |
| le.initCause( e ); |
| throw le; |
| } |
| } |
| |
| if ( IS_DEBUG ) |
| { |
| LOG.debug( "group cache contents on startup:\n {}", ehCache.getAllWithLoader( ehCache.getKeys(), null ) ); |
| } |
| } |
| |
| |
| /** |
| * Gets the member attribute regardless of whether groupOfNames or |
| * groupOfUniqueNames is used. |
| * |
| * @param entry the entry inspected for member attributes |
| * @return the member attribute |
| */ |
| private EntryAttribute getMemberAttribute( Entry entry ) throws LdapException |
| { |
| EntryAttribute oc = entry.get( OBJECT_CLASS_AT ); |
| |
| if ( oc == null ) |
| { |
| EntryAttribute member = entry.get( MEMBER_AT ); |
| |
| if ( member != null ) |
| { |
| return member; |
| } |
| |
| EntryAttribute uniqueMember = entry.get( UNIQUE_MEMBER_AT ); |
| |
| if ( uniqueMember != null ) |
| { |
| return uniqueMember; |
| } |
| |
| return null; |
| } |
| |
| if ( oc.contains( SchemaConstants.GROUP_OF_NAMES_OC ) || oc.contains( SchemaConstants.GROUP_OF_NAMES_OC_OID ) ) |
| { |
| return entry.get( MEMBER_AT ); |
| } |
| |
| if ( oc.contains( SchemaConstants.GROUP_OF_UNIQUE_NAMES_OC ) |
| || oc.contains( SchemaConstants.GROUP_OF_UNIQUE_NAMES_OC_OID ) ) |
| { |
| return entry.get( UNIQUE_MEMBER_AT ); |
| } |
| |
| return null; |
| } |
| |
| |
| /** |
| * Adds normalized member DNs to the set of normalized member names. |
| * |
| * @param memberSet the set of member Dns (Strings) |
| * @param members the member attribute values being added |
| * @throws LdapException if there are problems accessing the attr values |
| */ |
| private void addMembers( Set<String> memberSet, EntryAttribute members ) throws LdapException |
| { |
| for ( Value<?> value : members ) |
| { |
| |
| // get and normalize the DN of the member |
| String memberDn = value.getString(); |
| |
| try |
| { |
| memberDn = parseNormalized( memberDn ).getNormName(); |
| } |
| catch ( LdapException e ) |
| { |
| LOG.warn( "Malformed member DN in groupOf[Unique]Names entry. Member not added to GroupCache.", e ); |
| } |
| |
| memberSet.add( memberDn ); |
| } |
| } |
| |
| |
| /** |
| * Removes a set of member names from an existing set. |
| * |
| * @param memberSet the set of normalized member DNs |
| * @param members the set of member values |
| * @throws LdapException if there are problems accessing the attr values |
| */ |
| private void removeMembers( Set<String> memberSet, EntryAttribute members ) throws LdapException |
| { |
| for ( Value<?> value : members ) |
| { |
| // get and normalize the DN of the member |
| String memberDn = value.getString(); |
| |
| try |
| { |
| memberDn = parseNormalized( memberDn ).getNormName(); |
| } |
| catch ( LdapException e ) |
| { |
| LOG.warn( "Malformed member DN in groupOf[Unique]Names entry. Member not removed from GroupCache.", e ); |
| } |
| |
| memberSet.remove( memberDn ); |
| } |
| } |
| |
| |
| /** |
| * Adds a groups members to the cache. Called by interceptor to account for new |
| * group additions. |
| * |
| * @param name the user provided name for the group entry |
| * @param entry the group entry's attributes |
| * @throws LdapException if there are problems accessing the attr values |
| */ |
| public void groupAdded( DN name, Entry entry ) throws LdapException |
| { |
| EntryAttribute members = getMemberAttribute( entry ); |
| |
| if ( members == null ) |
| { |
| return; |
| } |
| |
| Set<String> memberSet = new HashSet<String>( members.size() ); |
| addMembers( memberSet, members ); |
| |
| Element cacheElement = new Element( name.getNormName(), memberSet ); |
| ehCache.put( cacheElement ); |
| |
| if ( IS_DEBUG ) |
| { |
| LOG.debug( "group cache contents after adding '{}' :\n {}", name.getName(), ehCache.getAllWithLoader( ehCache.getKeys(), null ) ); |
| } |
| } |
| |
| |
| /** |
| * Deletes a group's members from the cache. Called by interceptor to account for |
| * the deletion of groups. |
| * |
| * @param name the normalized DN of the group entry |
| * @param entry the attributes of entry being deleted |
| */ |
| public void groupDeleted( DN name, Entry entry ) throws LdapException |
| { |
| EntryAttribute members = getMemberAttribute( entry ); |
| |
| if ( members == null ) |
| { |
| return; |
| } |
| |
| ehCache.remove( name.getNormName() ); |
| |
| if ( IS_DEBUG ) |
| { |
| LOG.debug( "group cache contents after deleting '{}' :\n {}", name.getName(), ehCache.getAllWithLoader( ehCache.getKeys(), null ) ); |
| } |
| } |
| |
| |
| /** |
| * Utility method to modify a set of member names based on a modify operation |
| * that changes the members of a group. |
| * |
| * @param memberSet the set of members to be altered |
| * @param modOp the type of modify operation being performed |
| * @param members the members being added, removed or replaced |
| * @throws LdapException if there are problems accessing attribute values |
| */ |
| private void modify( Set<String> memberSet, ModificationOperation modOp, EntryAttribute members ) |
| throws LdapException |
| { |
| |
| switch ( modOp ) |
| { |
| case ADD_ATTRIBUTE: |
| addMembers( memberSet, members ); |
| break; |
| |
| case REPLACE_ATTRIBUTE: |
| if ( members.size() > 0 ) |
| { |
| memberSet.clear(); |
| addMembers( memberSet, members ); |
| } |
| |
| break; |
| |
| case REMOVE_ATTRIBUTE: |
| removeMembers( memberSet, members ); |
| break; |
| |
| default: |
| throw new InternalError( I18n.err( I18n.ERR_235, modOp ) ); |
| } |
| } |
| |
| |
| /** |
| * Modifies the cache to reflect changes via modify operations to the group entries. |
| * Called by the interceptor to account for modify ops on groups. |
| * |
| * @param name the normalized name of the group entry modified |
| * @param mods the modification operations being performed |
| * @param entry the group entry being modified |
| * @throws LdapException if there are problems accessing attribute values |
| */ |
| public void groupModified( DN name, List<Modification> mods, Entry entry, SchemaManager schemaManager ) |
| throws LdapException |
| { |
| EntryAttribute members = null; |
| String memberAttrId = null; |
| EntryAttribute oc = entry.get( OBJECT_CLASS_AT ); |
| |
| if ( oc.contains( SchemaConstants.GROUP_OF_NAMES_OC ) ) |
| { |
| members = entry.get( MEMBER_AT ); |
| memberAttrId = SchemaConstants.MEMBER_AT; |
| } |
| |
| if ( oc.contains( SchemaConstants.GROUP_OF_UNIQUE_NAMES_OC ) ) |
| { |
| members = entry.get( UNIQUE_MEMBER_AT ); |
| memberAttrId = SchemaConstants.UNIQUE_MEMBER_AT; |
| } |
| |
| if ( members == null ) |
| { |
| return; |
| } |
| |
| for ( Modification modification : mods ) |
| { |
| if ( memberAttrId.equalsIgnoreCase( modification.getAttribute().getId() ) ) |
| { |
| Set<String> memberSet = ( Set<String> ) ehCache.get( name.getNormName() ).getValue(); |
| |
| if ( memberSet != null ) |
| { |
| modify( memberSet, modification.getOperation(), modification.getAttribute() ); |
| } |
| |
| break; |
| } |
| } |
| |
| if ( IS_DEBUG ) |
| { |
| LOG.debug( "group cache contents after modifying '{}' :\n {}", name.getName(), ehCache.getAllWithLoader( ehCache.getKeys(), null ) ); |
| } |
| } |
| |
| |
| /** |
| * Modifies the cache to reflect changes via modify operations to the group entries. |
| * Called by the interceptor to account for modify ops on groups. |
| * |
| * @param name the normalized name of the group entry modified |
| * @param modOp the modify operation being performed |
| * @param mods the modifications being performed |
| * @throws LdapException if there are problems accessing attribute values |
| */ |
| public void groupModified( DN name, ModificationOperation modOp, Entry mods ) throws LdapException |
| { |
| EntryAttribute members = getMemberAttribute( mods ); |
| |
| if ( members == null ) |
| { |
| return; |
| } |
| |
| Set<String> memberSet = ( Set<String> ) ehCache.get( name.getNormName() ).getValue(); |
| |
| if ( memberSet != null ) |
| { |
| modify( memberSet, modOp, members ); |
| } |
| |
| if ( IS_DEBUG ) |
| { |
| LOG.debug( "group cache contents after modifying '{}' :\n {}", name.getName(), ehCache.getAllWithLoader( ehCache.getKeys(), null ) ); |
| } |
| } |
| |
| |
| /** |
| * An optimization. By having this method here we can directly access the group |
| * membership information and lookup to see if the principalDn is contained within. |
| * |
| * @param principalDn the normalized DN of the user to check if they are an admin |
| * @return true if the principal is an admin or the admin |
| */ |
| public final boolean isPrincipalAnAdministrator( DN principalDn ) |
| { |
| if ( principalDn.getNormName().equals( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED ) ) |
| { |
| return true; |
| } |
| |
| Set<String> members = ( Set<String> ) ehCache.get( administratorsGroupDn.getNormName() ).getValue(); |
| |
| if ( members == null ) |
| { |
| LOG.warn( "What do you mean there is no administrators group? This is bad news." ); |
| return false; |
| } |
| |
| return members.contains( principalDn.getNormName() ); |
| } |
| |
| |
| /** |
| * Gets the set of groups a user is a member of. The groups are returned |
| * as normalized Name objects within the set. |
| * |
| * @param member the member (user) to get the groups for |
| * @return a Set of Name objects representing the groups |
| * @throws LdapException if there are problems accessing attribute values |
| */ |
| public Set<DN> getGroups( String member ) throws LdapException |
| { |
| DN normMember; |
| |
| try |
| { |
| normMember = parseNormalized( member ); |
| } |
| catch ( LdapException e ) |
| { |
| LOG |
| .warn( |
| "Malformed member DN. Could not find groups for member '{}' in GroupCache. Returning empty set for groups!", |
| member, e ); |
| return EMPTY_GROUPS; |
| } |
| |
| Set<DN> memberGroups = null; |
| |
| for ( Object obj : ehCache.getKeys() ) |
| { |
| String group = ( String ) obj; |
| Set<String> members = ( Set<String> ) ehCache.get( group ).getValue(); |
| |
| if ( members == null ) |
| { |
| continue; |
| } |
| |
| if ( members.contains( normMember.getNormName() ) ) |
| { |
| if ( memberGroups == null ) |
| { |
| memberGroups = new HashSet<DN>(); |
| } |
| |
| memberGroups.add( parseNormalized( group ) ); |
| } |
| } |
| |
| if ( memberGroups == null ) |
| { |
| return EMPTY_GROUPS; |
| } |
| |
| return memberGroups; |
| } |
| |
| |
| public boolean groupRenamed( DN oldName, DN newName ) |
| { |
| Element membersElement = ehCache.get( oldName.getNormName() ); |
| |
| if ( membersElement != null ) |
| { |
| Set<String> members = ( Set<String> ) membersElement.getValue(); |
| |
| ehCache.remove( oldName.getNormName() ); |
| |
| Element cacheElement = new Element( newName.getNormName(), members ); |
| ehCache.put( cacheElement ); |
| |
| if ( IS_DEBUG ) |
| { |
| LOG.debug( "group cache contents after renaming '{}' :\n{}", oldName.getName(), ehCache.getAllWithLoader( ehCache.getKeys(), null ) ); |
| } |
| |
| return true; |
| } |
| |
| return false; |
| } |
| } |