You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by el...@apache.org on 2011/10/14 19:38:32 UTC
svn commit: r1183441 [1/4] - in
/directory/apacheds/trunk/interceptors/authz: ./ .settings/ src/ src/main/
src/main/java/ src/main/java/org/ src/main/java/org/apache/
src/main/java/org/apache/directory/
src/main/java/org/apache/directory/server/ src/ma...
Author: elecharny
Date: Fri Oct 14 17:38:30 2011
New Revision: 1183441
URL: http://svn.apache.org/viewvc?rev=1183441&view=rev
Log:
added the interceptors projects
Added:
directory/apacheds/trunk/interceptors/authz/ (with props)
directory/apacheds/trunk/interceptors/authz/.settings/
directory/apacheds/trunk/interceptors/authz/.settings/org.eclipse.jdt.core.prefs
directory/apacheds/trunk/interceptors/authz/pom.xml
directory/apacheds/trunk/interceptors/authz/src/
directory/apacheds/trunk/interceptors/authz/src/main/
directory/apacheds/trunk/interceptors/authz/src/main/java/
directory/apacheds/trunk/interceptors/authz/src/main/java/org/
directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/
directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/
directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/
directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/
directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/
directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/AciAuthorizationInterceptor.java
directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/DefaultAuthorizationInterceptor.java
directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/GroupCache.java
directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/TupleCache.java
directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/
directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/ACDFEngine.java
directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/ACITupleFilter.java
directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/AciContext.java
directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/HighestPrecedenceFilter.java
directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/MaxImmSubFilter.java
directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/MaxValueCountFilter.java
directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/MicroOperationFilter.java
directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/MostSpecificProtectedItemFilter.java
directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/MostSpecificUserClassFilter.java
directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/OperationScope.java
directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/RelatedProtectedItemFilter.java
directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/RelatedUserClassFilter.java
directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/RestrictedByFilter.java
directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/support/package-info.java
directory/apacheds/trunk/interceptors/authz/src/test/
directory/apacheds/trunk/interceptors/authz/src/test/java/
directory/apacheds/trunk/interceptors/authz/src/test/java/org/
directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/
directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/
directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/
directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/core/
directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/core/authz/
directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/core/authz/support/
directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/core/authz/support/DummyAttributeTypeRegistry.java
directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/core/authz/support/DummyOidRegistry.java
directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/core/authz/support/HighestPrecedenceFilterTest.java
directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/core/authz/support/MaxImmSubFilterTest.java
directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/core/authz/support/MaxValueCountFilterTest.java
directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/core/authz/support/MicroOperationFilterTest.java
directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/core/authz/support/MostSpecificProtectedItemFilterTest.java
directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/core/authz/support/MostSpecificUserClassFilterTest.java
directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/core/authz/support/OperationScopeTest.java
directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/core/authz/support/RelatedProtectedItemFilterTest.java
directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/core/authz/support/RelatedUserClassFilterTest.java
directory/apacheds/trunk/interceptors/authz/src/test/java/org/apache/directory/server/core/authz/support/RestrictedByFilterTest.java
Propchange: directory/apacheds/trunk/interceptors/authz/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Fri Oct 14 17:38:30 2011
@@ -0,0 +1,11 @@
+target
+.project
+.classpath
+.settings
+eclipse-classes
+*.log
+*.iml
+*.ipr
+dependency-reduced-pom.xml
+META-INF
+
Added: directory/apacheds/trunk/interceptors/authz/.settings/org.eclipse.jdt.core.prefs
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/interceptors/authz/.settings/org.eclipse.jdt.core.prefs?rev=1183441&view=auto
==============================================================================
--- directory/apacheds/trunk/interceptors/authz/.settings/org.eclipse.jdt.core.prefs (added)
+++ directory/apacheds/trunk/interceptors/authz/.settings/org.eclipse.jdt.core.prefs Fri Oct 14 17:38:30 2011
@@ -0,0 +1,9 @@
+#Thu Oct 13 14:00:09 CEST 2011
+encoding//src/test/java=ISO-8859-1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+eclipse.preferences.version=1
+encoding//src/test/resources=ISO-8859-1
+org.eclipse.jdt.core.compiler.source=1.6
+encoding//src/main/java=ISO-8859-1
+encoding//src/main/resources=ISO-8859-1
+org.eclipse.jdt.core.compiler.compliance=1.6
Added: directory/apacheds/trunk/interceptors/authz/pom.xml
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/interceptors/authz/pom.xml?rev=1183441&view=auto
==============================================================================
--- directory/apacheds/trunk/interceptors/authz/pom.xml (added)
+++ directory/apacheds/trunk/interceptors/authz/pom.xml Fri Oct 14 17:38:30 2011
@@ -0,0 +1,193 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ 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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.directory.server</groupId>
+ <artifactId>apacheds-interceptors</artifactId>
+ <version>2.0.0-M4-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>apacheds-interceptors-authz</artifactId>
+ <name>ApacheDS Authorization Interceptor</name>
+ <packaging>jar</packaging>
+
+ <description>
+ Authorization interceptor
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.directory.junit</groupId>
+ <artifactId>junit-addons</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>apacheds-i18n</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>apacheds-core-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>apacheds-core-api</artifactId>
+ <classifier>tests</classifier>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>apacheds-core-shared</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>apacheds-interceptors-event</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>apacheds-interceptors-subtree</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>commons-collections</groupId>
+ <artifactId>commons-collections</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.directory.shared</groupId>
+ <artifactId>shared-ldap-client-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.directory.shared</groupId>
+ <artifactId>shared-i18n</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.directory.shared</groupId>
+ <artifactId>shared-ldap-codec-standalone</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.directory.shared</groupId>
+ <artifactId>shared-ldap-codec-core</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.directory.shared</groupId>
+ <artifactId>shared-ldap-extras-aci</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.directory.shared</groupId>
+ <artifactId>shared-ldap-extras-trigger</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.directory.shared</groupId>
+ <artifactId>shared-ldap-extras-util</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.directory.shared</groupId>
+ <artifactId>shared-ldap-model</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.directory.shared</groupId>
+ <artifactId>shared-ldap-schema-data</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.directory.shared</groupId>
+ <artifactId>shared-util</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>bouncycastle</groupId>
+ <artifactId>bcprov-jdk15</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>net.sf.ehcache</groupId>
+ <artifactId>ehcache-core</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.directory.shared</groupId>
+ <artifactId>shared-ldap-extras-codec</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <systemPropertyVariables>
+ <workingDirectory>${basedir}/target/server-work</workingDirectory>
+ </systemPropertyVariables>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>attach-sources</id>
+ <phase>verify</phase>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+
+ <resources>
+ <resource>
+ <directory>src/main/resources</directory>
+ <filtering>true</filtering>
+ <excludes>
+ <exclude>**/*.gif</exclude>
+ </excludes>
+ </resource>
+ </resources>
+ </build>
+</project>
+
Added: directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/AciAuthorizationInterceptor.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/AciAuthorizationInterceptor.java?rev=1183441&view=auto
==============================================================================
--- directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/AciAuthorizationInterceptor.java (added)
+++ directory/apacheds/trunk/interceptors/authz/src/main/java/org/apache/directory/server/core/authz/AciAuthorizationInterceptor.java Fri Oct 14 17:38:30 2011
@@ -0,0 +1,1423 @@
+/*
+ * 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.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.server.constants.ServerDNConstants;
+import org.apache.directory.server.core.shared.DefaultCoreSession;
+import org.apache.directory.server.core.api.CoreSession;
+import org.apache.directory.server.core.api.DirectoryService;
+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.InterceptorChain;
+import org.apache.directory.server.core.api.interceptor.NextInterceptor;
+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.EntryOperationContext;
+import org.apache.directory.server.core.api.interceptor.context.ListOperationContext;
+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.interceptor.context.SearchingOperationContext;
+import org.apache.directory.server.core.api.partition.ByPassConstants;
+import org.apache.directory.server.core.api.partition.PartitionNexus;
+import org.apache.directory.server.core.authz.support.ACDFEngine;
+import org.apache.directory.server.core.authz.support.AciContext;
+import org.apache.directory.server.core.subtree.SubentryInterceptor;
+import org.apache.directory.server.i18n.I18n;
+import org.apache.directory.shared.ldap.aci.ACIItem;
+import org.apache.directory.shared.ldap.aci.ACIItemParser;
+import org.apache.directory.shared.ldap.aci.ACITuple;
+import org.apache.directory.shared.ldap.aci.MicroOperation;
+import org.apache.directory.shared.ldap.model.constants.AuthenticationLevel;
+import org.apache.directory.shared.ldap.model.constants.Loggers;
+import org.apache.directory.shared.ldap.model.constants.SchemaConstants;
+import org.apache.directory.shared.ldap.model.entry.Attribute;
+import org.apache.directory.shared.ldap.model.entry.Entry;
+import org.apache.directory.shared.ldap.model.entry.Modification;
+import org.apache.directory.shared.ldap.model.entry.StringValue;
+import org.apache.directory.shared.ldap.model.entry.Value;
+import org.apache.directory.shared.ldap.model.exception.LdapException;
+import org.apache.directory.shared.ldap.model.exception.LdapNoPermissionException;
+import org.apache.directory.shared.ldap.model.exception.LdapOperationErrorException;
+import org.apache.directory.shared.ldap.model.exception.LdapOperationException;
+import org.apache.directory.shared.ldap.model.filter.EqualityNode;
+import org.apache.directory.shared.ldap.model.filter.ExprNode;
+import org.apache.directory.shared.ldap.model.filter.OrNode;
+import org.apache.directory.shared.ldap.model.message.AliasDerefMode;
+import org.apache.directory.shared.ldap.model.name.Dn;
+import org.apache.directory.shared.ldap.model.schema.AttributeType;
+import org.apache.directory.shared.ldap.model.schema.normalizers.ConcreteNameComponentNormalizer;
+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<MicroOperation>( 2 );
+ set.add( MicroOperation.BROWSE );
+ set.add( MicroOperation.RETURN_DN );
+ SEARCH_ENTRY_PERMS = Collections.unmodifiableCollection( set );
+
+ set = new HashSet<MicroOperation>( 2 );
+ set.add( MicroOperation.READ );
+ set.add( MicroOperation.BROWSE );
+ LOOKUP_PERMS = Collections.unmodifiableCollection( set );
+
+ set = new HashSet<MicroOperation>( 2 );
+ set.add( MicroOperation.ADD );
+ set.add( MicroOperation.REMOVE );
+ REPLACE_PERMS = Collections.unmodifiableCollection( set );
+
+ set = new HashSet<MicroOperation>( 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;
+
+ /** interceptor chain */
+ private InterceptorChain chain;
+
+ /** the system wide subschemaSubentryDn */
+ private String subschemaSubentryDn;
+
+ /** A reference to the nexus for direct backend operations */
+ private PartitionNexus nexus;
+
+ public static final SearchControls DEFAULT_SEARCH_CONTROLS = new SearchControls();
+
+
+ /**
+ * Load the Tuples into the cache
+ */
+ private void initTupleCache() throws LdapException
+ {
+ // Load all the prescriptiveACI : they are stored in AccessControlSubentry entries
+ Dn adminDn = new Dn( schemaManager, ServerDNConstants.ADMIN_SYSTEM_DN );
+
+ SearchControls controls = new SearchControls();
+ controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
+ controls.setReturningAttributes( new String[]
+ { SchemaConstants.PRESCRIPTIVE_ACI_AT } );
+
+ ExprNode filter =
+ new EqualityNode<String>( OBJECT_CLASS_AT, new StringValue( SchemaConstants.ACCESS_CONTROL_SUBENTRY_OC ) );
+
+ CoreSession adminSession = new DefaultCoreSession( new LdapPrincipal( schemaManager, adminDn, AuthenticationLevel.STRONG ),
+ directoryService );
+
+ 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();
+
+ tupleCache.subentryAdded( entry.getDn(), entry );
+ }
+
+ results.close();
+ }
+ catch ( Exception e )
+ {
+ throw new LdapOperationException( e.getMessage(), e );
+ }
+ }
+
+
+ /**
+ * Load the Groups into the cache
+ */
+ private void initGroupCache() throws LdapException
+ {
+ // Load all the member/uniqueMember : they are stored in groupOfNames/groupOfUniqueName
+ Dn adminDn = new Dn( schemaManager, ServerDNConstants.ADMIN_SYSTEM_DN );
+
+ SearchControls controls = new SearchControls();
+ controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
+ controls.setReturningAttributes( new String[]
+ { SchemaConstants.MEMBER_AT, SchemaConstants.UNIQUE_MEMBER_AT } );
+
+ ExprNode filter =
+ new OrNode(
+ new EqualityNode<String>( OBJECT_CLASS_AT, new StringValue( SchemaConstants.GROUP_OF_NAMES_OC ) ),
+ new EqualityNode<String>( OBJECT_CLASS_AT, new StringValue( SchemaConstants.GROUP_OF_UNIQUE_NAMES_OC ) ) );
+
+ CoreSession adminSession = new DefaultCoreSession( new LdapPrincipal( schemaManager, adminDn, AuthenticationLevel.STRONG ),
+ directoryService );
+
+ 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(), 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 Exception if there are problems during initialization
+ */
+ public void init( DirectoryService directoryService ) throws LdapException
+ {
+ LOG.debug( "Initializing the AciAuthorizationInterceptor" );
+
+ super.init( directoryService );
+
+ nexus = directoryService.getPartitionNexus();
+
+ Dn adminDn = directoryService.getDnFactory().create( ServerDNConstants.ADMIN_SYSTEM_DN );
+ CoreSession adminSession = new DefaultCoreSession( new LdapPrincipal( schemaManager, adminDn, AuthenticationLevel.STRONG ),
+ directoryService );
+ chain = directoryService.getInterceptorChain();
+
+ // 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().getRootDSE( null ).get(
+ SchemaConstants.SUBSCHEMA_SUBENTRY_AT ).get();
+ Dn subschemaSubentryDnName = directoryService.getDnFactory().create( subschemaSubentry.getString() );
+ subschemaSubentryDn = subschemaSubentryDnName.getNormName();
+
+ // Init the caches now
+ initTupleCache();
+ initGroupCache();
+ }
+
+
+ private void protectCriticalEntries( Dn dn ) throws LdapException
+ {
+ Dn principalDn = getPrincipal().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 that access to is being controled
+ * @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 = null;
+
+ if ( entry instanceof ClonedServerEntry )
+ {
+ originalEntry = ((ClonedServerEntry)entry).getOriginalEntry();
+ }
+ else
+ {
+ originalEntry = entry;
+ }
+
+ Attribute oc = originalEntry.get( OBJECT_CLASS_AT );
+
+ /*
+ * 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();
+ originalEntry = opContext.lookup( parentDn, ByPassConstants.LOOKUP_BYPASS, SchemaConstants.ALL_ATTRIBUTES_ARRAY );
+ }
+
+ Attribute subentries = originalEntry.get( ACCESS_CONTROL_SUBENTRIES_AT );
+
+ if ( subentries == null )
+ {
+ return;
+ }
+
+ for ( Value<?> value : subentries )
+ {
+ String subentryDn = value.getString();
+ tuples.addAll( tupleCache.getACITuples( subentryDn ) );
+ }
+ }
+
+
+ /**
+ * 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( ENTRY_ACI_AT );
+
+ 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();
+ Entry administrativeEntry = ( ( ClonedServerEntry ) opContext.lookup( parentDn, ByPassConstants.LOOKUP_BYPASS, SchemaConstants.ALL_ATTRIBUTES_ARRAY ) )
+ .getOriginalEntry();
+
+ Attribute subentryAci = administrativeEntry.get( SUBENTRY_ACI_AT );
+
+ 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.
+ * -------------------------------------------------------------------------------
+ */
+
+ public void add( NextInterceptor next, AddOperationContext addContext ) throws LdapException
+ {
+ // bypass authz code if it was disabled
+ if ( !addContext.getSession().getDirectoryService().isAccessControlEnabled() )
+ {
+ ACI_LOG.debug( "ACI interceptor disabled" );
+ next.add( 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.add( addContext );
+ tupleCache.subentryAdded( dn, serverEntry );
+ groupCache.groupAdded( dn, serverEntry );
+ return;
+ }
+
+ // perform checks below here for all non-admin users
+ SubentryInterceptor subentryInterceptor = ( SubentryInterceptor ) chain.get( SubentryInterceptor.class
+ .getSimpleName() );
+ Entry subentry = subentryInterceptor.getSubentryAttributes( dn, serverEntry );
+
+ for ( Attribute attribute : serverEntry )
+ {
+ subentry.put( attribute );
+ }
+
+ // Assemble all the information required to make an access control decision
+ Set<Dn> userGroups = groupCache.getGroups( principalDn.getNormName() );
+ Collection<ACITuple> tuples = new HashSet<ACITuple>();
+
+ // 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.add( 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, serverEntry );
+ }
+
+
+ private boolean isTheAdministrator( Dn normalizedDn )
+ {
+ return normalizedDn.getNormName().equals( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
+ }
+
+
+ public void delete( NextInterceptor next, DeleteOperationContext deleteContext ) throws LdapException
+ {
+ CoreSession session = deleteContext.getSession();
+
+ // bypass authz code if we are disabled
+ if ( !session.getDirectoryService().isAccessControlEnabled() )
+ {
+ next.delete( deleteContext );
+ return;
+ }
+
+ Dn dn = deleteContext.getDn();
+ LdapPrincipal principal = session.getEffectivePrincipal();
+ Dn principalDn = principal.getDn();
+
+ Entry entry = deleteContext.getEntry();
+
+ protectCriticalEntries( dn );
+
+ // bypass authz code but manage caches if operation is performed by the admin
+ if ( isPrincipalAnAdministrator( principalDn ) )
+ {
+ next.delete( deleteContext );
+
+ tupleCache.subentryDeleted( dn, entry );
+ groupCache.groupDeleted( dn, entry );
+
+ return;
+ }
+
+ Set<Dn> userGroups = groupCache.getGroups( principalDn.getNormName() );
+ Collection<ACITuple> tuples = new HashSet<ACITuple>();
+ 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.delete( deleteContext );
+
+ tupleCache.subentryDeleted( dn, entry );
+ groupCache.groupDeleted( dn, entry );
+ }
+
+
+ // False positive, we want to keep the comment
+ @SuppressWarnings("PMD.CollapsibleIfStatements")
+ public void modify( NextInterceptor next, 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 ( !modifyContext.getSession().getDirectoryService().isAccessControlEnabled() )
+ {
+ next.modify( modifyContext );
+ return;
+ }
+
+ List<Modification> mods = modifyContext.getModItems();
+
+ // bypass authz code but manage caches if operation is performed by the admin
+ if ( isPrincipalAnAdministrator( principalDn ) )
+ {
+ next.modify( modifyContext );
+
+ /**
+ * @TODO: A virtual entry can be created here for not hitting the backend again.
+ */
+ Entry modifiedEntry = modifyContext.lookup( dn, ByPassConstants.LOOKUP_BYPASS, SchemaConstants.ALL_ATTRIBUTES_ARRAY );
+ tupleCache.subentryModified( dn, mods, modifiedEntry );
+ groupCache.groupModified( dn, mods, entry, schemaManager );
+ return;
+ }
+
+ Set<Dn> userGroups = groupCache.getGroups( principalDn.getName() );
+ Collection<ACITuple> tuples = new HashSet<ACITuple>();
+ 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 = null;
+ 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 )
+ {
+ // If there is only one value remaining in the attribute ...
+ if ( entryAttr.size() == 1 )
+ {
+ // ... 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;
+ }
+
+ /**
+ * 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.modify( modifyContext );
+ /**
+ * @TODO: A virtual entry can be created here for not hitting the backend again.
+ */
+ Entry modifiedEntry = modifyContext.lookup( dn, ByPassConstants.LOOKUP_BYPASS, SchemaConstants.ALL_ATTRIBUTES_ARRAY );
+ tupleCache.subentryModified( dn, mods, modifiedEntry );
+ groupCache.groupModified( dn, mods, entry, schemaManager );
+ }
+
+
+ public boolean hasEntry( NextInterceptor next, EntryOperationContext hasEntryContext ) throws LdapException
+ {
+ Dn dn = hasEntryContext.getDn();
+
+ if ( !hasEntryContext.getSession().getDirectoryService().isAccessControlEnabled() )
+ {
+ return ( dn.isRootDSE() || next.hasEntry( hasEntryContext ) );
+ }
+
+ boolean answer = next.hasEntry( 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;
+ }
+
+ // TODO - eventually replace this with a check on session.isAnAdministrator()
+ LdapPrincipal principal = hasEntryContext.getSession().getEffectivePrincipal();
+ Dn principalDn = principal.getDn();
+
+ if ( isPrincipalAnAdministrator( principalDn ) )
+ {
+ return answer;
+ }
+
+ Entry entry = hasEntryContext.lookup( dn, ByPassConstants.HAS_ENTRY_BYPASS, SchemaConstants.ALL_ATTRIBUTES_ARRAY );
+ Set<Dn> userGroups = groupCache.getGroups( principalDn.getNormName() );
+ Collection<ACITuple> tuples = new HashSet<ACITuple>();
+ 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.hasEntry( hasEntryContext );
+ }
+
+
+ /**
+ * 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<Dn> userGroups = groupCache.getGroups( userName.getNormName() );
+ Collection<ACITuple> tuples = new HashSet<ACITuple>();
+ 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 );
+ }
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Entry lookup( NextInterceptor next, LookupOperationContext lookupContext ) throws LdapException
+ {
+ CoreSession session = lookupContext.getSession();
+ DirectoryService directoryService = session.getDirectoryService();
+
+ LdapPrincipal principal = session.getEffectivePrincipal();
+ Dn principalDn = principal.getDn();
+
+ if ( !principalDn.isSchemaAware() )
+ {
+ principalDn.apply( schemaManager );
+ }
+
+ // Bypass this interceptor if we disabled the AC subsystem or if the principal is the admin
+ if ( isPrincipalAnAdministrator( principalDn ) || !directoryService.isAccessControlEnabled() )
+ {
+ return next.lookup( lookupContext );
+ }
+
+ lookupContext.setByPassed( ByPassConstants.LOOKUP_BYPASS );
+ Entry entry = directoryService.getOperationManager().lookup( lookupContext );
+
+ checkLookupAccess( lookupContext, entry );
+
+ return next.lookup( lookupContext );
+ }
+
+
+ public void rename( NextInterceptor next, RenameOperationContext renameContext ) throws LdapException
+ {
+ Dn oldName = renameContext.getDn();
+ Entry originalEntry = null;
+
+ 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 ( !renameContext.getSession().getDirectoryService().isAccessControlEnabled() )
+ {
+ next.rename( renameContext );
+ return;
+ }
+
+ protectCriticalEntries( oldName );
+
+ // bypass authz code but manage caches if operation is performed by the admin
+ if ( isPrincipalAnAdministrator( principalDn ) )
+ {
+ next.rename( renameContext );
+ tupleCache.subentryRenamed( oldName, newName );
+
+ // TODO : this method returns a boolean : what should we do with the result ?
+ groupCache.groupRenamed( oldName, newName );
+
+ return;
+ }
+
+ Set<Dn> userGroups = groupCache.getGroups( principalDn.getNormName() );
+ Collection<ACITuple> tuples = new HashSet<ACITuple>();
+ 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.rename( renameContext );
+ tupleCache.subentryRenamed( oldName, newName );
+ groupCache.groupRenamed( oldName, newName );
+ }
+
+
+ public void moveAndRename( NextInterceptor next, MoveAndRenameOperationContext moveAndRenameContext )
+ throws LdapException
+ {
+ Dn oldDn = moveAndRenameContext.getDn();
+
+ Entry entry = moveAndRenameContext.getOriginalEntry();
+
+ LdapPrincipal principal = moveAndRenameContext.getSession().getEffectivePrincipal();
+ Dn principalDn = principal.getDn();
+ Dn newDn = moveAndRenameContext.getNewDn();
+
+ // bypass authz code if we are disabled
+ if ( !moveAndRenameContext.getSession().getDirectoryService().isAccessControlEnabled() )
+ {
+ next.moveAndRename( moveAndRenameContext );
+ return;
+ }
+
+ protectCriticalEntries( oldDn );
+
+ // bypass authz code but manage caches if operation is performed by the admin
+ if ( isPrincipalAnAdministrator( principalDn ) )
+ {
+ next.moveAndRename( moveAndRenameContext );
+ tupleCache.subentryRenamed( oldDn, newDn );
+ groupCache.groupRenamed( oldDn, newDn );
+ return;
+ }
+
+ Set<Dn> userGroups = groupCache.getGroups( principalDn.getNormName() );
+ Collection<ACITuple> tuples = new HashSet<ACITuple>();
+ 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.
+
+ Entry importedEntry = moveAndRenameContext.lookup( oldDn,
+ ByPassConstants.LOOKUP_EXCLUDING_OPR_ATTRS_BYPASS, SchemaConstants.ALL_ATTRIBUTES_ARRAY );
+
+ // 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.
+ SubentryInterceptor subentryInterceptor = ( SubentryInterceptor ) chain.get( SubentryInterceptor.class
+ .getSimpleName() );
+ Entry subentryAttrs = subentryInterceptor.getSubentryAttributes( newDn, importedEntry );
+
+ for ( Attribute attribute : importedEntry )
+ {
+ subentryAttrs.put( attribute );
+ }
+
+ Collection<ACITuple> destTuples = new HashSet<ACITuple>();
+ // 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.moveAndRename( moveAndRenameContext );
+ tupleCache.subentryRenamed( oldDn, newDn );
+ groupCache.groupRenamed( oldDn, newDn );
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void move( NextInterceptor next, 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();
+
+ Dn newDn = moveContext.getNewDn();
+
+ LdapPrincipal principal = moveContext.getSession().getEffectivePrincipal();
+ Dn principalDn = principal.getDn();
+
+ // bypass authz code if we are disabled
+ if ( !moveContext.getSession().getDirectoryService().isAccessControlEnabled() )
+ {
+ next.move( moveContext );
+ return;
+ }
+
+ protectCriticalEntries( oriChildName );
+
+ // bypass authz code but manage caches if operation is performed by the admin
+ if ( isPrincipalAnAdministrator( principalDn ) )
+ {
+ next.move( moveContext );
+ tupleCache.subentryRenamed( oriChildName, newDn );
+ groupCache.groupRenamed( oriChildName, newDn );
+ return;
+ }
+
+ Set<Dn> userGroups = groupCache.getGroups( principalDn.getNormName() );
+ Collection<ACITuple> tuples = new HashSet<ACITuple>();
+ 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.
+ Entry importedEntry = moveContext.lookup( oriChildName, ByPassConstants.LOOKUP_EXCLUDING_OPR_ATTRS_BYPASS, SchemaConstants.ALL_ATTRIBUTES_ARRAY );
+
+ // 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.
+ SubentryInterceptor subentryInterceptor = ( SubentryInterceptor ) chain.get( SubentryInterceptor.class
+ .getSimpleName() );
+ Entry subentryAttrs = subentryInterceptor.getSubentryAttributes( newDn, importedEntry );
+
+ for ( Attribute attribute : importedEntry )
+ {
+ subentryAttrs.put( attribute );
+ }
+
+ Collection<ACITuple> destTuples = new HashSet<ACITuple>();
+ // 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.move( moveContext );
+ tupleCache.subentryRenamed( oriChildName, newDn );
+ groupCache.groupRenamed( oriChildName, newDn );
+ }
+
+
+ public EntryFilteringCursor list( NextInterceptor next, ListOperationContext listContext ) throws LdapException
+ {
+ LdapPrincipal user = listContext.getSession().getEffectivePrincipal();
+ EntryFilteringCursor cursor = next.list( listContext );
+
+ if ( isPrincipalAnAdministrator( user.getDn() )
+ || !listContext.getSession().getDirectoryService().isAccessControlEnabled() )
+ {
+ return cursor;
+ }
+
+ AuthorizationFilter authzFilter = new AuthorizationFilter();
+ cursor.addEntryFilter( authzFilter );
+ return cursor;
+ }
+
+
+ public EntryFilteringCursor search( NextInterceptor next, SearchOperationContext searchContext ) throws LdapException
+ {
+ LdapPrincipal user = searchContext.getSession().getEffectivePrincipal();
+ Dn principalDn = user.getDn();
+ EntryFilteringCursor cursor = next.search( searchContext );
+
+ boolean isSubschemaSubentryLookup = subschemaSubentryDn.equals( searchContext.getDn().getNormName() );
+ SearchControls searchCtls = searchContext.getSearchControls();
+ boolean isRootDSELookup = searchContext.getDn().size() == 0
+ && searchCtls.getSearchScope() == SearchControls.OBJECT_SCOPE;
+
+ if ( isPrincipalAnAdministrator( principalDn )
+ || !searchContext.getSession().getDirectoryService().isAccessControlEnabled() || isRootDSELookup
+ || isSubschemaSubentryLookup )
+ {
+ return cursor;
+ }
+
+ cursor.addEntryFilter( new AuthorizationFilter() );
+ return cursor;
+ }
+
+
+ public final boolean isPrincipalAnAdministrator( Dn principalDn )
+ {
+ return groupCache.isPrincipalAnAdministrator( principalDn );
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean compare( NextInterceptor next, CompareOperationContext compareContext ) throws LdapException
+ {
+ CoreSession session = compareContext.getSession();
+ Dn dn = compareContext.getDn();
+ String oid = compareContext.getOid();
+ Value<?> value = compareContext.getValue();
+
+ Entry entry = compareContext.getOriginalEntry();
+
+ LdapPrincipal principal = session.getEffectivePrincipal();
+ Dn principalDn = principal.getDn();
+
+ if ( isPrincipalAnAdministrator( principalDn ) || !session.getDirectoryService().isAccessControlEnabled() )
+ {
+ return next.compare( compareContext );
+ }
+
+ Set<Dn> userGroups = groupCache.getGroups( principalDn.getNormName() );
+ Collection<ACITuple> tuples = new HashSet<ACITuple>();
+ 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.compare( compareContext );
+ }
+
+
+ public void cacheNewGroup( Dn name, Entry entry ) throws Exception
+ {
+ groupCache.groupAdded( name, entry );
+ }
+
+
+ private boolean filter( OperationContext opContext, Dn normName, Entry clonedEntry ) throws Exception
+ {
+ /*
+ * 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<Dn> userGroups = groupCache.getGroups( userDn.getNormName() );
+ Collection<ACITuple> tuples = new HashSet<ACITuple>();
+ 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<AttributeType>();
+
+ 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<Value<?>>();
+
+ // 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
+ {
+ public boolean accept( SearchingOperationContext searchContext, Entry entry ) throws Exception
+ {
+ Dn normName = entry.getDn().apply( schemaManager );
+
+ return filter( searchContext, normName, entry );
+ }
+ }
+}