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