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