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