blob: d795680c3158c26e21c71dd995dd71178b753907 [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.jndi;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Set;
import javax.naming.Context;
import javax.naming.InvalidNameException;
import javax.naming.Name;
import javax.naming.NameNotFoundException;
import javax.naming.NameParser;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.event.EventContext;
import javax.naming.event.NamingListener;
import javax.naming.ldap.Control;
import javax.naming.spi.DirStateFactory;
import javax.naming.spi.DirectoryManager;
import org.apache.directory.server.core.DirectoryService;
import org.apache.directory.server.core.DirectoryServiceConfiguration;
import org.apache.directory.server.core.authn.AuthenticationService;
import org.apache.directory.server.core.authn.LdapPrincipal;
import org.apache.directory.server.core.interceptor.context.AddOperationContext;
import org.apache.directory.server.core.interceptor.context.BindOperationContext;
import org.apache.directory.server.core.interceptor.context.DeleteOperationContext;
import org.apache.directory.server.core.interceptor.context.EntryOperationContext;
import org.apache.directory.server.core.interceptor.context.ListOperationContext;
import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
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.RenameOperationContext;
import org.apache.directory.server.core.interceptor.context.SearchOperationContext;
import org.apache.directory.server.core.partition.PartitionNexus;
import org.apache.directory.server.core.partition.PartitionNexusProxy;
import org.apache.directory.shared.ldap.constants.JndiPropertyConstants;
import org.apache.directory.shared.ldap.constants.SchemaConstants;
import org.apache.directory.shared.ldap.exception.LdapNoPermissionException;
import org.apache.directory.shared.ldap.filter.ExprNode;
import org.apache.directory.shared.ldap.filter.PresenceNode;
import org.apache.directory.shared.ldap.message.AttributesImpl;
import org.apache.directory.shared.ldap.name.AttributeTypeAndValue;
import org.apache.directory.shared.ldap.name.LdapDN;
import org.apache.directory.shared.ldap.name.Rdn;
import org.apache.directory.shared.ldap.util.StringTools;
/**
* A non-federated abstract Context implementation.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
* @version $Rev$
*/
public abstract class ServerContext implements EventContext
{
/** property key used for deleting the old RDN on a rename */
public static final String DELETE_OLD_RDN_PROP = JndiPropertyConstants.JNDI_LDAP_DELETE_RDN;
/** The directory service which owns this context **/
private final DirectoryService service;
/** The interceptor proxy to the backend nexus */
private final PartitionNexus nexusProxy;
/** The cloned environment used by this Context */
private final Hashtable env;
/** The distinguished name of this Context */
private final LdapDN dn;
/** The set of registered NamingListeners */
private final Set listeners = new HashSet();
/** The Principal associated with this context */
private LdapPrincipal principal;
// ------------------------------------------------------------------------
// Constructors
// ------------------------------------------------------------------------
/**
* Must be called by all subclasses to initialize the nexus proxy and the
* environment settings to be used by this Context implementation. This
* specific contstructor relies on the presence of the {@link
* Context#PROVIDER_URL} key and value to determine the distinguished name
* of the newly created context. It also checks to make sure the
* referenced name actually exists within the system. This constructor
* is used for all InitialContext requests.
*
* @param service the parent service that manages this context
* @param env the environment properties used by this context.
* @throws NamingException if the environment parameters are not set
* correctly.
*/
protected ServerContext(DirectoryService service, Hashtable env) throws NamingException
{
this.service = service;
// set references to cloned env and the proxy
this.nexusProxy = new PartitionNexusProxy( this, service );
DirectoryServiceConfiguration cfg = service.getConfiguration();
this.env = ( Hashtable ) cfg.getEnvironment().clone();
this.env.putAll( env );
LdapJndiProperties props = LdapJndiProperties.getLdapJndiProperties( this.env );
dn = props.getProviderDn();
BindOperationContext bindContext = new BindOperationContext();
bindContext.setDn( props.getBindDn() );
bindContext.setCredentials( props.getCredentials() );
bindContext.setMechanisms( props.getAuthenticationMechanisms() );
bindContext.setSaslAuthId( props.getSaslAuthId() );
// need to issue a bind operation here
this.nexusProxy.bind( bindContext );
if ( ! nexusProxy.hasEntry( new EntryOperationContext( dn ) ) )
{
throw new NameNotFoundException( dn + " does not exist" );
}
}
/**
* Must be called by all subclasses to initialize the nexus proxy and the
* environment settings to be used by this Context implementation. This
* constructor is used to propagate new contexts from existing contexts.
*
* @param principal the directory user principal that is propagated
* @param dn the distinguished name of this context
*/
protected ServerContext(DirectoryService service, LdapPrincipal principal, Name dn) throws NamingException
{
this.service = service;
this.dn = ( LdapDN ) dn.clone();
this.env = ( Hashtable ) service.getConfiguration().getEnvironment().clone();
this.env.put( PROVIDER_URL, dn.toString() );
this.nexusProxy = new PartitionNexusProxy( this, service );
;
this.principal = principal;
}
// ------------------------------------------------------------------------
// New Impl Specific Public Methods
// ------------------------------------------------------------------------
/**
* Get's a handle on the root context of the DIT. The RootDSE as the present user.
* @throws NamingException
*/
public abstract ServerContext getRootContext() throws NamingException;
/**
* Returns the {@link DirectoryService} which manages this context.
*/
public DirectoryService getService()
{
return service;
}
/**
* Gets the principal of the authenticated user which also happens to own
*/
public LdapPrincipal getPrincipal()
{
return principal;
}
/**
* Sets the principal of the authenticated user which also happens to own.
* This method can be invoked only once to keep this property safe. This
* method has been changed to be public but it can only be set by the
* AuthenticationService to prevent malicious code from changing the
* effective principal.
*/
public void setPrincipal( AuthenticationService.TrustedPrincipalWrapper wrapper )
{
this.principal = wrapper.getPrincipal();
}
// ------------------------------------------------------------------------
// Protected Accessor Methods
// ------------------------------------------------------------------------
/**
* Gets the RootNexus proxy.
*
* @return the proxy to the backend nexus.
*/
protected PartitionNexus getNexusProxy()
{
return nexusProxy;
}
/**
* Gets the distinguished name of the entry associated with this Context.
*
* @return the distinguished name of this Context's entry.
*/
protected Name getDn()
{
return dn;
}
// ------------------------------------------------------------------------
// JNDI Context Interface Methods
// ------------------------------------------------------------------------
/**
* @see javax.naming.Context#close()
*/
public void close() throws NamingException
{
Iterator list = listeners.iterator();
while ( list.hasNext() )
{
( ( PartitionNexusProxy ) this.nexusProxy ).removeNamingListener( this, ( NamingListener ) list
.next() );
}
}
/**
* @see javax.naming.Context#getNameInNamespace()
*/
public String getNameInNamespace() throws NamingException
{
return dn.getUpName();
}
/**
* @see javax.naming.Context#getEnvironment()
*/
public Hashtable getEnvironment()
{
return env;
}
/**
* @see javax.naming.Context#addToEnvironment(java.lang.String,
* java.lang.Object)
*/
public Object addToEnvironment( String propName, Object propVal ) throws NamingException
{
return env.put( propName, propVal );
}
/**
* @see javax.naming.Context#removeFromEnvironment(java.lang.String)
*/
public Object removeFromEnvironment( String propName ) throws NamingException
{
return env.remove( propName );
}
/**
* @see javax.naming.Context#createSubcontext(java.lang.String)
*/
public Context createSubcontext( String name ) throws NamingException
{
return createSubcontext( new LdapDN( name ) );
}
/**
* @see javax.naming.Context#createSubcontext(javax.naming.Name)
*/
public Context createSubcontext( Name name ) throws NamingException
{
Attributes attributes = new AttributesImpl();
LdapDN target = buildTarget( name );
injectRdnAttributeValues( target, attributes );
attributes.put( SchemaConstants.OBJECT_CLASS_AT, JavaLdapSupport.JCONTAINER_ATTR );
attributes.put( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.TOP_OC );
/*
* Add the new context to the server which as a side effect adds
* operational attributes to the attributes refering instance which
* can them be used to initialize a new ServerLdapContext. Remember
* we need to copy over the controls as well to propagate the complete
* environment besides whats in the hashtable for env.
*/
nexusProxy.add( new AddOperationContext( target, attributes ) );
return new ServerLdapContext( service, principal, target );
}
/**
* @see javax.naming.Context#destroySubcontext(java.lang.String)
*/
public void destroySubcontext( String name ) throws NamingException
{
destroySubcontext( new LdapDN( name ) );
}
/**
* @see javax.naming.Context#destroySubcontext(javax.naming.Name)
*/
public void destroySubcontext( Name name ) throws NamingException
{
LdapDN target = buildTarget( name );
if ( target.size() == 0 )
{
throw new LdapNoPermissionException( "can't delete the rootDSE" );
}
nexusProxy.delete( new DeleteOperationContext( target ) );
}
/**
* @see javax.naming.Context#bind(java.lang.String, java.lang.Object)
*/
public void bind( String name, Object obj ) throws NamingException
{
bind( new LdapDN( name ), obj );
}
private void injectRdnAttributeValues( LdapDN target, Attributes attributes ) throws NamingException
{
// Add all the RDN attributes and their values to this entry
Rdn rdn = target.getRdn( target.size() - 1 );
if ( rdn.size() == 1 )
{
attributes.put( rdn.getUpType(), rdn.getValue() );
}
else
{
for ( Iterator ii = rdn.iterator(); ii.hasNext(); /**/ )
{
AttributeTypeAndValue atav = ( AttributeTypeAndValue ) ii.next();
attributes.put( atav.getUpType(), atav.getValue() );
}
}
}
/**
* @see javax.naming.Context#bind(javax.naming.Name, java.lang.Object)
*/
public void bind( Name name, Object obj ) throws NamingException
{
// First, use state factories to do a transformation
DirStateFactory.Result res = DirectoryManager.getStateToBind( obj, name, this, env, null );
Attributes outAttrs = res.getAttributes();
if ( outAttrs != null )
{
LdapDN target = buildTarget( name );
nexusProxy.add( new AddOperationContext( target, outAttrs ) );
return;
}
// Check for Referenceable
if ( obj instanceof Referenceable )
{
throw new NamingException( "Do not know how to store Referenceables yet!" );
}
// Store different formats
if ( obj instanceof Reference )
{
// Store as ref and add outAttrs
throw new NamingException( "Do not know how to store References yet!" );
}
else if ( obj instanceof Serializable )
{
// Serialize and add outAttrs
Attributes attributes = new AttributesImpl();
if ( outAttrs != null && outAttrs.size() > 0 )
{
NamingEnumeration list = outAttrs.getAll();
while ( list.hasMore() )
{
attributes.put( ( Attribute ) list.next() );
}
}
// Get target and inject all rdn attributes into entry
LdapDN target = buildTarget( name );
injectRdnAttributeValues( target, attributes );
// Serialize object into entry attributes and add it.
JavaLdapSupport.serialize( attributes, obj );
nexusProxy.add( new AddOperationContext( target, attributes ) );
}
else if ( obj instanceof DirContext )
{
// Grab attributes and merge with outAttrs
Attributes attributes = ( ( DirContext ) obj ).getAttributes( "" );
if ( outAttrs != null && outAttrs.size() > 0 )
{
NamingEnumeration list = outAttrs.getAll();
while ( list.hasMore() )
{
attributes.put( ( Attribute ) list.next() );
}
}
LdapDN target = buildTarget( name );
injectRdnAttributeValues( target, attributes );
nexusProxy.add( new AddOperationContext( target, attributes ) );
}
else
{
throw new NamingException( "Can't find a way to bind: " + obj );
}
}
/**
* @see javax.naming.Context#rename(java.lang.String, java.lang.String)
*/
public void rename( String oldName, String newName ) throws NamingException
{
rename( new LdapDN( oldName ), new LdapDN( newName ) );
}
/**
* @see javax.naming.Context#rename(javax.naming.Name, javax.naming.Name)
*/
public void rename( Name oldName, Name newName ) throws NamingException
{
LdapDN oldDn = buildTarget( oldName );
LdapDN newDn = buildTarget( newName );
if ( oldDn.size() == 0 )
{
throw new LdapNoPermissionException( "can't rename the rootDSE" );
}
// calculate parents
LdapDN oldBase = ( LdapDN ) oldName.clone();
oldBase.remove( oldName.size() - 1 );
LdapDN newBase = ( LdapDN ) newName.clone();
newBase.remove( newName.size() - 1 );
String newRdn = newName.get( newName.size() - 1 );
String oldRdn = oldName.get( oldName.size() - 1 );
boolean delOldRdn = true;
/*
* Attempt to use the java.naming.ldap.deleteRDN environment property
* to get an override for the deleteOldRdn option to modifyRdn.
*/
if ( null != env.get( DELETE_OLD_RDN_PROP ) )
{
String delOldRdnStr = ( String ) env.get( DELETE_OLD_RDN_PROP );
delOldRdn = !delOldRdnStr.equalsIgnoreCase( "false" ) &&
!delOldRdnStr.equalsIgnoreCase( "no" ) &&
!delOldRdnStr.equals( "0" );
}
/*
* We need to determine if this rename operation corresponds to a simple
* RDN name change or a move operation. If the two names are the same
* except for the RDN then it is a simple modifyRdn operation. If the
* names differ in size or have a different baseDN then the operation is
* a move operation. Furthermore if the RDN in the move operation
* changes it is both an RDN change and a move operation.
*/
if ( ( oldName.size() == newName.size() ) && oldBase.equals( newBase ) )
{
nexusProxy.rename( new RenameOperationContext( oldDn, newRdn, delOldRdn ) );
}
else
{
LdapDN target = ( LdapDN ) newDn.clone();
target.remove( newDn.size() - 1 );
if ( newRdn.equalsIgnoreCase( oldRdn ) )
{
nexusProxy.move( new MoveOperationContext( oldDn, target ) );
}
else
{
nexusProxy.moveAndRename( new MoveAndRenameOperationContext( oldDn, target, newRdn, delOldRdn ) );
}
}
}
/**
* @see javax.naming.Context#rebind(java.lang.String, java.lang.Object)
*/
public void rebind( String name, Object obj ) throws NamingException
{
rebind( new LdapDN( name ), obj );
}
/**
* @see javax.naming.Context#rebind(javax.naming.Name, java.lang.Object)
*/
public void rebind( Name name, Object obj ) throws NamingException
{
LdapDN target = buildTarget( name );
if ( nexusProxy.hasEntry( new EntryOperationContext( target ) ) )
{
nexusProxy.delete( new DeleteOperationContext( target ) );
}
bind( name, obj );
}
/**
* @see javax.naming.Context#unbind(java.lang.String)
*/
public void unbind( String name ) throws NamingException
{
unbind( new LdapDN( name ) );
}
/**
* @see javax.naming.Context#unbind(javax.naming.Name)
*/
public void unbind( Name name ) throws NamingException
{
nexusProxy.delete( new DeleteOperationContext( buildTarget( name ) ) );
}
/**
* @see javax.naming.Context#lookup(java.lang.String)
*/
public Object lookup( String name ) throws NamingException
{
if ( StringTools.isEmpty( name ) )
{
return lookup( LdapDN.EMPTY_LDAPDN );
}
else
{
return lookup( new LdapDN( name ) );
}
}
/**
* @see javax.naming.Context#lookup(javax.naming.Name)
*/
public Object lookup( Name name ) throws NamingException
{
Object obj;
LdapDN target = buildTarget( name );
Attributes attributes = null;
if ( name.size() == 0 )
{
attributes = nexusProxy.getRootDSE( new LookupOperationContext( target ) );
}
else
{
attributes = nexusProxy.lookup( new LookupOperationContext( target ) );
}
try
{
obj = DirectoryManager.getObjectInstance( null, name, this, env, attributes );
}
catch ( Exception e )
{
String msg = "Failed to create an object for " + target;
msg += " using object factories within the context's environment.";
NamingException ne = new NamingException( msg );
ne.setRootCause( e );
throw ne;
}
if ( obj != null )
{
return obj;
}
// First lets test and see if the entry is a serialized java object
if ( attributes.get( JavaLdapSupport.JCLASSNAME_ATTR ) != null )
{
// Give back serialized object and not a context
return JavaLdapSupport.deserialize( attributes );
}
// Initialize and return a context since the entry is not a java object
ServerLdapContext ctx = new ServerLdapContext( service, principal, target );
// Need to add controls to propagate extended ldap operational env
Control[] controls = ( ( ServerLdapContext ) this ).getRequestControls();
if ( null != controls )
{
ctx.setRequestControls( ( Control[] ) controls.clone() );
}
return ctx;
}
/**
* @see javax.naming.Context#lookupLink(java.lang.String)
*/
public Object lookupLink( String name ) throws NamingException
{
throw new UnsupportedOperationException();
}
/**
* @see javax.naming.Context#lookupLink(javax.naming.Name)
*/
public Object lookupLink( Name name ) throws NamingException
{
throw new UnsupportedOperationException();
}
/**
* Non-federated implementation presuming the name argument is not a
* composite name spanning multiple namespaces but a compound name in
* the same LDAP namespace. Hence the parser returned is always the
* same as calling this method with the empty String.
*
* @see javax.naming.Context#getNameParser(java.lang.String)
*/
public NameParser getNameParser( String name ) throws NamingException
{
return new NameParser()
{
public Name parse( String name ) throws NamingException
{
return new LdapDN( name );
}
};
}
/**
* Non-federated implementation presuming the name argument is not a
* composite name spanning multiple namespaces but a compound name in
* the same LDAP namespace. Hence the parser returned is always the
* same as calling this method with the empty String Name.
*
* @see javax.naming.Context#getNameParser(javax.naming.Name)
*/
public NameParser getNameParser( Name name ) throws NamingException
{
return new NameParser()
{
public Name parse( String name ) throws NamingException
{
return new LdapDN( name );
}
};
}
/**
* @see javax.naming.Context#list(java.lang.String)
*/
public NamingEnumeration list( String name ) throws NamingException
{
return list( new LdapDN( name ) );
}
/**
* @see javax.naming.Context#list(javax.naming.Name)
*/
public NamingEnumeration list( Name name ) throws NamingException
{
return nexusProxy.list( new ListOperationContext( buildTarget( name ) ) );
}
/**
* @see javax.naming.Context#listBindings(java.lang.String)
*/
public NamingEnumeration listBindings( String name ) throws NamingException
{
return listBindings( new LdapDN( name ) );
}
/**
* @see javax.naming.Context#listBindings(javax.naming.Name)
*/
public NamingEnumeration listBindings( Name name ) throws NamingException
{
// Conduct a special one level search at base for all objects
LdapDN base = buildTarget( name );
PresenceNode filter = new PresenceNode( SchemaConstants.OBJECT_CLASS_AT );
SearchControls ctls = new SearchControls();
ctls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
return nexusProxy.search( new SearchOperationContext( base, getEnvironment(), filter, ctls ) );
}
/**
* @see javax.naming.Context#composeName(java.lang.String, java.lang.String)
*/
public String composeName( String name, String prefix ) throws NamingException
{
return composeName( new LdapDN( name ), new LdapDN( prefix ) ).toString();
}
/**
* @see javax.naming.Context#composeName(javax.naming.Name,
* javax.naming.Name)
*/
public Name composeName( Name name, Name prefix ) throws NamingException
{
// No prefix reduces to name, or the name relative to this context
if ( prefix == null || prefix.size() == 0 )
{
return name;
}
/*
* Example: This context is ou=people and say name is the relative
* name of uid=jwalker and the prefix is dc=domain. Then we must
* compose the name relative to prefix which would be:
*
* uid=jwalker,ou=people,dc=domain.
*
* The following general algorithm generates the right name:
* 1). Find the Dn for name and walk it from the head to tail
* trying to match for the head of prefix.
* 2). Remove name components from the Dn until a match for the
* head of the prefix is found.
* 3). Return the remainder of the fqn or Dn after chewing off some
*/
// 1). Find the Dn for name and walk it from the head to tail
Name fqn = buildTarget( name );
String head = prefix.get( 0 );
// 2). Walk the fqn trying to match for the head of the prefix
while ( fqn.size() > 0 )
{
// match found end loop
if ( fqn.get( 0 ).equalsIgnoreCase( head ) )
{
return fqn;
}
else
// 2). Remove name components from the Dn until a match
{
fqn.remove( 0 );
}
}
String msg = "The prefix '" + prefix + "' is not an ancestor of this ";
msg += "entry '" + dn + "'";
throw new NamingException( msg );
}
// ------------------------------------------------------------------------
// EventContext implementations
// ------------------------------------------------------------------------
public void addNamingListener( Name name, int scope, NamingListener namingListener ) throws NamingException
{
ExprNode filter = new PresenceNode( SchemaConstants.OBJECT_CLASS_AT );
SearchControls controls = new SearchControls();
controls.setSearchScope( scope );
( ( PartitionNexusProxy ) this.nexusProxy ).addNamingListener( this, buildTarget( name ), filter,
controls, namingListener );
listeners.add( namingListener );
}
public void addNamingListener( String name, int scope, NamingListener namingListener ) throws NamingException
{
addNamingListener( new LdapDN( name ), scope, namingListener );
}
public void removeNamingListener( NamingListener namingListener ) throws NamingException
{
( ( PartitionNexusProxy ) this.nexusProxy ).removeNamingListener( this, namingListener );
listeners.remove( namingListener );
}
public boolean targetMustExist() throws NamingException
{
return false;
}
/**
* Allows subclasses to register and unregister listeners.
*
* @return the set of listeners used for tracking registered name listeners.
*/
protected Set getListeners()
{
return listeners;
}
// ------------------------------------------------------------------------
// Utility Methods to Reduce Code
// ------------------------------------------------------------------------
/**
* Clones this context's DN and adds the components of the name relative to
* this context to the left hand side of this context's cloned DN.
*
* @param relativeName a name relative to this context.
* @return the name of the target
* @throws InvalidNameException if relativeName is not a valid name in
* the LDAP namespace.
*/
LdapDN buildTarget( Name relativeName ) throws InvalidNameException
{
LdapDN target = ( LdapDN ) dn.clone();
// Add to left hand side of cloned DN the relative name arg
target.addAllNormalized( target.size(), relativeName );
return target;
}
}