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;
21  
22  import java.lang.reflect.Constructor;
23  import java.util.HashMap;
24  import java.util.Properties;
25  import java.util.Timer;
26  
27  import no.feide.moria.directory.backend.AuthenticationFailedException;
28  import no.feide.moria.directory.backend.BackendException;
29  import no.feide.moria.directory.backend.DirectoryManagerBackend;
30  import no.feide.moria.directory.backend.DirectoryManagerBackendFactory;
31  import no.feide.moria.directory.index.DirectoryManagerIndex;
32  import no.feide.moria.directory.index.IndexedReference;
33  import no.feide.moria.log.MessageLogger;
34  
35  /***
36   * The Directory Manager (sometimes referred to as DM) component in Moria 2.
37   * Responsible for all backend operations, such as user authentication and
38   * attribute retrieval from the backend sources.
39   */
40  public class DirectoryManager {
41  
42      /*** The message logger. */
43      private final MessageLogger log = new MessageLogger(DirectoryManager.class);
44  
45      /*** Internal representation of the index. */
46      private DirectoryManagerIndex index = null;
47  
48      /***
49       * This timer uses <code>IndexUpdater</code> to periodically call
50       * <code>updateIndex()</code>.
51       */
52      private Timer indexTimer = null;
53  
54      /*** Internal representation of the backend factory. */
55      private DirectoryManagerBackendFactory backendFactory = null;
56  
57      /*** The currently used (valid) Directory Manager configuration. */
58      private DirectoryManagerConfiguration configuration = null;
59  
60  
61      /***
62       * Destructor. Will call <code>stop()</code>.
63       * @see DirectoryManager#stop()
64       */
65      public final void destroy() {
66  
67          stop();
68  
69      }
70  
71  
72      /***
73       * Sets or updates the Directory Manager's configuration. The first time
74       * this method is used, it will force an initial index update by reading the
75       * index through <code>IndexUpdater.readIndex()</code>.
76       * @param config
77       *            The configuration. The actual parsing is done by the
78       *            <code>DirectoryManagerConfiguration</code> constructor.
79       * @throws DirectoryManagerConfigurationException
80       *             If unable create a new configuration object from config or
81       *             config is null, and also not able to fall back to a previous
82       *             working configuration to fall back to (in which case a
83       *             warning will be logged instead). Also thrown if unable to
84       *             resolve the backend factory class (as specified in the
85       *             configuration file) or if unable to instantiate this class.
86       * @see DirectoryManagerConfiguration#DirectoryManagerConfiguration(Properties)
87       * @see IndexUpdater#readIndex()
88       */
89      public final void setConfig(final Properties config) {
90  
91          // Update current configuration.
92          try {
93  
94              final DirectoryManagerConfiguration newConfiguration = new DirectoryManagerConfiguration(config);
95              configuration = newConfiguration;
96  
97          } catch (Exception e) {
98  
99              // Something happened while updating the configuration; can we
100             // recover?
101             if (configuration == null) {
102 
103                 // Critical error; we don't have a working configuration.
104                 throw new DirectoryManagerConfigurationException("Unable to set initial configuration", e);
105 
106             }
107 
108             // Non-critical error; we still have a working configuration.
109             log.logWarn("Unable to update existing configuration", e);
110 
111 
112         }
113 
114         // Update the index and (re-)start the index update timer.
115         IndexUpdater indexUpdater = new IndexUpdater(this, configuration.getIndexFilename());
116         if (indexTimer == null) {
117 
118             // The first time we set the configuration we manually force an
119             // index update to ensure we have a working index.
120             indexTimer = new Timer(true); // Daemon.
121             updateIndex(indexUpdater.readIndex());
122 
123         }
124         indexTimer.scheduleAtFixedRate(indexUpdater, configuration.getIndexUpdateFrequency(), configuration.getIndexUpdateFrequency());
125 
126         // Set the backend factory class and set its configuration.
127         Constructor constructor = null;
128         try {
129 
130             constructor = configuration.getBackendFactoryClass().getConstructor(null);
131             backendFactory = (DirectoryManagerBackendFactory) constructor.newInstance(null);
132 
133         } catch (NoSuchMethodException e) {
134             log.logCritical("Cannot find backend factory constructor", e);
135             throw new DirectoryManagerConfigurationException("Cannot find backend factory constructor", e);
136         } catch (Exception e) {
137             log.logCritical("Unable to instantiate backend factory object", e);
138             throw new DirectoryManagerConfigurationException("Unable to instantiate backend factory object", e);
139         }
140 
141         // Set backend configuration.
142         backendFactory.setConfig(configuration.getBackendElement());
143 
144     }
145 
146 
147     /***
148      * Sets or updates the internal index structure. Used by
149      * <code>IndexUpdater.run()</code> to periodically update the index.
150      * @param newIndex
151      *            The new index object. A <code>null</code> value is taken to
152      *            indicate that the index should <em>not</em> be updated.
153      * @throws DirectoryManagerConfigurationException
154      *             If <code>newIndex</code> is <code>null</code> and the
155      *             index has not been previously set.
156      * @see IndexUpdater#run()
157      */
158     protected final synchronized void updateIndex(final DirectoryManagerIndex newIndex) {
159 
160         // Sanity check.
161         if ((newIndex == null) && (index == null))
162             throw new DirectoryManagerConfigurationException("Unable to initialize index; aborting");
163 
164         // Update existing index.
165         if (newIndex != null)
166             index = newIndex;
167 
168     }
169 
170 
171     /***
172      * Checks that a user actually exists by querying the underlying backend.
173      * @param sessionTicket
174      *            Passed on to instances of <code>DirectoryManagerBackend</code>,
175      *            for logging purposes. May be <code>null</code> or an empty
176      *            string.
177      * @param username
178      *            The username to look up.
179      * @return <code>true</code> if the user element corresponding to the
180      *         username actually exists, otherwise <code>false</code>.
181      * @throws BackendException
182      *             A subclass of <code>BackendException</code> is thrown if
183      *             there was a problem accessing the backend.
184      * @throws IllegalStateException
185      *             If attempting to use this method without successfully using
186      *             <code>setConfig(Properties)</code> first.
187      * @see DirectoryManagerBackend#userExists(String)
188      */
189     public final boolean userExists(final String sessionTicket,
190                                     final String username) throws BackendException {
191 
192         // Sanity check.
193         if (configuration == null)
194             throw new IllegalStateException("Configuration not set");
195 
196         // Do the call through a temporary backend instance.
197         DirectoryManagerBackend backend = backendFactory.createBackend(sessionTicket);
198         IndexedReference[] references = index.getReferences(username);
199         try {
200             if (references != null) {
201 
202                 // Found at least one reference.
203                 backend.open(references);
204 
205             } else {
206 
207                 // Could not find the user.
208                 return false;
209 
210             }
211 
212             // Check that the user actually exists.
213             return backend.userExists(username);
214         } finally {
215 
216             // Close the backend.
217             backend.close();
218 
219         }
220     }
221 
222 
223     /***
224      * Forwards an authentication attempt to the underlying backend.
225      * @param sessionTicket
226      *            Passed on to instances of <code>DirectoryManagerBackend</code>,
227      *            for logging purposes. May be <code>null</code> or an empty
228      *            string.
229      * @param userCredentials
230      *            The user credentials passed on for authentication.
231      * @param attributeRequest
232      *            An array containing the attribute names requested for
233      *            retrieval after successful authentication.
234      * @return The user attributes matching the attribute request, if those were
235      *         available. The keys will be <code>String</code> objects, while
236      *         the values will be <code>String</code> arrays containing one or
237      *         more attribute values. Note that if any of the requested
238      *         attributes could not be retrieved from the backend following a
239      *         successful authentication (for example, if they simply do not
240      *         exist in the backend in question), the <code>HashMap</code>
241      *         will still include those attributes that <em>could</em> be
242      *         retrieved. If no attributes were requested, or if no attributes
243      *         were retrievable from the backend, an empty <code>HashMap</code>
244      *         will be returned. This still indicates a successful
245      *         authentication.
246      * @throws BackendException
247      *             A subclass of <code>BackendException</code> is thrown if
248      *             there was a problem accessing the backend.
249      * @throws AuthenticationFailedException
250      *             If we managed to access the backend, and the authentication
251      *             failed. In other words, the user credentials are incorrect.
252      *             Also thrown if the user credentials are <code>null</code>.
253      * @throws IllegalStateException
254      *             If attempting to use this method without successfully using
255      *             <code>setConfig(Properties)</code> first.
256      * @see #setConfig(Properties)
257      * @see DirectoryManagerBackend#authenticate(Credentials, String[])
258      */
259     public final HashMap authenticate(final String sessionTicket,
260                                       final Credentials userCredentials,
261                                       final String[] attributeRequest) throws AuthenticationFailedException,
262                                                                       BackendException,
263                                                                       IllegalStateException {
264 
265         // Sanity checks.
266         if (configuration == null)
267             throw new IllegalStateException("Configuration not set");
268         if (index == null)
269             throw new IllegalStateException("Index has not been initialized");
270         if (userCredentials == null)
271             throw new AuthenticationFailedException("User credentials cannot be NULL");
272 
273         // Do the call through a temporary backend instance.
274         DirectoryManagerBackend backend = backendFactory.createBackend(sessionTicket);
275         IndexedReference[] references = index.getReferences(userCredentials.getUsername());
276         if (references != null) {
277 
278             // Found at least one reference.
279             backend.open(references);
280 
281         } else {
282 
283             // Could not locate the user in the index.
284             throw new AuthenticationFailedException("User " + userCredentials.getUsername() + " is unknown");
285 
286         }
287 
288         // Authenticate the user.
289         HashMap attributes = backend.authenticate(userCredentials, attributeRequest);
290 
291         // Close the backend and return any attributes.
292         backend.close();
293         return attributes;
294 
295     }
296 
297 
298     /***
299      * Resolves the realm of a given username. Even for usernames on the form
300      * <i>user@realm </i> this method should be used, since it is possible to
301      * retain such a username even when changing one's realm or home
302      * organization.
303      * @param username
304      *            The username to check.
305      * @return The realm, or <code>null</code> if no such realm can be
306      *         resolved.
307      * @see DirectoryManagerIndex#getRealm(String)
308      */
309     public final String getRealm(final String username) {
310 
311         return index.getRealm(username);
312 
313     }
314 
315 
316     /***
317      * Stops the Directory Manager. <br>
318      * <br>
319      * Will stop the index updater thread. Note that the Directory Manager may
320      * be used after <code>stop()</code>, but this is discouraged.
321      */
322     public final void stop() {
323 
324         // Stop the index update timer, if it has been initialized.
325         if (indexTimer != null)
326             indexTimer.cancel();
327 
328     }
329 
330 }