View Javadoc

1   /*
2    * Copyright (c) 2004 UNINETT FAS
3    *
4    * This program is free software; you can redistribute it and/or modify it
5    * under the terms of the GNU General Public License as published by the Free
6    * Software Foundation; either version 2 of the License, or (at your option)
7    * any later version.
8    *
9    * This program is distributed in the hope that it will be useful, but WITHOUT
10   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11   * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12   * more details.
13   *
14   * You should have received a copy of the GNU General Public License along with
15   * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
16   * Place - Suite 330, Boston, MA 02111-1307, USA.
17   *
18   */
19  
20  package no.feide.moria.directory.backend;
21  
22  import java.io.File;
23  import java.security.Security;
24  
25  import no.feide.moria.directory.DirectoryManagerConfigurationException;
26  
27  import org.jdom.Element;
28  
29  /***
30   * Factory class for JNDI backends.
31   */
32  public class JNDIBackendFactory
33  implements DirectoryManagerBackendFactory {
34  
35      /***
36       * The number of seconds before a backend connection times out. Default is
37       * 15 seconds.
38       */
39      private int backendTimeouts = 15;
40  
41      /***
42       * Whether the backend should use SSL. Default is <code>false</code>.
43       */
44      private boolean useSSL = false;
45  
46      /*** The name of the attribute containing the username. */
47      private String usernameAttribute;
48  
49      /***
50       * The name of the attribute used to guess the user element's (R)DN, if it
51       * cannot be found by searching.
52       */
53      private String guessedAttribute;
54  
55  
56      /***
57       * Sets the factory-specific configuration. Must be called before creating a
58       * new backend, or the backend will not work as intended. <br>
59       * <br>
60       * Note that using this method with an updated configuration that modifies
61       * any JVM global settings (currently all settings in the
62       * <code>Security</code> element) is likely to cause any open backend
63       * connections to fail.
64       * @param config
65       *            The configuration for this backend factory. The root node must
66       *            be a <code>JNDI</code> element, containing an optional
67       *            <code>Security</code> element, which in turn may contain an
68       *            optional <code>Truststore</code> element. If the
69       *            <code>Truststore</code> exists, it must contain the
70       *            attributes <code>filename</code> and <code>password</code>,
71       *            giving the truststore file location and password,
72       *            respectively. The <code>JNDI</code> element may contain an
73       *            optional attribute <code>timeout</code>, which gives the
74       *            number of seconds before a backend connection should time out.
75       *            If this value is a negative number, the timeout value will be
76       *            set to zero (meaning the connection will never time out).
77       *            Also, the <code>JNDI</code> element may contain an attribute
78       *            <code>guessedAttribute</code>, which should give the name
79       *            of an attribute used to construct "guessed" user element
80       *            (R)DNs if the actual element cannot be found by searching (the
81       *            default value is <code>uid</code>). Finally the
82       *            <code>JNDI</code> element must contain an attribute
83       *            <code>usernameAttribute</code>, which should give the name
84       *            of the attribute holding the username.
85       * @throws IllegalArgumentException
86       *             If <code>config</code> is null.
87       * @throws DirectoryManagerConfigurationException
88       *             If the configuration element is not a <code>JNDI</code>
89       *             <code>Backend</code>
90       *             element, or if the optional <code>Truststore</code> element
91       *             is found, but without either of the <code>filename</code>
92       *             or <code>password</code> attributes. Also thrown if the
93       *             <code>timeout</code> attribute contains an illegal timeout
94       *             value, if the <code>username</code> attribute does not
95       *             exist, if the <code>guess</code> attribute does not exist,
96       *             or if the <code>filename</code> file does not exist.
97       * @see DirectoryManagerBackendFactory#setConfig(Element)
98       */
99      public final synchronized void setConfig(final Element config) throws DirectoryManagerConfigurationException {
100 
101         // Sanity checks.
102         if (config == null)
103             throw new IllegalArgumentException("Configuration cannot be NULL");
104         if (!config.getName().equalsIgnoreCase("Backend"))
105             throw new DirectoryManagerConfigurationException("Cannot find backend configuration element");
106 
107         // Get JNDI element, with sanity check.
108         final Element jndiElement = config.getChild("JNDI");
109         if (jndiElement == null)
110             throw new DirectoryManagerConfigurationException("Cannot find JNDI configuration element");
111 
112         // Get optional timeout value.
113         String timeout = jndiElement.getAttributeValue("timeout");
114         if (timeout != null) {
115             try {
116                 backendTimeouts = Integer.parseInt(timeout);
117             } catch (NumberFormatException e) {
118                 throw new DirectoryManagerConfigurationException("\"" + timeout + "\" is not a legal timeout value", e);
119             }
120         }
121         if (backendTimeouts < 0)
122             backendTimeouts = 0;
123 
124         // Get username attribute and guessed attribute.
125         usernameAttribute = jndiElement.getAttributeValue("usernameAttribute");
126         if (usernameAttribute == null)
127             throw new DirectoryManagerConfigurationException("Attribute \"usernameAttribute\" not found in JNDI element");
128         guessedAttribute = jndiElement.getAttributeValue("guessedAttribute", "uid");
129 
130         // TODO: Add support for javax.net.ssl.keystore.
131 
132         // Get the optional Security element.
133         final Element securityElement = jndiElement.getChild("Security");
134         if (securityElement != null) {
135 
136             // Get the optional Truststore element.
137             final Element trustStoreElement = securityElement.getChild("Truststore");
138             if (trustStoreElement != null) {
139 
140                 // Get truststore filename and check that it exists.
141                 String value = trustStoreElement.getAttributeValue("filename");
142                 if (value == null)
143                     throw new DirectoryManagerConfigurationException("Attribute \"filename\" not found in Truststore element");
144                 if (!(new File(value).exists()))
145                     throw new DirectoryManagerConfigurationException("Truststore file " + value + " does not exist");
146 
147                 // Explicitly set com.sun.net.ssl.internal.ssl.Provider...
148                 com.sun.net.ssl.internal.ssl.Provider.install();
149                 Security.insertProviderAt(new com.sun.net.ssl.internal.ssl.Provider(), 1);
150                 System.setProperty("java.protocol.handler.pkgs", "javax.net.ssl");
151 
152                 // Get and set truststore filename.
153                 System.setProperty("javax.net.ssl.trustStore", value);
154 
155                 // Get and set truststore password.
156                 value = trustStoreElement.getAttributeValue("password");
157                 if (value == null)
158                     throw new DirectoryManagerConfigurationException("Attribute \"password\" not found in Truststore element");
159                 System.setProperty("javax.net.ssl.trustStorePassword", value);
160 
161                 // Now we're ready to use SSL.
162                 Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
163                 useSSL = true;
164 
165             }
166 
167         }
168 
169     }
170 
171 
172     /***
173      * Creates a new <code>JNDIBackend</code> instance.
174      * @param sessionTicket
175      *            The session ticket passed on to instances of
176      *            <code>DirectoryManagerBackend</code> (actually
177      *            <code>JNDIBackend</code> instances) for logging purposes.
178      *            May be <code>null</code> or an empty string.
179      * @return The new JNDIBackend.
180      * @see DirectoryManagerBackendFactory#createBackend(String)
181      */
182     public final synchronized DirectoryManagerBackend createBackend(final String sessionTicket) {
183 
184         return new JNDIBackend(sessionTicket, backendTimeouts, useSSL, usernameAttribute, guessedAttribute);
185 
186     }
187 
188 }