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 */
020package org.apache.directory.server.core.authz;
021
022
023import java.text.ParseException;
024import java.util.ArrayList;
025import java.util.Collections;
026import java.util.HashMap;
027import java.util.List;
028import java.util.Map;
029import java.util.Set;
030
031import javax.naming.directory.SearchControls;
032
033import org.apache.directory.api.ldap.aci.ACIItem;
034import org.apache.directory.api.ldap.aci.ACIItemParser;
035import org.apache.directory.api.ldap.aci.ACITuple;
036import org.apache.directory.api.ldap.model.constants.SchemaConstants;
037import org.apache.directory.api.ldap.model.entry.Attribute;
038import org.apache.directory.api.ldap.model.entry.Entry;
039import org.apache.directory.api.ldap.model.entry.Modification;
040import org.apache.directory.api.ldap.model.entry.Value;
041import org.apache.directory.api.ldap.model.exception.LdapException;
042import org.apache.directory.api.ldap.model.exception.LdapOperationErrorException;
043import org.apache.directory.api.ldap.model.exception.LdapSchemaViolationException;
044import org.apache.directory.api.ldap.model.filter.EqualityNode;
045import org.apache.directory.api.ldap.model.filter.ExprNode;
046import org.apache.directory.api.ldap.model.message.AliasDerefMode;
047import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
048import org.apache.directory.api.ldap.model.name.Dn;
049import org.apache.directory.api.ldap.model.schema.AttributeType;
050import org.apache.directory.api.ldap.model.schema.SchemaManager;
051import org.apache.directory.api.ldap.model.schema.normalizers.ConcreteNameComponentNormalizer;
052import org.apache.directory.api.ldap.model.schema.normalizers.NameComponentNormalizer;
053import org.apache.directory.server.core.api.CoreSession;
054import org.apache.directory.server.core.api.DirectoryService;
055import org.apache.directory.server.core.api.DnFactory;
056import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
057import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
058import org.apache.directory.server.core.api.partition.Partition;
059import org.apache.directory.server.core.api.partition.PartitionNexus;
060import org.apache.directory.server.i18n.I18n;
061import org.slf4j.Logger;
062import org.slf4j.LoggerFactory;
063
064
065/**
066 * A cache for tuple sets which responds to specific events to perform
067 * cache house keeping as access control subentries are added, deleted
068 * and modified.
069 *
070 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
071 */
072public class TupleCache
073{
074    /** the logger for this class */
075    private static final Logger LOG = LoggerFactory.getLogger( TupleCache.class );
076
077    /** a map of strings to ACITuple collections */
078    private final Map<String, List<ACITuple>> tuples = new HashMap<>();
079
080    /** the directory service */
081    private final DirectoryService directoryService;
082
083    /** the Dn factory */
084    private final DnFactory dnFactory;
085
086    /** a handle on the partition nexus */
087    private final PartitionNexus nexus;
088
089    /** a normalizing ACIItem parser */
090    private final ACIItemParser aciParser;
091
092
093    /**
094     * Creates a ACITuple cache.
095     *
096     * @param session the session with the directory core services
097     * @throws LdapException if initialization fails
098     */
099    public TupleCache( CoreSession session ) throws LdapException
100    {
101        this.directoryService = session.getDirectoryService();
102        SchemaManager schemaManager = directoryService.getSchemaManager();
103        this.dnFactory = directoryService.getDnFactory();
104        this.nexus = directoryService.getPartitionNexus();
105        NameComponentNormalizer ncn = new ConcreteNameComponentNormalizer( schemaManager );
106        aciParser = new ACIItemParser( ncn, schemaManager );
107        initialize( session );
108    }
109
110
111    private void initialize( CoreSession session ) throws LdapException
112    {
113        // search all naming contexts for access control subentenries
114        // generate ACITuple Arrays for each subentry
115        // add that subentry to the hash
116        Set<String> suffixes = nexus.listSuffixes();
117
118        for ( String suffix : suffixes )
119        {
120            AttributeType ocAt = directoryService.getAtProvider().getObjectClass();
121
122            ExprNode filter = new EqualityNode<String>( ocAt, 
123                new Value( ocAt, SchemaConstants.ACCESS_CONTROL_SUBENTRY_OC ) );
124            SearchControls ctls = new SearchControls();
125            ctls.setSearchScope( SearchControls.SUBTREE_SCOPE );
126            ctls.setReturningAttributes( new String[]
127                { SchemaConstants.ALL_USER_ATTRIBUTES, SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES } );
128
129            Dn baseDn = dnFactory.create( suffix );
130            Partition partition = nexus.getPartition( baseDn );
131            
132            SearchOperationContext searchOperationContext = new SearchOperationContext( session,
133                baseDn, filter, ctls );
134            searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES );
135            searchOperationContext.setPartition( partition );
136            searchOperationContext.setTransaction( partition.beginReadTransaction() );
137
138            EntryFilteringCursor results = nexus.search( searchOperationContext );
139
140            try
141            {
142                while ( results.next() )
143                {
144                    Entry result = results.get();
145                    
146                    Dn subentryDn = result.getDn();
147                    
148                    if ( !subentryDn.isSchemaAware() )
149                    {
150                        subentryDn = new Dn( session.getDirectoryService().getSchemaManager(), subentryDn );
151                    }
152
153                    Attribute aci = result.get( directoryService.getAtProvider().getPrescriptiveACI() );
154
155                    if ( aci == null )
156                    {
157                        LOG.warn( "Found accessControlSubentry '{}' without any {}", subentryDn, SchemaConstants.PRESCRIPTIVE_ACI_AT );
158                        continue;
159                    }
160
161                    subentryAdded( subentryDn, result );
162                }
163
164                results.close();
165            }
166            catch ( Exception e )
167            {
168                throw new LdapOperationErrorException( e.getMessage(), e );
169            }
170        }
171    }
172
173
174    /**
175     * Check if the Entry contains a prescriptiveACI
176     */
177    private boolean hasPrescriptiveACI( Entry entry ) throws LdapException
178    {
179        // only do something if the entry contains prescriptiveACI
180        Attribute aci = entry.get( directoryService.getAtProvider().getPrescriptiveACI() );
181
182        if ( aci == null )
183        {
184            if ( entry.contains( directoryService.getAtProvider().getObjectClass(),
185                SchemaConstants.ACCESS_CONTROL_SUBENTRY_OC ) )
186            {
187                // should not be necessary because of schema interceptor but schema checking
188                // can be turned off and in this case we must protect against being able to
189                // add access control information to anything other than an AC subentry
190                throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, "" );
191            }
192            else
193            {
194                return false;
195            }
196        }
197
198        return true;
199    }
200
201
202    public void subentryAdded( Dn dn, Entry entry ) throws LdapException
203    {
204        // only do something if the entry contains a prescriptiveACI
205        if ( !hasPrescriptiveACI( entry ) )
206        {
207            return;
208        }
209
210        // Get the prescriptiveACI
211        Attribute prescriptiveAci = entry.get( directoryService.getAtProvider().getPrescriptiveACI() );
212
213        List<ACITuple> entryTuples = new ArrayList<>();
214
215        // Loop on all the ACI, parse each of them and
216        // store the associated tuples into the cache
217        for ( Value value : prescriptiveAci )
218        {
219            String aci = value.getString();
220            ACIItem item = null;
221
222            try
223            {
224                item = aciParser.parse( aci );
225                entryTuples.addAll( item.toTuples() );
226            }
227            catch ( ParseException e )
228            {
229                String msg = I18n.err( I18n.ERR_28, item );
230                LOG.error( msg, e );
231
232                // do not process this ACI Item because it will be null
233                // continue on to process the next ACI item in the entry
234            }
235        }
236
237        tuples.put( dn.getNormName(), entryTuples );
238    }
239
240
241    public void subentryDeleted( Dn dn, Entry entry ) throws LdapException
242    {
243        if ( !hasPrescriptiveACI( entry ) )
244        {
245            return;
246        }
247
248        tuples.remove( dn.getNormName() );
249    }
250
251
252    public void subentryModified( Dn normName, List<Modification> mods, Entry entry ) throws LdapException
253    {
254        if ( !hasPrescriptiveACI( entry ) )
255        {
256            return;
257        }
258
259        for ( Modification mod : mods )
260        {
261            if ( mod.getAttribute().isInstanceOf( directoryService.getAtProvider().getPrescriptiveACI() ) )
262            {
263                subentryDeleted( normName, entry );
264                subentryAdded( normName, entry );
265            }
266        }
267    }
268
269
270    public void subentryModified( Dn normName, Entry mods, Entry entry ) throws LdapException
271    {
272        if ( !hasPrescriptiveACI( entry ) )
273        {
274            return;
275        }
276
277        if ( mods.get( directoryService.getAtProvider().getPrescriptiveACI() ) != null )
278        {
279            subentryDeleted( normName, entry );
280            subentryAdded( normName, entry );
281        }
282    }
283
284
285    public List<ACITuple> getACITuples( String subentryDn )
286    {
287        List<ACITuple> aciTuples = tuples.get( subentryDn );
288
289        if ( aciTuples == null )
290        {
291            return Collections.emptyList();
292        }
293
294        return Collections.unmodifiableList( aciTuples );
295    }
296
297
298    public void subentryRenamed( Dn oldName, Dn newName )
299    {
300        tuples.put( newName.getNormName(), tuples.remove( oldName.getNormName() ) );
301    }
302}