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 }