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