blob: 4c7d53198755c1d990e804a35c3f4dd5b8daf792 [file] [log] [blame]
/*
* Copyright 2004 The Apache Software Foundation
*
* Licensed 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.ldap.server.jndi;
import java.io.Serializable;
import java.util.Hashtable;
import javax.naming.ConfigurationException;
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.ldap.Control;
import javax.naming.spi.DirStateFactory;
import javax.naming.spi.DirectoryManager;
import org.apache.ldap.common.exception.LdapNoPermissionException;
import org.apache.ldap.common.filter.PresenceNode;
import org.apache.ldap.common.message.LockableAttributesImpl;
import org.apache.ldap.common.name.LdapName;
import org.apache.ldap.common.util.NamespaceTools;
import org.apache.ldap.server.authn.AuthenticationService;
import org.apache.ldap.server.authn.LdapPrincipal;
import org.apache.ldap.server.partition.ContextPartitionNexus;
/**
* 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 Context
{
/** property key used for deleting the old RDN on a rename */
public static final String DELETE_OLD_RDN_PROP = "java.naming.ldap.deleteRDN";
/** The interceptor proxy to the backend nexus */
private final ContextPartitionNexus nexusProxy;
/** The cloned environment used by this Context */
private final Hashtable env;
/** The distinguished name of this Context */
private final LdapName dn;
/** 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( ContextFactoryService service, Hashtable env ) throws NamingException
{
// set references to cloned env and the proxy
this.nexusProxy = new ContextPartitionNexusProxy( this, service );
ContextFactoryConfiguration cfg = service.getConfiguration();
this.env = ( Hashtable ) cfg.getEnvironment().clone();
this.env.putAll( env );
/* --------------------------------------------------------------------
* check for the provider URL property and make sure it exists
* as a valid value or else we need to throw a configuration error
* ------------------------------------------------------------------ */
if ( ! env.containsKey( Context.PROVIDER_URL ) )
{
String msg = "Expected property " + Context.PROVIDER_URL;
msg += " but could not find it in env!";
throw new ConfigurationException( msg );
}
String url = ( String ) env.get( Context.PROVIDER_URL );
if ( url == null )
{
String msg = "Expected value for property " + Context.PROVIDER_URL;
msg += " but it was set to null in env!";
throw new ConfigurationException( msg );
}
dn = new LdapName( url );
if ( ! nexusProxy.hasEntry( 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 nexusProxy the intercepting proxy to the nexus
* @param env the environment properties used by this context
* @param dn the distinguished name of this context
*/
protected ServerContext( LdapPrincipal principal, ContextPartitionNexus nexusProxy, Hashtable env, Name dn )
{
this.dn = ( LdapName ) dn.clone();
this.env = ( Hashtable ) env.clone();
this.env.put( PROVIDER_URL, dn.toString() );
this.nexusProxy = nexusProxy;
this.principal = principal;
}
// ------------------------------------------------------------------------
// New Impl Specific Public Methods
// ------------------------------------------------------------------------
/**
* 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 ContextPartitionNexus 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
{
// Does nothing yet?
}
/**
* @see javax.naming.Context#getNameInNamespace()
*/
public String getNameInNamespace() throws NamingException
{
return dn.toString();
}
/**
* @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 LdapName( name ) );
}
/**
* @see javax.naming.Context#createSubcontext(javax.naming.Name)
*/
public Context createSubcontext( Name name ) throws NamingException
{
Attributes attributes = new LockableAttributesImpl();
LdapName target = buildTarget( name );
String rdn = name.get( name.size() - 1 );
String rdnAttribute = NamespaceTools.getRdnAttribute( rdn );
String rdnValue = NamespaceTools.getRdnValue( rdn );
attributes.put( rdnAttribute, rdnValue );
attributes.put( JavaLdapSupport.OBJECTCLASS_ATTR, JavaLdapSupport.JCONTAINER_ATTR );
attributes.put( JavaLdapSupport.OBJECTCLASS_ATTR, JavaLdapSupport.TOP_ATTR );
/*
* 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( target.toString(), target, attributes );
ServerLdapContext ctx = new ServerLdapContext( principal, nexusProxy, env, target );
Control [] controls = ( Control [] ) ( ( ServerLdapContext ) this ).getRequestControls().clone();
ctx.setRequestControls( controls );
return ctx;
}
/**
* @see javax.naming.Context#destroySubcontext(java.lang.String)
*/
public void destroySubcontext( String name ) throws NamingException
{
destroySubcontext( new LdapName( name ) );
}
/**
* @see javax.naming.Context#destroySubcontext(javax.naming.Name)
*/
public void destroySubcontext( Name name ) throws NamingException
{
Name target = buildTarget( name );
if ( target.size() == 0 )
{
throw new LdapNoPermissionException( "can't delete the rootDSE" );
}
nexusProxy.delete( target );
}
/**
* @see javax.naming.Context#bind(java.lang.String, java.lang.Object)
*/
public void bind( String name, Object obj ) throws NamingException
{
bind( new LdapName( name ), obj );
}
/**
* @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 )
{
Name target = buildTarget( name );
nexusProxy.add( target.toString(), target, outAttrs );
return;
}
// Check for Referenceable
if ( obj instanceof Referenceable )
{
obj = ( ( Referenceable ) obj ).getReference();
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 LockableAttributesImpl();
if ( outAttrs != null && outAttrs.size() > 0 )
{
NamingEnumeration list = outAttrs.getAll();
while ( list.hasMore() )
{
attributes.put( ( Attribute ) list.next() );
}
}
Name target = buildTarget( name );
// Serialize object into entry attributes and add it.
JavaLdapSupport.serialize( attributes, obj );
nexusProxy.add( target.toString(), 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() );
}
}
Name target = buildTarget( name );
nexusProxy.add( target.toString(), 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 LdapName( oldName ), new LdapName( newName ) );
}
/**
* @see javax.naming.Context#rename(javax.naming.Name, javax.naming.Name)
*/
public void rename( Name oldName, Name newName ) throws NamingException
{
Name oldDn = buildTarget( oldName );
Name newDn = buildTarget( newName );
if ( oldDn.size() == 0 )
{
throw new LdapNoPermissionException( "can't rename the rootDSE" );
}
Name oldBase = oldName.getSuffix( 1 );
Name newBase = newName.getSuffix( 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.equals( "false" );
delOldRdn = delOldRdn || delOldRdnStr.equals( "no" );
delOldRdn = delOldRdn || 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.modifyRn( oldDn, newRdn, delOldRdn );
}
else
{
Name parent = newDn.getSuffix( 1 );
if ( newRdn.equalsIgnoreCase( oldRdn ) )
{
nexusProxy.move( oldDn, parent );
}
else
{
nexusProxy.move( oldDn, parent, newRdn, delOldRdn );
}
}
}
/**
* @see javax.naming.Context#rebind(java.lang.String, java.lang.Object)
*/
public void rebind( String name, Object obj ) throws NamingException
{
rebind( new LdapName( name ), obj );
}
/**
* @see javax.naming.Context#rebind(javax.naming.Name, java.lang.Object)
*/
public void rebind( Name name, Object obj ) throws NamingException
{
Name target = buildTarget( name );
if ( nexusProxy.hasEntry( target ) )
{
nexusProxy.delete( target );
}
bind( name, obj );
}
/**
* @see javax.naming.Context#unbind(java.lang.String)
*/
public void unbind( String name ) throws NamingException
{
unbind( new LdapName( name ) );
}
/**
* @see javax.naming.Context#unbind(javax.naming.Name)
*/
public void unbind( Name name ) throws NamingException
{
nexusProxy.delete( buildTarget( name ) );
}
/**
* @see javax.naming.Context#lookup(java.lang.String)
*/
public Object lookup( String name ) throws NamingException
{
return lookup( new LdapName( name ) );
}
/**
* @see javax.naming.Context#lookup(javax.naming.Name)
*/
public Object lookup( Name name ) throws NamingException
{
Object obj = null;
LdapName target = buildTarget( name );
Attributes attributes = nexusProxy.lookup( target );
try
{
obj = DirectoryManager.getObjectInstance( null, name, this, env, attributes );
}
catch ( Exception e )
{
throw new NamingException( e.getMessage() );
}
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( principal, nexusProxy, env, 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 LdapName.getNameParser();
}
/**
* 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 LdapName.getNameParser();
}
/**
* @see javax.naming.Context#list(java.lang.String)
*/
public NamingEnumeration list( String name ) throws NamingException
{
return list( new LdapName( name ) );
}
/**
* @see javax.naming.Context#list(javax.naming.Name)
*/
public NamingEnumeration list( Name name ) throws NamingException
{
return nexusProxy.list( buildTarget( name ) );
}
/**
* @see javax.naming.Context#listBindings(java.lang.String)
*/
public NamingEnumeration listBindings( String name ) throws NamingException
{
return listBindings( new LdapName( 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
Name base = buildTarget( name );
PresenceNode filter = new PresenceNode( "objectClass" );
SearchControls ctls = new SearchControls();
ctls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
return nexusProxy.search( 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 LdapName( name ), new LdapName( 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 );
}
// ------------------------------------------------------------------------
// 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.
*/
LdapName buildTarget( Name relativeName ) throws InvalidNameException
{
// Clone our DN or absolute path
LdapName target = ( LdapName ) dn.clone();
// Add to left hand side of cloned DN the relative name arg
target.addAll( target.size(), relativeName );
return target;
}
}