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
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.controls;
028    import org.opends.messages.Message;
029    
030    
031    
032    import java.util.concurrent.locks.Lock;
033    
034    import org.opends.server.api.IdentityMapper;
035    import org.opends.server.core.DirectoryServer;
036    import org.opends.server.core.PasswordPolicyState;
037    import org.opends.server.protocols.asn1.ASN1Exception;
038    import org.opends.server.protocols.asn1.ASN1OctetString;
039    import org.opends.server.protocols.ldap.LDAPResultCode;
040    import org.opends.server.types.Control;
041    import org.opends.server.types.DirectoryException;
042    import org.opends.server.types.DN;
043    import org.opends.server.types.Entry;
044    import org.opends.server.types.LDAPException;
045    import org.opends.server.types.LockManager;
046    import org.opends.server.types.ResultCode;
047    
048    import static org.opends.server.loggers.debug.DebugLogger.*;
049    import org.opends.server.loggers.debug.DebugTracer;
050    import org.opends.server.types.DebugLogLevel;
051    import static org.opends.messages.ProtocolMessages.*;
052    import static org.opends.server.util.ServerConstants.*;
053    import static org.opends.server.util.StaticUtils.*;
054    import static org.opends.server.util.Validator.*;
055    
056    
057    
058    /**
059     * This class implements version 2 of the proxied authorization control as
060     * defined in RFC 4370.  It makes it possible for one user to request that an
061     * operation be performed under the authorization of another.  The target user
062     * is specified using an authorization ID, which may be in the form "dn:"
063     * immediately followed by the DN of that user, or "u:" followed by a user ID
064     * string.
065     */
066    public class ProxiedAuthV2Control
067           extends Control
068    {
069      /**
070       * The tracer object for the debug logger.
071       */
072      private static final DebugTracer TRACER = getTracer();
073    
074    
075    
076    
077      // The authorization ID from the control value.
078      private ASN1OctetString authorizationID;
079    
080    
081    
082      /**
083       * Creates a new instance of the proxied authorization v2 control with the
084       * provided information.
085       *
086       * @param  authorizationID  The authorization ID from the control value.
087       */
088      public ProxiedAuthV2Control(ASN1OctetString authorizationID)
089      {
090        super(OID_PROXIED_AUTH_V2, true, authorizationID);
091    
092    
093        ensureNotNull(authorizationID);
094        this.authorizationID = authorizationID;
095      }
096    
097    
098    
099      /**
100       * Creates a new instance of the proxied authorization v2 control with the
101       * provided information.
102       *
103       * @param  oid              The OID to use for this control.
104       * @param  isCritical       Indicates whether support for this control
105       *                          should be considered a critical part of the
106       *                          server processing.
107       * @param  authorizationID  The authorization ID from the control value.
108       */
109      private ProxiedAuthV2Control(String oid, boolean isCritical,
110                                 ASN1OctetString authorizationID)
111      {
112        super(oid, isCritical, authorizationID);
113    
114    
115        this.authorizationID = authorizationID;
116      }
117    
118    
119    
120      /**
121       * Creates a new proxied authorization v2 control from the contents of the
122       * provided control.
123       *
124       * @param  control  The generic control containing the information to use to
125       *                  create this proxied authorization v2 control.  It must not
126       *                  be {@code null}.
127       *
128       * @return  The proxied authorization v2 control decoded from the provided
129       *          control.
130       *
131       * @throws  LDAPException  If this control cannot be decoded as a valid
132       *                         proxied authorization v2 control.
133       */
134      public static ProxiedAuthV2Control decodeControl(Control control)
135             throws LDAPException
136      {
137        ensureNotNull(control);
138    
139        if (! control.isCritical())
140        {
141          Message message = ERR_PROXYAUTH2_CONTROL_NOT_CRITICAL.get();
142          throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
143        }
144    
145        if (! control.hasValue())
146        {
147          Message message = ERR_PROXYAUTH2_NO_CONTROL_VALUE.get();
148          throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
149        }
150    
151        ASN1OctetString authorizationID;
152        try
153        {
154          authorizationID =
155               ASN1OctetString.decodeAsOctetString(control.getValue().value());
156        }
157        catch (ASN1Exception ae)
158        {
159          String lowerAuthZIDStr = toLowerCase(control.getValue().stringValue());
160          if (lowerAuthZIDStr.startsWith("dn:") || lowerAuthZIDStr.startsWith("u:"))
161          {
162            authorizationID = control.getValue();
163          }
164          else
165          {
166            if (debugEnabled())
167            {
168              TRACER.debugCaught(DebugLogLevel.ERROR, ae);
169            }
170    
171            Message message =
172                ERR_PROXYAUTH2_CANNOT_DECODE_VALUE.get(getExceptionMessage(ae));
173            throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message,
174                                    ae);
175          }
176        }
177        catch (Exception e)
178        {
179          if (debugEnabled())
180          {
181            TRACER.debugCaught(DebugLogLevel.ERROR, e);
182          }
183    
184          Message message =
185              ERR_PROXYAUTH2_CANNOT_DECODE_VALUE.get(getExceptionMessage(e));
186          throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message, e);
187        }
188    
189        return new ProxiedAuthV2Control(control.getOID(), control.isCritical(),
190                                        authorizationID);
191      }
192    
193    
194    
195      /**
196       * Retrieves the authorization ID for this proxied authorization V2 control.
197       *
198       * @return  The authorization ID for this proxied authorization V2 control.
199       */
200      public ASN1OctetString getAuthorizationID()
201      {
202        return authorizationID;
203      }
204    
205    
206    
207      /**
208       * Specifies the authorization ID for this proxied authorization V2 control.
209       *
210       * @param  authorizationID  The authorization ID for this proxied
211       *                          authorization V2 control.
212       */
213      public void setAuthorizationID(ASN1OctetString authorizationID)
214      {
215        if (authorizationID == null)
216        {
217          this.authorizationID = new ASN1OctetString();
218          setValue(this.authorizationID);
219        }
220        else
221        {
222          this.authorizationID = authorizationID;
223          setValue(authorizationID);
224        }
225      }
226    
227    
228    
229      /**
230       * Retrieves the authorization entry for this proxied authorization V2
231       * control.  It will also perform any necessary password policy checks to
232       * ensure that the associated user account is suitable for use in performing
233       * this processing.
234       *
235       * @return  The entry for user specified as the authorization identity in this
236       *          proxied authorization V1 control, or {@code null} if the
237       *          authorization DN is the null DN.
238       *
239       * @throws  DirectoryException  If the target user does not exist or is not
240       *                              available for use, or if a problem occurs
241       *                              while making the determination.
242       */
243      public Entry getAuthorizationEntry()
244             throws DirectoryException
245      {
246        // Check for a zero-length value, which would be for an anonymous user.
247        if (authorizationID.value().length == 0)
248        {
249          return null;
250        }
251    
252    
253        // Get a lowercase string representation.  It must start with either "dn:"
254        // or "u:".
255        String authzID = authorizationID.stringValue();
256        String lowerAuthzID = toLowerCase(authzID);
257        if (lowerAuthzID.startsWith("dn:"))
258        {
259          // It's a DN, so decode it and see if it exists.  If it's the null DN,
260          // then just assume that it does.
261          DN authzDN = DN.decode(authzID.substring(3));
262          if (authzDN.isNullDN())
263          {
264            return null;
265          }
266          else
267          {
268            // See if the authorization DN is one of the alternate bind DNs for one
269            // of the root users and if so then map it accordingly.
270            DN actualDN = DirectoryServer.getActualRootBindDN(authzDN);
271            if (actualDN != null)
272            {
273              authzDN = actualDN;
274            }
275    
276            Lock entryLock = null;
277            for (int i=0; i < 3; i++)
278            {
279              entryLock = LockManager.lockRead(authzDN);
280              if (entryLock != null)
281              {
282                break;
283              }
284            }
285    
286            if (entryLock == null)
287            {
288              Message message =
289                  ERR_PROXYAUTH2_CANNOT_LOCK_USER.get(String.valueOf(authzDN));
290              throw new DirectoryException(
291                      ResultCode.AUTHORIZATION_DENIED, message);
292            }
293    
294            try
295            {
296              Entry userEntry = DirectoryServer.getEntry(authzDN);
297              if (userEntry == null)
298              {
299                // The requested user does not exist.
300                Message message = ERR_PROXYAUTH2_NO_SUCH_USER.get(authzID);
301                throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
302                                             message);
303              }
304    
305              // FIXME -- We should provide some mechanism for enabling debug
306              // processing.
307              PasswordPolicyState pwpState =
308                   new PasswordPolicyState(userEntry, false);
309              if (pwpState.isDisabled() || pwpState.isAccountExpired() ||
310                  pwpState.lockedDueToFailures() ||
311                  pwpState.lockedDueToIdleInterval() ||
312                  pwpState.lockedDueToMaximumResetAge() ||
313                  pwpState.isPasswordExpired())
314              {
315                Message message =
316                    ERR_PROXYAUTH2_UNUSABLE_ACCOUNT.get(String.valueOf(authzDN));
317                throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
318                                             message);
319              }
320    
321    
322              // If we've made it here, then the user is acceptable.
323              return userEntry;
324            }
325            finally
326            {
327              LockManager.unlock(authzDN, entryLock);
328            }
329          }
330        }
331        else if (lowerAuthzID.startsWith("u:"))
332        {
333          // If the authorization ID is just "u:", then it's an anonymous request.
334          if (lowerAuthzID.length() == 2)
335          {
336            return null;
337          }
338    
339    
340          // Use the proxied authorization identity mapper to resolve the username
341          // to an entry.
342          IdentityMapper proxyMapper =
343               DirectoryServer.getProxiedAuthorizationIdentityMapper();
344          if (proxyMapper == null)
345          {
346            Message message = ERR_PROXYAUTH2_NO_IDENTITY_MAPPER.get();
347            throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED, message);
348          }
349    
350          Entry userEntry = proxyMapper.getEntryForID(authzID.substring(2));
351          if (userEntry == null)
352          {
353            Message message = ERR_PROXYAUTH2_NO_SUCH_USER.get(authzID);
354            throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED, message);
355          }
356          else
357          {
358            // FIXME -- We should provide some mechanism for enabling debug
359            // processing.
360            PasswordPolicyState pwpState =
361                 new PasswordPolicyState(userEntry, false);
362            if (pwpState.isDisabled() || pwpState.isAccountExpired() ||
363                pwpState.lockedDueToFailures() ||
364                pwpState.lockedDueToIdleInterval() ||
365                pwpState.lockedDueToMaximumResetAge() ||
366                pwpState.isPasswordExpired())
367            {
368              Message message = ERR_PROXYAUTH2_UNUSABLE_ACCOUNT.get(
369                  String.valueOf(userEntry.getDN()));
370              throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
371                                           message);
372            }
373    
374            return userEntry;
375          }
376        }
377        else
378        {
379          Message message = ERR_PROXYAUTH2_INVALID_AUTHZID.get(authzID);
380          throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message);
381        }
382      }
383    
384    
385    
386      /**
387       * Retrieves a string representation of this proxied auth v2 control.
388       *
389       * @return  A string representation of this proxied auth v2 control.
390       */
391      public String toString()
392      {
393        StringBuilder buffer = new StringBuilder();
394        toString(buffer);
395        return buffer.toString();
396      }
397    
398    
399    
400      /**
401       * Appends a string representation of this proxied auth v2 control to the
402       * provided buffer.
403       *
404       * @param  buffer  The buffer to which the information should be appended.
405       */
406      public void toString(StringBuilder buffer)
407      {
408        buffer.append("ProxiedAuthorizationV2Control(authzID=\"");
409        authorizationID.toString(buffer);
410        buffer.append("\")");
411      }
412    }
413