001 /*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License"). You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at
010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012 * See the License for the specific language governing permissions
013 * and limitations under the License.
014 *
015 * When distributing Covered Code, include this CDDL HEADER in each
016 * file and include the License file at
017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
018 * add the following below this CDDL HEADER, with the fields enclosed
019 * by brackets "[]" replaced with your own identifying information:
020 * Portions Copyright [yyyy] [name of copyright owner]
021 *
022 * CDDL HEADER END
023 *
024 *
025 * Copyright 2006-2008 Sun Microsystems, Inc.
026 */
027 package org.opends.server.core;
028 import org.opends.messages.Message;
029
030
031
032 import static org.opends.server.loggers.ErrorLogger.*;
033 import static org.opends.server.loggers.debug.DebugLogger.*;
034 import static org.opends.messages.ConfigMessages.*;
035 import static org.opends.server.util.ServerConstants.*;
036 import static org.opends.server.util.StaticUtils.*;
037
038 import java.lang.reflect.Method;
039 import java.util.ArrayList;
040 import java.util.Iterator;
041 import java.util.LinkedHashMap;
042 import java.util.List;
043 import java.util.concurrent.atomic.AtomicReference;
044
045 import org.opends.server.admin.ClassPropertyDefinition;
046 import org.opends.server.admin.server.ConfigurationChangeListener;
047 import org.opends.server.admin.server.ServerManagementContext;
048 import org.opends.server.admin.std.meta.AccessControlHandlerCfgDefn;
049 import org.opends.server.admin.std.server.AccessControlHandlerCfg;
050 import org.opends.server.admin.std.server.RootCfg;
051 import org.opends.server.api.AccessControlHandler;
052 import org.opends.server.api.AlertGenerator;
053 import org.opends.server.config.ConfigException;
054 import org.opends.server.loggers.debug.DebugTracer;
055 import org.opends.server.types.ConfigChangeResult;
056 import org.opends.server.types.DebugLogLevel;
057 import org.opends.server.types.DN;
058 import org.opends.server.types.InitializationException;
059 import org.opends.server.types.ResultCode;
060
061
062
063 /**
064 * This class manages the application-wide access-control configuration.
065 * <p>
066 * When access control is disabled a default "permissive" access control
067 * implementation is used, which permits all operations regardless of the
068 * identity of the user.
069 */
070 public final class AccessControlConfigManager
071 implements AlertGenerator ,
072 ConfigurationChangeListener<AccessControlHandlerCfg>
073 {
074 /**
075 * The tracer object for the debug logger.
076 */
077 private static final DebugTracer TRACER = getTracer();
078
079 // Fully qualified class name.
080 private static final String CLASS_NAME =
081 "org.opends.server.core.AccessControlConfigManager";
082
083 // The single application-wide instance.
084 private static AccessControlConfigManager instance = null;
085
086 // The active access control implementation.
087 private AtomicReference<AccessControlHandler> accessControlHandler;
088
089 // The current configuration.
090 private AccessControlHandlerCfg currentConfiguration;
091
092
093
094 /**
095 * Creates a new instance of this access control configuration
096 * manager.
097 */
098 private AccessControlConfigManager()
099 {
100 this.accessControlHandler = new AtomicReference<AccessControlHandler>(
101 new DefaultAccessControlHandler());
102 this.currentConfiguration = null;
103 }
104
105
106
107 /**
108 * Get the single application-wide access control manager instance.
109 *
110 * @return The access control manager.
111 */
112 public static AccessControlConfigManager getInstance()
113 {
114 if (instance == null)
115 {
116 instance = new AccessControlConfigManager();
117 }
118
119 return instance;
120 }
121
122
123
124 /**
125 * Determine if access control is enabled according to the current
126 * configuration.
127 *
128 * @return {@code true} if access control is enabled, {@code false}
129 * otherwise.
130 */
131 public boolean isAccessControlEnabled()
132 {
133 return currentConfiguration.isEnabled();
134 }
135
136
137
138 /**
139 * Get the active access control handler.
140 * <p>
141 * When access control is disabled, this method returns a default access
142 * control implementation which permits all operations.
143 *
144 * @return The active access control handler (never {@code null}).
145 */
146 public AccessControlHandler getAccessControlHandler()
147 {
148 return accessControlHandler.get();
149 }
150
151
152
153 /**
154 * Initializes the access control sub-system. This should only be
155 * called at Directory Server startup. If an error occurs then an
156 * exception will be thrown and the Directory Server will fail to
157 * start (this prevents accidental exposure of user data due to
158 * misconfiguration).
159 *
160 * @throws ConfigException
161 * If an access control configuration error is detected.
162 * @throws InitializationException
163 * If a problem occurs while initializing the access control
164 * handler that is not related to the Directory Server
165 * configuration.
166 */
167 public void initializeAccessControl()
168 throws ConfigException, InitializationException
169 {
170 // Get the root configuration object.
171 ServerManagementContext managementContext =
172 ServerManagementContext.getInstance();
173 RootCfg rootConfiguration =
174 managementContext.getRootConfiguration();
175
176 // Don't register as an add and delete listener with the root configuration
177 // as we can have only one object at a given time.
178
179 // //Initialize the current Access control.
180 AccessControlHandlerCfg accessControlConfiguration =
181 rootConfiguration.getAccessControlHandler();
182
183 // We have a valid usable entry, so register a change listener in
184 // order to handle configuration changes.
185 accessControlConfiguration.addChangeListener(this);
186
187 //This makes TestCaseUtils.reStartServer happy.
188 currentConfiguration=null;
189
190 // The configuration looks valid, so install it.
191 updateConfiguration(accessControlConfiguration);
192 }
193
194
195
196 /**
197 * Updates the access control configuration based on the contents of a
198 * valid configuration entry.
199 *
200 * @param newConfiguration The new configuration object.
201 *
202 * @throws ConfigException If the access control configuration is invalid.
203 *
204 * @throws InitializationException If the access control handler provider
205 * could not be instantiated.
206 */
207
208 private void updateConfiguration(AccessControlHandlerCfg newConfiguration)
209 throws ConfigException, InitializationException
210 {
211 String newHandlerClass = null;
212 boolean enabledOld = false, enabledNew = newConfiguration.isEnabled();
213
214 if (currentConfiguration == null)
215 {
216 // Initialization phase.
217 if (enabledNew)
218 {
219 newHandlerClass = newConfiguration.getJavaClass();
220 }
221 else
222 {
223 newHandlerClass = DefaultAccessControlHandler.class.getName();
224 }
225 //Get a new handler, initialize it and make it the current handler.
226 accessControlHandler.getAndSet(getHandler(newHandlerClass,
227 newConfiguration, true, false));
228 } else {
229 enabledOld = currentConfiguration.isEnabled();
230 if(enabledNew) {
231 //Access control is either being enabled or a attribute in the
232 //configuration has changed such as class name or a global ACI.
233 newHandlerClass = newConfiguration.getJavaClass();
234 String oldHandlerClass = currentConfiguration.getJavaClass();
235 //Check if moving from not enabled to enabled state.
236 if(!enabledOld) {
237 AccessControlHandler oldHandler =
238 accessControlHandler.getAndSet(getHandler(newHandlerClass,
239 newConfiguration, true,
240 true));
241 oldHandler.finalizeAccessControlHandler();
242 } else {
243 //Check if the class name is being changed.
244 if(!newHandlerClass.equals(oldHandlerClass)) {
245 AccessControlHandler oldHandler =
246 accessControlHandler.getAndSet(getHandler(newHandlerClass,
247 newConfiguration, true, true));
248 oldHandler.finalizeAccessControlHandler();
249 } else {
250 //Some other attribute has changed, try to get a new non-initialized
251 //handler, but keep the old handler.
252 getHandler(newHandlerClass,newConfiguration, false, false);
253 }
254 }
255 } else if (enabledOld && (! enabledNew)) {
256 //Access control has been disabled, switch to the default handler and
257 //finalize the old handler.
258 newHandlerClass = DefaultAccessControlHandler.class.getName();
259 AccessControlHandler oldHandler =
260 accessControlHandler.getAndSet(getHandler(newHandlerClass,
261 newConfiguration, false, true));
262 oldHandler.finalizeAccessControlHandler();
263 }
264 }
265 // Switch in the local configuration.
266 currentConfiguration = newConfiguration;
267 }
268
269 /**
270 * Instantiates a new Access Control Handler using the specified class name,
271 * configuration.
272 *
273 * @param handlerClassName The name of the handler to instantiate.
274 * @param config The configuration to use when instantiating a new handler.
275 * @param initHandler <code>True</code> if the new handler should be
276 * initialized.
277 * @param logMessage <code>True</code> if an error message should be logged
278 * and an alert should be sent.
279 * @return The newly instantiated handler.
280 *
281 * @throws InitializationException If an error occurs instantiating the
282 * the new handler.
283 */
284 AccessControlHandler<? extends AccessControlHandlerCfg>
285 getHandler(String handlerClassName, AccessControlHandlerCfg config,
286 boolean initHandler, boolean logMessage)
287 throws InitializationException {
288 AccessControlHandler<? extends AccessControlHandlerCfg> newHandler;
289 try {
290 if(handlerClassName.equals(DefaultAccessControlHandler.class.getName())) {
291 newHandler = new DefaultAccessControlHandler();
292 newHandler.initializeAccessControlHandler(null);
293 if(logMessage) {
294 Message message = WARN_CONFIG_AUTHZ_DISABLED.get();
295 logError(message);
296 if (currentConfiguration != null) {
297 DirectoryServer.sendAlertNotification(this,
298 ALERT_TYPE_ACCESS_CONTROL_DISABLED, message);
299 }
300 }
301 } else {
302 newHandler = loadHandler(handlerClassName, config, initHandler);
303 if(logMessage) {
304 Message message = NOTE_CONFIG_AUTHZ_ENABLED.get(handlerClassName);
305 logError(message);
306 if (currentConfiguration != null) {
307 DirectoryServer.sendAlertNotification(this,
308 ALERT_TYPE_ACCESS_CONTROL_ENABLED, message);
309 }
310 }
311 }
312 } catch (Exception e) {
313 if (debugEnabled()) {
314 TRACER.debugCaught(DebugLogLevel.ERROR, e);
315 }
316 Message message = ERR_CONFIG_AUTHZ_UNABLE_TO_INSTANTIATE_HANDLER.
317 get(handlerClassName, String.valueOf(config.dn().toString()),
318 stackTraceToSingleLineString(e));
319 throw new InitializationException(message, e);
320 }
321 return newHandler;
322 }
323
324
325 /**
326 * {@inheritDoc}
327 */
328 public boolean isConfigurationChangeAcceptable(
329 AccessControlHandlerCfg configuration,
330 List<Message> unacceptableReasons)
331 {
332 try
333 {
334 // If the access control handler is disabled, we don't care about the
335 // configuration. If it is enabled, then all we care about is whether we
336 // can load the access control handler class.
337 if (configuration.isEnabled())
338 {
339 loadHandler(configuration.getJavaClass(), configuration, false);
340 }
341 }
342 catch (InitializationException e)
343 {
344 unacceptableReasons.add(e.getMessageObject());
345 return false;
346 }
347
348 return true;
349 }
350
351
352
353 /**
354 * {@inheritDoc}
355 */
356 public ConfigChangeResult applyConfigurationChange(
357 AccessControlHandlerCfg configuration)
358 {
359 ResultCode resultCode = ResultCode.SUCCESS;
360 ArrayList<Message> messages = new ArrayList<Message>();
361
362 try
363 {
364 // Attempt to install the new configuration.
365 updateConfiguration(configuration);
366 }
367 catch (ConfigException e)
368 {
369 messages.add(e.getMessageObject());
370 resultCode = ResultCode.CONSTRAINT_VIOLATION;
371 }
372 catch (InitializationException e)
373 {
374 messages.add(e.getMessageObject());
375 resultCode = DirectoryServer.getServerErrorResultCode();
376 }
377
378 return new ConfigChangeResult(resultCode, false, messages);
379 }
380
381
382
383 /**
384 * {@inheritDoc}
385 */
386 public DN getComponentEntryDN()
387 {
388 return currentConfiguration.dn();
389 }
390
391
392
393 /**
394 * {@inheritDoc}
395 */
396 public String getClassName()
397 {
398 return CLASS_NAME;
399 }
400
401
402
403 /**
404 * {@inheritDoc}
405 */
406 public LinkedHashMap<String,String> getAlerts()
407 {
408 LinkedHashMap<String,String> alerts = new LinkedHashMap<String,String>();
409
410 alerts.put(ALERT_TYPE_ACCESS_CONTROL_DISABLED,
411 ALERT_DESCRIPTION_ACCESS_CONTROL_DISABLED);
412 alerts.put(ALERT_TYPE_ACCESS_CONTROL_ENABLED,
413 ALERT_DESCRIPTION_ACCESS_CONTROL_ENABLED);
414
415 return alerts;
416 }
417
418
419
420 /**
421 * Loads the specified class, instantiates it as a AccessControlHandler, and
422 * optionally initializes that instance.
423 *
424 * @param className The fully-qualified name of the Access Control
425 * provider class to load, instantiate, and initialize.
426 * @param configuration The configuration to use to initialize the
427 * Access Control Handler. It must not be
428 * {@code null}.
429 * @param initialize Indicates whether the access control handler
430 * instance should be initialized.
431 *
432 * @return The possibly initialized Access Control Handler.
433 *
434 * @throws InitializationException If a problem occurred while attempting to
435 * initialize the Access Control Handler.
436 */
437 private AccessControlHandler<? extends AccessControlHandlerCfg>
438 loadHandler(String className,
439 AccessControlHandlerCfg configuration,
440 boolean initialize)
441 throws InitializationException
442 {
443 try
444 {
445 AccessControlHandlerCfgDefn definition =
446 AccessControlHandlerCfgDefn.getInstance();
447 ClassPropertyDefinition propertyDefinition =
448 definition.getJavaClassPropertyDefinition();
449 Class<? extends AccessControlHandler> providerClass =
450 propertyDefinition.loadClass(className, AccessControlHandler.class);
451 AccessControlHandler<? extends AccessControlHandlerCfg> provider =
452 (AccessControlHandler<? extends AccessControlHandlerCfg>)
453 providerClass.newInstance();
454
455 if (configuration != null)
456 {
457 Method method = provider.getClass().getMethod(
458 "initializeAccessControlHandler",
459 configuration.configurationClass());
460 if(initialize) {
461 method.invoke(provider, configuration);
462 }
463 }
464 else
465 {
466 Method method =
467 provider.getClass().getMethod("isConfigurationAcceptable",
468 AccessControlHandlerCfg.class,
469 List.class);
470
471 List<Message> unacceptableReasons = new ArrayList<Message>();
472 Boolean acceptable = (Boolean) method.invoke(provider, configuration,
473 unacceptableReasons);
474 if (! acceptable)
475 {
476 StringBuilder buffer = new StringBuilder();
477 if (! unacceptableReasons.isEmpty())
478 {
479 Iterator<Message> iterator = unacceptableReasons.iterator();
480 buffer.append(iterator.next());
481 while (iterator.hasNext())
482 {
483 buffer.append(". ");
484 buffer.append(iterator.next());
485 }
486 }
487
488 Message message = ERR_CONFIG_AUTHZ_CONFIG_NOT_ACCEPTABLE.get(
489 String.valueOf(configuration.dn()), buffer.toString());
490 throw new InitializationException(message);
491 }
492 }
493
494 return provider;
495 }
496 catch (Exception e)
497 {
498 Message message = ERR_CONFIG_AUTHZ_UNABLE_TO_INSTANTIATE_HANDLER.
499 get(className, String.valueOf(configuration.dn()),
500 stackTraceToSingleLineString(e));
501 throw new InitializationException(message, e);
502 }
503 }
504 }
505