View Javadoc

1   /*
2    *   Copyright 2004 The Apache Software Foundation
3    *
4    *   Licensed under the Apache License, Version 2.0 (the "License");
5    *   you may not use this file except in compliance with the License.
6    *   You may obtain a copy of the License at
7    *
8    *       http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *   Unless required by applicable law or agreed to in writing, software
11   *   distributed under the License is distributed on an "AS IS" BASIS,
12   *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *   See the License for the specific language governing permissions and
14   *   limitations under the License.
15   *
16   */
17  package org.apache.ldap.server.jndi;
18  
19  
20  import java.io.Serializable;
21  import java.util.Hashtable;
22  
23  import javax.naming.ConfigurationException;
24  import javax.naming.Context;
25  import javax.naming.InvalidNameException;
26  import javax.naming.Name;
27  import javax.naming.NameNotFoundException;
28  import javax.naming.NameParser;
29  import javax.naming.NamingEnumeration;
30  import javax.naming.NamingException;
31  import javax.naming.Reference;
32  import javax.naming.Referenceable;
33  import javax.naming.directory.Attribute;
34  import javax.naming.directory.Attributes;
35  import javax.naming.directory.DirContext;
36  import javax.naming.directory.SearchControls;
37  import javax.naming.ldap.Control;
38  import javax.naming.spi.DirStateFactory;
39  import javax.naming.spi.DirectoryManager;
40  
41  import org.apache.ldap.common.exception.LdapNoPermissionException;
42  import org.apache.ldap.common.filter.PresenceNode;
43  import org.apache.ldap.common.message.LockableAttributesImpl;
44  import org.apache.ldap.common.name.LdapName;
45  import org.apache.ldap.common.util.NamespaceTools;
46  import org.apache.ldap.server.authn.AuthenticationService;
47  import org.apache.ldap.server.authn.LdapPrincipal;
48  import org.apache.ldap.server.partition.ContextPartitionNexus;
49  
50  
51  /***
52   * A non-federated abstract Context implementation.
53   *
54   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
55   * @version $Rev: 264732 $
56   */
57  public abstract class ServerContext implements Context
58  {
59      /*** property key used for deleting the old RDN on a rename */
60      public static final String DELETE_OLD_RDN_PROP = "java.naming.ldap.deleteRDN";
61  
62      /*** The interceptor proxy to the backend nexus */
63      private final ContextPartitionNexus nexusProxy;
64  
65      /*** The cloned environment used by this Context */
66      private final Hashtable env;
67  
68      /*** The distinguished name of this Context */
69      private final LdapName dn;
70  
71      /*** The Principal associated with this context */
72      private LdapPrincipal principal;
73  
74      // ------------------------------------------------------------------------
75      // Constructors
76      // ------------------------------------------------------------------------
77  
78  
79      /***
80       * Must be called by all subclasses to initialize the nexus proxy and the
81       * environment settings to be used by this Context implementation.  This
82       * specific contstructor relies on the presence of the {@link
83       * Context#PROVIDER_URL} key and value to determine the distinguished name
84       * of the newly created context.  It also checks to make sure the
85       * referenced name actually exists within the system.  This constructor
86       * is used for all InitialContext requests.
87       * 
88       * @param service the parent service that manages this context
89       * @param env the environment properties used by this context.
90       * @throws NamingException if the environment parameters are not set 
91       * correctly.
92       */
93      protected ServerContext( ContextFactoryService service, Hashtable env ) throws NamingException
94      {
95          // set references to cloned env and the proxy
96          this.nexusProxy = new ContextPartitionNexusProxy( this, service );
97          
98          ContextFactoryConfiguration cfg = service.getConfiguration();
99          
100         this.env = ( Hashtable ) cfg.getEnvironment().clone();
101         this.env.putAll( env );
102 
103         /* --------------------------------------------------------------------
104          * check for the provider URL property and make sure it exists
105          * as a valid value or else we need to throw a configuration error
106          * ------------------------------------------------------------------ */
107         if ( ! env.containsKey( Context.PROVIDER_URL ) )
108         {
109             String msg = "Expected property " + Context.PROVIDER_URL;
110             msg += " but could not find it in env!";
111 
112             throw new ConfigurationException( msg );
113         }
114 
115         String url = ( String ) env.get( Context.PROVIDER_URL );
116 
117         if ( url == null )
118         {
119             String msg = "Expected value for property " + Context.PROVIDER_URL;
120             msg += " but it was set to null in env!";
121 
122             throw new ConfigurationException( msg );
123         }
124 
125         dn = new LdapName( url );
126 
127         if ( ! nexusProxy.hasEntry( dn ) )
128         {
129             throw new NameNotFoundException( dn + " does not exist" );
130         }
131     }
132 
133 
134     /***
135      * Must be called by all subclasses to initialize the nexus proxy and the
136      * environment settings to be used by this Context implementation.  This
137      * constructor is used to propagate new contexts from existing contexts.
138      *
139      * @param principal the directory user principal that is propagated
140      * @param nexusProxy the intercepting proxy to the nexus
141      * @param env the environment properties used by this context
142      * @param dn the distinguished name of this context
143      */
144     protected ServerContext( LdapPrincipal principal, ContextPartitionNexus nexusProxy, Hashtable env, Name dn )
145     {
146         this.dn = ( LdapName ) dn.clone();
147 
148         this.env = ( Hashtable ) env.clone();
149 
150         this.env.put( PROVIDER_URL, dn.toString() );
151 
152         this.nexusProxy = nexusProxy;
153 
154         this.principal = principal;
155     }
156 
157 
158     // ------------------------------------------------------------------------
159     // New Impl Specific Public Methods
160     // ------------------------------------------------------------------------
161 
162 
163     /***
164      * Gets the principal of the authenticated user which also happens to own
165      */
166     public LdapPrincipal getPrincipal()
167     {
168         return principal;
169     }
170 
171 
172     /***
173      * Sets the principal of the authenticated user which also happens to own.
174      * This method can be invoked only once to keep this property safe.  This
175      * method has been changed to be public but it can only be set by the
176      * AuthenticationService to prevent malicious code from changing the
177      * effective principal.
178      */
179     public void setPrincipal( AuthenticationService.TrustedPrincipalWrapper wrapper )
180     {
181         this.principal = wrapper.getPrincipal();
182     }
183 
184 
185     // ------------------------------------------------------------------------
186     // Protected Accessor Methods
187     // ------------------------------------------------------------------------
188 
189 
190     /***
191      * Gets the RootNexus proxy.
192      * 
193      * @return the proxy to the backend nexus.
194      */
195     protected ContextPartitionNexus getNexusProxy()
196     {
197        return nexusProxy ;
198     }
199     
200     
201     /***
202      * Gets the distinguished name of the entry associated with this Context.
203      * 
204      * @return the distinguished name of this Context's entry.
205      */
206     protected Name getDn()
207     {
208         return dn;
209     }
210 
211 
212     // ------------------------------------------------------------------------
213     // JNDI Context Interface Methods
214     // ------------------------------------------------------------------------
215 
216 
217     /***
218      * @see javax.naming.Context#close()
219      */
220     public void close() throws NamingException
221     {
222         // Does nothing yet?
223     }
224 
225 
226     /***
227      * @see javax.naming.Context#getNameInNamespace()
228      */
229     public String getNameInNamespace() throws NamingException
230     {
231         return dn.toString();
232     }
233 
234 
235     /***
236      * @see javax.naming.Context#getEnvironment()
237      */
238     public Hashtable getEnvironment()
239     {
240         return env;
241     }
242 
243 
244     /***
245      * @see javax.naming.Context#addToEnvironment(java.lang.String, 
246      * java.lang.Object)
247      */
248     public Object addToEnvironment( String propName, Object propVal ) throws NamingException
249     {
250         return env.put( propName, propVal );
251     }
252 
253 
254     /***
255      * @see javax.naming.Context#removeFromEnvironment(java.lang.String)
256      */
257     public Object removeFromEnvironment( String propName ) throws NamingException
258     {
259         return env.remove( propName );
260     }
261 
262 
263     /***
264      * @see javax.naming.Context#createSubcontext(java.lang.String)
265      */
266     public Context createSubcontext( String name ) throws NamingException
267     {
268         return createSubcontext( new LdapName( name ) );
269     }
270 
271 
272     /***
273      * @see javax.naming.Context#createSubcontext(javax.naming.Name)
274      */
275     public Context createSubcontext( Name name ) throws NamingException
276     {
277         Attributes attributes = new LockableAttributesImpl();
278 
279         LdapName target = buildTarget( name );
280 
281         String rdn = name.get( name.size() - 1 );
282 
283         String rdnAttribute = NamespaceTools.getRdnAttribute( rdn );
284 
285         String rdnValue = NamespaceTools.getRdnValue( rdn );
286 
287         attributes.put( rdnAttribute, rdnValue );
288 
289         attributes.put( JavaLdapSupport.OBJECTCLASS_ATTR, JavaLdapSupport.JCONTAINER_ATTR );
290 
291         attributes.put( JavaLdapSupport.OBJECTCLASS_ATTR, JavaLdapSupport.TOP_ATTR );
292         
293         /*
294          * Add the new context to the server which as a side effect adds 
295          * operational attributes to the attributes refering instance which
296          * can them be used to initialize a new ServerLdapContext.  Remember
297          * we need to copy over the controls as well to propagate the complete 
298          * environment besides whats in the hashtable for env.
299          */
300         nexusProxy.add( target.toString(), target, attributes );
301         
302         ServerLdapContext ctx = new ServerLdapContext( principal, nexusProxy, env, target );
303 
304         Control [] controls = ( Control [] ) ( ( ServerLdapContext ) this ).getRequestControls().clone();
305 
306         ctx.setRequestControls( controls );
307 
308         return ctx;
309     }
310 
311 
312     /***
313      * @see javax.naming.Context#destroySubcontext(java.lang.String)
314      */
315     public void destroySubcontext( String name ) throws NamingException
316     {
317         destroySubcontext( new LdapName( name ) );
318     }
319 
320 
321     /***
322      * @see javax.naming.Context#destroySubcontext(javax.naming.Name)
323      */
324     public void destroySubcontext( Name name ) throws NamingException
325     {
326         Name target = buildTarget( name );
327 
328         if ( target.size() == 0 )
329         {
330             throw new LdapNoPermissionException( "can't delete the rootDSE" );
331         }
332 
333         nexusProxy.delete( target );
334     }
335 
336 
337     /***
338      * @see javax.naming.Context#bind(java.lang.String, java.lang.Object)
339      */
340     public void bind( String name, Object obj ) throws NamingException
341     {
342         bind( new LdapName( name ), obj );
343     }
344     
345 
346     /***
347      * @see javax.naming.Context#bind(javax.naming.Name, java.lang.Object)
348      */
349     public void bind( Name name, Object obj ) throws NamingException
350     {
351         // First, use state factories to do a transformation
352         DirStateFactory.Result res = DirectoryManager.getStateToBind( obj, name, this, env, null );
353 
354         Attributes outAttrs = res.getAttributes();
355 
356         if ( outAttrs != null )
357         {
358             Name target = buildTarget( name );
359 
360             nexusProxy.add( target.toString(), target, outAttrs );
361 
362             return;
363         }
364 
365         // Check for Referenceable
366         if ( obj instanceof Referenceable )
367         {
368             obj = ( ( Referenceable ) obj ).getReference();
369 
370             throw new NamingException( "Do not know how to store Referenceables yet!" );
371         }
372 
373         // Store different formats
374         if ( obj instanceof Reference )
375         {
376             // Store as ref and add outAttrs
377 
378             throw new NamingException( "Do not know how to store References yet!" );
379         }
380         else if ( obj instanceof Serializable )
381         {
382             // Serialize and add outAttrs
383 
384             Attributes attributes = new LockableAttributesImpl();
385 
386             if ( outAttrs != null && outAttrs.size() > 0 )
387             {
388                 NamingEnumeration list = outAttrs.getAll();
389 
390                 while ( list.hasMore() )
391                 {
392                     attributes.put( ( Attribute ) list.next() );
393                 }
394             }
395 
396             Name target = buildTarget( name );
397 
398             // Serialize object into entry attributes and add it.
399 
400             JavaLdapSupport.serialize( attributes, obj );
401 
402             nexusProxy.add( target.toString(), target, attributes );
403         }
404         else if ( obj instanceof DirContext )
405         {
406             // Grab attributes and merge with outAttrs
407 
408             Attributes attributes = ( ( DirContext ) obj ).getAttributes( "" );
409 
410             if ( outAttrs != null && outAttrs.size() > 0 )
411             {
412                 NamingEnumeration list = outAttrs.getAll();
413 
414                 while ( list.hasMore() )
415                 {
416                     attributes.put( ( Attribute ) list.next() );
417                 }
418             }
419 
420             Name target = buildTarget( name );
421 
422             nexusProxy.add( target.toString(), target, attributes );
423         }
424         else
425         {
426             throw new NamingException( "Can't find a way to bind: " + obj );
427         }
428     }
429 
430 
431     /***
432      * @see javax.naming.Context#rename(java.lang.String, java.lang.String)
433      */
434     public void rename( String oldName, String newName ) throws NamingException
435     {
436         rename( new LdapName( oldName ), new LdapName( newName ) );
437     }
438 
439 
440     /***
441      * @see javax.naming.Context#rename(javax.naming.Name, javax.naming.Name)
442      */
443     public void rename( Name oldName, Name newName ) throws NamingException
444     {
445         Name oldDn = buildTarget( oldName );
446 
447         Name newDn = buildTarget( newName );
448 
449         if ( oldDn.size() == 0 )
450         {
451             throw new LdapNoPermissionException( "can't rename the rootDSE" );
452         }
453 
454         Name oldBase = oldName.getSuffix( 1 );
455 
456         Name newBase = newName.getSuffix( 1 );
457 
458         String newRdn = newName.get( newName.size() - 1 );
459 
460         String oldRdn = oldName.get( oldName.size() - 1 );
461                 
462         boolean delOldRdn = true;
463             
464         /*
465          * Attempt to use the java.naming.ldap.deleteRDN environment property
466          * to get an override for the deleteOldRdn option to modifyRdn.  
467          */
468         if ( null != env.get( DELETE_OLD_RDN_PROP ) )
469         {
470             String delOldRdnStr = ( String ) env.get( DELETE_OLD_RDN_PROP );
471 
472             delOldRdn = ! delOldRdnStr.equals( "false" );
473 
474             delOldRdn = delOldRdn || delOldRdnStr.equals( "no" );
475 
476             delOldRdn = delOldRdn || delOldRdnStr.equals( "0" );
477         }
478 
479         /*
480          * We need to determine if this rename operation corresponds to a simple
481          * RDN name change or a move operation.  If the two names are the same
482          * except for the RDN then it is a simple modifyRdn operation.  If the
483          * names differ in size or have a different baseDN then the operation is
484          * a move operation.  Furthermore if the RDN in the move operation 
485          * changes it is both an RDN change and a move operation.
486          */
487         if ( oldName.size() == newName.size() && oldBase.equals( newBase ) )
488         {
489             nexusProxy.modifyRn( oldDn, newRdn, delOldRdn );
490         }
491         else
492         {
493             Name parent = newDn.getSuffix( 1 );
494             
495             if ( newRdn.equalsIgnoreCase( oldRdn ) )
496             {
497                 nexusProxy.move( oldDn, parent );
498             }
499             else
500             {
501                 nexusProxy.move( oldDn, parent, newRdn, delOldRdn );
502             }
503         }
504     }
505 
506 
507     /***
508      * @see javax.naming.Context#rebind(java.lang.String, java.lang.Object)
509      */
510     public void rebind( String name, Object obj ) throws NamingException
511     {
512         rebind( new LdapName( name ), obj );
513     }
514 
515 
516     /***
517      * @see javax.naming.Context#rebind(javax.naming.Name, java.lang.Object)
518      */
519     public void rebind( Name name, Object obj ) throws NamingException
520     {
521         Name target = buildTarget( name );
522 
523         if ( nexusProxy.hasEntry( target ) )
524         {
525             nexusProxy.delete( target );
526         }
527 
528         bind( name, obj );
529     }
530 
531 
532     /***
533      * @see javax.naming.Context#unbind(java.lang.String)
534      */
535     public void unbind( String name ) throws NamingException
536     {
537         unbind( new LdapName( name ) );
538     }
539 
540 
541     /***
542      * @see javax.naming.Context#unbind(javax.naming.Name)
543      */
544     public void unbind( Name name ) throws NamingException
545     {
546         nexusProxy.delete( buildTarget( name ) );
547     }
548 
549 
550     /***
551      * @see javax.naming.Context#lookup(java.lang.String)
552      */
553     public Object lookup( String name ) throws NamingException
554     {
555         return lookup( new LdapName( name ) );
556     }
557 
558 
559     /***
560      * @see javax.naming.Context#lookup(javax.naming.Name)
561      */
562     public Object lookup( Name name ) throws NamingException
563     {
564         Object obj = null;
565 
566         LdapName target = buildTarget( name );
567 
568         Attributes attributes = nexusProxy.lookup( target );
569 
570         try
571         {
572             obj = DirectoryManager.getObjectInstance( null, name, this, env, attributes );
573         }
574         catch ( Exception e )
575         {
576             throw new NamingException( e.getMessage() );
577         }
578 
579         if ( obj != null )
580         {
581             return obj;
582         }
583 
584         // First lets test and see if the entry is a serialized java object
585         if ( attributes.get( JavaLdapSupport.JCLASSNAME_ATTR ) != null )
586         {
587             // Give back serialized object and not a context
588             return JavaLdapSupport.deserialize( attributes );
589         }
590         
591         // Initialize and return a context since the entry is not a java object
592         ServerLdapContext ctx = new ServerLdapContext( principal, nexusProxy, env, target );
593             
594         // Need to add controls to propagate extended ldap operational env
595         Control [] controls = ( ( ServerLdapContext ) this ).getRequestControls();
596 
597         if ( null != controls )
598         {    
599             ctx.setRequestControls( ( Control [] ) controls.clone() );
600         }
601         
602         return ctx;
603     }
604 
605 
606     /***
607      * @see javax.naming.Context#lookupLink(java.lang.String)
608      */
609     public Object lookupLink( String name ) throws NamingException
610     {
611         throw new UnsupportedOperationException();
612     }
613 
614 
615     /***
616      * @see javax.naming.Context#lookupLink(javax.naming.Name)
617      */
618     public Object lookupLink( Name name ) throws NamingException
619     {
620         throw new UnsupportedOperationException();
621     }
622 
623 
624     /***
625      * Non-federated implementation presuming the name argument is not a 
626      * composite name spanning multiple namespaces but a compound name in 
627      * the same LDAP namespace.  Hence the parser returned is always the
628      * same as calling this method with the empty String.
629      * 
630      * @see javax.naming.Context#getNameParser(java.lang.String)
631      */
632     public NameParser getNameParser( String name ) throws NamingException
633     {
634         return LdapName.getNameParser();
635     }
636 
637 
638     /***
639      * Non-federated implementation presuming the name argument is not a 
640      * composite name spanning multiple namespaces but a compound name in 
641      * the same LDAP namespace.  Hence the parser returned is always the
642      * same as calling this method with the empty String Name.
643      * 
644      * @see javax.naming.Context#getNameParser(javax.naming.Name)
645      */
646     public NameParser getNameParser( Name name ) throws NamingException
647     {
648         return LdapName.getNameParser();
649     }
650 
651 
652     /***
653      * @see javax.naming.Context#list(java.lang.String)
654      */
655     public NamingEnumeration list( String name ) throws NamingException
656     {
657         return list( new LdapName( name ) );
658     }
659 
660 
661     /***
662      * @see javax.naming.Context#list(javax.naming.Name)
663      */
664     public NamingEnumeration list( Name name ) throws NamingException
665     {
666         return nexusProxy.list( buildTarget( name ) );
667     }
668 
669 
670     /***
671      * @see javax.naming.Context#listBindings(java.lang.String)
672      */
673     public NamingEnumeration listBindings( String name ) throws NamingException
674     {
675         return listBindings( new LdapName( name ) );
676     }
677 
678 
679     /***
680      * @see javax.naming.Context#listBindings(javax.naming.Name)
681      */
682     public NamingEnumeration listBindings( Name name ) throws NamingException
683     {
684         // Conduct a special one level search at base for all objects
685         Name base = buildTarget( name );
686 
687         PresenceNode filter = new PresenceNode( "objectClass" );
688 
689         SearchControls ctls = new SearchControls();
690 
691         ctls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
692 
693         return nexusProxy.search( base , getEnvironment(), filter, ctls );
694     }
695 
696 
697     /***
698      * @see javax.naming.Context#composeName(java.lang.String, java.lang.String)
699      */
700     public String composeName( String name, String prefix ) throws NamingException
701     {
702         return composeName( new LdapName( name ), new LdapName( prefix ) ).toString();
703     }
704 
705 
706     /***
707      * @see javax.naming.Context#composeName(javax.naming.Name,
708      * javax.naming.Name)
709      */
710     public Name composeName( Name name, Name prefix ) throws NamingException
711     {
712         // No prefix reduces to name, or the name relative to this context
713         if ( prefix == null || prefix.size() == 0 )
714         {
715             return name;
716         }
717 
718         /*
719          * Example: This context is ou=people and say name is the relative
720          * name of uid=jwalker and the prefix is dc=domain.  Then we must
721          * compose the name relative to prefix which would be:
722          * 
723          * uid=jwalker,ou=people,dc=domain.
724          * 
725          * The following general algorithm generates the right name:
726          *      1). Find the Dn for name and walk it from the head to tail
727          *          trying to match for the head of prefix.
728          *      2). Remove name components from the Dn until a match for the 
729          *          head of the prefix is found.
730          *      3). Return the remainder of the fqn or Dn after chewing off some
731          */
732          
733         // 1). Find the Dn for name and walk it from the head to tail
734         Name fqn = buildTarget( name );
735 
736         String head = prefix.get( 0 );
737         
738         // 2). Walk the fqn trying to match for the head of the prefix
739         while ( fqn.size() > 0 )
740         {
741             // match found end loop
742             if ( fqn.get( 0 ).equalsIgnoreCase( head ) )
743             {
744                 return fqn;
745             }
746             else // 2). Remove name components from the Dn until a match 
747             {
748                 fqn.remove( 0 );
749             }
750         }
751 
752         String msg = "The prefix '" + prefix + "' is not an ancestor of this ";
753 
754         msg += "entry '" + dn + "'";
755 
756         throw new NamingException( msg );
757     }
758     
759     
760     // ------------------------------------------------------------------------
761     // Utility Methods to Reduce Code
762     // ------------------------------------------------------------------------
763     
764     
765     /***
766      * Clones this context's DN and adds the components of the name relative to 
767      * this context to the left hand side of this context's cloned DN. 
768      * 
769      * @param relativeName a name relative to this context.
770      * @return the name of the target
771      * @throws InvalidNameException if relativeName is not a valid name in
772      *      the LDAP namespace.
773      */
774     LdapName buildTarget( Name relativeName ) throws InvalidNameException
775     {
776         // Clone our DN or absolute path
777         LdapName target = ( LdapName ) dn.clone();
778         
779         // Add to left hand side of cloned DN the relative name arg
780         target.addAll( target.size(), relativeName );
781 
782         return target;
783     }
784 }