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