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 package org.opends.server.workflowelement.localbackend;
028
029
030
031 import java.util.HashSet;
032 import java.util.List;
033 import java.util.concurrent.locks.Lock;
034
035 import org.opends.server.api.Backend;
036 import org.opends.server.api.ClientConnection;
037 import org.opends.server.api.plugin.PluginResult;
038 import org.opends.server.controls.LDAPAssertionRequestControl;
039 import org.opends.server.controls.ProxiedAuthV1Control;
040 import org.opends.server.controls.ProxiedAuthV2Control;
041 import org.opends.server.core.AccessControlConfigManager;
042 import org.opends.server.core.CompareOperation;
043 import org.opends.server.core.CompareOperationWrapper;
044 import org.opends.server.core.DirectoryServer;
045 import org.opends.server.core.PluginConfigManager;
046 import org.opends.server.loggers.debug.DebugTracer;
047 import org.opends.server.types.*;
048 import org.opends.server.types.operation.PostOperationCompareOperation;
049 import org.opends.server.types.operation.PostResponseCompareOperation;
050 import org.opends.server.types.operation.PreOperationCompareOperation;
051
052 import static org.opends.messages.CoreMessages.*;
053 import static org.opends.server.loggers.debug.DebugLogger.*;
054 import static org.opends.server.util.ServerConstants.*;
055 import static org.opends.server.util.StaticUtils.*;
056
057
058
059 /**
060 * This class defines an operation that may be used to determine whether a
061 * specified entry in the Directory Server contains a given attribute-value
062 * pair.
063 */
064 public class LocalBackendCompareOperation
065 extends CompareOperationWrapper
066 implements PreOperationCompareOperation, PostOperationCompareOperation,
067 PostResponseCompareOperation
068 {
069 /**
070 * The tracer object for the debug logger.
071 */
072 private static final DebugTracer TRACER = getTracer();
073
074
075
076 // The backend in which the comparison is to be performed.
077 private Backend backend;
078
079 // The client connection for this operation.
080 private ClientConnection clientConnection;
081
082 // The DN of the entry to compare.
083 private DN entryDN;
084
085 // The entry to be compared.
086 private Entry entry = null;
087
088
089
090 /**
091 * Creates a new compare operation based on the provided compare operation.
092 *
093 * @param compare the compare operation
094 */
095 public LocalBackendCompareOperation(CompareOperation compare)
096 {
097 super(compare);
098 LocalBackendWorkflowElement.attachLocalOperation (compare, this);
099 }
100
101
102
103 /**
104 * Retrieves the entry to target with the compare operation.
105 *
106 * @return The entry to target with the compare operation, or
107 * <CODE>null</CODE> if the entry is not yet available.
108 */
109 public Entry getEntryToCompare()
110 {
111 return entry;
112 }
113
114
115
116 /**
117 * Process this compare operation in a local backend.
118 *
119 * @param backend The backend in which the compare operation should be
120 * processed.
121 *
122 * @throws CanceledOperationException if this operation should be
123 * cancelled
124 */
125 void processLocalCompare(Backend backend) throws CanceledOperationException {
126 boolean executePostOpPlugins = false;
127
128 this.backend = backend;
129
130 clientConnection = getClientConnection();
131
132 // Get the plugin config manager that will be used for invoking plugins.
133 PluginConfigManager pluginConfigManager =
134 DirectoryServer.getPluginConfigManager();
135
136
137 // Get a reference to the client connection
138 ClientConnection clientConnection = getClientConnection();
139
140
141 // Check for a request to cancel this operation.
142 checkIfCanceled(false);
143
144
145 // Create a labeled block of code that we can break out of if a problem is
146 // detected.
147 compareProcessing:
148 {
149 // Process the entry DN to convert it from the raw form to the form
150 // required for the rest of the compare processing.
151 entryDN = getEntryDN();
152 if (entryDN == null)
153 {
154 break compareProcessing;
155 }
156
157
158 // If the target entry is in the server configuration, then make sure the
159 // requester has the CONFIG_READ privilege.
160 if (DirectoryServer.getConfigHandler().handlesEntry(entryDN) &&
161 (! clientConnection.hasPrivilege(Privilege.CONFIG_READ, this)))
162 {
163 appendErrorMessage(ERR_COMPARE_CONFIG_INSUFFICIENT_PRIVILEGES.get());
164 setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
165 break compareProcessing;
166 }
167
168
169 // Check for a request to cancel this operation.
170 checkIfCanceled(false);
171
172
173 // Grab a read lock on the entry.
174 Lock readLock = null;
175 for (int i=0; i < 3; i++)
176 {
177 readLock = LockManager.lockRead(entryDN);
178 if (readLock != null)
179 {
180 break;
181 }
182 }
183
184 if (readLock == null)
185 {
186 setResultCode(DirectoryServer.getServerErrorResultCode());
187 appendErrorMessage(ERR_COMPARE_CANNOT_LOCK_ENTRY.get(
188 String.valueOf(entryDN)));
189
190 break compareProcessing;
191 }
192
193 try
194 {
195 // Get the entry. If it does not exist, then fail.
196 try
197 {
198 entry = DirectoryServer.getEntry(entryDN);
199
200 if (entry == null)
201 {
202 setResultCode(ResultCode.NO_SUCH_OBJECT);
203 appendErrorMessage(
204 ERR_COMPARE_NO_SUCH_ENTRY.get(String.valueOf(entryDN)));
205
206 // See if one of the entry's ancestors exists.
207 DN parentDN = entryDN.getParentDNInSuffix();
208 while (parentDN != null)
209 {
210 try
211 {
212 if (DirectoryServer.entryExists(parentDN))
213 {
214 setMatchedDN(parentDN);
215 break;
216 }
217 }
218 catch (Exception e)
219 {
220 if (debugEnabled())
221 {
222 TRACER.debugCaught(DebugLogLevel.ERROR, e);
223 }
224 break;
225 }
226
227 parentDN = parentDN.getParentDNInSuffix();
228 }
229
230 break compareProcessing;
231 }
232 }
233 catch (DirectoryException de)
234 {
235 if (debugEnabled())
236 {
237 TRACER.debugCaught(DebugLogLevel.ERROR, de);
238 }
239
240 setResultCode(de.getResultCode());
241 appendErrorMessage(de.getMessageObject());
242 break compareProcessing;
243 }
244
245 // Check to see if there are any controls in the request. If so, then
246 // see if there is any special processing required.
247 try
248 {
249 handleRequestControls();
250 }
251 catch (DirectoryException de)
252 {
253 if (debugEnabled())
254 {
255 TRACER.debugCaught(DebugLogLevel.ERROR, de);
256 }
257
258 setResponseData(de);
259 break compareProcessing;
260 }
261
262
263 // Check to see if the client has permission to perform the
264 // compare.
265
266 // FIXME: for now assume that this will check all permission
267 // pertinent to the operation. This includes proxy authorization
268 // and any other controls specified.
269
270 // FIXME: earlier checks to see if the entry already exists may
271 // have already exposed sensitive information to the client.
272 if (! AccessControlConfigManager.getInstance().
273 getAccessControlHandler().isAllowed(this))
274 {
275 setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
276 appendErrorMessage(ERR_COMPARE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
277 String.valueOf(entryDN)));
278 break compareProcessing;
279 }
280
281 // Check for a request to cancel this operation.
282 checkIfCanceled(false);
283
284
285 // Invoke the pre-operation compare plugins.
286 executePostOpPlugins = true;
287 PluginResult.PreOperation preOpResult =
288 pluginConfigManager.invokePreOperationComparePlugins(this);
289 if (!preOpResult.continueProcessing())
290 {
291 setResultCode(preOpResult.getResultCode());
292 appendErrorMessage(preOpResult.getErrorMessage());
293 setMatchedDN(preOpResult.getMatchedDN());
294 setReferralURLs(preOpResult.getReferralURLs());
295 break compareProcessing;
296 }
297
298
299 // Get the base attribute type and set of options.
300 String baseName;
301 HashSet<String> options;
302 String rawAttributeType = getRawAttributeType();
303 int semicolonPos = rawAttributeType.indexOf(';');
304 if (semicolonPos > 0)
305 {
306 baseName = toLowerCase(rawAttributeType.substring(0, semicolonPos));
307
308 options = new HashSet<String>();
309 int nextPos = rawAttributeType.indexOf(';', semicolonPos+1);
310 while (nextPos > 0)
311 {
312 options.add(rawAttributeType.substring(semicolonPos+1, nextPos));
313 semicolonPos = nextPos;
314 nextPos = rawAttributeType.indexOf(';', semicolonPos+1);
315 }
316
317 options.add(rawAttributeType.substring(semicolonPos+1));
318 }
319 else
320 {
321 baseName = toLowerCase(rawAttributeType);
322 options = null;
323 }
324
325
326 // Actually perform the compare operation.
327 AttributeType attrType = getAttributeType();
328 if (attrType == null)
329 {
330 attrType = DirectoryServer.getAttributeType(baseName, true);
331 setAttributeType(attrType);
332 }
333
334 List<Attribute> attrList = entry.getAttribute(attrType, options);
335 if ((attrList == null) || attrList.isEmpty())
336 {
337 setResultCode(ResultCode.NO_SUCH_ATTRIBUTE);
338 if (options == null)
339 {
340 appendErrorMessage(WARN_COMPARE_OP_NO_SUCH_ATTR.get(
341 String.valueOf(entryDN), baseName));
342 }
343 else
344 {
345 appendErrorMessage(WARN_COMPARE_OP_NO_SUCH_ATTR_WITH_OPTIONS.get(
346 String.valueOf(entryDN), baseName));
347 }
348 }
349 else
350 {
351 AttributeValue value = new AttributeValue(attrType,
352 getAssertionValue());
353
354 boolean matchFound = false;
355 for (Attribute a : attrList)
356 {
357 if (a.hasValue(value))
358 {
359 matchFound = true;
360 break;
361 }
362 }
363
364 if (matchFound)
365 {
366 setResultCode(ResultCode.COMPARE_TRUE);
367 }
368 else
369 {
370 setResultCode(ResultCode.COMPARE_FALSE);
371 }
372 }
373 }
374 finally
375 {
376 LockManager.unlock(entryDN, readLock);
377 }
378 }
379
380
381 // Check for a request to cancel this operation.
382 checkIfCanceled(false);
383
384
385 // Invoke the post-operation compare plugins.
386 if (executePostOpPlugins)
387 {
388 PluginResult.PostOperation postOpResult =
389 pluginConfigManager.invokePostOperationComparePlugins(this);
390 if (!postOpResult.continueProcessing())
391 {
392 setResultCode(postOpResult.getResultCode());
393 appendErrorMessage(postOpResult.getErrorMessage());
394 setMatchedDN(postOpResult.getMatchedDN());
395 setReferralURLs(postOpResult.getReferralURLs());
396 }
397 }
398 }
399
400
401
402 /**
403 * Performs any processing required for the controls included in the request.
404 *
405 * @throws DirectoryException If a problem occurs that should prevent the
406 * operation from succeeding.
407 */
408 private void handleRequestControls()
409 throws DirectoryException
410 {
411 List<Control> requestControls = getRequestControls();
412 if ((requestControls != null) && (! requestControls.isEmpty()))
413 {
414 for (int i=0; i < requestControls.size(); i++)
415 {
416 Control c = requestControls.get(i);
417 String oid = c.getOID();
418
419 if (! AccessControlConfigManager.getInstance().
420 getAccessControlHandler().isAllowed(entryDN, this, c))
421 {
422 throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
423 ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
424 }
425
426 if (oid.equals(OID_LDAP_ASSERTION))
427 {
428 LDAPAssertionRequestControl assertControl;
429 if (c instanceof LDAPAssertionRequestControl)
430 {
431 assertControl = (LDAPAssertionRequestControl) c;
432 }
433 else
434 {
435 try
436 {
437 assertControl = LDAPAssertionRequestControl.decodeControl(c);
438 requestControls.set(i, assertControl);
439 }
440 catch (LDAPException le)
441 {
442 if (debugEnabled())
443 {
444 TRACER.debugCaught(DebugLogLevel.ERROR, le);
445 }
446
447 throw new DirectoryException(
448 ResultCode.valueOf(le.getResultCode()),
449 le.getMessageObject());
450 }
451 }
452
453 try
454 {
455 // FIXME -- We need to determine whether the current user has
456 // permission to make this determination.
457 SearchFilter filter = assertControl.getSearchFilter();
458 if (! filter.matchesEntry(entry))
459 {
460 throw new DirectoryException(ResultCode.ASSERTION_FAILED,
461 ERR_COMPARE_ASSERTION_FAILED.get(
462 String.valueOf(entryDN)));
463 }
464 }
465 catch (DirectoryException de)
466 {
467 if (de.getResultCode() == ResultCode.ASSERTION_FAILED)
468 {
469 throw de;
470 }
471
472 if (debugEnabled())
473 {
474 TRACER.debugCaught(DebugLogLevel.ERROR, de);
475 }
476
477 throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
478 ERR_COMPARE_CANNOT_PROCESS_ASSERTION_FILTER.get(
479 String.valueOf(entryDN),
480 de.getMessageObject()));
481 }
482 }
483 else if (oid.equals(OID_PROXIED_AUTH_V1))
484 {
485 // The requester must have the PROXIED_AUTH privilige in order to
486 // be able to use this control.
487 if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
488 {
489 throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
490 ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
491 }
492
493
494 ProxiedAuthV1Control proxyControl;
495 if (c instanceof ProxiedAuthV1Control)
496 {
497 proxyControl = (ProxiedAuthV1Control) c;
498 }
499 else
500 {
501 try
502 {
503 proxyControl = ProxiedAuthV1Control.decodeControl(c);
504 }
505 catch (LDAPException le)
506 {
507 if (debugEnabled())
508 {
509 TRACER.debugCaught(DebugLogLevel.ERROR, le);
510 }
511
512 throw new DirectoryException(
513 ResultCode.valueOf(le.getResultCode()),
514 le.getMessageObject());
515 }
516 }
517
518
519 Entry authorizationEntry = proxyControl.getAuthorizationEntry();
520 setAuthorizationEntry(authorizationEntry);
521 if (authorizationEntry == null)
522 {
523 setProxiedAuthorizationDN(DN.nullDN());
524 }
525 else
526 {
527 setProxiedAuthorizationDN(authorizationEntry.getDN());
528 }
529 }
530 else if (oid.equals(OID_PROXIED_AUTH_V2))
531 {
532 // The requester must have the PROXIED_AUTH privilige in order to
533 // be able to use this control.
534 if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
535 {
536 throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
537 ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
538 }
539
540
541 ProxiedAuthV2Control proxyControl;
542 if (c instanceof ProxiedAuthV2Control)
543 {
544 proxyControl = (ProxiedAuthV2Control) c;
545 }
546 else
547 {
548 try
549 {
550 proxyControl = ProxiedAuthV2Control.decodeControl(c);
551 }
552 catch (LDAPException le)
553 {
554 if (debugEnabled())
555 {
556 TRACER.debugCaught(DebugLogLevel.ERROR, le);
557 }
558
559 throw new DirectoryException(
560 ResultCode.valueOf(le.getResultCode()),
561 le.getMessageObject());
562 }
563 }
564
565
566 Entry authorizationEntry = proxyControl.getAuthorizationEntry();
567 setAuthorizationEntry(authorizationEntry);
568 if (authorizationEntry == null)
569 {
570 setProxiedAuthorizationDN(DN.nullDN());
571 }
572 else
573 {
574 setProxiedAuthorizationDN(authorizationEntry.getDN());
575 }
576 }
577
578 // NYI -- Add support for additional controls.
579 else if (c.isCritical())
580 {
581 if ((backend == null) || (! backend.supportsControl(oid)))
582 {
583 throw new DirectoryException(
584 ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
585 ERR_COMPARE_UNSUPPORTED_CRITICAL_CONTROL.get(
586 String.valueOf(entryDN), oid));
587 }
588 }
589 }
590 }
591 }
592 }
593