001    /*
002     *  Licensed to the Apache Software Foundation (ASF) under one
003     *  or more contributor license agreements.  See the NOTICE file
004     *  distributed with this work for additional information
005     *  regarding copyright ownership.  The ASF licenses this file
006     *  to you under the Apache License, Version 2.0 (the
007     *  "License"); you may not use this file except in compliance
008     *  with the License.  You may obtain a copy of the License at
009     *  
010     *    http://www.apache.org/licenses/LICENSE-2.0
011     *  
012     *  Unless required by applicable law or agreed to in writing,
013     *  software distributed under the License is distributed on an
014     *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     *  KIND, either express or implied.  See the License for the
016     *  specific language governing permissions and limitations
017     *  under the License. 
018     *  
019     */
020    package org.apache.directory.server.core.authz;
021    
022    
023    import java.text.ParseException;
024    import java.util.ArrayList;
025    import java.util.Collections;
026    import java.util.HashMap;
027    import java.util.List;
028    import java.util.Map;
029    import java.util.Set;
030    
031    import javax.naming.NamingException;
032    import javax.naming.directory.SearchControls;
033    
034    import org.apache.directory.server.core.CoreSession;
035    import org.apache.directory.server.core.filtering.EntryFilteringCursor;
036    import org.apache.directory.server.core.interceptor.context.SearchOperationContext;
037    import org.apache.directory.server.core.partition.PartitionNexus;
038    import org.apache.directory.server.i18n.I18n;
039    import org.apache.directory.shared.ldap.aci.ACIItem;
040    import org.apache.directory.shared.ldap.aci.ACIItemParser;
041    import org.apache.directory.shared.ldap.aci.ACITuple;
042    import org.apache.directory.shared.ldap.constants.SchemaConstants;
043    import org.apache.directory.shared.ldap.entry.StringValue;
044    import org.apache.directory.shared.ldap.entry.EntryAttribute;
045    import org.apache.directory.shared.ldap.entry.Modification;
046    import org.apache.directory.shared.ldap.entry.ServerEntry;
047    import org.apache.directory.shared.ldap.entry.Value;
048    import org.apache.directory.shared.ldap.exception.LdapException;
049    import org.apache.directory.shared.ldap.exception.LdapSchemaViolationException;
050    import org.apache.directory.shared.ldap.filter.EqualityNode;
051    import org.apache.directory.shared.ldap.filter.ExprNode;
052    import org.apache.directory.shared.ldap.message.AliasDerefMode;
053    import org.apache.directory.shared.ldap.message.ResultCodeEnum;
054    import org.apache.directory.shared.ldap.name.DN;
055    import org.apache.directory.shared.ldap.name.NameComponentNormalizer;
056    import org.apache.directory.shared.ldap.schema.AttributeType;
057    import org.apache.directory.shared.ldap.schema.SchemaManager;
058    import org.apache.directory.shared.ldap.schema.normalizers.ConcreteNameComponentNormalizer;
059    import org.slf4j.Logger;
060    import org.slf4j.LoggerFactory;
061    
062    
063    /**
064     * A cache for tuple sets which responds to specific events to perform
065     * cache house keeping as access control subentries are added, deleted
066     * and modified.
067     *
068     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
069     * @version $Rev: 928945 $
070     */
071    public class TupleCache
072    {
073        /** the logger for this class */
074        private static final Logger LOG = LoggerFactory.getLogger( TupleCache.class );
075    
076        /** a map of strings to ACITuple collections */
077        private final Map<String, List<ACITuple>> tuples = new HashMap<String, List<ACITuple>>();
078    
079        /** a handle on the partition nexus */
080        private final PartitionNexus nexus;
081    
082        /** a normalizing ACIItem parser */
083        private final ACIItemParser aciParser;
084    
085        /** A storage for the PrescriptiveACI attributeType */
086        private AttributeType prescriptiveAciAT;
087    
088    
089        /**
090         * Creates a ACITuple cache.
091         *
092         * @param directoryService the context factory configuration for the server
093         * @throws NamingException if initialization fails
094         */
095        public TupleCache( CoreSession session ) throws Exception
096        {
097            SchemaManager schemaManager = session.getDirectoryService().getSchemaManager();
098            this.nexus = session.getDirectoryService().getPartitionNexus();
099            NameComponentNormalizer ncn = new ConcreteNameComponentNormalizer( schemaManager );
100            aciParser = new ACIItemParser( ncn, schemaManager.getNormalizerMapping() );
101            prescriptiveAciAT = schemaManager.lookupAttributeTypeRegistry( SchemaConstants.PRESCRIPTIVE_ACI_AT );
102            initialize( session );
103        }
104    
105    
106        private DN parseNormalized( SchemaManager schemaManager, String name ) throws LdapException
107        {
108            DN dn = new DN( name );
109            dn.normalize( schemaManager.getNormalizerMapping() );
110            return dn;
111        }
112    
113    
114        private void initialize( CoreSession session ) throws Exception
115        {
116            // search all naming contexts for access control subentenries
117            // generate ACITuple Arrays for each subentry
118            // add that subentry to the hash
119            Set<String> suffixes = nexus.listSuffixes( null );
120    
121            for ( String suffix:suffixes )
122            {
123                DN baseDn = parseNormalized( session.getDirectoryService().getSchemaManager(), suffix );
124                ExprNode filter = new EqualityNode<String>( SchemaConstants.OBJECT_CLASS_AT, 
125                    new StringValue( SchemaConstants.ACCESS_CONTROL_SUBENTRY_OC ) );
126                SearchControls ctls = new SearchControls();
127                ctls.setSearchScope( SearchControls.SUBTREE_SCOPE );
128                
129                SearchOperationContext searchOperationContext = new SearchOperationContext( session,
130                    baseDn, filter, ctls );
131                searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES );
132    
133                EntryFilteringCursor results = nexus.search( searchOperationContext );
134    
135                while ( results.next() )
136                {
137                    ServerEntry result = results.get();
138                    DN subentryDn = result.getDn().normalize( session.getDirectoryService().getSchemaManager().
139                            getNormalizerMapping() );
140                    EntryAttribute aci = result.get( prescriptiveAciAT );
141    
142                    if ( aci == null )
143                    {
144                        LOG.warn( "Found accessControlSubentry '" + subentryDn + "' without any "
145                            + SchemaConstants.PRESCRIPTIVE_ACI_AT );
146                        continue;
147                    }
148    
149                    subentryAdded( subentryDn, result );
150                }
151    
152                results.close();
153            }
154        }
155    
156    
157        private boolean hasPrescriptiveACI( ServerEntry entry ) throws LdapException
158        {
159            // only do something if the entry contains prescriptiveACI
160            EntryAttribute aci = entry.get( prescriptiveAciAT );
161    
162            if ( aci == null )
163            {
164                if ( entry.contains( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.ACCESS_CONTROL_SUBENTRY_OC )
165                    || entry.contains( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.ACCESS_CONTROL_SUBENTRY_OC_OID ) )
166                {
167                    // should not be necessary because of schema interceptor but schema checking
168                    // can be turned off and in this case we must protect against being able to
169                    // add access control information to anything other than an AC subentry
170                    throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, "" );
171                }
172                else
173                {
174                    return false;
175                }
176            }
177    
178            return true;
179        }
180    
181    
182        public void subentryAdded( DN normName, ServerEntry entry ) throws LdapException
183        {
184            // only do something if the entry contains prescriptiveACI
185            EntryAttribute aciAttr = entry.get( prescriptiveAciAT );
186    
187            if ( !hasPrescriptiveACI( entry ) )
188            {
189                return;
190            }
191    
192            List<ACITuple> entryTuples = new ArrayList<ACITuple>();
193    
194            for ( Value<?> value : aciAttr )
195            {
196                String aci = value.getString();
197                ACIItem item = null;
198    
199                try
200                {
201                    item = aciParser.parse( aci );
202                    entryTuples.addAll( item.toTuples() );
203                }
204                catch ( ParseException e )
205                {
206                    String msg = I18n.err( I18n.ERR_28, item );
207                    LOG.error( msg, e );
208    
209                    // do not process this ACI Item because it will be null
210                    // continue on to process the next ACI item in the entry
211                }
212            }
213    
214            tuples.put( normName.getNormName(), entryTuples );
215        }
216    
217    
218        public void subentryDeleted( DN normName, ServerEntry entry ) throws LdapException
219        {
220            if ( !hasPrescriptiveACI( entry ) )
221            {
222                return;
223            }
224    
225            tuples.remove( normName.toString() );
226        }
227    
228    
229        public void subentryModified( DN normName, List<Modification> mods, ServerEntry entry ) throws LdapException
230        {
231            if ( !hasPrescriptiveACI( entry ) )
232            {
233                return;
234            }
235    
236            for ( Modification mod : mods )
237            {
238                if ( mod.getAttribute().instanceOf( SchemaConstants.PRESCRIPTIVE_ACI_AT ) )
239                {
240                    subentryDeleted( normName, entry );
241                    subentryAdded( normName, entry );
242                }
243            }
244        }
245    
246    
247        public void subentryModified( DN normName, ServerEntry mods, ServerEntry entry ) throws LdapException
248        {
249            if ( !hasPrescriptiveACI( entry ) )
250            {
251                return;
252            }
253    
254            if ( mods.get( prescriptiveAciAT ) != null )
255            {
256                subentryDeleted( normName, entry );
257                subentryAdded( normName, entry );
258            }
259        }
260    
261    
262        @SuppressWarnings("unchecked")
263        public List<ACITuple> getACITuples( String subentryDn )
264        {
265            List aciTuples = tuples.get( subentryDn );
266            if ( aciTuples == null )
267            {
268                return Collections.EMPTY_LIST;
269            }
270            return Collections.unmodifiableList( aciTuples );
271        }
272    
273    
274        public void subentryRenamed( DN oldName, DN newName )
275        {
276            tuples.put( newName.getNormName(), tuples.remove( oldName.getNormName() ) );
277        }
278    }