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.Collection;
026 import java.util.Collections;
027 import java.util.HashSet;
028 import java.util.List;
029 import java.util.Set;
030
031 import javax.naming.directory.SearchControls;
032
033 import org.apache.directory.server.constants.ServerDNConstants;
034 import org.apache.directory.server.core.CoreSession;
035 import org.apache.directory.server.core.DefaultCoreSession;
036 import org.apache.directory.server.core.DirectoryService;
037 import org.apache.directory.server.core.LdapPrincipal;
038 import org.apache.directory.server.core.authz.support.ACDFEngine;
039 import org.apache.directory.server.core.entry.ClonedServerEntry;
040 import org.apache.directory.server.core.entry.ServerEntryUtils;
041 import org.apache.directory.server.core.filtering.EntryFilter;
042 import org.apache.directory.server.core.filtering.EntryFilteringCursor;
043 import org.apache.directory.server.core.interceptor.BaseInterceptor;
044 import org.apache.directory.server.core.interceptor.InterceptorChain;
045 import org.apache.directory.server.core.interceptor.NextInterceptor;
046 import org.apache.directory.server.core.interceptor.context.AddOperationContext;
047 import org.apache.directory.server.core.interceptor.context.CompareOperationContext;
048 import org.apache.directory.server.core.interceptor.context.DeleteOperationContext;
049 import org.apache.directory.server.core.interceptor.context.EntryOperationContext;
050 import org.apache.directory.server.core.interceptor.context.GetMatchedNameOperationContext;
051 import org.apache.directory.server.core.interceptor.context.ListOperationContext;
052 import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
053 import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
054 import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext;
055 import org.apache.directory.server.core.interceptor.context.MoveOperationContext;
056 import org.apache.directory.server.core.interceptor.context.OperationContext;
057 import org.apache.directory.server.core.interceptor.context.RenameOperationContext;
058 import org.apache.directory.server.core.interceptor.context.SearchOperationContext;
059 import org.apache.directory.server.core.interceptor.context.SearchingOperationContext;
060 import org.apache.directory.server.core.partition.ByPassConstants;
061 import org.apache.directory.server.core.subtree.SubentryInterceptor;
062 import org.apache.directory.server.i18n.I18n;
063 import org.apache.directory.shared.ldap.aci.ACIItem;
064 import org.apache.directory.shared.ldap.aci.ACIItemParser;
065 import org.apache.directory.shared.ldap.aci.ACITuple;
066 import org.apache.directory.shared.ldap.aci.MicroOperation;
067 import org.apache.directory.shared.ldap.constants.AuthenticationLevel;
068 import org.apache.directory.shared.ldap.constants.SchemaConstants;
069 import org.apache.directory.shared.ldap.entry.EntryAttribute;
070 import org.apache.directory.shared.ldap.entry.Modification;
071 import org.apache.directory.shared.ldap.entry.ServerEntry;
072 import org.apache.directory.shared.ldap.entry.Value;
073 import org.apache.directory.shared.ldap.exception.LdapNoPermissionException;
074 import org.apache.directory.shared.ldap.exception.LdapOperationErrorException;
075 import org.apache.directory.shared.ldap.name.DN;
076 import org.apache.directory.shared.ldap.schema.AttributeType;
077 import org.apache.directory.shared.ldap.schema.SchemaManager;
078 import org.apache.directory.shared.ldap.schema.normalizers.ConcreteNameComponentNormalizer;
079 import org.slf4j.Logger;
080 import org.slf4j.LoggerFactory;
081
082
083 /**
084 * An ACI based authorization service.
085 *
086 * @org.apache.xbean.XBean
087 *
088 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
089 * @version $Rev: 928938 $
090 */
091 public class AciAuthorizationInterceptor extends BaseInterceptor
092 {
093 /** the logger for this class */
094 private static final Logger LOG = LoggerFactory.getLogger( AciAuthorizationInterceptor.class );
095
096 /**
097 * the multivalued op attr used to track the prescriptive access control
098 * subentries that apply to an entry.
099 */
100 private static final String AC_SUBENTRY_ATTR = "accessControlSubentries";
101
102 private static final Collection<MicroOperation> ADD_PERMS;
103 private static final Collection<MicroOperation> READ_PERMS;
104 private static final Collection<MicroOperation> COMPARE_PERMS;
105 private static final Collection<MicroOperation> SEARCH_ENTRY_PERMS;
106 private static final Collection<MicroOperation> SEARCH_ATTRVAL_PERMS;
107 private static final Collection<MicroOperation> REMOVE_PERMS;
108 private static final Collection<MicroOperation> MATCHEDNAME_PERMS;
109 private static final Collection<MicroOperation> BROWSE_PERMS;
110 private static final Collection<MicroOperation> LOOKUP_PERMS;
111 private static final Collection<MicroOperation> REPLACE_PERMS;
112 private static final Collection<MicroOperation> RENAME_PERMS;
113 private static final Collection<MicroOperation> EXPORT_PERMS;
114 private static final Collection<MicroOperation> IMPORT_PERMS;
115 private static final Collection<MicroOperation> MOVERENAME_PERMS;
116
117 static
118 {
119 Set<MicroOperation> set = new HashSet<MicroOperation>( 2 );
120 set.add( MicroOperation.BROWSE );
121 set.add( MicroOperation.RETURN_DN );
122 SEARCH_ENTRY_PERMS = Collections.unmodifiableCollection( set );
123
124 set = new HashSet<MicroOperation>( 2 );
125 set.add( MicroOperation.READ );
126 set.add( MicroOperation.BROWSE );
127 LOOKUP_PERMS = Collections.unmodifiableCollection( set );
128
129 set = new HashSet<MicroOperation>( 2 );
130 set.add( MicroOperation.ADD );
131 set.add( MicroOperation.REMOVE );
132 REPLACE_PERMS = Collections.unmodifiableCollection( set );
133
134 set = new HashSet<MicroOperation>( 2 );
135 set.add( MicroOperation.EXPORT );
136 set.add( MicroOperation.RENAME );
137 MOVERENAME_PERMS = Collections.unmodifiableCollection( set );
138
139 SEARCH_ATTRVAL_PERMS = Collections.singleton( MicroOperation.READ );
140 ADD_PERMS = Collections.singleton( MicroOperation.ADD );
141 READ_PERMS = Collections.singleton( MicroOperation.READ );
142 COMPARE_PERMS = Collections.singleton( MicroOperation.COMPARE );
143 REMOVE_PERMS = Collections.singleton( MicroOperation.REMOVE );
144 MATCHEDNAME_PERMS = Collections.singleton( MicroOperation.DISCLOSE_ON_ERROR );
145 BROWSE_PERMS = Collections.singleton( MicroOperation.BROWSE );
146 RENAME_PERMS = Collections.singleton( MicroOperation.RENAME );
147 EXPORT_PERMS = Collections.singleton( MicroOperation.EXPORT );
148 IMPORT_PERMS = Collections.singleton( MicroOperation.IMPORT );
149 }
150
151 /** a tupleCache that responds to add, delete, and modify attempts */
152 private TupleCache tupleCache;
153
154 /** a groupCache that responds to add, delete, and modify attempts */
155 private GroupCache groupCache;
156
157 /** a normalizing ACIItem parser */
158 private ACIItemParser aciParser;
159
160 /** use and instance of the ACDF engine */
161 private ACDFEngine engine;
162
163 /** interceptor chain */
164 private InterceptorChain chain;
165
166 /** Global registries */
167 private SchemaManager schemaManager;
168
169 /** the system wide subschemaSubentryDn */
170 private String subschemaSubentryDn;
171
172 private AttributeType objectClassType;
173 private AttributeType acSubentryType;
174
175 private String subentryOid;
176
177 /** A storage for the entryACI attributeType */
178 private AttributeType entryAciType;
179
180 /** the subentry ACI attribute type */
181 private AttributeType subentryAciType;
182
183 public static final SearchControls DEFAULT_SEARCH_CONTROLS = new SearchControls();
184
185 /**
186 * Initializes this interceptor based service by getting a handle on the nexus, setting up
187 * the tupe and group membership caches and the ACIItem parser and the ACDF engine.
188 *
189 * @param directoryService the directory service core
190 * @throws Exception if there are problems during initialization
191 */
192 public void init( DirectoryService directoryService ) throws Exception
193 {
194 super.init( directoryService );
195
196 DN adminDn = new DN( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
197 adminDn.normalize( directoryService.getSchemaManager().getNormalizerMapping() );
198 CoreSession adminSession = new DefaultCoreSession(
199 new LdapPrincipal( adminDn, AuthenticationLevel.STRONG ), directoryService );
200
201 tupleCache = new TupleCache( adminSession );
202 groupCache = new GroupCache( adminSession );
203 schemaManager = directoryService.getSchemaManager();
204 //ocRegistry = registries.getObjectClassRegistry();
205
206 // look up some constant information
207 String objectClassOid = schemaManager.getAttributeTypeRegistry().getOidByName( SchemaConstants.OBJECT_CLASS_AT );
208 subentryOid = schemaManager.getObjectClassRegistry().getOidByName( SchemaConstants.SUBENTRY_OC );
209 String acSubentryOid = schemaManager.getAttributeTypeRegistry().getOidByName( AC_SUBENTRY_ATTR );
210 objectClassType = schemaManager.lookupAttributeTypeRegistry( objectClassOid );
211 acSubentryType = schemaManager.lookupAttributeTypeRegistry( acSubentryOid );
212 entryAciType = schemaManager.lookupAttributeTypeRegistry( SchemaConstants.ENTRY_ACI_AT_OID );
213 subentryAciType = schemaManager.lookupAttributeTypeRegistry( SchemaConstants.SUBENTRY_ACI_AT_OID );
214
215 aciParser = new ACIItemParser( new ConcreteNameComponentNormalizer( schemaManager ), schemaManager.getNormalizerMapping() );
216 engine = new ACDFEngine( schemaManager.getGlobalOidRegistry(), schemaManager );
217 chain = directoryService.getInterceptorChain();
218
219 // stuff for dealing with subentries (garbage for now)
220 Value<?> subschemaSubentry =
221 directoryService.getPartitionNexus().getRootDSE( null ).
222 get( SchemaConstants.SUBSCHEMA_SUBENTRY_AT ).get();
223 DN subschemaSubentryDnName = new DN( subschemaSubentry.getString() );
224 subschemaSubentryDnName.normalize( schemaManager.getNormalizerMapping() );
225 subschemaSubentryDn = subschemaSubentryDnName.getNormName();
226 }
227
228
229 private void protectCriticalEntries( DN dn ) throws Exception
230 {
231 DN principalDn = getPrincipal().getClonedName();
232
233 if ( dn.isEmpty() )
234 {
235 String msg = I18n.err( I18n.ERR_8 );
236 LOG.error( msg );
237 throw new LdapNoPermissionException( msg );
238 }
239
240 if ( isTheAdministrator( dn ) )
241 {
242 String msg = I18n.err( I18n.ERR_9, principalDn.getName(), dn.getName() );
243 LOG.error( msg );
244 throw new LdapNoPermissionException( msg );
245 }
246 }
247
248
249 /**
250 * Adds perscriptiveACI tuples to a collection of tuples by accessing the
251 * tupleCache. The tuple cache is accessed for each A/C subentry
252 * associated with the protected entry. Note that subentries are handled
253 * differently: their parent, the administrative entry is accessed to
254 * determine the perscriptiveACIs effecting the AP and hence the subentry
255 * which is considered to be in the same context.
256 *
257 * @param tuples the collection of tuples to add to
258 * @param dn the normalized distinguished name of the protected entry
259 * @param entry the target entry that access to is being controled
260 * @throws Exception if there are problems accessing attribute values
261 * @param proxy the partition nexus proxy object
262 */
263 private void addPerscriptiveAciTuples( OperationContext opContext, Collection<ACITuple> tuples, DN dn,
264 ServerEntry entry ) throws Exception
265 {
266 EntryAttribute oc = null;
267
268 if ( entry instanceof ClonedServerEntry )
269 {
270 oc = ((ClonedServerEntry)entry).getOriginalEntry().get( objectClassType );
271 }
272 else
273 {
274 oc = entry.get( objectClassType );
275 }
276
277 /*
278 * If the protected entry is a subentry, then the entry being evaluated
279 * for perscriptiveACIs is in fact the administrative entry. By
280 * substituting the administrative entry for the actual subentry the
281 * code below this "if" statement correctly evaluates the effects of
282 * perscriptiveACI on the subentry. Basically subentries are considered
283 * to be in the same naming context as their access point so the subentries
284 * effecting their parent entry applies to them as well.
285 */
286 if ( oc.contains( SchemaConstants.SUBENTRY_OC ) || oc.contains( subentryOid ) )
287 {
288 DN parentDn = ( DN ) dn.clone();
289 parentDn.remove( dn.size() - 1 );
290 entry = opContext.lookup( parentDn, ByPassConstants.LOOKUP_BYPASS );
291 }
292
293 EntryAttribute subentries = entry.get( acSubentryType );
294
295 if ( subentries == null )
296 {
297 return;
298 }
299
300 for ( Value<?> value:subentries )
301 {
302 String subentryDn = value.getString();
303 tuples.addAll( tupleCache.getACITuples( subentryDn ) );
304 }
305 }
306
307
308 /**
309 * Adds the set of entryACI tuples to a collection of tuples. The entryACI
310 * is parsed and tuples are generated on they fly then added to the collection.
311 *
312 * @param tuples the collection of tuples to add to
313 * @param entry the target entry that access to is being regulated
314 * @throws Exception if there are problems accessing attribute values
315 */
316 private void addEntryAciTuples( Collection<ACITuple> tuples, ServerEntry entry ) throws Exception
317 {
318 EntryAttribute entryAci = entry.get( entryAciType );
319
320 if ( entryAci == null )
321 {
322 return;
323 }
324
325 for ( Value<?> value:entryAci )
326 {
327 String aciString = value.getString();
328 ACIItem item;
329
330 try
331 {
332 item = aciParser.parse( aciString );
333 }
334 catch ( ParseException e )
335 {
336 String msg = I18n.err( I18n.ERR_10, aciString );
337 LOG.error( msg, e );
338 throw new LdapOperationErrorException( msg );
339 }
340
341 tuples.addAll( item.toTuples() );
342 }
343 }
344
345
346 /**
347 * Adds the set of subentryACI tuples to a collection of tuples. The subentryACI
348 * is parsed and tuples are generated on the fly then added to the collection.
349 *
350 * @param tuples the collection of tuples to add to
351 * @param dn the normalized distinguished name of the protected entry
352 * @param entry the target entry that access to is being regulated
353 * @throws Exception if there are problems accessing attribute values
354 * @param proxy the partition nexus proxy object
355 */
356 private void addSubentryAciTuples( OperationContext opContext, Collection<ACITuple> tuples, DN dn, ServerEntry entry )
357 throws Exception
358 {
359 // only perform this for subentries
360 if ( !entry.contains( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.SUBENTRY_OC ) )
361 {
362 return;
363 }
364
365 // get the parent or administrative entry for this subentry since it
366 // will contain the subentryACI attributes that effect subentries
367 DN parentDn = ( DN ) dn.clone();
368 parentDn.remove( dn.size() - 1 );
369 ServerEntry administrativeEntry = opContext.lookup( parentDn, ByPassConstants.LOOKUP_BYPASS ).getOriginalEntry();
370
371 EntryAttribute subentryAci = administrativeEntry.get( subentryAciType );
372
373 if ( subentryAci == null )
374 {
375 return;
376 }
377
378 for ( Value<?> value:subentryAci )
379 {
380 String aciString = value.getString();
381 ACIItem item;
382
383 try
384 {
385 item = aciParser.parse( aciString );
386 }
387 catch ( ParseException e )
388 {
389 String msg = I18n.err( I18n.ERR_11, aciString );
390 LOG.error( msg, e );
391 throw new LdapOperationErrorException( msg );
392 }
393
394 tuples.addAll( item.toTuples() );
395 }
396 }
397
398
399 /* -------------------------------------------------------------------------------
400 * Within every access controled interceptor method we must retrieve the ACITuple
401 * set for all the perscriptiveACIs that apply to the candidate, the target entry
402 * operated upon. This ACITuple set is gotten from the TupleCache by looking up
403 * the subentries referenced by the accessControlSubentries operational attribute
404 * within the target entry.
405 *
406 * Then the entry is inspected for an entryACI. This is not done for the add op
407 * since it could introduce a security breech. So for non-add ops if present a
408 * set of ACITuples are generated for all the entryACIs within the entry. This
409 * set is combined with the ACITuples cached for the perscriptiveACI affecting
410 * the target entry. If the entry is a subentry the ACIs are also processed for
411 * the subentry to generate more ACITuples. This subentry TupleACI set is joined
412 * with the entry and perscriptive ACI.
413 *
414 * The union of ACITuples are fed into the engine along with other parameters
415 * to decide whether a permission is granted or rejected for the specific
416 * operation.
417 * -------------------------------------------------------------------------------
418 */
419
420 public void add( NextInterceptor next, AddOperationContext addContext ) throws Exception
421 {
422 // Access the principal requesting the operation, and bypass checks if it is the admin
423 LdapPrincipal principal = addContext.getSession().getEffectivePrincipal();
424 DN principalDn = principal.getClonedName();
425
426 ServerEntry serverEntry = addContext.getEntry();
427 //Attributes entry = ServerEntryUtils.toAttributesImpl( serverEntry );
428
429 DN name = addContext.getDn();
430
431 // bypass authz code if we are disabled
432 if ( !addContext.getSession().getDirectoryService().isAccessControlEnabled() )
433 {
434 next.add( addContext );
435 return;
436 }
437
438 // bypass authz code but manage caches if operation is performed by the admin
439 if ( isPrincipalAnAdministrator( principalDn ) )
440 {
441 next.add( addContext );
442 tupleCache.subentryAdded( name, serverEntry );
443 groupCache.groupAdded( name, serverEntry );
444 return;
445 }
446
447 // perform checks below here for all non-admin users
448 SubentryInterceptor subentryInterceptor = ( SubentryInterceptor ) chain.get( SubentryInterceptor.class.getName() );
449 ServerEntry subentryAttrs = subentryInterceptor.getSubentryAttributes( name, serverEntry );
450
451 for ( EntryAttribute attribute:serverEntry )
452 {
453 subentryAttrs.put( attribute );
454 }
455
456 // Assemble all the information required to make an access control decision
457 Set<DN> userGroups = groupCache.getGroups( principalDn.getNormName() );
458 Collection<ACITuple> tuples = new HashSet<ACITuple>();
459
460 // Build the total collection of tuples to be considered for add rights
461 // NOTE: entryACI are NOT considered in adds (it would be a security breech)
462 addPerscriptiveAciTuples( addContext, tuples, name, subentryAttrs );
463 addSubentryAciTuples( addContext, tuples, name, subentryAttrs );
464
465 // check if entry scope permission is granted
466 engine.checkPermission( schemaManager, addContext, userGroups, principalDn, principal.getAuthenticationLevel(), name, null, null,
467 ADD_PERMS, tuples, subentryAttrs, null );
468
469 // now we must check if attribute type and value scope permission is granted
470 for ( EntryAttribute attribute:serverEntry )
471 {
472 for ( Value<?> value:attribute )
473 {
474 engine.checkPermission( schemaManager, addContext, userGroups, principalDn,
475 principal.getAuthenticationLevel(), name, attribute.getUpId(), value,
476 ADD_PERMS, tuples, serverEntry, null );
477 }
478 }
479
480 // if we've gotten this far then access has been granted
481 next.add( addContext );
482
483 // if the entry added is a subentry or a groupOf[Unique]Names we must
484 // update the ACITuple cache and the groups cache to keep them in sync
485 tupleCache.subentryAdded( name, serverEntry );
486 groupCache.groupAdded( name, serverEntry );
487 }
488
489
490 private boolean isTheAdministrator( DN normalizedDn )
491 {
492 return normalizedDn.getNormName().equals( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
493 }
494
495
496 public void delete( NextInterceptor next, DeleteOperationContext deleteContext ) throws Exception
497 {
498 DN name = deleteContext.getDn();
499
500 LdapPrincipal principal = deleteContext.getSession().getEffectivePrincipal();
501 DN principalDn = principal.getClonedName();
502
503 // bypass authz code if we are disabled
504 if ( ! deleteContext.getSession().getDirectoryService().isAccessControlEnabled() )
505 {
506 next.delete( deleteContext );
507 return;
508 }
509
510 ClonedServerEntry entry = deleteContext.lookup( name, ByPassConstants.LOOKUP_BYPASS );
511
512 protectCriticalEntries( name );
513
514 // bypass authz code but manage caches if operation is performed by the admin
515 if ( isPrincipalAnAdministrator( principalDn ) )
516 {
517 next.delete( deleteContext );
518 tupleCache.subentryDeleted( name, entry );
519 groupCache.groupDeleted( name, entry );
520 return;
521 }
522
523 Set<DN> userGroups = groupCache.getGroups( principalDn.getNormName() );
524 Collection<ACITuple> tuples = new HashSet<ACITuple>();
525 addPerscriptiveAciTuples( deleteContext, tuples, name, entry.getOriginalEntry() );
526 addEntryAciTuples( tuples, entry );
527 addSubentryAciTuples( deleteContext, tuples, name, entry );
528
529 engine.checkPermission( schemaManager, deleteContext, userGroups, principalDn,
530 principal.getAuthenticationLevel(), name, null, null, REMOVE_PERMS, tuples, entry, null );
531
532 next.delete( deleteContext );
533 tupleCache.subentryDeleted( name, entry );
534 groupCache.groupDeleted( name, entry );
535 }
536
537
538 public void modify( NextInterceptor next, ModifyOperationContext opContext ) throws Exception
539 {
540 DN name = opContext.getDn();
541
542 // Access the principal requesting the operation, and bypass checks if it is the admin
543 ClonedServerEntry entry = opContext.lookup( name, ByPassConstants.LOOKUP_BYPASS );
544
545 LdapPrincipal principal = opContext.getSession().getEffectivePrincipal();
546 DN principalDn = principal.getClonedName();
547
548 // bypass authz code if we are disabled
549 if ( !opContext.getSession().getDirectoryService().isAccessControlEnabled() )
550 {
551 next.modify( opContext );
552 return;
553 }
554
555 List<Modification> mods = opContext.getModItems();
556
557 // bypass authz code but manage caches if operation is performed by the admin
558 if ( isPrincipalAnAdministrator( principalDn ) )
559 {
560 next.modify( opContext );
561 /**
562 * @TODO: A virtual entry can be created here for not hitting the backend again.
563 */
564 ServerEntry modifiedEntry = opContext.lookup( name, ByPassConstants.LOOKUP_BYPASS );
565 tupleCache.subentryModified( name, mods, modifiedEntry );
566 groupCache.groupModified( name, mods, entry, schemaManager );
567 return;
568 }
569
570 Set<DN> userGroups = groupCache.getGroups( principalDn.getName() );
571 Collection<ACITuple> tuples = new HashSet<ACITuple>();
572 addPerscriptiveAciTuples( opContext, tuples, name, entry.getOriginalEntry() );
573 addEntryAciTuples( tuples, entry );
574 addSubentryAciTuples( opContext, tuples, name, entry );
575
576 engine.checkPermission( schemaManager, opContext, userGroups, principalDn,
577 principal.getAuthenticationLevel(), name, null, null,
578 Collections.singleton( MicroOperation.MODIFY ), tuples, entry, null );
579
580 Collection<MicroOperation> perms = null;
581 ServerEntry entryView = ( ServerEntry ) entry.clone();
582
583 for ( Modification mod : mods )
584 {
585 EntryAttribute attr = mod.getAttribute();
586
587 switch ( mod.getOperation() )
588 {
589 case ADD_ATTRIBUTE :
590 perms = ADD_PERMS;
591
592 // If the attribute is being created with an initial value ...
593 if ( entry.get( attr.getId() ) == null )
594 {
595 // ... we also need to check if adding the attribute is permitted
596 engine.checkPermission( schemaManager, opContext, userGroups, principalDn, principal.getAuthenticationLevel(), name,
597 attr.getId(), null, perms, tuples, entry, null );
598 }
599
600 break;
601
602 case REMOVE_ATTRIBUTE :
603 perms = REMOVE_PERMS;
604 EntryAttribute entryAttr = entry.get( attr.getId() );
605
606 if ( entryAttr != null )
607 {
608 // If there is only one value remaining in the attribute ...
609 if ( entryAttr.size() == 1 )
610 {
611 // ... we also need to check if removing the attribute at all is permitted
612 engine.checkPermission( schemaManager, opContext, userGroups, principalDn,
613 principal.getAuthenticationLevel(), name, attr.getId(),
614 null, perms, tuples, entry, null );
615 }
616 }
617
618 break;
619
620 case REPLACE_ATTRIBUTE :
621 perms = REPLACE_PERMS;
622 break;
623 }
624
625 /**
626 * Update the entry view as the current modification is applied to the original entry.
627 * This is especially required for handling the MaxValueCount protected item. Number of
628 * values for an attribute after a modification should be known in advance in order to
629 * check permissions for MaxValueCount protected item. So during addition of the first
630 * value of an attribute it can be rejected if the permission denied due the the
631 * MaxValueCount protected item. This is not the perfect implementation as required by
632 * the specification because the system should reject the addition exactly on the right
633 * value of the attribute. However as we do not have that much granularity in our
634 * implementation (we consider an Attribute Addition itself a Micro Operation,
635 * not the individual Value Additions) we just handle this when the first value of an
636 * attribute is being checked for relevant permissions below.
637 */
638 entryView = ServerEntryUtils.getTargetEntry( mod, entryView, schemaManager );
639
640 for ( Value<?> value:attr )
641 {
642 engine.checkPermission( schemaManager, opContext, userGroups, principalDn,
643 principal.getAuthenticationLevel(), name, attr.getId(), value,
644 perms, tuples, entry, entryView );
645 }
646 }
647
648
649
650 next.modify( opContext );
651 /**
652 * @TODO: A virtual entry can be created here for not hitting the backend again.
653 */
654 ServerEntry modifiedEntry = opContext.lookup( name, ByPassConstants.LOOKUP_BYPASS );
655 tupleCache.subentryModified( name, mods, modifiedEntry );
656 groupCache.groupModified( name, mods, entry, schemaManager );
657 }
658
659
660 public boolean hasEntry( NextInterceptor next, EntryOperationContext entryContext ) throws Exception
661 {
662 DN name = entryContext.getDn();
663
664 if ( ! entryContext.getSession().getDirectoryService().isAccessControlEnabled() )
665 {
666 return name.size() == 0 || next.hasEntry( entryContext );
667 }
668
669 boolean answer = next.hasEntry( entryContext );
670
671 // no checks on the RootDSE
672 if ( name.size() == 0 )
673 {
674 // No need to go down to the stack, if the dn is empty
675 // It's the rootDSE, and it exists !
676 return answer;
677 }
678
679 // TODO - eventually replace this with a check on session.isAnAdministrator()
680 LdapPrincipal principal = entryContext.getSession().getEffectivePrincipal();
681 DN principalDn = principal.getClonedName();
682 if ( isPrincipalAnAdministrator( principalDn ) )
683 {
684 return answer;
685 }
686
687 ClonedServerEntry entry = entryContext.lookup( name, ByPassConstants.HAS_ENTRY_BYPASS );
688 Set<DN> userGroups = groupCache.getGroups( principalDn.getNormName() );
689 Collection<ACITuple> tuples = new HashSet<ACITuple>();
690 addPerscriptiveAciTuples( entryContext, tuples, name, entry.getOriginalEntry() );
691 addEntryAciTuples( tuples, entry.getOriginalEntry() );
692 addSubentryAciTuples( entryContext, tuples, name, entry.getOriginalEntry() );
693
694 // check that we have browse access to the entry
695 engine.checkPermission( schemaManager, entryContext, userGroups, principalDn,
696 principal.getAuthenticationLevel(), name, null, null,
697 BROWSE_PERMS, tuples, entry.getOriginalEntry(), null );
698
699 return next.hasEntry( entryContext );
700 }
701
702
703 /**
704 * Checks if the READ permissions exist to the entry and to each attribute type and
705 * value.
706 *
707 * @todo not sure if we should hide attribute types/values or throw an exception
708 * instead. I think we're going to have to use a filter to restrict the return
709 * of attribute types and values instead of throwing an exception. Lack of read
710 * perms to attributes and their values results in their removal when returning
711 * the entry.
712 *
713 * @param principal the user associated with the call
714 * @param dn the name of the entry being looked up
715 * @param entry the raw entry pulled from the nexus
716 * @throws Exception if undlying access to the DIT fails
717 */
718 private void checkLookupAccess( LookupOperationContext lookupContext, ServerEntry entry ) throws Exception
719 {
720 // no permissions checks on the RootDSE
721 if ( lookupContext.getDn().getNormName().trim().equals( "" ) )
722 {
723 return;
724 }
725
726 LdapPrincipal principal = lookupContext.getSession().getEffectivePrincipal();
727 DN userName = principal.getClonedName();
728 Set<DN> userGroups = groupCache.getGroups( userName.getNormName() );
729 Collection<ACITuple> tuples = new HashSet<ACITuple>();
730 addPerscriptiveAciTuples( lookupContext, tuples, lookupContext.getDn(), entry );
731 addEntryAciTuples( tuples, entry );
732 addSubentryAciTuples( lookupContext, tuples, lookupContext.getDn(), entry );
733
734 // check that we have read access to the entry
735 engine.checkPermission( schemaManager, lookupContext, userGroups, userName, principal.getAuthenticationLevel(),
736 lookupContext.getDn(), null, null,
737 LOOKUP_PERMS, tuples, entry, null );
738
739 // check that we have read access to every attribute type and value
740 for ( EntryAttribute attribute:entry )
741 {
742
743 for ( Value<?> value:attribute )
744 {
745 engine.checkPermission(
746 schemaManager,
747 lookupContext,
748 userGroups,
749 userName,
750 principal.getAuthenticationLevel(),
751 lookupContext.getDn(),
752 attribute.getUpId(),
753 value,
754 READ_PERMS,
755 tuples,
756 entry,
757 null );
758 }
759 }
760 }
761
762
763 public ClonedServerEntry lookup( NextInterceptor next, LookupOperationContext lookupContext ) throws Exception
764 {
765 LdapPrincipal principal = lookupContext.getSession().getEffectivePrincipal();
766 DN principalDn = principal.getClonedName();
767
768 if ( !principalDn.isNormalized() )
769 {
770 principalDn.normalize( schemaManager.getNormalizerMapping() );
771 }
772
773 if ( isPrincipalAnAdministrator( principalDn ) || !lookupContext.getSession().getDirectoryService().isAccessControlEnabled() )
774 {
775 return next.lookup( lookupContext );
776 }
777
778 lookupContext.setByPassed( ByPassConstants.LOOKUP_BYPASS );
779 ServerEntry entry = lookupContext.getSession().getDirectoryService()
780 .getOperationManager().lookup( lookupContext );
781
782 checkLookupAccess( lookupContext, entry );
783 return next.lookup( lookupContext );
784 }
785
786
787 public void rename( NextInterceptor next, RenameOperationContext renameContext ) throws Exception
788 {
789 DN oldName = renameContext.getDn();
790 ServerEntry originalEntry = null;
791
792 if ( renameContext.getEntry() != null )
793 {
794 originalEntry = renameContext.getEntry().getOriginalEntry();
795 }
796
797 LdapPrincipal principal = renameContext.getSession().getEffectivePrincipal();
798 DN principalDn = principal.getClonedName();
799 DN newName = renameContext.getNewDn();
800
801 // bypass authz code if we are disabled
802 if ( !renameContext.getSession().getDirectoryService().isAccessControlEnabled() )
803 {
804 next.rename( renameContext );
805 return;
806 }
807
808 protectCriticalEntries( oldName );
809
810 // bypass authz code but manage caches if operation is performed by the admin
811 if ( isPrincipalAnAdministrator( principalDn ) )
812 {
813 next.rename( renameContext );
814 tupleCache.subentryRenamed( oldName, newName );
815
816 // TODO : this method returns a boolean : what should we do with the result ?
817 groupCache.groupRenamed( oldName, newName );
818
819 return;
820 }
821
822 Set<DN> userGroups = groupCache.getGroups( principalDn.getNormName() );
823 Collection<ACITuple> tuples = new HashSet<ACITuple>();
824 addPerscriptiveAciTuples( renameContext, tuples, oldName, originalEntry );
825 addEntryAciTuples( tuples, originalEntry );
826 addSubentryAciTuples( renameContext, tuples, oldName, originalEntry );
827
828 engine.checkPermission( schemaManager, renameContext, userGroups, principalDn,
829 principal.getAuthenticationLevel(), oldName, null, null,
830 RENAME_PERMS, tuples, originalEntry, null );
831
832 next.rename( renameContext );
833 tupleCache.subentryRenamed( oldName, newName );
834 groupCache.groupRenamed( oldName, newName );
835 }
836
837
838 public void moveAndRename( NextInterceptor next, MoveAndRenameOperationContext moveAndRenameContext )
839 throws Exception
840 {
841 DN oriChildName = moveAndRenameContext.getDn();
842 DN newParentName = moveAndRenameContext.getParent();
843
844 ClonedServerEntry entry = moveAndRenameContext.lookup( oriChildName, ByPassConstants.LOOKUP_BYPASS );
845
846 LdapPrincipal principal = moveAndRenameContext.getSession().getEffectivePrincipal();
847 DN principalDn = principal.getClonedName();
848 DN newName = ( DN ) newParentName.clone();
849 newName.add( moveAndRenameContext.getNewRdn().getName() );
850
851 // bypass authz code if we are disabled
852 if ( !moveAndRenameContext.getSession().getDirectoryService().isAccessControlEnabled() )
853 {
854 next.moveAndRename( moveAndRenameContext );
855 return;
856 }
857
858 protectCriticalEntries( oriChildName );
859
860 // bypass authz code but manage caches if operation is performed by the admin
861 if ( isPrincipalAnAdministrator( principalDn ) )
862 {
863 next.moveAndRename( moveAndRenameContext );
864 tupleCache.subentryRenamed( oriChildName, newName );
865 groupCache.groupRenamed( oriChildName, newName );
866 return;
867 }
868
869 Set<DN> userGroups = groupCache.getGroups( principalDn.getNormName() );
870 Collection<ACITuple> tuples = new HashSet<ACITuple>();
871 addPerscriptiveAciTuples( moveAndRenameContext, tuples, oriChildName, entry.getOriginalEntry() );
872 addEntryAciTuples( tuples, entry );
873 addSubentryAciTuples( moveAndRenameContext, tuples, oriChildName, entry );
874
875 engine.checkPermission( schemaManager, moveAndRenameContext, userGroups,
876 principalDn, principal.getAuthenticationLevel(), oriChildName, null,
877 null, MOVERENAME_PERMS, tuples, entry, null );
878
879 // Get the entry again without operational attributes
880 // because access control subentry operational attributes
881 // will not be valid at the new location.
882 // This will certainly be fixed by the SubentryInterceptor,
883 // but after this service.
884
885 ClonedServerEntry importedEntry = moveAndRenameContext.lookup( oriChildName,
886 ByPassConstants.LOOKUP_EXCLUDING_OPR_ATTRS_BYPASS );
887
888 // As the target entry does not exist yet and so
889 // its subentry operational attributes are not there,
890 // we need to construct an entry to represent it
891 // at least with minimal requirements which are object class
892 // and access control subentry operational attributes.
893 SubentryInterceptor subentryInterceptor = ( SubentryInterceptor ) chain.get( SubentryInterceptor.class.getName() );
894 ServerEntry subentryAttrs = subentryInterceptor.getSubentryAttributes( newName, importedEntry );
895
896 for ( EntryAttribute attribute:importedEntry )
897 {
898 subentryAttrs.put( attribute );
899 }
900
901 Collection<ACITuple> destTuples = new HashSet<ACITuple>();
902 // Import permission is only valid for prescriptive ACIs
903 addPerscriptiveAciTuples( moveAndRenameContext, destTuples, newName, subentryAttrs );
904 // Evaluate the target context to see whether it
905 // allows an entry named newName to be imported as a subordinate.
906 engine.checkPermission( schemaManager, moveAndRenameContext, userGroups, principalDn,
907 principal.getAuthenticationLevel(), newName, null,
908 null, IMPORT_PERMS, destTuples, subentryAttrs, null );
909
910
911 next.moveAndRename( moveAndRenameContext );
912 tupleCache.subentryRenamed( oriChildName, newName );
913 groupCache.groupRenamed( oriChildName, newName );
914 }
915
916
917 public void move( NextInterceptor next, MoveOperationContext moveContext ) throws Exception
918 {
919 DN oriChildName = moveContext.getDn();
920 DN newParentName = moveContext.getParent();
921
922 // Access the principal requesting the operation, and bypass checks if it is the admin
923 ClonedServerEntry entry = moveContext.lookup( oriChildName, ByPassConstants.LOOKUP_BYPASS );
924
925 DN newName = ( DN ) newParentName.clone();
926 newName.add( oriChildName.get( oriChildName.size() - 1 ) );
927 LdapPrincipal principal = moveContext.getSession().getEffectivePrincipal();
928 DN principalDn = principal.getClonedName();
929
930 // bypass authz code if we are disabled
931 if ( !moveContext.getSession().getDirectoryService().isAccessControlEnabled() )
932 {
933 next.move( moveContext );
934 return;
935 }
936
937 protectCriticalEntries( oriChildName);
938
939 // bypass authz code but manage caches if operation is performed by the admin
940 if ( isPrincipalAnAdministrator( principalDn ) )
941 {
942 next.move( moveContext );
943 tupleCache.subentryRenamed( oriChildName, newName );
944 groupCache.groupRenamed( oriChildName, newName );
945 return;
946 }
947
948 Set<DN> userGroups = groupCache.getGroups( principalDn.getNormName() );
949 Collection<ACITuple> tuples = new HashSet<ACITuple>();
950 addPerscriptiveAciTuples( moveContext, tuples, oriChildName, entry.getOriginalEntry() );
951 addEntryAciTuples( tuples, entry );
952 addSubentryAciTuples( moveContext, tuples, oriChildName, entry );
953
954 engine.checkPermission( schemaManager, moveContext, userGroups, principalDn,
955 principal.getAuthenticationLevel(), oriChildName, null,
956 null, EXPORT_PERMS, tuples, entry, null );
957
958 // Get the entry again without operational attributes
959 // because access control subentry operational attributes
960 // will not be valid at the new location.
961 // This will certainly be fixed by the SubentryInterceptor,
962 // but after this service.
963 ServerEntry importedEntry = moveContext.lookup( oriChildName,
964 ByPassConstants.LOOKUP_EXCLUDING_OPR_ATTRS_BYPASS );
965
966 // As the target entry does not exist yet and so
967 // its subentry operational attributes are not there,
968 // we need to construct an entry to represent it
969 // at least with minimal requirements which are object class
970 // and access control subentry operational attributes.
971 SubentryInterceptor subentryInterceptor = ( SubentryInterceptor )
972 chain.get( SubentryInterceptor.class.getName() );
973 ServerEntry subentryAttrs = subentryInterceptor.getSubentryAttributes( newName, importedEntry );
974
975 for ( EntryAttribute attribute:importedEntry )
976 {
977 subentryAttrs.put( attribute );
978 }
979
980 Collection<ACITuple> destTuples = new HashSet<ACITuple>();
981 // Import permission is only valid for prescriptive ACIs
982 addPerscriptiveAciTuples( moveContext, destTuples, newName, subentryAttrs );
983 // Evaluate the target context to see whether it
984 // allows an entry named newName to be imported as a subordinate.
985 engine.checkPermission( schemaManager, moveContext, userGroups, principalDn,
986 principal.getAuthenticationLevel(), newName, null,
987 null, IMPORT_PERMS, destTuples, subentryAttrs, null );
988
989 next.move( moveContext );
990 tupleCache.subentryRenamed( oriChildName, newName );
991 groupCache.groupRenamed( oriChildName, newName );
992 }
993
994
995 public EntryFilteringCursor list( NextInterceptor next, ListOperationContext opContext ) throws Exception
996 {
997 LdapPrincipal user = opContext.getSession().getEffectivePrincipal();
998 EntryFilteringCursor cursor = next.list( opContext );
999
1000 if ( isPrincipalAnAdministrator( user.getClonedName() ) || !opContext.getSession().getDirectoryService().isAccessControlEnabled() )
1001 {
1002 return cursor;
1003 }
1004
1005 AuthorizationFilter authzFilter = new AuthorizationFilter();
1006 cursor.addEntryFilter( authzFilter );
1007 return cursor;
1008 }
1009
1010
1011 public EntryFilteringCursor search( NextInterceptor next, SearchOperationContext opContext ) throws Exception
1012 {
1013 LdapPrincipal user = opContext.getSession().getEffectivePrincipal();
1014 DN principalDn = user.getClonedName();
1015 EntryFilteringCursor cursor = next.search( opContext );
1016
1017 boolean isSubschemaSubentryLookup = subschemaSubentryDn.equals( opContext.getDn().getNormName() );
1018 SearchControls searchCtls = opContext.getSearchControls();
1019 boolean isRootDSELookup = opContext.getDn().size() == 0 && searchCtls.getSearchScope() == SearchControls.OBJECT_SCOPE;
1020
1021 if ( isPrincipalAnAdministrator( principalDn ) || !opContext.getSession().getDirectoryService().isAccessControlEnabled() || isRootDSELookup || isSubschemaSubentryLookup )
1022 {
1023 return cursor;
1024 }
1025
1026 cursor.addEntryFilter( new AuthorizationFilter() );
1027 return cursor;
1028 }
1029
1030
1031 public final boolean isPrincipalAnAdministrator( DN principalDn )
1032 {
1033 return groupCache.isPrincipalAnAdministrator( principalDn );
1034 }
1035
1036
1037 public boolean compare( NextInterceptor next, CompareOperationContext opContext ) throws Exception
1038 {
1039 DN name = opContext.getDn();
1040 String oid = opContext.getOid();
1041 Value<?> value = ( Value<?> ) opContext.getValue();
1042
1043 ClonedServerEntry entry = opContext.lookup( name, ByPassConstants.LOOKUP_BYPASS );
1044
1045 LdapPrincipal principal = opContext.getSession().getEffectivePrincipal();
1046 DN principalDn = principal.getClonedName();
1047
1048 if ( isPrincipalAnAdministrator( principalDn ) || !opContext.getSession().getDirectoryService().isAccessControlEnabled() )
1049 {
1050 return next.compare( opContext );
1051 }
1052
1053 Set<DN> userGroups = groupCache.getGroups( principalDn.getNormName() );
1054 Collection<ACITuple> tuples = new HashSet<ACITuple>();
1055 addPerscriptiveAciTuples( opContext, tuples, name, entry.getOriginalEntry() );
1056 addEntryAciTuples( tuples, entry );
1057 addSubentryAciTuples( opContext, tuples, name, entry );
1058
1059 engine.checkPermission( schemaManager, opContext, userGroups, principalDn,
1060 principal.getAuthenticationLevel(), name, null, null,
1061 READ_PERMS, tuples, entry, null );
1062 engine.checkPermission( schemaManager, opContext, userGroups, principalDn,
1063 principal.getAuthenticationLevel(), name, oid, value,
1064 COMPARE_PERMS, tuples, entry, null );
1065
1066 return next.compare( opContext );
1067 }
1068
1069
1070 public DN getMatchedName ( NextInterceptor next, GetMatchedNameOperationContext opContext ) throws Exception
1071 {
1072 // Access the principal requesting the operation, and bypass checks if it is the admin
1073 LdapPrincipal principal = opContext.getSession().getEffectivePrincipal();
1074 DN principalDn = principal.getClonedName();
1075
1076 if ( isPrincipalAnAdministrator( principalDn ) || !opContext.getSession().getDirectoryService().isAccessControlEnabled() )
1077 {
1078 return next.getMatchedName( opContext );
1079 }
1080
1081 // get the present matched name
1082 ClonedServerEntry entry;
1083 DN matched = next.getMatchedName( opContext );
1084
1085 // check if we have disclose on error permission for the entry at the matched dn
1086 // if not remove rdn and check that until nothing is left in the name and return
1087 // that but if permission is granted then short the process and return the dn
1088 while ( matched.size() > 0 )
1089 {
1090 entry = opContext.lookup( matched, ByPassConstants.GETMATCHEDDN_BYPASS );
1091
1092 Set<DN> userGroups = groupCache.getGroups( principalDn.getNormName() );
1093 Collection<ACITuple> tuples = new HashSet<ACITuple>();
1094 addPerscriptiveAciTuples( opContext, tuples, matched, entry.getOriginalEntry() );
1095 addEntryAciTuples( tuples, entry );
1096 addSubentryAciTuples( opContext, tuples, matched, entry );
1097
1098 if ( engine.hasPermission( schemaManager, opContext, userGroups, principalDn,
1099 principal.getAuthenticationLevel(), matched, null,
1100 null, MATCHEDNAME_PERMS, tuples, entry, null ) )
1101 {
1102 return matched;
1103 }
1104
1105 matched.remove( matched.size() - 1 );
1106 }
1107
1108 return matched;
1109 }
1110
1111
1112 public void cacheNewGroup( DN name, ServerEntry entry ) throws Exception
1113 {
1114 groupCache.groupAdded( name, entry );
1115 }
1116
1117
1118 private boolean filter( OperationContext opContext, DN normName, ClonedServerEntry clonedEntry )
1119 throws Exception
1120 {
1121 /*
1122 * First call hasPermission() for entry level "Browse" and "ReturnDN" perm
1123 * tests. If we hasPermission() returns false we immediately short the
1124 * process and return false.
1125 */
1126
1127 LdapPrincipal principal = opContext.getSession().getEffectivePrincipal();
1128 DN userDn = principal.getClonedName();
1129 Set<DN> userGroups = groupCache.getGroups( userDn.getNormName() );
1130 Collection<ACITuple> tuples = new HashSet<ACITuple>();
1131 addPerscriptiveAciTuples( opContext, tuples, normName, clonedEntry.getOriginalEntry() );
1132 addEntryAciTuples( tuples, clonedEntry.getOriginalEntry() );
1133 addSubentryAciTuples( opContext, tuples, normName, clonedEntry.getOriginalEntry() );
1134
1135 if ( !engine.hasPermission(
1136 schemaManager,
1137 opContext,
1138 userGroups,
1139 userDn,
1140 principal.getAuthenticationLevel(),
1141 normName,
1142 null,
1143 null,
1144 SEARCH_ENTRY_PERMS,
1145 tuples,
1146 clonedEntry.getOriginalEntry(),
1147 null ) )
1148 {
1149 return false;
1150 }
1151
1152 /*
1153 * For each attribute type we check if access is allowed to the type. If not
1154 * the attribute is yanked out of the entry to be returned. If permission is
1155 * allowed we move on to check if the values are allowed. Values that are
1156 * not allowed are removed from the attribute. If the attribute has no more
1157 * values remaining then the entire attribute is removed.
1158 */
1159 List<AttributeType> attributeToRemove = new ArrayList<AttributeType>();
1160
1161 for ( AttributeType attributeType:clonedEntry.getAttributeTypes() )
1162 {
1163 // if attribute type scope access is not allowed then remove the attribute and continue
1164 String id = attributeType.getName();
1165 EntryAttribute attr = clonedEntry.get( attributeType );
1166
1167 if ( !engine.hasPermission(
1168 schemaManager,
1169 opContext,
1170 userGroups,
1171 userDn,
1172 principal.getAuthenticationLevel(),
1173 normName,
1174 id,
1175 null,
1176 SEARCH_ATTRVAL_PERMS,
1177 tuples,
1178 clonedEntry,
1179 null ) )
1180 {
1181 attributeToRemove.add( attributeType );
1182
1183 continue;
1184 }
1185
1186 List<Value<?>> valueToRemove = new ArrayList<Value<?>>();
1187
1188 // attribute type scope is ok now let's determine value level scope
1189 for ( Value<?> value:attr )
1190 {
1191 if ( !engine.hasPermission(
1192 schemaManager,
1193 opContext,
1194 userGroups,
1195 userDn,
1196 principal.getAuthenticationLevel(),
1197 normName,
1198 attr.getUpId(),
1199 value,
1200 SEARCH_ATTRVAL_PERMS,
1201 tuples,
1202 clonedEntry,
1203 null ) )
1204 {
1205 valueToRemove.add( value );
1206 }
1207 }
1208
1209 for ( Value<?> value:valueToRemove )
1210 {
1211 attr.remove( value );
1212 }
1213
1214 if ( attr.size() == 0 )
1215 {
1216 attributeToRemove.add( attributeType );
1217 }
1218 }
1219
1220 for ( AttributeType attributeType:attributeToRemove )
1221 {
1222 clonedEntry.removeAttributes( attributeType );
1223 }
1224
1225 return true;
1226 }
1227
1228
1229 /**
1230 * WARNING: create one of these filters fresh every time for each new search.
1231 */
1232 class AuthorizationFilter implements EntryFilter
1233 {
1234 public boolean accept( SearchingOperationContext operationContext, ClonedServerEntry entry )
1235 throws Exception
1236 {
1237 DN normName = entry.getDn().normalize( schemaManager.getNormalizerMapping() );
1238 return filter( operationContext, normName, entry );
1239 }
1240 }
1241 }