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 2008 Sun Microsystems, Inc.
026 */
027
028 package org.opends.server.authorization.dseecompat;
029 import org.opends.messages.Message;
030
031 import org.opends.server.workflowelement.localbackend.*;
032 import org.opends.server.api.ChangeNotificationListener;
033 import org.opends.server.api.BackendInitializationListener;
034 import org.opends.server.api.Backend;
035 import org.opends.server.api.AlertGenerator;
036 import org.opends.server.types.operation.PostResponseAddOperation;
037 import org.opends.server.types.operation.PostResponseDeleteOperation;
038 import org.opends.server.types.operation.PostResponseModifyOperation;
039 import org.opends.server.types.operation.PostResponseModifyDNOperation;
040 import org.opends.server.protocols.internal.InternalClientConnection;
041 import org.opends.server.protocols.internal.InternalSearchOperation;
042 import static org.opends.server.loggers.ErrorLogger.logError;
043 import static org.opends.server.loggers.debug.DebugLogger.*;
044 import org.opends.server.loggers.debug.DebugTracer;
045 import org.opends.server.types.*;
046 import static org.opends.messages.AccessControlMessages.*;
047 import org.opends.server.core.DirectoryServer;
048 import static org.opends.server.util.ServerConstants.*;
049
050 import java.util.*;
051
052 /**
053 * The AciListenerManager updates an ACI list after each
054 * modification operation. Also, updates ACI list when backends are initialized
055 * and finalized.
056 */
057 public class AciListenerManager
058 implements ChangeNotificationListener, BackendInitializationListener,
059 AlertGenerator {
060 /**
061 * The tracer object for the debug logger.
062 */
063 private static final DebugTracer TRACER = getTracer();
064
065
066 /**
067 * The fully-qualified name of this class.
068 */
069 private static final String CLASS_NAME =
070 "org.opends.server.authorization.dseecompat.AciListenerManager";
071
072 /*
073 * The configuration DN.
074 */
075 private DN configurationDN;
076
077
078 /*
079 * True if the server is in lockdown mode.
080 */
081 private boolean inLockDownMode=false;
082
083 /*
084 * The AciList caches the ACIs.
085 */
086 private AciList aciList;
087
088 /*
089 * Search filter used in context search for "aci" attribute types.
090 */
091 private static SearchFilter aciFilter;
092
093 /*
094 * The aci attribute type is operational so we need to specify it to be
095 * returned.
096 */
097 private static LinkedHashSet<String> attrs = new LinkedHashSet<String>();
098
099 static {
100 /*
101 * Set up the filter used to search private and public contexts.
102 */
103 try {
104 aciFilter=SearchFilter.createFilterFromString("(aci=*)");
105 } catch (DirectoryException ex) {
106 //TODO should never happen, error message?
107 }
108 attrs.add("aci");
109 }
110
111 /**
112 * Save the list created by the AciHandler routine. Registers as an
113 * Alert Generator that can send alerts when the server is being put
114 * in lockdown mode. Registers as backend initialization listener that is
115 * used to manage the ACI list cache when backends are
116 * initialized/finalized. Registers as a change notification listener that
117 * is used to manage the ACI list cache after ACI modifications have been
118 * performed.
119 *
120 * @param aciList The list object created and loaded by the handler.
121 * @param cfgDN The DN of the access control configuration entry.
122 */
123 public AciListenerManager(AciList aciList, DN cfgDN) {
124 this.aciList=aciList;
125 this.configurationDN=cfgDN;
126 DirectoryServer.registerChangeNotificationListener(this);
127 DirectoryServer.registerBackendInitializationListener(this);
128 DirectoryServer.registerAlertGenerator(this);
129 }
130
131 /**
132 * Deregister from the change notification listener, the backend
133 * initialization listener and the alert generator.
134 */
135 public void finalizeListenerManager() {
136 DirectoryServer.deregisterChangeNotificationListener(this);
137 DirectoryServer.deregisterBackendInitializationListener(this);
138 DirectoryServer.deregisterAlertGenerator(this);
139 }
140
141
142 /**
143 * A delete operation succeeded. Remove any ACIs associated with the
144 * entry deleted.
145 * @param deleteOperation The delete operation.
146 * @param entry The entry being deleted.
147 */
148 public void handleDeleteOperation(PostResponseDeleteOperation
149 deleteOperation, Entry entry) {
150 boolean hasAci, hasGlobalAci=false;
151 //This entry might have both global and aci attribute types.
152 if((hasAci=entry.hasOperationalAttribute(AciHandler.aciType)) ||
153 (hasGlobalAci=entry.hasAttribute(AciHandler.globalAciType)))
154 aciList.removeAci(entry, hasAci, hasGlobalAci);
155 }
156
157 /**
158 * An Add operation succeeded. Add any ACIs associated with the
159 * entry being added.
160 * @param addOperation The add operation.
161 * @param entry The entry being added.
162 */
163 public void handleAddOperation(PostResponseAddOperation addOperation,
164 Entry entry) {
165 boolean hasAci, hasGlobalAci=false;
166 //Ignore this list, the ACI syntax has already passed and it should be
167 //empty.
168 LinkedList<Message>failedACIMsgs=new LinkedList<Message>();
169 //This entry might have both global and aci attribute types.
170 if((hasAci=entry.hasOperationalAttribute(AciHandler.aciType)) ||
171 (hasGlobalAci=entry.hasAttribute(AciHandler.globalAciType)))
172 aciList.addAci(entry, hasAci, hasGlobalAci, failedACIMsgs);
173 }
174
175 /**
176 * A modify operation succeeded. Adjust the ACIs by removing
177 * ACIs based on the oldEntry and then adding ACIs based on the new
178 * entry.
179 * @param modOperation the modify operation.
180 * @param oldEntry The old entry to examine.
181 * @param newEntry The new entry to examine.
182 */
183 public void handleModifyOperation(PostResponseModifyOperation modOperation,
184 Entry oldEntry, Entry newEntry)
185 {
186 // A change to the ACI list is expensive so let's first make sure that
187 // the modification included changes to the ACI. We'll check for
188 //both "aci" attribute types and global "ds-cfg-global-aci" attribute
189 //types.
190 boolean hasAci = false, hasGlobalAci=false;
191 List<Modification> mods = modOperation.getModifications();
192 for (Modification mod : mods) {
193 AttributeType attributeType=mod.getAttribute().getAttributeType();
194 if (attributeType.equals(AciHandler.aciType))
195 hasAci = true;
196 else if(attributeType.equals(AciHandler.globalAciType))
197 hasGlobalAci=true;
198 if(hasAci && hasGlobalAci)
199 break;
200 }
201 if (hasAci || hasGlobalAci)
202 aciList.modAciOldNewEntry(oldEntry, newEntry, hasAci, hasGlobalAci);
203 }
204
205 /**
206 * A modify DN operation has succeeded. Adjust the ACIs by moving ACIs
207 * under the old entry DN to the new entry DN.
208 * @param modifyDNOperation The LDAP modify DN operation.
209 * @param oldEntry The old entry.
210 * @param newEntry The new entry.
211 */
212 public void handleModifyDNOperation(
213 PostResponseModifyDNOperation modifyDNOperation,
214 Entry oldEntry, Entry newEntry)
215 {
216 aciList.renameAci(oldEntry.getDN(), newEntry.getDN());
217 }
218
219 /**
220 * {@inheritDoc} In this case, the server will search the backend to find
221 * all aci attribute type values that it may contain and add them to the
222 * ACI list.
223 */
224 public void performBackendInitializationProcessing(Backend backend) {
225 // Check to make sure that the backend has a presence index defined for
226 // the ACI attribute. If it does not, then log a warning message because
227 // this processing could be very expensive.
228 AttributeType aciType = DirectoryServer.getAttributeType("aci", true);
229 if (! backend.isIndexed(aciType, IndexType.PRESENCE))
230 {
231 logError(WARN_ACI_ATTRIBUTE_NOT_INDEXED.get(backend.getBackendID(),
232 "aci"));
233 }
234
235
236 InternalClientConnection conn =
237 InternalClientConnection.getRootConnection();
238 LinkedList<Message>failedACIMsgs=new LinkedList<Message>();
239 //Add manageDsaIT control so any ACIs in referral entries will be
240 //picked up.
241 ArrayList<Control> controls = new ArrayList<Control>(1);
242 controls.add(new Control(OID_MANAGE_DSAIT_CONTROL, true));
243 for (DN baseDN : backend.getBaseDNs()) {
244 try {
245 if (! backend.entryExists(baseDN)) {
246 continue;
247 }
248 } catch (Exception e) {
249 if (debugEnabled())
250 {
251 TRACER.debugCaught(DebugLogLevel.ERROR, e);
252 }
253 continue;
254 }
255 InternalSearchOperation internalSearch =
256 new InternalSearchOperation(
257 conn,
258 InternalClientConnection.nextOperationID(),
259 InternalClientConnection.nextMessageID(),
260 controls, baseDN, SearchScope.WHOLE_SUBTREE,
261 DereferencePolicy.NEVER_DEREF_ALIASES,
262 0, 0, false, aciFilter, attrs, null);
263 LocalBackendSearchOperation localInternalSearch =
264 new LocalBackendSearchOperation(internalSearch);
265 try {
266 backend.search(localInternalSearch);
267 } catch (Exception e) {
268 if (debugEnabled())
269 {
270 TRACER.debugCaught(DebugLogLevel.ERROR, e);
271 }
272 continue;
273 }
274 if(!internalSearch.getSearchEntries().isEmpty()) {
275 int validAcis = aciList.addAci(
276 internalSearch.getSearchEntries(), failedACIMsgs);
277 if(!failedACIMsgs.isEmpty())
278 logMsgsSetLockDownMode(failedACIMsgs);
279 Message message = INFO_ACI_ADD_LIST_ACIS.get(
280 Integer.toString(validAcis), String.valueOf(baseDN));
281 logError(message);
282 }
283 }
284 }
285
286 /**
287 * {@inheritDoc} In this case, the server will remove all aci attribute
288 * type values associated with entries in the provided backend.
289 */
290 public void performBackendFinalizationProcessing(Backend backend) {
291 aciList.removeAci(backend);
292 }
293
294
295
296 /**
297 * Retrieves the fully-qualified name of the Java class for this alert
298 * generator implementation.
299 *
300 * @return The fully-qualified name of the Java class for this alert
301 * generator implementation.
302 */
303 public String getClassName()
304 {
305 return CLASS_NAME;
306 }
307
308
309 /**
310 * Retrieves the DN of the configuration entry used to configure the
311 * handler.
312 *
313 * @return The DN of the configuration entry containing the Access Control
314 * configuration information.
315 */
316 public DN getComponentEntryDN()
317 {
318 return this.configurationDN;
319 }
320
321
322 /**
323 * Retrieves information about the set of alerts that this generator may
324 * produce. The map returned should be between the notification type for a
325 * particular notification and the human-readable description for that
326 * notification. This alert generator must not generate any alerts with
327 * types that are not contained in this list.
328 *
329 * @return Information about the set of alerts that this generator may
330 * produce.
331 */
332 public LinkedHashMap<String,String> getAlerts()
333 {
334 LinkedHashMap<String,String> alerts =
335 new LinkedHashMap<String,String>();
336 alerts.put(ALERT_TYPE_ACCESS_CONTROL_PARSE_FAILED,
337 ALERT_DESCRIPTION_ACCESS_CONTROL_PARSE_FAILED);
338 return alerts;
339
340 }
341
342 /**
343 * Log the exception messages from the failed ACI decode and then put the
344 * server in lockdown mode -- if needed.
345 *
346 * @param failedACIMsgs List of exception messages from failed ACI decodes.
347 */
348 public void logMsgsSetLockDownMode(LinkedList<Message> failedACIMsgs) {
349
350 for(Message msg : failedACIMsgs) {
351 Message message=WARN_ACI_SERVER_DECODE_FAILED.get(msg);
352 logError(message);
353 }
354 if(!inLockDownMode)
355 setLockDownMode();
356 }
357
358
359 /**
360 * Send an WARN_ACI_ENTER_LOCKDOWN_MODE alert notification and put the
361 * server in lockdown mode.
362 *
363 */
364 private void setLockDownMode() {
365 if(!inLockDownMode) {
366 inLockDownMode=true;
367 //Send ALERT_TYPE_ACCESS_CONTROL_PARSE_FAILED alert that
368 //lockdown is about to be entered.
369 Message lockDownMsg=WARN_ACI_ENTER_LOCKDOWN_MODE.get();
370 DirectoryServer.sendAlertNotification(this,
371 ALERT_TYPE_ACCESS_CONTROL_PARSE_FAILED,
372 lockDownMsg );
373 //Enter lockdown mode.
374 DirectoryServer.setLockdownMode(true);
375
376 }
377 }
378 }