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