| /* |
| * 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.authz; |
| |
| |
| import java.io.IOException; |
| import java.text.ParseException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| import javax.naming.directory.SearchControls; |
| |
| import org.apache.directory.api.ldap.aci.ACIItem; |
| import org.apache.directory.api.ldap.aci.ACIItemParser; |
| import org.apache.directory.api.ldap.aci.ACITuple; |
| import org.apache.directory.api.ldap.aci.MicroOperation; |
| import org.apache.directory.api.ldap.model.constants.Loggers; |
| import org.apache.directory.api.ldap.model.constants.SchemaConstants; |
| import org.apache.directory.api.ldap.model.entry.Attribute; |
| import org.apache.directory.api.ldap.model.entry.Entry; |
| import org.apache.directory.api.ldap.model.entry.Modification; |
| import org.apache.directory.api.ldap.model.entry.Value; |
| import org.apache.directory.api.ldap.model.exception.LdapException; |
| import org.apache.directory.api.ldap.model.exception.LdapNoPermissionException; |
| import org.apache.directory.api.ldap.model.exception.LdapOperationErrorException; |
| import org.apache.directory.api.ldap.model.exception.LdapOperationException; |
| import org.apache.directory.api.ldap.model.exception.LdapOtherException; |
| import org.apache.directory.api.ldap.model.filter.EqualityNode; |
| import org.apache.directory.api.ldap.model.filter.ExprNode; |
| import org.apache.directory.api.ldap.model.filter.OrNode; |
| import org.apache.directory.api.ldap.model.message.AliasDerefMode; |
| import org.apache.directory.api.ldap.model.message.SearchScope; |
| import org.apache.directory.api.ldap.model.name.Dn; |
| import org.apache.directory.api.ldap.model.schema.AttributeType; |
| import org.apache.directory.api.ldap.model.schema.normalizers.ConcreteNameComponentNormalizer; |
| import org.apache.directory.server.constants.ServerDNConstants; |
| import org.apache.directory.server.core.api.CoreSession; |
| import org.apache.directory.server.core.api.DirectoryService; |
| import org.apache.directory.server.core.api.InterceptorEnum; |
| import org.apache.directory.server.core.api.LdapPrincipal; |
| import org.apache.directory.server.core.api.entry.ClonedServerEntry; |
| import org.apache.directory.server.core.api.entry.ServerEntryUtils; |
| import org.apache.directory.server.core.api.filtering.EntryFilter; |
| import org.apache.directory.server.core.api.filtering.EntryFilteringCursor; |
| import org.apache.directory.server.core.api.interceptor.BaseInterceptor; |
| import org.apache.directory.server.core.api.interceptor.context.AddOperationContext; |
| import org.apache.directory.server.core.api.interceptor.context.CompareOperationContext; |
| import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext; |
| import org.apache.directory.server.core.api.interceptor.context.HasEntryOperationContext; |
| import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext; |
| import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext; |
| import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext; |
| import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext; |
| import org.apache.directory.server.core.api.interceptor.context.OperationContext; |
| import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext; |
| import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext; |
| import org.apache.directory.server.core.api.partition.Partition; |
| import org.apache.directory.server.core.api.partition.PartitionNexus; |
| import org.apache.directory.server.core.api.partition.PartitionTxn; |
| import org.apache.directory.server.core.api.subtree.SubentryUtils; |
| import org.apache.directory.server.core.authz.support.ACDFEngine; |
| import org.apache.directory.server.core.authz.support.AciContext; |
| import org.apache.directory.server.i18n.I18n; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| |
| /** |
| * An ACI based authorization service. |
| * |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| */ |
| public class AciAuthorizationInterceptor extends BaseInterceptor |
| { |
| /** the logger for this class */ |
| private static final Logger LOG = LoggerFactory.getLogger( AciAuthorizationInterceptor.class ); |
| |
| /** the dedicated logger for ACI */ |
| private static final Logger ACI_LOG = LoggerFactory.getLogger( Loggers.ACI_LOG.getName() ); |
| |
| private static final Collection<MicroOperation> ADD_PERMS; |
| private static final Collection<MicroOperation> READ_PERMS; |
| private static final Collection<MicroOperation> COMPARE_PERMS; |
| private static final Collection<MicroOperation> SEARCH_ENTRY_PERMS; |
| private static final Collection<MicroOperation> SEARCH_ATTRVAL_PERMS; |
| private static final Collection<MicroOperation> REMOVE_PERMS; |
| private static final Collection<MicroOperation> BROWSE_PERMS; |
| private static final Collection<MicroOperation> LOOKUP_PERMS; |
| private static final Collection<MicroOperation> REPLACE_PERMS; |
| private static final Collection<MicroOperation> RENAME_PERMS; |
| private static final Collection<MicroOperation> EXPORT_PERMS; |
| private static final Collection<MicroOperation> IMPORT_PERMS; |
| private static final Collection<MicroOperation> MOVERENAME_PERMS; |
| |
| static |
| { |
| Set<MicroOperation> set = new HashSet<>( 2 ); |
| set.add( MicroOperation.BROWSE ); |
| set.add( MicroOperation.RETURN_DN ); |
| SEARCH_ENTRY_PERMS = Collections.unmodifiableCollection( set ); |
| |
| set = new HashSet<>( 2 ); |
| set.add( MicroOperation.READ ); |
| set.add( MicroOperation.BROWSE ); |
| LOOKUP_PERMS = Collections.unmodifiableCollection( set ); |
| |
| set = new HashSet<>( 2 ); |
| set.add( MicroOperation.ADD ); |
| set.add( MicroOperation.REMOVE ); |
| REPLACE_PERMS = Collections.unmodifiableCollection( set ); |
| |
| set = new HashSet<>( 2 ); |
| set.add( MicroOperation.EXPORT ); |
| set.add( MicroOperation.RENAME ); |
| MOVERENAME_PERMS = Collections.unmodifiableCollection( set ); |
| |
| SEARCH_ATTRVAL_PERMS = Collections.singleton( MicroOperation.READ ); |
| ADD_PERMS = Collections.singleton( MicroOperation.ADD ); |
| READ_PERMS = Collections.singleton( MicroOperation.READ ); |
| COMPARE_PERMS = Collections.singleton( MicroOperation.COMPARE ); |
| REMOVE_PERMS = Collections.singleton( MicroOperation.REMOVE ); |
| BROWSE_PERMS = Collections.singleton( MicroOperation.BROWSE ); |
| RENAME_PERMS = Collections.singleton( MicroOperation.RENAME ); |
| EXPORT_PERMS = Collections.singleton( MicroOperation.EXPORT ); |
| IMPORT_PERMS = Collections.singleton( MicroOperation.IMPORT ); |
| } |
| |
| /** a tupleCache that responds to add, delete, and modify attempts */ |
| private TupleCache tupleCache; |
| |
| /** a groupCache that responds to add, delete, and modify attempts */ |
| private GroupCache groupCache; |
| |
| /** a normalizing ACIItem parser */ |
| private ACIItemParser aciParser; |
| |
| /** use and instance of the ACDF engine */ |
| private ACDFEngine engine; |
| |
| /** the system wide subschemaSubentryDn */ |
| private Dn subschemaSubentryDn; |
| |
| /** A reference to the nexus for direct backend operations */ |
| private PartitionNexus nexus; |
| |
| public static final SearchControls DEFAULT_SEARCH_CONTROLS = new SearchControls(); |
| |
| /** The SubentryUtils instance */ |
| private static SubentryUtils subentryUtils; |
| |
| |
| /** |
| * Create a AciAuthorizationInterceptor instance |
| */ |
| public AciAuthorizationInterceptor() |
| { |
| super( InterceptorEnum.ACI_AUTHORIZATION_INTERCEPTOR ); |
| } |
| |
| |
| /** |
| * Load the Tuples into the cache |
| */ |
| private void initTupleCache() throws LdapException |
| { |
| // Load all the prescriptiveACI : they are stored in AccessControlSubentry entries |
| SearchControls controls = new SearchControls(); |
| controls.setSearchScope( SearchControls.SUBTREE_SCOPE ); |
| controls.setReturningAttributes( new String[] |
| { SchemaConstants.PRESCRIPTIVE_ACI_AT } ); |
| |
| AttributeType ocAt = directoryService.getAtProvider().getObjectClass(); |
| ExprNode filter = new EqualityNode<String>( ocAt, |
| new Value( ocAt, SchemaConstants.ACCESS_CONTROL_SUBENTRY_OC ) ); |
| |
| CoreSession adminSession = directoryService.getAdminSession(); |
| |
| SearchOperationContext searchOperationContext = new SearchOperationContext( adminSession, Dn.ROOT_DSE, filter, controls ); |
| |
| searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES ); |
| Partition partition = nexus.getPartition( Dn.ROOT_DSE ); |
| searchOperationContext.setPartition( partition ); |
| |
| try ( PartitionTxn partitionTxn = partition.beginReadTransaction() ) |
| { |
| searchOperationContext.setTransaction( partitionTxn ); |
| |
| EntryFilteringCursor results = nexus.search( searchOperationContext ); |
| |
| try |
| { |
| while ( results.next() ) |
| { |
| Entry entry = results.get(); |
| |
| tupleCache.subentryAdded( entry.getDn(), entry ); |
| } |
| |
| results.close(); |
| } |
| catch ( Exception e ) |
| { |
| throw new LdapOperationException( e.getMessage(), e ); |
| } |
| } |
| catch ( IOException ioe ) |
| { |
| throw new LdapOtherException( ioe.getMessage(), ioe ); |
| } |
| } |
| |
| |
| /** |
| * Load the Groups into the cache |
| */ |
| private void initGroupCache() throws LdapException |
| { |
| // Load all the member/uniqueMember : they are stored in groupOfNames/groupOfUniqueName |
| SearchControls controls = new SearchControls(); |
| controls.setSearchScope( SearchControls.SUBTREE_SCOPE ); |
| controls.setReturningAttributes( new String[] |
| { SchemaConstants.MEMBER_AT, SchemaConstants.UNIQUE_MEMBER_AT } ); |
| AttributeType ocAt = directoryService.getAtProvider().getObjectClass(); |
| |
| ExprNode filter = |
| new OrNode( |
| new EqualityNode<String>( ocAt, new Value( ocAt, SchemaConstants.GROUP_OF_NAMES_OC ) ), |
| new EqualityNode<String>( ocAt, new Value( ocAt, SchemaConstants.GROUP_OF_UNIQUE_NAMES_OC ) ) ); |
| |
| CoreSession adminSession = directoryService.getAdminSession(); |
| |
| SearchOperationContext searchOperationContext = new SearchOperationContext( adminSession, Dn.ROOT_DSE, filter, |
| controls ); |
| |
| searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES ); |
| |
| EntryFilteringCursor results = nexus.search( searchOperationContext ); |
| |
| try |
| { |
| while ( results.next() ) |
| { |
| Entry entry = results.get(); |
| |
| groupCache.groupAdded( entry.getDn().getNormName(), entry ); |
| } |
| |
| results.close(); |
| } |
| catch ( Exception e ) |
| { |
| throw new LdapOperationException( e.getMessage(), e ); |
| } |
| } |
| |
| |
| /** |
| * Initializes this interceptor based service by getting a handle on the nexus, setting up |
| * the tuple and group membership caches, the ACIItem parser and the ACDF engine. |
| * |
| * @param directoryService the directory service core |
| * @throws LdapException if there are problems during initialization |
| */ |
| @Override |
| public void init( DirectoryService directoryService ) throws LdapException |
| { |
| LOG.debug( "Initializing the AciAuthorizationInterceptor" ); |
| |
| super.init( directoryService ); |
| |
| nexus = directoryService.getPartitionNexus(); |
| |
| CoreSession adminSession = directoryService.getAdminSession(); |
| |
| // Create the caches |
| tupleCache = new TupleCache( adminSession ); |
| groupCache = new GroupCache( directoryService ); |
| |
| // Iitialize the ACI PARSER and ACDF engine |
| aciParser = new ACIItemParser( new ConcreteNameComponentNormalizer( schemaManager ), schemaManager ); |
| engine = new ACDFEngine( schemaManager ); |
| |
| // stuff for dealing with subentries (garbage for now) |
| Value subschemaSubentry = directoryService.getPartitionNexus().getRootDseValue( |
| directoryService.getAtProvider().getSubschemaSubentry() ); |
| subschemaSubentryDn = dnFactory.create( subschemaSubentry.getString() ); |
| |
| // Init the caches now |
| initTupleCache(); |
| initGroupCache(); |
| |
| // Init the SubentryUtils instance |
| subentryUtils = new SubentryUtils( directoryService ); |
| } |
| |
| |
| private void protectCriticalEntries( OperationContext opCtx, Dn dn ) throws LdapException |
| { |
| Dn principalDn = getPrincipal( opCtx ).getDn(); |
| |
| if ( dn.isEmpty() ) |
| { |
| String msg = I18n.err( I18n.ERR_8 ); |
| LOG.error( msg ); |
| throw new LdapNoPermissionException( msg ); |
| } |
| |
| if ( isTheAdministrator( dn ) ) |
| { |
| String msg = I18n.err( I18n.ERR_9, principalDn.getName(), dn.getName() ); |
| LOG.error( msg ); |
| throw new LdapNoPermissionException( msg ); |
| } |
| } |
| |
| |
| /** |
| * Adds perscriptiveACI tuples to a collection of tuples by accessing the |
| * tupleCache. The tuple cache is accessed for each A/C subentry |
| * associated with the protected entry. Note that subentries are handled |
| * differently: their parent, the administrative entry is accessed to |
| * determine the perscriptiveACIs effecting the AP and hence the subentry |
| * which is considered to be in the same context. |
| * |
| * @param tuples the collection of tuples to add to |
| * @param dn the normalized distinguished name of the protected entry |
| * @param entry the target entry whose access is being controlled |
| * @throws Exception if there are problems accessing attribute values |
| * @param proxy the partition nexus proxy object |
| */ |
| private void addPerscriptiveAciTuples( OperationContext opContext, Collection<ACITuple> tuples, Dn dn, Entry entry ) |
| throws LdapException |
| { |
| Entry originalEntry; |
| |
| if ( entry instanceof ClonedServerEntry ) |
| { |
| originalEntry = ( ( ClonedServerEntry ) entry ).getOriginalEntry(); |
| } |
| else |
| { |
| originalEntry = entry; |
| } |
| |
| Attribute oc = originalEntry.get( directoryService.getAtProvider().getObjectClass() ); |
| |
| /* |
| * If the protected entry is a subentry, then the entry being evaluated |
| * for perscriptiveACIs is in fact the administrative entry. By |
| * substituting the administrative entry for the actual subentry the |
| * code below this "if" statement correctly evaluates the effects of |
| * perscriptiveACI on the subentry. Basically subentries are considered |
| * to be in the same naming context as their access point so the subentries |
| * effecting their parent entry applies to them as well. |
| */ |
| if ( oc.contains( SchemaConstants.SUBENTRY_OC ) ) |
| { |
| Dn parentDn = dn.getParent(); |
| CoreSession session = opContext.getSession(); |
| LookupOperationContext lookupContext = new LookupOperationContext( session, parentDn, |
| SchemaConstants.ALL_ATTRIBUTES_ARRAY ); |
| lookupContext.setPartition( opContext.getPartition() ); |
| lookupContext.setTransaction( opContext.getTransaction() ); |
| |
| originalEntry = directoryService.getPartitionNexus().lookup( lookupContext ); |
| } |
| |
| Attribute subentries = originalEntry.get( directoryService.getAtProvider().getAccessControlSubentries() ); |
| |
| if ( subentries == null ) |
| { |
| return; |
| } |
| |
| for ( Value value : subentries ) |
| { |
| String subentryDnStr = value.getString(); |
| Dn subentryDn = dnFactory.create( subentryDnStr ); |
| tuples.addAll( tupleCache.getACITuples( subentryDn.getNormName() ) ); |
| } |
| } |
| |
| |
| /** |
| * Adds the set of entryACI tuples to a collection of tuples. The entryACI |
| * is parsed and tuples are generated on they fly then added to the collection. |
| * |
| * @param tuples the collection of tuples to add to |
| * @param entry the target entry that access to is being regulated |
| * @throws Exception if there are problems accessing attribute values |
| */ |
| private void addEntryAciTuples( Collection<ACITuple> tuples, Entry entry ) throws LdapException |
| { |
| Attribute entryAci = entry.get( directoryService.getAtProvider().getEntryACI() ); |
| |
| if ( entryAci == null ) |
| { |
| return; |
| } |
| |
| for ( Value value : entryAci ) |
| { |
| String aciString = value.getString(); |
| ACIItem item; |
| |
| try |
| { |
| item = aciParser.parse( aciString ); |
| } |
| catch ( ParseException e ) |
| { |
| String msg = I18n.err( I18n.ERR_10, aciString ); |
| LOG.error( msg, e ); |
| throw new LdapOperationErrorException( msg ); |
| } |
| |
| tuples.addAll( item.toTuples() ); |
| } |
| } |
| |
| |
| /** |
| * Adds the set of subentryACI tuples to a collection of tuples. The subentryACI |
| * is parsed and tuples are generated on the fly then added to the collection. |
| * |
| * @param tuples the collection of tuples to add to |
| * @param dn the normalized distinguished name of the protected entry |
| * @param entry the target entry that access to is being regulated |
| * @throws Exception if there are problems accessing attribute values |
| * @param proxy the partition nexus proxy object |
| */ |
| private void addSubentryAciTuples( OperationContext opContext, Collection<ACITuple> tuples, Dn dn, Entry entry ) |
| throws LdapException |
| { |
| // only perform this for subentries |
| if ( !entry.contains( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.SUBENTRY_OC ) ) |
| { |
| return; |
| } |
| |
| // get the parent or administrative entry for this subentry since it |
| // will contain the subentryACI attributes that effect subentries |
| Dn parentDn = dn.getParent(); |
| |
| CoreSession session = opContext.getSession(); |
| LookupOperationContext lookupContext = new LookupOperationContext( session, parentDn, |
| SchemaConstants.ALL_ATTRIBUTES_ARRAY ); |
| lookupContext.setPartition( opContext.getPartition() ); |
| lookupContext.setTransaction( opContext.getTransaction() ); |
| |
| Entry administrativeEntry = ( ( ClonedServerEntry ) directoryService.getPartitionNexus().lookup( lookupContext ) ) |
| .getOriginalEntry(); |
| |
| Attribute subentryAci = administrativeEntry.get( directoryService.getAtProvider().getSubentryACI() ); |
| |
| if ( subentryAci == null ) |
| { |
| return; |
| } |
| |
| for ( Value value : subentryAci ) |
| { |
| String aciString = value.getString(); |
| ACIItem item; |
| |
| try |
| { |
| item = aciParser.parse( aciString ); |
| } |
| catch ( ParseException e ) |
| { |
| String msg = I18n.err( I18n.ERR_11, aciString ); |
| LOG.error( msg, e ); |
| throw new LdapOperationErrorException( msg ); |
| } |
| |
| tuples.addAll( item.toTuples() ); |
| } |
| } |
| |
| |
| /* ------------------------------------------------------------------------------- |
| * Within every access controled interceptor method we must retrieve the ACITuple |
| * set for all the perscriptiveACIs that apply to the candidate, the target entry |
| * operated upon. This ACITuple set is gotten from the TupleCache by looking up |
| * the subentries referenced by the accessControlSubentries operational attribute |
| * within the target entry. |
| * |
| * Then the entry is inspected for an entryACI. This is not done for the add op |
| * since it could introduce a security breech. So for non-add ops if present a |
| * set of ACITuples are generated for all the entryACIs within the entry. This |
| * set is combined with the ACITuples cached for the perscriptiveACI affecting |
| * the target entry. If the entry is a subentry the ACIs are also processed for |
| * the subentry to generate more ACITuples. This subentry TupleACI set is joined |
| * with the entry and perscriptive ACI. |
| * |
| * The union of ACITuples are fed into the engine along with other parameters |
| * to decide whether a permission is granted or rejected for the specific |
| * operation. |
| * ------------------------------------------------------------------------------- |
| */ |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void add( AddOperationContext addContext ) throws LdapException |
| { |
| // bypass authz code if it was disabled |
| if ( !directoryService.isAccessControlEnabled() ) |
| { |
| ACI_LOG.debug( "ACI interceptor disabled" ); |
| next( addContext ); |
| return; |
| } |
| |
| ACI_LOG.debug( "Adding the entry {}", addContext.getEntry() ); |
| |
| // Access the principal requesting the operation, and bypass checks if it is the admin |
| LdapPrincipal principal = addContext.getSession().getEffectivePrincipal(); |
| Dn principalDn = principal.getDn(); |
| |
| Entry serverEntry = addContext.getEntry(); |
| |
| Dn dn = addContext.getDn(); |
| |
| // bypass authz code but manage caches if operation is performed by the admin |
| if ( isPrincipalAnAdministrator( principalDn ) ) |
| { |
| ACI_LOG.debug( "Addition done by the administartor : no check" ); |
| |
| next( addContext ); |
| tupleCache.subentryAdded( dn, serverEntry ); |
| groupCache.groupAdded( dn.getNormName(), serverEntry ); |
| return; |
| } |
| |
| // perform checks below here for all non-admin users |
| Entry subentry = subentryUtils.getSubentryAttributes( dn, serverEntry ); |
| |
| for ( Attribute attribute : serverEntry ) |
| { |
| subentry.put( attribute ); |
| } |
| |
| // Assemble all the information required to make an access control decision |
| Set<String> userGroups = groupCache.getGroups( principalDn.getNormName() ); |
| Collection<ACITuple> tuples = new HashSet<>(); |
| |
| // Build the total collection of tuples to be considered for add rights |
| // NOTE: entryACI are NOT considered in adds (it would be a security breech) |
| addPerscriptiveAciTuples( addContext, tuples, dn, subentry ); |
| addSubentryAciTuples( addContext, tuples, dn, subentry ); |
| |
| // check if entry scope permission is granted |
| AciContext entryAciCtx = new AciContext( schemaManager, addContext ); |
| entryAciCtx.setUserGroupNames( userGroups ); |
| entryAciCtx.setUserDn( principalDn ); |
| entryAciCtx.setAuthenticationLevel( principal.getAuthenticationLevel() ); |
| entryAciCtx.setEntryDn( dn ); |
| entryAciCtx.setMicroOperations( ADD_PERMS ); |
| entryAciCtx.setAciTuples( tuples ); |
| entryAciCtx.setEntry( subentry ); |
| |
| engine.checkPermission( entryAciCtx ); |
| |
| // now we must check if attribute type and value scope permission is granted |
| for ( Attribute attribute : serverEntry ) |
| { |
| for ( Value value : attribute ) |
| { |
| AciContext attrAciContext = new AciContext( schemaManager, addContext ); |
| attrAciContext.setUserGroupNames( userGroups ); |
| attrAciContext.setUserDn( principalDn ); |
| attrAciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); |
| attrAciContext.setEntryDn( dn ); |
| attrAciContext.setAttributeType( attribute.getAttributeType() ); |
| attrAciContext.setAttrValue( value ); |
| attrAciContext.setMicroOperations( ADD_PERMS ); |
| attrAciContext.setAciTuples( tuples ); |
| attrAciContext.setEntry( serverEntry ); |
| |
| engine.checkPermission( attrAciContext ); |
| } |
| } |
| |
| // if we've gotten this far then access has been granted |
| next( addContext ); |
| |
| // if the entry added is a subentry or a groupOf[Unique]Names we must |
| // update the ACITuple cache and the groups cache to keep them in sync |
| tupleCache.subentryAdded( dn, serverEntry ); |
| groupCache.groupAdded( dn.getNormName(), serverEntry ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean compare( CompareOperationContext compareContext ) throws LdapException |
| { |
| CoreSession session = compareContext.getSession(); |
| Dn dn = compareContext.getDn(); |
| String oid = compareContext.getOid(); |
| |
| Entry entry = compareContext.getOriginalEntry(); |
| |
| LdapPrincipal principal = session.getEffectivePrincipal(); |
| Dn principalDn = principal.getDn(); |
| |
| if ( isPrincipalAnAdministrator( principalDn ) || !directoryService.isAccessControlEnabled() ) |
| { |
| return next( compareContext ); |
| } |
| |
| Set<String> userGroups = groupCache.getGroups( principalDn.getNormName() ); |
| Collection<ACITuple> tuples = new HashSet<>(); |
| addPerscriptiveAciTuples( compareContext, tuples, dn, entry ); |
| addEntryAciTuples( tuples, entry ); |
| addSubentryAciTuples( compareContext, tuples, dn, entry ); |
| |
| AciContext aciContext = new AciContext( schemaManager, compareContext ); |
| aciContext.setUserGroupNames( userGroups ); |
| aciContext.setUserDn( principalDn ); |
| aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); |
| aciContext.setEntryDn( dn ); |
| aciContext.setMicroOperations( READ_PERMS ); |
| aciContext.setAciTuples( tuples ); |
| aciContext.setEntry( entry ); |
| |
| engine.checkPermission( aciContext ); |
| |
| AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( oid ); |
| |
| aciContext = new AciContext( schemaManager, compareContext ); |
| aciContext.setUserGroupNames( userGroups ); |
| aciContext.setUserDn( principalDn ); |
| aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); |
| aciContext.setEntryDn( dn ); |
| aciContext.setAttributeType( attributeType ); |
| aciContext.setMicroOperations( COMPARE_PERMS ); |
| aciContext.setAciTuples( tuples ); |
| aciContext.setEntry( entry ); |
| |
| engine.checkPermission( aciContext ); |
| |
| return next( compareContext ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void delete( DeleteOperationContext deleteContext ) throws LdapException |
| { |
| CoreSession session = deleteContext.getSession(); |
| |
| // bypass authz code if we are disabled |
| if ( !directoryService.isAccessControlEnabled() ) |
| { |
| next( deleteContext ); |
| return; |
| } |
| |
| Dn dn = deleteContext.getDn(); |
| LdapPrincipal principal = session.getEffectivePrincipal(); |
| Dn principalDn = principal.getDn(); |
| |
| Entry entry = deleteContext.getEntry(); |
| |
| protectCriticalEntries( deleteContext, dn ); |
| |
| // bypass authz code but manage caches if operation is performed by the admin |
| if ( isPrincipalAnAdministrator( principalDn ) ) |
| { |
| next( deleteContext ); |
| |
| tupleCache.subentryDeleted( dn, entry ); |
| groupCache.groupDeleted( dn, entry ); |
| |
| return; |
| } |
| |
| Set<String> userGroups = groupCache.getGroups( principalDn.getNormName() ); |
| Collection<ACITuple> tuples = new HashSet<>(); |
| addPerscriptiveAciTuples( deleteContext, tuples, dn, entry ); |
| addEntryAciTuples( tuples, entry ); |
| addSubentryAciTuples( deleteContext, tuples, dn, entry ); |
| |
| AciContext aciContext = new AciContext( schemaManager, deleteContext ); |
| aciContext.setUserGroupNames( userGroups ); |
| aciContext.setUserDn( principalDn ); |
| aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); |
| aciContext.setEntryDn( dn ); |
| aciContext.setMicroOperations( REMOVE_PERMS ); |
| aciContext.setAciTuples( tuples ); |
| aciContext.setEntry( entry ); |
| |
| engine.checkPermission( aciContext ); |
| |
| next( deleteContext ); |
| |
| tupleCache.subentryDeleted( dn, entry ); |
| groupCache.groupDeleted( dn, entry ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean hasEntry( HasEntryOperationContext hasEntryContext ) throws LdapException |
| { |
| Dn dn = hasEntryContext.getDn(); |
| |
| if ( !directoryService.isAccessControlEnabled() ) |
| { |
| return dn.isRootDse() || next( hasEntryContext ); |
| } |
| |
| boolean answer = next( hasEntryContext ); |
| |
| // no checks on the RootDSE |
| if ( dn.isRootDse() ) |
| { |
| // No need to go down to the stack, if the dn is empty |
| // It's the rootDSE, and it exists ! |
| return answer; |
| } |
| |
| CoreSession session = hasEntryContext.getSession(); |
| |
| // TODO - eventually replace this with a check on session.isAnAdministrator() |
| LdapPrincipal principal = session.getEffectivePrincipal(); |
| Dn principalDn = principal.getDn(); |
| |
| if ( isPrincipalAnAdministrator( principalDn ) ) |
| { |
| return answer; |
| } |
| |
| LookupOperationContext lookupContext = new LookupOperationContext( session, dn, |
| SchemaConstants.ALL_ATTRIBUTES_ARRAY ); |
| lookupContext.setPartition( hasEntryContext.getPartition() ); |
| lookupContext.setTransaction( hasEntryContext.getTransaction() ); |
| |
| Entry entry = directoryService.getPartitionNexus().lookup( lookupContext ); |
| |
| Set<String> userGroups = groupCache.getGroups( principalDn.getNormName() ); |
| Collection<ACITuple> tuples = new HashSet<>(); |
| addPerscriptiveAciTuples( hasEntryContext, tuples, dn, entry ); |
| addEntryAciTuples( tuples, ( ( ClonedServerEntry ) entry ).getOriginalEntry() ); |
| addSubentryAciTuples( hasEntryContext, tuples, dn, ( ( ClonedServerEntry ) entry ).getOriginalEntry() ); |
| |
| // check that we have browse access to the entry |
| AciContext aciContext = new AciContext( schemaManager, hasEntryContext ); |
| aciContext.setUserGroupNames( userGroups ); |
| aciContext.setUserDn( principalDn ); |
| aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); |
| aciContext.setEntryDn( dn ); |
| aciContext.setMicroOperations( BROWSE_PERMS ); |
| aciContext.setAciTuples( tuples ); |
| aciContext.setEntry( ( ( ClonedServerEntry ) entry ).getOriginalEntry() ); |
| |
| engine.checkPermission( aciContext ); |
| |
| return next( hasEntryContext ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Entry lookup( LookupOperationContext lookupContext ) throws LdapException |
| { |
| CoreSession session = lookupContext.getSession(); |
| |
| Entry entry = next( lookupContext ); |
| |
| LdapPrincipal principal = session.getEffectivePrincipal(); |
| Dn principalDn = principal.getDn(); |
| |
| if ( !principalDn.isSchemaAware() ) |
| { |
| principalDn = new Dn( schemaManager, principalDn ); |
| } |
| |
| // Bypass this interceptor if we disabled the AC subsystem or if the principal is the admin |
| if ( isPrincipalAnAdministrator( principalDn ) || !directoryService.isAccessControlEnabled() ) |
| { |
| return entry; |
| } |
| |
| checkLookupAccess( lookupContext, entry ); |
| |
| return entry; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void modify( ModifyOperationContext modifyContext ) throws LdapException |
| { |
| Dn dn = modifyContext.getDn(); |
| |
| // Access the principal requesting the operation, and bypass checks if it is the admin |
| Entry entry = modifyContext.getEntry(); |
| |
| LdapPrincipal principal = modifyContext.getSession().getEffectivePrincipal(); |
| Dn principalDn = principal.getDn(); |
| |
| // bypass authz code if we are disabled |
| if ( !directoryService.isAccessControlEnabled() ) |
| { |
| next( modifyContext ); |
| return; |
| } |
| |
| List<Modification> mods = modifyContext.getModItems(); |
| |
| // bypass authz code but manage caches if operation is performed by the admin |
| if ( isPrincipalAnAdministrator( principalDn ) ) |
| { |
| next( modifyContext ); |
| |
| Entry modifiedEntry = modifyContext.getAlteredEntry(); |
| tupleCache.subentryModified( dn, mods, modifiedEntry ); |
| groupCache.groupModified( dn, mods, entry, schemaManager ); |
| |
| return; |
| } |
| |
| Set<String> userGroups = groupCache.getGroups( principalDn.getNormName() ); |
| Collection<ACITuple> tuples = new HashSet<>(); |
| addPerscriptiveAciTuples( modifyContext, tuples, dn, entry ); |
| addEntryAciTuples( tuples, entry ); |
| addSubentryAciTuples( modifyContext, tuples, dn, entry ); |
| |
| AciContext entryAciContext = new AciContext( schemaManager, modifyContext ); |
| entryAciContext.setUserGroupNames( userGroups ); |
| entryAciContext.setUserDn( principalDn ); |
| entryAciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); |
| entryAciContext.setEntryDn( dn ); |
| entryAciContext.setMicroOperations( Collections.singleton( MicroOperation.MODIFY ) ); |
| entryAciContext.setAciTuples( tuples ); |
| entryAciContext.setEntry( entry ); |
| |
| engine.checkPermission( entryAciContext ); |
| |
| Collection<MicroOperation> perms; |
| Entry entryView = entry.clone(); |
| |
| for ( Modification mod : mods ) |
| { |
| Attribute attr = mod.getAttribute(); |
| |
| switch ( mod.getOperation() ) |
| { |
| case ADD_ATTRIBUTE: |
| perms = ADD_PERMS; |
| |
| // If the attribute is being created with an initial value ... |
| if ( entry.get( attr.getId() ) == null ) |
| { |
| AciContext attrAciContext = new AciContext( schemaManager, modifyContext ); |
| attrAciContext.setUserGroupNames( userGroups ); |
| attrAciContext.setUserDn( principalDn ); |
| attrAciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); |
| attrAciContext.setEntryDn( dn ); |
| attrAciContext.setAttributeType( attr.getAttributeType() ); |
| attrAciContext.setMicroOperations( perms ); |
| attrAciContext.setAciTuples( tuples ); |
| attrAciContext.setEntry( entry ); |
| |
| // ... we also need to check if adding the attribute is permitted |
| engine.checkPermission( attrAciContext ); |
| } |
| |
| break; |
| |
| case REMOVE_ATTRIBUTE: |
| perms = REMOVE_PERMS; |
| Attribute entryAttr = entry.get( attr.getId() ); |
| |
| if ( ( entryAttr != null ) && ( entryAttr.size() == 1 ) ) |
| { |
| // If there is only one value remaining in the attribute ... |
| // ... we also need to check if removing the attribute at all is permitted |
| AciContext aciContext = new AciContext( schemaManager, modifyContext ); |
| aciContext.setUserGroupNames( userGroups ); |
| aciContext.setUserDn( principalDn ); |
| aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); |
| aciContext.setEntryDn( dn ); |
| aciContext.setAttributeType( attr.getAttributeType() ); |
| aciContext.setMicroOperations( perms ); |
| aciContext.setAciTuples( tuples ); |
| aciContext.setEntry( entry ); |
| |
| engine.checkPermission( aciContext ); |
| } |
| |
| break; |
| |
| case REPLACE_ATTRIBUTE: |
| perms = REPLACE_PERMS; |
| break; |
| |
| default: |
| throw new IllegalArgumentException( "Unexpected modify operation " + mod.getOperation() ); |
| } |
| |
| /** |
| * Update the entry view as the current modification is applied to the original entry. |
| * This is especially required for handling the MaxValueCount protected item. Number of |
| * values for an attribute after a modification should be known in advance in order to |
| * check permissions for MaxValueCount protected item. So during addition of the first |
| * value of an attribute it can be rejected if the permission denied due the the |
| * MaxValueCount protected item. This is not the perfect implementation as required by |
| * the specification because the system should reject the addition exactly on the right |
| * value of the attribute. However as we do not have that much granularity in our |
| * implementation (we consider an Attribute Addition itself a Micro Operation, |
| * not the individual Value Additions) we just handle this when the first value of an |
| * attribute is being checked for relevant permissions below. |
| */ |
| entryView = ServerEntryUtils.getTargetEntry( mod, entryView, schemaManager ); |
| |
| for ( Value value : attr ) |
| { |
| AciContext aciContext = new AciContext( schemaManager, modifyContext ); |
| aciContext.setUserGroupNames( userGroups ); |
| aciContext.setUserDn( principalDn ); |
| aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); |
| aciContext.setEntryDn( dn ); |
| aciContext.setAttributeType( attr.getAttributeType() ); |
| aciContext.setAttrValue( value ); |
| aciContext.setMicroOperations( perms ); |
| aciContext.setAciTuples( tuples ); |
| aciContext.setEntry( entry ); |
| aciContext.setEntryView( entryView ); |
| |
| engine.checkPermission( aciContext ); |
| } |
| } |
| |
| next( modifyContext ); |
| |
| Entry modifiedEntry = modifyContext.getAlteredEntry(); |
| tupleCache.subentryModified( dn, mods, modifiedEntry ); |
| groupCache.groupModified( dn, mods, entry, schemaManager ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void move( MoveOperationContext moveContext ) throws LdapException |
| { |
| Dn oriChildName = moveContext.getDn(); |
| |
| // Access the principal requesting the operation, and bypass checks if it is the admin |
| Entry entry = moveContext.getOriginalEntry(); |
| CoreSession session = moveContext.getSession(); |
| |
| Dn newDn = moveContext.getNewDn(); |
| |
| LdapPrincipal principal = session.getEffectivePrincipal(); |
| Dn principalDn = principal.getDn(); |
| |
| // bypass authz code if we are disabled |
| if ( !directoryService.isAccessControlEnabled() ) |
| { |
| next( moveContext ); |
| return; |
| } |
| |
| protectCriticalEntries( moveContext, oriChildName ); |
| |
| // bypass authz code but manage caches if operation is performed by the admin |
| if ( isPrincipalAnAdministrator( principalDn ) ) |
| { |
| next( moveContext ); |
| tupleCache.subentryRenamed( oriChildName, newDn ); |
| groupCache.groupRenamed( oriChildName, newDn ); |
| return; |
| } |
| |
| Set<String> userGroups = groupCache.getGroups( principalDn.getNormName() ); |
| Collection<ACITuple> tuples = new HashSet<>(); |
| addPerscriptiveAciTuples( moveContext, tuples, oriChildName, entry ); |
| addEntryAciTuples( tuples, entry ); |
| addSubentryAciTuples( moveContext, tuples, oriChildName, entry ); |
| |
| AciContext aciContext = new AciContext( schemaManager, moveContext ); |
| aciContext.setUserGroupNames( userGroups ); |
| aciContext.setUserDn( principalDn ); |
| aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); |
| aciContext.setEntryDn( oriChildName ); |
| aciContext.setMicroOperations( EXPORT_PERMS ); |
| aciContext.setAciTuples( tuples ); |
| aciContext.setEntry( entry ); |
| |
| engine.checkPermission( aciContext ); |
| |
| // Get the entry again without operational attributes |
| // because access control subentry operational attributes |
| // will not be valid at the new location. |
| // This will certainly be fixed by the SubentryInterceptor, |
| // but after this service. |
| LookupOperationContext lookupContext = new LookupOperationContext( session, oriChildName, |
| SchemaConstants.ALL_USER_ATTRIBUTES_ARRAY ); |
| lookupContext.setPartition( moveContext.getPartition() ); |
| lookupContext.setTransaction( moveContext.getTransaction() ); |
| |
| Entry importedEntry = directoryService.getPartitionNexus().lookup( lookupContext ); |
| |
| // As the target entry does not exist yet and so |
| // its subentry operational attributes are not there, |
| // we need to construct an entry to represent it |
| // at least with minimal requirements which are object class |
| // and access control subentry operational attributes. |
| Entry subentryAttrs = subentryUtils.getSubentryAttributes( newDn, importedEntry ); |
| |
| for ( Attribute attribute : importedEntry ) |
| { |
| subentryAttrs.put( attribute ); |
| } |
| |
| Collection<ACITuple> destTuples = new HashSet<>(); |
| // Import permission is only valid for prescriptive ACIs |
| addPerscriptiveAciTuples( moveContext, destTuples, newDn, subentryAttrs ); |
| |
| // Evaluate the target context to see whether it |
| // allows an entry named newName to be imported as a subordinate. |
| aciContext = new AciContext( schemaManager, moveContext ); |
| aciContext.setUserGroupNames( userGroups ); |
| aciContext.setUserDn( principalDn ); |
| aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); |
| aciContext.setEntryDn( newDn ); |
| aciContext.setMicroOperations( IMPORT_PERMS ); |
| aciContext.setAciTuples( destTuples ); |
| aciContext.setEntry( subentryAttrs ); |
| |
| engine.checkPermission( aciContext ); |
| |
| next( moveContext ); |
| tupleCache.subentryRenamed( oriChildName, newDn ); |
| groupCache.groupRenamed( oriChildName, newDn ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void moveAndRename( MoveAndRenameOperationContext moveAndRenameContext ) throws LdapException |
| { |
| Dn oldDn = moveAndRenameContext.getDn(); |
| CoreSession session = moveAndRenameContext.getSession(); |
| |
| Entry entry = moveAndRenameContext.getOriginalEntry(); |
| |
| LdapPrincipal principal = session.getEffectivePrincipal(); |
| Dn principalDn = principal.getDn(); |
| Dn newDn = moveAndRenameContext.getNewDn(); |
| |
| // bypass authz code if we are disabled |
| if ( !directoryService.isAccessControlEnabled() ) |
| { |
| next( moveAndRenameContext ); |
| |
| return; |
| } |
| |
| protectCriticalEntries( moveAndRenameContext, oldDn ); |
| |
| // bypass authz code but manage caches if operation is performed by the admin |
| if ( isPrincipalAnAdministrator( principalDn ) ) |
| { |
| next( moveAndRenameContext ); |
| tupleCache.subentryRenamed( oldDn, newDn ); |
| groupCache.groupRenamed( oldDn, newDn ); |
| |
| return; |
| } |
| |
| Set<String> userGroups = groupCache.getGroups( principalDn.getNormName() ); |
| Collection<ACITuple> tuples = new HashSet<>(); |
| addPerscriptiveAciTuples( moveAndRenameContext, tuples, oldDn, entry ); |
| addEntryAciTuples( tuples, entry ); |
| addSubentryAciTuples( moveAndRenameContext, tuples, oldDn, entry ); |
| |
| AciContext aciContext = new AciContext( schemaManager, moveAndRenameContext ); |
| aciContext.setUserGroupNames( userGroups ); |
| aciContext.setUserDn( principalDn ); |
| aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); |
| aciContext.setEntryDn( oldDn ); |
| aciContext.setMicroOperations( MOVERENAME_PERMS ); |
| aciContext.setAciTuples( tuples ); |
| aciContext.setEntry( entry ); |
| |
| engine.checkPermission( aciContext ); |
| |
| // Get the entry again without operational attributes |
| // because access control subentry operational attributes |
| // will not be valid at the new location. |
| // This will certainly be fixed by the SubentryInterceptor, |
| // but after this service. |
| |
| LookupOperationContext lookupContext = new LookupOperationContext( session, oldDn, |
| SchemaConstants.ALL_USER_ATTRIBUTES_ARRAY ); |
| lookupContext.setPartition( moveAndRenameContext.getPartition() ); |
| lookupContext.setTransaction( moveAndRenameContext.getTransaction() ); |
| |
| Entry importedEntry = directoryService.getPartitionNexus().lookup( lookupContext ); |
| |
| // As the target entry does not exist yet and so |
| // its subentry operational attributes are not there, |
| // we need to construct an entry to represent it |
| // at least with minimal requirements which are object class |
| // and access control subentry operational attributes. |
| Entry subentryAttrs = subentryUtils.getSubentryAttributes( newDn, importedEntry ); |
| |
| for ( Attribute attribute : importedEntry ) |
| { |
| subentryAttrs.put( attribute ); |
| } |
| |
| Collection<ACITuple> destTuples = new HashSet<>(); |
| // Import permission is only valid for prescriptive ACIs |
| addPerscriptiveAciTuples( moveAndRenameContext, destTuples, newDn, subentryAttrs ); |
| |
| // Evaluate the target context to see whether it |
| // allows an entry named newName to be imported as a subordinate. |
| aciContext = new AciContext( schemaManager, moveAndRenameContext ); |
| aciContext.setUserGroupNames( userGroups ); |
| aciContext.setUserDn( principalDn ); |
| aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); |
| aciContext.setEntryDn( newDn ); |
| aciContext.setMicroOperations( IMPORT_PERMS ); |
| aciContext.setAciTuples( destTuples ); |
| aciContext.setEntry( subentryAttrs ); |
| |
| engine.checkPermission( aciContext ); |
| |
| next( moveAndRenameContext ); |
| tupleCache.subentryRenamed( oldDn, newDn ); |
| groupCache.groupRenamed( oldDn, newDn ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void rename( RenameOperationContext renameContext ) throws LdapException |
| { |
| Dn oldName = renameContext.getDn(); |
| Entry originalEntry = renameContext.getOriginalEntry(); |
| |
| if ( renameContext.getEntry() != null ) |
| { |
| originalEntry = ( ( ClonedServerEntry ) renameContext.getEntry() ).getOriginalEntry(); |
| } |
| |
| LdapPrincipal principal = renameContext.getSession().getEffectivePrincipal(); |
| Dn principalDn = principal.getDn(); |
| Dn newName = renameContext.getNewDn(); |
| |
| // bypass authz code if we are disabled |
| if ( !directoryService.isAccessControlEnabled() ) |
| { |
| next( renameContext ); |
| return; |
| } |
| |
| protectCriticalEntries( renameContext, oldName ); |
| |
| // bypass authz code but manage caches if operation is performed by the admin |
| if ( isPrincipalAnAdministrator( principalDn ) ) |
| { |
| next( renameContext ); |
| tupleCache.subentryRenamed( oldName, newName ); |
| |
| // TODO : this method returns a boolean : what should we do with the result ? |
| groupCache.groupRenamed( oldName, newName ); |
| |
| return; |
| } |
| |
| Set<String> userGroups = groupCache.getGroups( principalDn.getNormName() ); |
| Collection<ACITuple> tuples = new HashSet<>(); |
| addPerscriptiveAciTuples( renameContext, tuples, oldName, originalEntry ); |
| addEntryAciTuples( tuples, originalEntry ); |
| addSubentryAciTuples( renameContext, tuples, oldName, originalEntry ); |
| |
| AciContext aciContext = new AciContext( schemaManager, renameContext ); |
| aciContext.setUserGroupNames( userGroups ); |
| aciContext.setUserDn( principalDn ); |
| aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); |
| aciContext.setEntryDn( oldName ); |
| aciContext.setMicroOperations( RENAME_PERMS ); |
| aciContext.setAciTuples( tuples ); |
| aciContext.setEntry( originalEntry ); |
| |
| engine.checkPermission( aciContext ); |
| |
| next( renameContext ); |
| tupleCache.subentryRenamed( oldName, newName ); |
| groupCache.groupRenamed( oldName, newName ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public EntryFilteringCursor search( SearchOperationContext searchContext ) throws LdapException |
| { |
| LdapPrincipal user = searchContext.getSession().getEffectivePrincipal(); |
| Dn principalDn = user.getDn(); |
| EntryFilteringCursor cursor = next( searchContext ); |
| |
| boolean isSubschemaSubentryLookup = subschemaSubentryDn.equals( searchContext.getDn() ); |
| |
| boolean isRootDseLookup = ( searchContext.getDn().size() == 0 ) |
| && ( searchContext.getScope() == SearchScope.OBJECT ); |
| |
| if ( isPrincipalAnAdministrator( principalDn ) |
| || !directoryService.isAccessControlEnabled() || isRootDseLookup |
| || isSubschemaSubentryLookup ) |
| { |
| return cursor; |
| } |
| |
| cursor.addEntryFilter( new AuthorizationFilter() ); |
| return cursor; |
| } |
| |
| |
| /** |
| * Checks if the READ permissions exist to the entry and to each attribute type and |
| * value. |
| * |
| * @todo not sure if we should hide attribute types/values or throw an exception |
| * instead. I think we're going to have to use a filter to restrict the return |
| * of attribute types and values instead of throwing an exception. Lack of read |
| * perms to attributes and their values results in their removal when returning |
| * the entry. |
| * |
| * @param principal the user associated with the call |
| * @param dn the name of the entry being looked up |
| * @param entry the raw entry pulled from the nexus |
| * @throws Exception if undlying access to the DIT fails |
| */ |
| private void checkLookupAccess( LookupOperationContext lookupContext, Entry entry ) throws LdapException |
| { |
| Dn dn = lookupContext.getDn(); |
| |
| // no permissions checks on the RootDSE |
| if ( dn.isRootDse() ) |
| { |
| return; |
| } |
| |
| LdapPrincipal principal = lookupContext.getSession().getEffectivePrincipal(); |
| Dn userName = principal.getDn(); |
| Set<String> userGroups = groupCache.getGroups( userName.getNormName() ); |
| Collection<ACITuple> tuples = new HashSet<>(); |
| addPerscriptiveAciTuples( lookupContext, tuples, dn, entry ); |
| addEntryAciTuples( tuples, entry ); |
| addSubentryAciTuples( lookupContext, tuples, dn, entry ); |
| |
| // check that we have read access to the entry |
| AciContext aciContext = new AciContext( schemaManager, lookupContext ); |
| aciContext.setUserGroupNames( userGroups ); |
| aciContext.setUserDn( userName ); |
| aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); |
| aciContext.setEntryDn( dn ); |
| aciContext.setMicroOperations( LOOKUP_PERMS ); |
| aciContext.setAciTuples( tuples ); |
| aciContext.setEntry( entry ); |
| |
| engine.checkPermission( aciContext ); |
| |
| // check that we have read access to every attribute type and value |
| for ( Attribute attribute : entry ) |
| { |
| |
| for ( Value value : attribute ) |
| { |
| AciContext valueAciContext = new AciContext( schemaManager, lookupContext ); |
| valueAciContext.setUserGroupNames( userGroups ); |
| valueAciContext.setUserDn( userName ); |
| valueAciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); |
| valueAciContext.setEntryDn( dn ); |
| valueAciContext.setAttributeType( attribute.getAttributeType() ); |
| valueAciContext.setAttrValue( value ); |
| valueAciContext.setMicroOperations( READ_PERMS ); |
| valueAciContext.setAciTuples( tuples ); |
| valueAciContext.setEntry( entry ); |
| |
| engine.checkPermission( valueAciContext ); |
| } |
| } |
| } |
| |
| |
| public final boolean isPrincipalAnAdministrator( Dn principalDn ) |
| { |
| return groupCache.isPrincipalAnAdministrator( principalDn.getNormName() ); |
| } |
| |
| |
| public void cacheNewGroup( String name, Entry entry ) throws LdapException |
| { |
| groupCache.groupAdded( name, entry ); |
| } |
| |
| |
| private boolean filter( OperationContext opContext, Dn normName, Entry clonedEntry ) throws LdapException |
| { |
| /* |
| * First call hasPermission() for entry level "Browse" and "ReturnDN" perm |
| * tests. If we hasPermission() returns false we immediately short the |
| * process and return false. |
| */ |
| |
| LdapPrincipal principal = opContext.getSession().getEffectivePrincipal(); |
| Dn userDn = principal.getDn(); |
| Set<String> userGroups = groupCache.getGroups( userDn.getNormName() ); |
| Collection<ACITuple> tuples = new HashSet<>(); |
| addPerscriptiveAciTuples( opContext, tuples, normName, clonedEntry ); |
| addEntryAciTuples( tuples, ( ( ClonedServerEntry ) clonedEntry ).getOriginalEntry() ); |
| addSubentryAciTuples( opContext, tuples, normName, ( ( ClonedServerEntry ) clonedEntry ).getOriginalEntry() ); |
| |
| AciContext aciContext = new AciContext( schemaManager, opContext ); |
| aciContext.setUserGroupNames( userGroups ); |
| aciContext.setUserDn( userDn ); |
| aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); |
| aciContext.setEntryDn( normName ); |
| aciContext.setMicroOperations( SEARCH_ENTRY_PERMS ); |
| aciContext.setAciTuples( tuples ); |
| aciContext.setEntry( ( ( ClonedServerEntry ) clonedEntry ).getOriginalEntry() ); |
| |
| if ( !engine.hasPermission( aciContext ) ) |
| { |
| return false; |
| } |
| |
| /* |
| * For each attribute type we check if access is allowed to the type. If not |
| * the attribute is yanked out of the entry to be returned. If permission is |
| * allowed we move on to check if the values are allowed. Values that are |
| * not allowed are removed from the attribute. If the attribute has no more |
| * values remaining then the entire attribute is removed. |
| */ |
| List<AttributeType> attributeToRemove = new ArrayList<>(); |
| |
| for ( Attribute attribute : clonedEntry.getAttributes() ) |
| { |
| // if attribute type scope access is not allowed then remove the attribute and continue |
| AttributeType attributeType = attribute.getAttributeType(); |
| Attribute attr = clonedEntry.get( attributeType ); |
| |
| aciContext = new AciContext( schemaManager, opContext ); |
| aciContext.setUserGroupNames( userGroups ); |
| aciContext.setUserDn( userDn ); |
| aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); |
| aciContext.setEntryDn( normName ); |
| aciContext.setAttributeType( attributeType ); |
| aciContext.setMicroOperations( SEARCH_ATTRVAL_PERMS ); |
| aciContext.setAciTuples( tuples ); |
| aciContext.setEntry( clonedEntry ); |
| |
| if ( !engine.hasPermission( aciContext ) ) |
| { |
| attributeToRemove.add( attributeType ); |
| |
| continue; |
| } |
| |
| List<Value> valueToRemove = new ArrayList<>(); |
| |
| // attribute type scope is ok now let's determine value level scope |
| for ( Value value : attr ) |
| { |
| aciContext = new AciContext( schemaManager, opContext ); |
| aciContext.setUserGroupNames( userGroups ); |
| aciContext.setUserDn( userDn ); |
| aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); |
| aciContext.setEntryDn( normName ); |
| aciContext.setAttributeType( attr.getAttributeType() ); |
| aciContext.setAttrValue( value ); |
| aciContext.setMicroOperations( SEARCH_ATTRVAL_PERMS ); |
| aciContext.setAciTuples( tuples ); |
| aciContext.setEntry( clonedEntry ); |
| |
| if ( !engine.hasPermission( aciContext ) ) |
| { |
| valueToRemove.add( value ); |
| } |
| } |
| |
| for ( Value value : valueToRemove ) |
| { |
| attr.remove( value ); |
| } |
| |
| if ( attr.size() == 0 ) |
| { |
| attributeToRemove.add( attributeType ); |
| } |
| } |
| |
| for ( AttributeType attributeType : attributeToRemove ) |
| { |
| clonedEntry.removeAttributes( attributeType ); |
| } |
| |
| return true; |
| } |
| |
| /** |
| * WARNING: create one of these filters fresh every time for each new search. |
| */ |
| private class AuthorizationFilter implements EntryFilter |
| { |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean accept( SearchOperationContext searchContext, Entry entry ) throws LdapException |
| { |
| if ( !entry.getDn().isSchemaAware() ) |
| { |
| entry.setDn( new Dn( schemaManager, entry.getDn() ) ); |
| } |
| |
| return filter( searchContext, entry.getDn(), entry ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public String toString( String tabs ) |
| { |
| return tabs + "AuthorizationFilter"; |
| } |
| } |
| |
| |
| private boolean isTheAdministrator( Dn normalizedDn ) |
| { |
| return normalizedDn.equals( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED ); |
| } |
| } |