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 legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2006-2008 Sun Microsystems, Inc. 025 * Portions Copyright 2011-2015 ForgeRock AS. 026 */ 027package org.opends.server.controls; 028 029import java.io.IOException; 030import org.forgerock.i18n.LocalizableMessage; 031import org.opends.server.api.AuthenticationPolicyState; 032import org.opends.server.api.IdentityMapper; 033import org.opends.server.core.DirectoryServer; 034import org.opends.server.core.PasswordPolicyState; 035import org.forgerock.i18n.slf4j.LocalizedLogger; 036import org.forgerock.opendj.io.ASN1; 037import org.forgerock.opendj.io.ASN1Reader; 038import org.forgerock.opendj.io.ASN1Writer; 039import org.opends.server.types.*; 040import org.forgerock.opendj.ldap.ResultCode; 041import org.forgerock.opendj.ldap.ByteString; 042import static org.opends.messages.ProtocolMessages.*; 043import static org.opends.server.util.ServerConstants.*; 044import static org.opends.server.util.StaticUtils.*; 045import static org.forgerock.util.Reject.*; 046 047/** 048 * This class implements version 2 of the proxied authorization control as 049 * defined in RFC 4370. It makes it possible for one user to request that an 050 * operation be performed under the authorization of another. The target user 051 * is specified using an authorization ID, which may be in the form "dn:" 052 * immediately followed by the DN of that user, or "u:" followed by a user ID 053 * string. 054 */ 055public class ProxiedAuthV2Control 056 extends Control 057{ 058 /** 059 * ControlDecoder implementation to decode this control from a ByteString. 060 */ 061 private static final class Decoder 062 implements ControlDecoder<ProxiedAuthV2Control> 063 { 064 /** {@inheritDoc} */ 065 @Override 066 public ProxiedAuthV2Control decode(boolean isCritical, ByteString value) 067 throws DirectoryException 068 { 069 if (!isCritical) 070 { 071 LocalizableMessage message = ERR_PROXYAUTH2_CONTROL_NOT_CRITICAL.get(); 072 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message); 073 } 074 075 if (value == null) 076 { 077 LocalizableMessage message = ERR_PROXYAUTH2_NO_CONTROL_VALUE.get(); 078 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message); 079 } 080 081 ASN1Reader reader = ASN1.getReader(value); 082 ByteString authorizationID; 083 try 084 { 085 // Try the legacy encoding where the value is wrapped by an 086 // extra octet string 087 authorizationID = reader.readOctetString(); 088 } 089 catch (Exception e) 090 { 091 // Try just getting the value. 092 authorizationID = value; 093 String lowerAuthZIDStr = toLowerCase(authorizationID.toString()); 094 if (!lowerAuthZIDStr.startsWith("dn:") && 095 !lowerAuthZIDStr.startsWith("u:")) 096 { 097 logger.traceException(e); 098 099 LocalizableMessage message = 100 ERR_PROXYAUTH2_INVALID_AUTHZID.get(lowerAuthZIDStr); 101 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message, 102 e); 103 } 104 } 105 106 return new ProxiedAuthV2Control(isCritical, authorizationID); 107 } 108 109 @Override 110 public String getOID() 111 { 112 return OID_PROXIED_AUTH_V2; 113 } 114 115 } 116 117 /** 118 * The Control Decoder that can be used to decode this control. 119 */ 120 public static final ControlDecoder<ProxiedAuthV2Control> DECODER = 121 new Decoder(); 122 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 123 124 125 126 127 /** The authorization ID from the control value. */ 128 private ByteString authorizationID; 129 130 131 132 /** 133 * Creates a new instance of the proxied authorization v2 control with the 134 * provided information. 135 * 136 * @param authorizationID The authorization ID from the control value. 137 */ 138 public ProxiedAuthV2Control(ByteString authorizationID) 139 { 140 this(true, authorizationID); 141 } 142 143 144 145 /** 146 * Creates a new instance of the proxied authorization v2 control with the 147 * provided information. 148 * 149 * @param isCritical Indicates whether support for this control 150 * should be considered a critical part of the 151 * server processing. 152 * @param authorizationID The authorization ID from the control value. 153 */ 154 public ProxiedAuthV2Control(boolean isCritical, ByteString authorizationID) 155 { 156 super(OID_PROXIED_AUTH_V2, isCritical); 157 158 ifNull(authorizationID); 159 160 this.authorizationID = authorizationID; 161 } 162 163 164 165 /** 166 * Writes this control's value to an ASN.1 writer. The value (if any) must be 167 * written as an ASN1OctetString. 168 * 169 * @param writer The ASN.1 writer to use. 170 * @throws IOException If a problem occurs while writing to the stream. 171 */ 172 @Override 173 protected void writeValue(ASN1Writer writer) throws IOException { 174 writer.writeOctetString(authorizationID); 175 } 176 177 178 179 /** 180 * Retrieves the authorization ID for this proxied authorization V2 control. 181 * 182 * @return The authorization ID for this proxied authorization V2 control. 183 */ 184 public ByteString getAuthorizationID() 185 { 186 return authorizationID; 187 } 188 189 190 191 /** 192 * Retrieves the authorization entry for this proxied authorization V2 193 * control. It will also perform any necessary password policy checks to 194 * ensure that the associated user account is suitable for use in performing 195 * this processing. 196 * 197 * @return The entry for user specified as the authorization identity in this 198 * proxied authorization V1 control, or {@code null} if the 199 * authorization DN is the null DN. 200 * 201 * @throws DirectoryException If the target user does not exist or is not 202 * available for use, or if a problem occurs 203 * while making the determination. 204 */ 205 public Entry getAuthorizationEntry() 206 throws DirectoryException 207 { 208 // Check for a zero-length value, which would be for an anonymous user. 209 if (authorizationID.length() == 0) 210 { 211 return null; 212 } 213 214 215 // Get a lowercase string representation. It must start with either "dn:" 216 // or "u:". 217 String lowerAuthzID = toLowerCase(authorizationID.toString()); 218 if (lowerAuthzID.startsWith("dn:")) 219 { 220 // It's a DN, so decode it and see if it exists. If it's the null DN, 221 // then just assume that it does. 222 DN authzDN = DN.valueOf(lowerAuthzID.substring(3)); 223 if (authzDN.isRootDN()) 224 { 225 return null; 226 } 227 else 228 { 229 // See if the authorization DN is one of the alternate bind DNs for one 230 // of the root users and if so then map it accordingly. 231 DN actualDN = DirectoryServer.getActualRootBindDN(authzDN); 232 if (actualDN != null) 233 { 234 authzDN = actualDN; 235 } 236 237 Entry userEntry = DirectoryServer.getEntry(authzDN); 238 if (userEntry == null) 239 { 240 // The requested user does not exist. 241 LocalizableMessage message = ERR_PROXYAUTH2_NO_SUCH_USER.get(lowerAuthzID); 242 throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED, message); 243 } 244 245 // FIXME -- We should provide some mechanism for enabling debug 246 // processing. 247 checkAccountIsUsable(userEntry); 248 249 // If we've made it here, then the user is acceptable. 250 return userEntry; 251 } 252 } 253 else if (lowerAuthzID.startsWith("u:")) 254 { 255 // If the authorization ID is just "u:", then it's an anonymous request. 256 if (lowerAuthzID.length() == 2) 257 { 258 return null; 259 } 260 261 262 // Use the proxied authorization identity mapper to resolve the username 263 // to an entry. 264 IdentityMapper<?> proxyMapper = 265 DirectoryServer.getProxiedAuthorizationIdentityMapper(); 266 if (proxyMapper == null) 267 { 268 LocalizableMessage message = ERR_PROXYAUTH2_NO_IDENTITY_MAPPER.get(); 269 throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED, message); 270 } 271 272 Entry userEntry = proxyMapper.getEntryForID(lowerAuthzID.substring(2)); 273 if (userEntry == null) 274 { 275 LocalizableMessage message = ERR_PROXYAUTH2_NO_SUCH_USER.get(lowerAuthzID); 276 throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED, message); 277 } 278 else 279 { 280 // FIXME -- We should provide some mechanism for enabling debug 281 // processing. 282 checkAccountIsUsable(userEntry); 283 284 return userEntry; 285 } 286 } 287 else 288 { 289 LocalizableMessage message = ERR_PROXYAUTH2_INVALID_AUTHZID.get(lowerAuthzID); 290 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message); 291 } 292 } 293 294 295 296 private void checkAccountIsUsable(Entry userEntry) 297 throws DirectoryException 298 { 299 AuthenticationPolicyState state = AuthenticationPolicyState.forUser( 300 userEntry, false); 301 302 if (state.isDisabled()) 303 { 304 LocalizableMessage message = ERR_PROXYAUTH2_UNUSABLE_ACCOUNT.get(userEntry.getName()); 305 throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED, message); 306 } 307 308 if (state.isPasswordPolicy()) 309 { 310 PasswordPolicyState pwpState = (PasswordPolicyState) state; 311 if (pwpState.isAccountExpired() || pwpState.isLocked() || pwpState.isPasswordExpired()) 312 { 313 LocalizableMessage message = ERR_PROXYAUTH2_UNUSABLE_ACCOUNT.get(userEntry.getName()); 314 throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED, message); 315 } 316 } 317 } 318 319 320 321 /** 322 * Appends a string representation of this proxied authorization v2 control 323 * to the provided buffer. 324 * 325 * @param buffer The buffer to which the information should be appended. 326 */ 327 @Override 328 public void toString(StringBuilder buffer) 329 { 330 buffer.append("ProxiedAuthorizationV2Control(authzID=\""); 331 buffer.append(authorizationID); 332 buffer.append("\")"); 333 } 334} 335