1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
92 try {
93
94 final DirectoryManagerConfiguration newConfiguration = new DirectoryManagerConfiguration(config);
95 configuration = newConfiguration;
96
97 } catch (Exception e) {
98
99
100
101 if (configuration == null) {
102
103
104 throw new DirectoryManagerConfigurationException("Unable to set initial configuration", e);
105
106 }
107
108
109 log.logWarn("Unable to update existing configuration", e);
110
111
112 }
113
114
115 IndexUpdater indexUpdater = new IndexUpdater(this, configuration.getIndexFilename());
116 if (indexTimer == null) {
117
118
119
120 indexTimer = new Timer(true);
121 updateIndex(indexUpdater.readIndex());
122
123 }
124 indexTimer.scheduleAtFixedRate(indexUpdater, configuration.getIndexUpdateFrequency(), configuration.getIndexUpdateFrequency());
125
126
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
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
161 if ((newIndex == null) && (index == null))
162 throw new DirectoryManagerConfigurationException("Unable to initialize index; aborting");
163
164
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
193 if (configuration == null)
194 throw new IllegalStateException("Configuration not set");
195
196
197 DirectoryManagerBackend backend = backendFactory.createBackend(sessionTicket);
198 IndexedReference[] references = index.getReferences(username);
199 try {
200 if (references != null) {
201
202
203 backend.open(references);
204
205 } else {
206
207
208 return false;
209
210 }
211
212
213 return backend.userExists(username);
214 } finally {
215
216
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
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
274 DirectoryManagerBackend backend = backendFactory.createBackend(sessionTicket);
275 IndexedReference[] references = index.getReferences(userCredentials.getUsername());
276 if (references != null) {
277
278
279 backend.open(references);
280
281 } else {
282
283
284 throw new AuthenticationFailedException("User " + userCredentials.getUsername() + " is unknown");
285
286 }
287
288
289 HashMap attributes = backend.authenticate(userCredentials, attributeRequest);
290
291
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
325 if (indexTimer != null)
326 indexTimer.cancel();
327
328 }
329
330 }