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.controls;
028 import org.opends.messages.Message;
029
030
031
032 import java.util.concurrent.locks.Lock;
033
034 import org.opends.server.api.IdentityMapper;
035 import org.opends.server.core.DirectoryServer;
036 import org.opends.server.core.PasswordPolicyState;
037 import org.opends.server.protocols.asn1.ASN1Exception;
038 import org.opends.server.protocols.asn1.ASN1OctetString;
039 import org.opends.server.protocols.ldap.LDAPResultCode;
040 import org.opends.server.types.Control;
041 import org.opends.server.types.DirectoryException;
042 import org.opends.server.types.DN;
043 import org.opends.server.types.Entry;
044 import org.opends.server.types.LDAPException;
045 import org.opends.server.types.LockManager;
046 import org.opends.server.types.ResultCode;
047
048 import static org.opends.server.loggers.debug.DebugLogger.*;
049 import org.opends.server.loggers.debug.DebugTracer;
050 import org.opends.server.types.DebugLogLevel;
051 import static org.opends.messages.ProtocolMessages.*;
052 import static org.opends.server.util.ServerConstants.*;
053 import static org.opends.server.util.StaticUtils.*;
054 import static org.opends.server.util.Validator.*;
055
056
057
058 /**
059 * This class implements version 2 of the proxied authorization control as
060 * defined in RFC 4370. It makes it possible for one user to request that an
061 * operation be performed under the authorization of another. The target user
062 * is specified using an authorization ID, which may be in the form "dn:"
063 * immediately followed by the DN of that user, or "u:" followed by a user ID
064 * string.
065 */
066 public class ProxiedAuthV2Control
067 extends Control
068 {
069 /**
070 * The tracer object for the debug logger.
071 */
072 private static final DebugTracer TRACER = getTracer();
073
074
075
076
077 // The authorization ID from the control value.
078 private ASN1OctetString authorizationID;
079
080
081
082 /**
083 * Creates a new instance of the proxied authorization v2 control with the
084 * provided information.
085 *
086 * @param authorizationID The authorization ID from the control value.
087 */
088 public ProxiedAuthV2Control(ASN1OctetString authorizationID)
089 {
090 super(OID_PROXIED_AUTH_V2, true, authorizationID);
091
092
093 ensureNotNull(authorizationID);
094 this.authorizationID = authorizationID;
095 }
096
097
098
099 /**
100 * Creates a new instance of the proxied authorization v2 control with the
101 * provided information.
102 *
103 * @param oid The OID to use for this control.
104 * @param isCritical Indicates whether support for this control
105 * should be considered a critical part of the
106 * server processing.
107 * @param authorizationID The authorization ID from the control value.
108 */
109 private ProxiedAuthV2Control(String oid, boolean isCritical,
110 ASN1OctetString authorizationID)
111 {
112 super(oid, isCritical, authorizationID);
113
114
115 this.authorizationID = authorizationID;
116 }
117
118
119
120 /**
121 * Creates a new proxied authorization v2 control from the contents of the
122 * provided control.
123 *
124 * @param control The generic control containing the information to use to
125 * create this proxied authorization v2 control. It must not
126 * be {@code null}.
127 *
128 * @return The proxied authorization v2 control decoded from the provided
129 * control.
130 *
131 * @throws LDAPException If this control cannot be decoded as a valid
132 * proxied authorization v2 control.
133 */
134 public static ProxiedAuthV2Control decodeControl(Control control)
135 throws LDAPException
136 {
137 ensureNotNull(control);
138
139 if (! control.isCritical())
140 {
141 Message message = ERR_PROXYAUTH2_CONTROL_NOT_CRITICAL.get();
142 throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
143 }
144
145 if (! control.hasValue())
146 {
147 Message message = ERR_PROXYAUTH2_NO_CONTROL_VALUE.get();
148 throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
149 }
150
151 ASN1OctetString authorizationID;
152 try
153 {
154 authorizationID =
155 ASN1OctetString.decodeAsOctetString(control.getValue().value());
156 }
157 catch (ASN1Exception ae)
158 {
159 String lowerAuthZIDStr = toLowerCase(control.getValue().stringValue());
160 if (lowerAuthZIDStr.startsWith("dn:") || lowerAuthZIDStr.startsWith("u:"))
161 {
162 authorizationID = control.getValue();
163 }
164 else
165 {
166 if (debugEnabled())
167 {
168 TRACER.debugCaught(DebugLogLevel.ERROR, ae);
169 }
170
171 Message message =
172 ERR_PROXYAUTH2_CANNOT_DECODE_VALUE.get(getExceptionMessage(ae));
173 throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message,
174 ae);
175 }
176 }
177 catch (Exception e)
178 {
179 if (debugEnabled())
180 {
181 TRACER.debugCaught(DebugLogLevel.ERROR, e);
182 }
183
184 Message message =
185 ERR_PROXYAUTH2_CANNOT_DECODE_VALUE.get(getExceptionMessage(e));
186 throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message, e);
187 }
188
189 return new ProxiedAuthV2Control(control.getOID(), control.isCritical(),
190 authorizationID);
191 }
192
193
194
195 /**
196 * Retrieves the authorization ID for this proxied authorization V2 control.
197 *
198 * @return The authorization ID for this proxied authorization V2 control.
199 */
200 public ASN1OctetString getAuthorizationID()
201 {
202 return authorizationID;
203 }
204
205
206
207 /**
208 * Specifies the authorization ID for this proxied authorization V2 control.
209 *
210 * @param authorizationID The authorization ID for this proxied
211 * authorization V2 control.
212 */
213 public void setAuthorizationID(ASN1OctetString authorizationID)
214 {
215 if (authorizationID == null)
216 {
217 this.authorizationID = new ASN1OctetString();
218 setValue(this.authorizationID);
219 }
220 else
221 {
222 this.authorizationID = authorizationID;
223 setValue(authorizationID);
224 }
225 }
226
227
228
229 /**
230 * Retrieves the authorization entry for this proxied authorization V2
231 * control. It will also perform any necessary password policy checks to
232 * ensure that the associated user account is suitable for use in performing
233 * this processing.
234 *
235 * @return The entry for user specified as the authorization identity in this
236 * proxied authorization V1 control, or {@code null} if the
237 * authorization DN is the null DN.
238 *
239 * @throws DirectoryException If the target user does not exist or is not
240 * available for use, or if a problem occurs
241 * while making the determination.
242 */
243 public Entry getAuthorizationEntry()
244 throws DirectoryException
245 {
246 // Check for a zero-length value, which would be for an anonymous user.
247 if (authorizationID.value().length == 0)
248 {
249 return null;
250 }
251
252
253 // Get a lowercase string representation. It must start with either "dn:"
254 // or "u:".
255 String authzID = authorizationID.stringValue();
256 String lowerAuthzID = toLowerCase(authzID);
257 if (lowerAuthzID.startsWith("dn:"))
258 {
259 // It's a DN, so decode it and see if it exists. If it's the null DN,
260 // then just assume that it does.
261 DN authzDN = DN.decode(authzID.substring(3));
262 if (authzDN.isNullDN())
263 {
264 return null;
265 }
266 else
267 {
268 // See if the authorization DN is one of the alternate bind DNs for one
269 // of the root users and if so then map it accordingly.
270 DN actualDN = DirectoryServer.getActualRootBindDN(authzDN);
271 if (actualDN != null)
272 {
273 authzDN = actualDN;
274 }
275
276 Lock entryLock = null;
277 for (int i=0; i < 3; i++)
278 {
279 entryLock = LockManager.lockRead(authzDN);
280 if (entryLock != null)
281 {
282 break;
283 }
284 }
285
286 if (entryLock == null)
287 {
288 Message message =
289 ERR_PROXYAUTH2_CANNOT_LOCK_USER.get(String.valueOf(authzDN));
290 throw new DirectoryException(
291 ResultCode.AUTHORIZATION_DENIED, message);
292 }
293
294 try
295 {
296 Entry userEntry = DirectoryServer.getEntry(authzDN);
297 if (userEntry == null)
298 {
299 // The requested user does not exist.
300 Message message = ERR_PROXYAUTH2_NO_SUCH_USER.get(authzID);
301 throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
302 message);
303 }
304
305 // FIXME -- We should provide some mechanism for enabling debug
306 // processing.
307 PasswordPolicyState pwpState =
308 new PasswordPolicyState(userEntry, false);
309 if (pwpState.isDisabled() || pwpState.isAccountExpired() ||
310 pwpState.lockedDueToFailures() ||
311 pwpState.lockedDueToIdleInterval() ||
312 pwpState.lockedDueToMaximumResetAge() ||
313 pwpState.isPasswordExpired())
314 {
315 Message message =
316 ERR_PROXYAUTH2_UNUSABLE_ACCOUNT.get(String.valueOf(authzDN));
317 throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
318 message);
319 }
320
321
322 // If we've made it here, then the user is acceptable.
323 return userEntry;
324 }
325 finally
326 {
327 LockManager.unlock(authzDN, entryLock);
328 }
329 }
330 }
331 else if (lowerAuthzID.startsWith("u:"))
332 {
333 // If the authorization ID is just "u:", then it's an anonymous request.
334 if (lowerAuthzID.length() == 2)
335 {
336 return null;
337 }
338
339
340 // Use the proxied authorization identity mapper to resolve the username
341 // to an entry.
342 IdentityMapper proxyMapper =
343 DirectoryServer.getProxiedAuthorizationIdentityMapper();
344 if (proxyMapper == null)
345 {
346 Message message = ERR_PROXYAUTH2_NO_IDENTITY_MAPPER.get();
347 throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED, message);
348 }
349
350 Entry userEntry = proxyMapper.getEntryForID(authzID.substring(2));
351 if (userEntry == null)
352 {
353 Message message = ERR_PROXYAUTH2_NO_SUCH_USER.get(authzID);
354 throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED, message);
355 }
356 else
357 {
358 // FIXME -- We should provide some mechanism for enabling debug
359 // processing.
360 PasswordPolicyState pwpState =
361 new PasswordPolicyState(userEntry, false);
362 if (pwpState.isDisabled() || pwpState.isAccountExpired() ||
363 pwpState.lockedDueToFailures() ||
364 pwpState.lockedDueToIdleInterval() ||
365 pwpState.lockedDueToMaximumResetAge() ||
366 pwpState.isPasswordExpired())
367 {
368 Message message = ERR_PROXYAUTH2_UNUSABLE_ACCOUNT.get(
369 String.valueOf(userEntry.getDN()));
370 throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
371 message);
372 }
373
374 return userEntry;
375 }
376 }
377 else
378 {
379 Message message = ERR_PROXYAUTH2_INVALID_AUTHZID.get(authzID);
380 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message);
381 }
382 }
383
384
385
386 /**
387 * Retrieves a string representation of this proxied auth v2 control.
388 *
389 * @return A string representation of this proxied auth v2 control.
390 */
391 public String toString()
392 {
393 StringBuilder buffer = new StringBuilder();
394 toString(buffer);
395 return buffer.toString();
396 }
397
398
399
400 /**
401 * Appends a string representation of this proxied auth v2 control to the
402 * provided buffer.
403 *
404 * @param buffer The buffer to which the information should be appended.
405 */
406 public void toString(StringBuilder buffer)
407 {
408 buffer.append("ProxiedAuthorizationV2Control(authzID=\"");
409 authorizationID.toString(buffer);
410 buffer.append("\")");
411 }
412 }
413