blob: a360006f2e04860a1966407db542e3e207388fea [file] [log] [blame]
/*
* 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.referral;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import org.apache.directory.server.core.DirectoryService;
import org.apache.directory.server.core.authn.AuthenticationInterceptor;
import org.apache.directory.server.core.authz.AciAuthorizationInterceptor;
import org.apache.directory.server.core.authz.DefaultAuthorizationInterceptor;
import org.apache.directory.server.core.entry.ServerAttribute;
import org.apache.directory.server.core.entry.ServerEntry;
import org.apache.directory.server.core.entry.ServerSearchResult;
import org.apache.directory.server.core.entry.ServerStringValue;
import org.apache.directory.server.core.enumeration.ReferralHandlingEnumeration;
import org.apache.directory.server.core.enumeration.SearchResultFilter;
import org.apache.directory.server.core.enumeration.SearchResultFilteringEnumeration;
import org.apache.directory.server.core.event.EventInterceptor;
import org.apache.directory.server.core.interceptor.BaseInterceptor;
import org.apache.directory.server.core.interceptor.NextInterceptor;
import org.apache.directory.server.core.interceptor.context.AddContextPartitionOperationContext;
import org.apache.directory.server.core.interceptor.context.AddOperationContext;
import org.apache.directory.server.core.interceptor.context.CompareOperationContext;
import org.apache.directory.server.core.interceptor.context.DeleteOperationContext;
import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext;
import org.apache.directory.server.core.interceptor.context.MoveOperationContext;
import org.apache.directory.server.core.interceptor.context.RemoveContextPartitionOperationContext;
import org.apache.directory.server.core.interceptor.context.RenameOperationContext;
import org.apache.directory.server.core.interceptor.context.SearchOperationContext;
import org.apache.directory.server.core.invocation.Invocation;
import org.apache.directory.server.core.invocation.InvocationStack;
import org.apache.directory.server.core.jndi.ServerLdapContext;
import org.apache.directory.server.core.normalization.NormalizationInterceptor;
import org.apache.directory.server.core.operational.OperationalAttributeInterceptor;
import org.apache.directory.server.core.partition.Partition;
import org.apache.directory.server.core.partition.PartitionNexus;
import org.apache.directory.server.core.partition.PartitionNexusProxy;
import org.apache.directory.server.core.schema.SchemaInterceptor;
import org.apache.directory.server.core.subtree.SubentryInterceptor;
import org.apache.directory.server.core.trigger.TriggerInterceptor;
import org.apache.directory.server.schema.registries.AttributeTypeRegistry;
import org.apache.directory.server.schema.registries.OidRegistry;
import org.apache.directory.server.schema.registries.Registries;
import org.apache.directory.shared.ldap.NotImplementedException;
import org.apache.directory.shared.ldap.codec.util.LdapURL;
import org.apache.directory.shared.ldap.codec.util.LdapURLEncodingException;
import org.apache.directory.shared.ldap.constants.SchemaConstants;
import org.apache.directory.shared.ldap.entry.EntryAttribute;
import org.apache.directory.shared.ldap.entry.Modification;
import org.apache.directory.shared.ldap.entry.Value;
import org.apache.directory.shared.ldap.entry.client.ClientStringValue;
import org.apache.directory.shared.ldap.exception.LdapNamingException;
import org.apache.directory.shared.ldap.exception.LdapReferralException;
import org.apache.directory.shared.ldap.filter.EqualityNode;
import org.apache.directory.shared.ldap.filter.ExprNode;
import org.apache.directory.shared.ldap.message.AliasDerefMode;
import org.apache.directory.shared.ldap.message.ResultCodeEnum;
import org.apache.directory.shared.ldap.name.LdapDN;
import org.apache.directory.shared.ldap.schema.AttributeType;
import org.apache.directory.shared.ldap.util.StringTools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.SearchControls;
/**
* An service which is responsible referral handling behavoirs. It manages
* referral handling behavoir when the {@link Context#REFERRAL} is implicitly
* or explicitly set to "ignore", when set to "throw" and when set to "follow".
*
* @org.apache.xbean.XBean
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
* @version $Rev$
*/
public class ReferralInterceptor extends BaseInterceptor
{
private static final Logger LOG = LoggerFactory.getLogger( ReferralInterceptor.class );
private static final String IGNORE = "ignore";
private static final String THROW_FINDING_BASE = "throw-finding-base";
private static final String THROW = "throw";
private static final String FOLLOW = "follow";
private static final Collection<String> SEARCH_BYPASS;
private ReferralLut lut = new ReferralLut();
private PartitionNexus nexus;
/** The attributeType registry */
private AttributeTypeRegistry atRegistry;
/** Thre global registries */
private Registries registries;
/** The OID registry */
private OidRegistry oidRegistry;
static
{
/*
* These are the services that we will bypass while searching for referrals in
* partitions of the system during startup and during add/remove partition ops
*/
Collection<String> c = new HashSet<String>();
c.add( NormalizationInterceptor.class.getName() );
c.add( AuthenticationInterceptor.class.getName() );
c.add( ReferralInterceptor.class.getName() );
c.add( AciAuthorizationInterceptor.class.getName() );
c.add( DefaultAuthorizationInterceptor.class.getName() );
// c.add( ExceptionInterceptor.class.getName() );
c.add( OperationalAttributeInterceptor.class.getName() );
c.add( SchemaInterceptor.class.getName() );
c.add( SubentryInterceptor.class.getName() );
// c.add( CollectiveAttributeInterceptor.class.getName() );
c.add( EventInterceptor.class.getName() );
c.add( TriggerInterceptor.class.getName() );
SEARCH_BYPASS = Collections.unmodifiableCollection( c );
}
static boolean isReferral( ServerEntry entry ) throws NamingException
{
EntryAttribute oc = entry.get( SchemaConstants.OBJECT_CLASS_AT );
if ( oc == null )
{
LOG.warn( "could not find objectClass attribute in entry: " + entry );
return false;
}
if ( oc.contains( SchemaConstants.REFERRAL_OC ) )
{
//We have a referral ObjectClass, let's check that the ref is
// valid, accordingly to the RFC
// Get the 'ref' attributeType
EntryAttribute refAttr = entry.get( SchemaConstants.REF_AT );
if ( refAttr == null )
{
// very unlikely, as we have already checked the entry in SchemaInterceptor
String message = "An entry with a 'referral' ObjectClass must contains a 'ref' Attribute";
LOG.error( message );
throw new NamingException( message );
}
for ( Value<?> value : refAttr )
{
ServerStringValue ref = ( ServerStringValue ) value;
String refVal = ref.get();
try
{
LdapURL ldapUrl = new LdapURL( refVal );
// We have a LDAP URL, we have to check that :
// - we don't have scope specifier
// - we don't have filters
// - we don't have attribute description list
// - we don't have extensions
// - the DN is not empty
if ( ldapUrl.getScope() != SearchControls.OBJECT_SCOPE )
{
// This is the default value if we don't have any scope
// Let's assume that it's incorrect if we get something
// else in the LdapURL
String message = "An LDAPURL should not contains a scope";
LOG.error( message );
throw new NamingException( message );
}
if ( !StringTools.isEmpty( ldapUrl.getFilter() ) )
{
String message = "An LDAPURL should not contains filters";
LOG.error( message );
throw new NamingException( message );
}
if ( ( ldapUrl.getAttributes() != null ) && ( ldapUrl.getAttributes().size() != 0 ) )
{
String message = "An LDAPURL should not contains any description attribute list";
LOG.error( message );
throw new NamingException( message );
}
if ( ( ldapUrl.getExtensions() != null ) && ( ldapUrl.getExtensions().size() != 0 ) )
{
String message = "An LDAPURL should not contains any extension";
LOG.error( message );
throw new NamingException( message );
}
if ( ( ldapUrl.getCriticalExtensions() != null ) && ( ldapUrl.getCriticalExtensions().size() != 0 ) )
{
String message = "An LDAPURL should not contains any critical extension";
LOG.error( message );
throw new NamingException( message );
}
LdapDN dn = ldapUrl.getDn();
if ( ( dn == null ) || dn.isEmpty() )
{
String message = "An LDAPURL should contains a non-empty DN";
LOG.error( message );
throw new NamingException( message );
}
}
catch ( LdapURLEncodingException luee )
{
// Either the URL is invalid, or it's not a LDAP URL.
// we will just ignore this LdapURL.
}
}
return true;
}
return false;
}
public void init( DirectoryService directoryService ) throws NamingException
{
nexus = directoryService.getPartitionNexus();
registries = directoryService.getRegistries();
atRegistry = registries.getAttributeTypeRegistry();
oidRegistry = registries.getOidRegistry();
Iterator<String> suffixes = nexus.listSuffixes( null );
while ( suffixes.hasNext() )
{
LdapDN suffix = new LdapDN( suffixes.next() );
addReferrals( nexus.search( new SearchOperationContext( registries, suffix, AliasDerefMode.DEREF_ALWAYS,
getReferralFilter(), getControls() ) ), suffix );
}
}
public void doReferralException( LdapDN farthest, LdapDN targetUpdn, EntryAttribute refs ) throws NamingException
{
// handle referral here
List<String> list = new ArrayList<String>( refs.size() );
for ( Value<?> value : refs )
{
String val = ( String ) value.get();
// need to add non-ldap URLs as-is
if ( !val.startsWith( "ldap" ) )
{
list.add( val );
continue;
}
// parse the ref value and normalize the DN according to schema
LdapURL ldapUrl = new LdapURL();
try
{
ldapUrl.parse( val.toCharArray() );
}
catch ( LdapURLEncodingException e )
{
LOG.error( "Bad URL (" + val + ") for ref in " + farthest + ". Reference will be ignored." );
}
LdapDN urlDn = new LdapDN( ldapUrl.getDn().toNormName() );
urlDn.normalize( atRegistry.getNormalizerMapping() );
if ( urlDn.equals( farthest ) )
{
// according to the protocol there is no need for the dn since it is the same as this request
StringBuilder buf = new StringBuilder();
buf.append( ldapUrl.getScheme() );
buf.append( ldapUrl.getHost() );
if ( ldapUrl.getPort() > 0 )
{
buf.append( ":" );
buf.append( ldapUrl.getPort() );
}
list.add( buf.toString() );
continue;
}
/*
* If we get here then the DN of the referral was not the same as the
* DN of the ref LDAP URL. We must calculate the remaining (difference)
* name past the farthest referral DN which the target name extends.
*/
int diff = targetUpdn.size() - farthest.size();
LdapDN extra = new LdapDN();
for ( int jj = 0; jj < diff; jj++ )
{
extra.add( targetUpdn.get( farthest.size() + jj ) );
}
urlDn.addAll( extra );
StringBuilder buf = new StringBuilder();
buf.append( ldapUrl.getScheme() );
buf.append( ldapUrl.getHost() );
if ( ldapUrl.getPort() > 0 )
{
buf.append( ":" );
buf.append( ldapUrl.getPort() );
}
buf.append( "/" );
buf.append( LdapURL.urlEncode( urlDn.getUpName(), false ) );
list.add( buf.toString() );
}
throw new LdapReferralException( list );
}
public void add( NextInterceptor next, AddOperationContext opContext ) throws NamingException
{
Invocation invocation = InvocationStack.getInstance().peek();
ServerLdapContext caller = ( ServerLdapContext ) invocation.getCaller();
String refval = ( String ) caller.getEnvironment().get( Context.REFERRAL );
LdapDN name = opContext.getDn();
ServerEntry entry = opContext.getEntry();
// handle a normal add without following referrals
if ( ( refval == null ) || refval.equals( IGNORE ) )
{
next.add( opContext );
if ( isReferral( entry ) )
{
lut.referralAdded( name );
}
}
else if ( refval.equals( THROW ) )
{
LdapDN farthest = lut.getFarthestReferralAncestor( name );
if ( farthest == null )
{
next.add( opContext );
if ( isReferral( entry ) )
{
lut.referralAdded( name );
}
return;
}
ServerEntry referral = invocation.getProxy().lookup( new LookupOperationContext( registries, farthest ),
PartitionNexusProxy.LOOKUP_BYPASS );
AttributeType refsType = atRegistry.lookup( oidRegistry.getOid( SchemaConstants.REF_AT ) );
EntryAttribute refs = referral.get( refsType );
doReferralException( farthest, new LdapDN( name.getUpName() ), refs );
}
else if ( refval.equals( FOLLOW ) )
{
throw new NotImplementedException( FOLLOW + " referral handling mode not implemented" );
}
else
{
throw new LdapNamingException( "Undefined value for " + Context.REFERRAL + " key: " + refval,
ResultCodeEnum.OTHER );
}
}
public boolean compare( NextInterceptor next, CompareOperationContext opContext ) throws NamingException
{
LdapDN name = opContext.getDn();
Invocation invocation = InvocationStack.getInstance().peek();
ServerLdapContext caller = ( ServerLdapContext ) invocation.getCaller();
String refval = ( String ) caller.getEnvironment().get( Context.REFERRAL );
// handle a normal add without following referrals
if ( refval == null || refval.equals( IGNORE ) )
{
return next.compare( opContext );
}
if ( refval.equals( THROW ) )
{
LdapDN farthest = lut.getFarthestReferralAncestor( name );
if ( farthest == null )
{
return next.compare( opContext );
}
ServerEntry referral = invocation.getProxy().lookup( new LookupOperationContext( registries, farthest ),
PartitionNexusProxy.LOOKUP_BYPASS );
EntryAttribute refs = referral.get( SchemaConstants.REF_AT );
doReferralException( farthest, new LdapDN( name.getUpName() ), refs );
// we really can't get here since doReferralException will throw an exception
return false;
}
else if ( refval.equals( FOLLOW ) )
{
throw new NotImplementedException( FOLLOW + " referral handling mode not implemented" );
}
else
{
throw new LdapNamingException( "Undefined value for " + Context.REFERRAL + " key: " + refval,
ResultCodeEnum.OTHER );
}
}
public void delete( NextInterceptor next, DeleteOperationContext opContext ) throws NamingException
{
LdapDN name = opContext.getDn();
Invocation invocation = InvocationStack.getInstance().peek();
ServerLdapContext caller = ( ServerLdapContext ) invocation.getCaller();
String refval = ( String ) caller.getEnvironment().get( Context.REFERRAL );
// handle a normal delete without following referrals
if ( refval == null || refval.equals( IGNORE ) )
{
next.delete( opContext );
if ( lut.isReferral( name ) )
{
lut.referralDeleted( name );
}
return;
}
if ( refval.equals( THROW ) )
{
LdapDN farthest = lut.getFarthestReferralAncestor( name );
if ( farthest == null )
{
next.delete( opContext );
if ( lut.isReferral( name ) )
{
lut.referralDeleted( name );
}
return;
}
ServerEntry referral = invocation.getProxy().lookup( new LookupOperationContext( registries, farthest ),
PartitionNexusProxy.LOOKUP_BYPASS );
EntryAttribute refs = referral.get( SchemaConstants.REF_AT );
doReferralException( farthest, new LdapDN( name.getUpName() ), refs );
}
else if ( refval.equals( FOLLOW ) )
{
throw new NotImplementedException( FOLLOW + " referral handling mode not implemented" );
}
else
{
throw new LdapNamingException( "Undefined value for " + Context.REFERRAL + " key: " + refval,
ResultCodeEnum.OTHER );
}
}
/* -----------------------------------------------------------------------
* Special handling instructions for ModifyDn operations:
* ======================================================
*
* From RFC 3296 here => http://www.ietf.org/rfc/rfc3296.txt
*
* 5.6.2 Modify DN
*
* If the newSuperior is a referral object or is subordinate to a
* referral object, the server SHOULD return affectsMultipleDSAs. If
* the newRDN already exists but is a referral object, the server SHOULD
* return affectsMultipleDSAs instead of entryAlreadyExists.
* -----------------------------------------------------------------------
*/
public void move( NextInterceptor next, MoveOperationContext opContext ) throws NamingException
{
LdapDN oldName = opContext.getDn();
Invocation invocation = InvocationStack.getInstance().peek();
ServerLdapContext caller = ( ServerLdapContext ) invocation.getCaller();
String refval = ( String ) caller.getEnvironment().get( Context.REFERRAL );
LdapDN newName = ( LdapDN ) opContext.getParent().clone();
newName.add( oldName.get( oldName.size() - 1 ) );
// handle a normal modify without following referrals
if ( refval == null || refval.equals( IGNORE ) )
{
next.move( opContext );
if ( lut.isReferral( oldName ) )
{
lut.referralChanged( oldName, newName );
}
return;
}
if ( refval.equals( THROW ) )
{
LdapDN farthestSrc = lut.getFarthestReferralAncestor( oldName );
LdapDN farthestDst = lut.getFarthestReferralAncestor( newName ); // note will not return newName so safe
if ( farthestSrc == null && farthestDst == null && !lut.isReferral( newName ) )
{
next.move( opContext );
if ( lut.isReferral( oldName ) )
{
lut.referralChanged( oldName, newName );
}
return;
}
else if ( farthestSrc != null )
{
ServerEntry referral = invocation.getProxy().lookup(
new LookupOperationContext( registries, farthestSrc ), PartitionNexusProxy.LOOKUP_BYPASS );
EntryAttribute refs = referral.get( SchemaConstants.REF_AT );
doReferralException( farthestSrc, new LdapDN( oldName.getUpName() ), refs );
}
else if ( farthestDst != null )
{
throw new LdapNamingException( farthestDst + " ancestor is a referral for modifyDn on " + newName
+ " so it affects multiple DSAs", ResultCodeEnum.AFFECTS_MULTIPLE_DSAS );
}
else if ( lut.isReferral( newName ) )
{
throw new LdapNamingException( newName
+ " exists and is a referral for modifyDn destination so it affects multiple DSAs",
ResultCodeEnum.AFFECTS_MULTIPLE_DSAS );
}
throw new IllegalStateException( "If you get this exception the server's logic was flawed in handling a "
+ "modifyDn operation while processing referrals. Report this as a bug!" );
}
else if ( refval.equals( FOLLOW ) )
{
throw new NotImplementedException( FOLLOW + " referral handling mode not implemented" );
}
else
{
throw new LdapNamingException( "Undefined value for " + Context.REFERRAL + " key: " + refval,
ResultCodeEnum.OTHER );
}
}
public void moveAndRename( NextInterceptor next, MoveAndRenameOperationContext opContext ) throws NamingException
{
LdapDN oldName = opContext.getDn();
Invocation invocation = InvocationStack.getInstance().peek();
ServerLdapContext caller = ( ServerLdapContext ) invocation.getCaller();
String refval = ( String ) caller.getEnvironment().get( Context.REFERRAL );
LdapDN newName = ( LdapDN ) opContext.getParent().clone();
newName.add( opContext.getNewRdn() );
// handle a normal modify without following referrals
if ( refval == null || refval.equals( IGNORE ) )
{
next.moveAndRename( opContext );
if ( lut.isReferral( oldName ) )
{
lut.referralChanged( oldName, newName );
}
return;
}
if ( refval.equals( THROW ) )
{
LdapDN farthestSrc = lut.getFarthestReferralAncestor( oldName );
LdapDN farthestDst = lut.getFarthestReferralAncestor( newName ); // safe to use - does not return newName
if ( farthestSrc == null && farthestDst == null && !lut.isReferral( newName ) )
{
next.moveAndRename( opContext );
if ( lut.isReferral( oldName ) )
{
lut.referralChanged( oldName, newName );
}
return;
}
else if ( farthestSrc != null )
{
ServerEntry referral = invocation.getProxy().lookup(
new LookupOperationContext( registries, farthestSrc ), PartitionNexusProxy.LOOKUP_BYPASS );
EntryAttribute refs = referral.get( SchemaConstants.REF_AT );
doReferralException( farthestSrc, new LdapDN( oldName.getUpName() ), refs );
}
else if ( farthestDst != null )
{
throw new LdapNamingException( farthestDst + " ancestor is a referral for modifyDn on " + newName
+ " so it affects multiple DSAs", ResultCodeEnum.AFFECTS_MULTIPLE_DSAS );
}
else if ( lut.isReferral( newName ) )
{
throw new LdapNamingException( newName
+ " exists and is a referral for modifyDn destination so it affects multiple DSAs",
ResultCodeEnum.AFFECTS_MULTIPLE_DSAS );
}
throw new IllegalStateException( "If you get this exception the server's logic was flawed in handling a "
+ "modifyDn operation while processing referrals. Report this as a bug!" );
}
else if ( refval.equals( FOLLOW ) )
{
throw new NotImplementedException( FOLLOW + " referral handling mode not implemented" );
}
else
{
throw new LdapNamingException( "Undefined value for " + Context.REFERRAL + " key: " + refval,
ResultCodeEnum.OTHER );
}
}
public void rename( NextInterceptor next, RenameOperationContext opContext ) throws NamingException
{
LdapDN oldName = opContext.getDn();
Invocation invocation = InvocationStack.getInstance().peek();
ServerLdapContext caller = ( ServerLdapContext ) invocation.getCaller();
String refval = ( String ) caller.getEnvironment().get( Context.REFERRAL );
LdapDN newName = ( LdapDN ) oldName.clone();
newName.remove( oldName.size() - 1 );
newName.add( opContext.getNewRdn() );
// handle a normal modify without following referrals
if ( refval == null || refval.equals( IGNORE ) )
{
next.rename( opContext );
if ( lut.isReferral( oldName ) )
{
lut.referralChanged( oldName, newName );
}
return;
}
if ( refval.equals( THROW ) )
{
LdapDN farthestSrc = lut.getFarthestReferralAncestor( oldName );
LdapDN farthestDst = lut.getFarthestReferralAncestor( newName );
if ( farthestSrc == null && farthestDst == null && !lut.isReferral( newName ) )
{
next.rename( opContext );
if ( lut.isReferral( oldName ) )
{
lut.referralChanged( oldName, newName );
}
return;
}
if ( farthestSrc != null )
{
ServerEntry referral = invocation.getProxy().lookup(
new LookupOperationContext( registries, farthestSrc ), PartitionNexusProxy.LOOKUP_BYPASS );
EntryAttribute refs = referral.get( SchemaConstants.REF_AT );
doReferralException( farthestSrc, new LdapDN( oldName.getUpName() ), refs );
}
else if ( farthestDst != null )
{
throw new LdapNamingException( farthestDst + " ancestor is a referral for modifyDn on " + newName
+ " so it affects multiple DSAs", ResultCodeEnum.AFFECTS_MULTIPLE_DSAS );
}
else if ( lut.isReferral( newName ) )
{
throw new LdapNamingException( newName
+ " exists and is a referral for modifyDn destination so it affects multiple DSAs",
ResultCodeEnum.AFFECTS_MULTIPLE_DSAS );
}
throw new IllegalStateException( "If you get this exception the server's logic was flawed in handling a "
+ "modifyDn operation while processing referrals. Report this as a bug!" );
}
else if ( refval.equals( FOLLOW ) )
{
throw new NotImplementedException( FOLLOW + " referral handling mode not implemented" );
}
else
{
throw new LdapNamingException( "Undefined value for " + Context.REFERRAL + " key: " + refval,
ResultCodeEnum.OTHER );
}
}
private void checkModify( LdapDN name, List<Modification> mods ) throws NamingException
{
boolean isTargetReferral = lut.isReferral( name );
// -------------------------------------------------------------------
// Check and update lut if we change the objectClass
// -------------------------------------------------------------------
for ( Modification mod : mods )
{
if ( mod.getAttribute().getId().equalsIgnoreCase( SchemaConstants.OBJECT_CLASS_AT ) )
{
boolean modsOcHasReferral = ( ( ServerAttribute ) mod.getAttribute() )
.contains( SchemaConstants.REFERRAL_OC );
switch ( mod.getOperation() )
{
/*
* if ADD op where refferal is added to objectClass of a
* non-referral entry then we add a new referral to lut
*/
case ADD_ATTRIBUTE:
if ( modsOcHasReferral && !isTargetReferral )
{
lut.referralAdded( name );
}
break;
/*
* if REMOVE op where refferal is removed from objectClass of a
* referral entry then we remove the referral from lut
*/
case REMOVE_ATTRIBUTE:
if ( modsOcHasReferral && isTargetReferral )
{
lut.referralDeleted( name );
}
break;
/*
* if REPLACE op on referral has new set of OC values which does
* not contain a referral value then we remove the referral from
* the lut
*
* if REPLACE op on non-referral has new set of OC values with
* referral value then we add the new referral to the lut
*/
case REPLACE_ATTRIBUTE:
if ( isTargetReferral && !modsOcHasReferral )
{
lut.referralDeleted( name );
}
else if ( !isTargetReferral && modsOcHasReferral )
{
lut.referralAdded( name );
}
break;
default:
throw new IllegalStateException( "undefined modification operation" );
}
break;
}
}
}
public void modify( NextInterceptor next, ModifyOperationContext opContext ) throws NamingException
{
Invocation invocation = InvocationStack.getInstance().peek();
ServerLdapContext caller = ( ServerLdapContext ) invocation.getCaller();
String refval = ( String ) caller.getEnvironment().get( Context.REFERRAL );
LdapDN name = opContext.getDn();
List<Modification> mods = opContext.getModItems();
// handle a normal modify without following referrals
if ( refval == null || refval.equals( IGNORE ) )
{
next.modify( opContext );
checkModify( name, mods );
return;
}
if ( refval.equals( THROW ) )
{
LdapDN farthest = lut.getFarthestReferralAncestor( name );
if ( farthest == null )
{
next.modify( opContext );
checkModify( name, mods );
return;
}
ServerEntry referral = invocation.getProxy().lookup( new LookupOperationContext( registries, farthest ),
PartitionNexusProxy.LOOKUP_BYPASS );
EntryAttribute refs = referral.get( SchemaConstants.REF_AT );
doReferralException( farthest, new LdapDN( name.getUpName() ), refs );
}
else if ( refval.equals( FOLLOW ) )
{
throw new NotImplementedException( FOLLOW + " referral handling mode not implemented" );
}
else
{
throw new LdapNamingException( "Undefined value for " + Context.REFERRAL + " key: " + refval,
ResultCodeEnum.OTHER );
}
}
static ExprNode getReferralFilter()
{
return new EqualityNode( SchemaConstants.OBJECT_CLASS_AT, new ClientStringValue( SchemaConstants.REFERRAL_OC ) );
}
static SearchControls getControls()
{
SearchControls controls = new SearchControls();
controls.setReturningObjFlag( false );
controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
return controls;
}
public void addContextPartition( NextInterceptor next, AddContextPartitionOperationContext opContext )
throws NamingException
{
next.addContextPartition( opContext );
// add referrals immediately after adding the new partition
Partition partition = opContext.getPartition();
LdapDN suffix = partition.getSuffixDn();
Invocation invocation = InvocationStack.getInstance().peek();
NamingEnumeration<ServerSearchResult> list = invocation.getProxy().search(
new SearchOperationContext( registries, suffix, AliasDerefMode.DEREF_ALWAYS, getReferralFilter(),
getControls() ), SEARCH_BYPASS );
addReferrals( list, suffix );
}
public void removeContextPartition( NextInterceptor next, RemoveContextPartitionOperationContext opContext )
throws NamingException
{
// remove referrals immediately before removing the partition
Invocation invocation = InvocationStack.getInstance().peek();
NamingEnumeration<ServerSearchResult> list = invocation.getProxy().search(
new SearchOperationContext( registries, opContext.getDn(), AliasDerefMode.DEREF_ALWAYS,
getReferralFilter(), getControls() ), SEARCH_BYPASS );
deleteReferrals( list, opContext.getDn() );
next.removeContextPartition( opContext );
}
private void addReferrals( NamingEnumeration<ServerSearchResult> referrals, LdapDN base ) throws NamingException
{
while ( referrals.hasMore() )
{
ServerSearchResult r = referrals.next();
LdapDN referral;
LdapDN result = new LdapDN( r.getDn() );
result.normalize( atRegistry.getNormalizerMapping() );
if ( r.isRelative() )
{
referral = ( LdapDN ) base.clone();
referral.addAll( result );
}
// Now, add the referral to the cache
lut.referralAdded( result );
}
}
private void deleteReferrals( NamingEnumeration<ServerSearchResult> referrals, LdapDN base ) throws NamingException
{
while ( referrals.hasMore() )
{
ServerSearchResult r = referrals.next();
LdapDN referral;
LdapDN result = new LdapDN( r.getDn() );
result.normalize( atRegistry.getNormalizerMapping() );
if ( r.isRelative() )
{
referral = ( LdapDN ) base.clone();
referral.addAll( result );
}
// Now, remove the referral from the cache
lut.referralDeleted( result );
}
}
public NamingEnumeration<ServerSearchResult> search( NextInterceptor next, SearchOperationContext opContext )
throws NamingException
{
Invocation invocation = InvocationStack.getInstance().peek();
ServerLdapContext caller = ( ServerLdapContext ) invocation.getCaller();
String refval = ( String ) caller.getEnvironment().get( Context.REFERRAL );
// handle a normal search without following referrals
if ( refval == null || refval.equals( IGNORE ) )
{
return next.search( opContext );
}
LdapDN base = opContext.getDn();
SearchControls controls = opContext.getSearchControls();
/**
* THROW_FINDING_BASE is a special setting which allows for finding base to
* throw exceptions but not when searching. While search all results are
* returned as if they are regular entries.
*/
if ( refval.equals( THROW_FINDING_BASE ) )
{
if ( lut.isReferral( base ) )
{
ServerEntry referral = invocation.getProxy().lookup( new LookupOperationContext( registries, base ),
PartitionNexusProxy.LOOKUP_BYPASS );
EntryAttribute refs = referral.get( SchemaConstants.REF_AT );
doReferralExceptionOnSearchBase( base, refs, controls.getSearchScope() );
}
LdapDN farthest = lut.getFarthestReferralAncestor( base );
if ( farthest == null )
{
return next.search( opContext );
}
ServerEntry referral = invocation.getProxy().lookup( new LookupOperationContext( registries, farthest ),
PartitionNexusProxy.LOOKUP_BYPASS );
EntryAttribute refs = referral.get( SchemaConstants.REF_AT );
doReferralExceptionOnSearchBase( farthest, new LdapDN( base.getUpName() ), refs, controls.getSearchScope() );
throw new IllegalStateException( "Should never get here: shutting up compiler" );
}
if ( refval.equals( THROW ) )
{
if ( lut.isReferral( base ) )
{
ServerEntry referral = invocation.getProxy().lookup( new LookupOperationContext( registries, base ),
PartitionNexusProxy.LOOKUP_BYPASS );
EntryAttribute refs = referral.get( SchemaConstants.REF_AT );
doReferralExceptionOnSearchBase( base, refs, controls.getSearchScope() );
}
LdapDN farthest = lut.getFarthestReferralAncestor( base );
if ( farthest == null )
{
SearchResultFilteringEnumeration srfe = ( SearchResultFilteringEnumeration ) next.search( opContext );
return new ReferralHandlingEnumeration( srfe, lut, opContext.getRegistries(), nexus, controls
.getSearchScope(), true );
}
ServerEntry referral = invocation.getProxy().lookup( new LookupOperationContext( registries, farthest ),
PartitionNexusProxy.LOOKUP_BYPASS );
EntryAttribute refs = referral.get( SchemaConstants.REF_AT );
doReferralExceptionOnSearchBase( farthest, new LdapDN( base.getUpName() ), refs, controls.getSearchScope() );
throw new IllegalStateException( "Should never get here: shutting up compiler" );
}
else if ( refval.equals( FOLLOW ) )
{
throw new NotImplementedException( FOLLOW + " referral handling mode not implemented" );
}
else
{
throw new LdapNamingException( "Undefined value for " + Context.REFERRAL + " key: " + refval,
ResultCodeEnum.OTHER );
}
}
class ReferralFilter implements SearchResultFilter//, SearchResultEnumerationAppender
{
public boolean accept( Invocation invocation, ServerSearchResult result, SearchControls controls )
throws NamingException
{
return false;
}
}
public void doReferralExceptionOnSearchBase( LdapDN base, EntryAttribute refs, int scope ) throws NamingException
{
// handle referral here
List<String> list = new ArrayList<String>( refs.size() );
for ( Value<?> value : refs )
{
String val = ( String ) value.get();
// need to add non-ldap URLs as-is
if ( !val.startsWith( "ldap" ) )
{
list.add( val );
continue;
}
// parse the ref value and normalize the DN according to schema
LdapURL ldapUrl = new LdapURL();
try
{
ldapUrl.parse( val.toCharArray() );
}
catch ( LdapURLEncodingException e )
{
LOG.error( "Bad URL (" + val + ") for ref in " + base + ". Reference will be ignored." );
}
StringBuilder buf = new StringBuilder();
buf.append( ldapUrl.getScheme() );
buf.append( ldapUrl.getHost() );
if ( ldapUrl.getPort() > 0 )
{
buf.append( ":" );
buf.append( ldapUrl.getPort() );
}
buf.append( "/" );
buf.append( LdapURL.urlEncode( ldapUrl.getDn().getUpName(), false ) );
buf.append( "??" );
switch ( scope )
{
case ( SearchControls.SUBTREE_SCOPE ):
buf.append( "sub" );
break;
case ( SearchControls.ONELEVEL_SCOPE ):
buf.append( "one" );
break;
case ( SearchControls.OBJECT_SCOPE ):
buf.append( "base" );
break;
default:
throw new IllegalStateException( "Unknown recognized search scope: " + scope );
}
list.add( buf.toString() );
}
throw new LdapReferralException( list );
}
public void doReferralExceptionOnSearchBase( LdapDN farthest, LdapDN targetUpdn, EntryAttribute refs, int scope )
throws NamingException
{
// handle referral here
List<String> list = new ArrayList<String>( refs.size() );
for ( Value<?> value : refs )
{
String val = ( String ) value.get();
// need to add non-ldap URLs as-is
if ( !val.startsWith( "ldap" ) )
{
list.add( val );
continue;
}
// parse the ref value and normalize the DN according to schema
LdapURL ldapUrl = new LdapURL();
try
{
ldapUrl.parse( val.toCharArray() );
}
catch ( LdapURLEncodingException e )
{
LOG.error( "Bad URL (" + val + ") for ref in " + farthest + ". Reference will be ignored." );
}
LdapDN urlDn = new LdapDN( ldapUrl.getDn().toNormName() );
urlDn.normalize( atRegistry.getNormalizerMapping() );
int diff = targetUpdn.size() - farthest.size();
LdapDN extra = new LdapDN();
for ( int jj = 0; jj < diff; jj++ )
{
extra.add( targetUpdn.get( farthest.size() + jj ) );
}
urlDn.addAll( extra );
StringBuilder buf = new StringBuilder();
buf.append( ldapUrl.getScheme() );
buf.append( ldapUrl.getHost() );
if ( ldapUrl.getPort() > 0 )
{
buf.append( ":" );
buf.append( ldapUrl.getPort() );
}
buf.append( "/" );
buf.append( LdapURL.urlEncode( urlDn.getUpName(), false ) );
buf.append( "??" );
switch ( scope )
{
case ( SearchControls.SUBTREE_SCOPE ):
buf.append( "sub" );
break;
case ( SearchControls.ONELEVEL_SCOPE ):
buf.append( "one" );
break;
case ( SearchControls.OBJECT_SCOPE ):
buf.append( "base" );
break;
default:
throw new IllegalStateException( "Unknown recognized search scope: " + scope );
}
list.add( buf.toString() );
}
throw new LdapReferralException( list );
}
/**
* Check if the given name is a referral or not.
*
* @param name The DN to check
* @return <code>true</code> if the DN is a referral
* @throws NamingException I fthe DN is incorrect
*/
public boolean isReferral( String name ) throws NamingException
{
if ( lut.isReferral( name ) )
{
return true;
}
LdapDN dn = new LdapDN( name );
dn.normalize( atRegistry.getNormalizerMapping() );
return lut.isReferral( dn );
}
/**
* Check if the given name is a referral or not.
*
* @param name The DN to check
* @return <code>true</code> if the DN is a referral
* @throws NamingException I fthe DN is incorrect
*/
public boolean isReferral( LdapDN name ) throws NamingException
{
return lut
.isReferral( name.isNormalized() ? name : LdapDN.normalize( name, atRegistry.getNormalizerMapping() ) );
}
}