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 2011-2016 ForgeRock AS.
015 */
016package org.opends.server.api;
017
018import static org.opends.messages.CoreMessages.*;
019import static org.opends.server.config.ConfigConstants.*;
020
021import java.util.List;
022
023import org.forgerock.i18n.LocalizableMessage;
024import org.forgerock.i18n.LocalizedIllegalArgumentException;
025import org.forgerock.i18n.slf4j.LocalizedLogger;
026import org.forgerock.opendj.ldap.ByteString;
027import org.forgerock.opendj.ldap.DN;
028import org.forgerock.opendj.ldap.ResultCode;
029import org.forgerock.opendj.ldap.schema.AttributeType;
030import org.opends.server.core.DirectoryServer;
031import org.opends.server.types.Attribute;
032import org.opends.server.types.DirectoryException;
033import org.opends.server.types.Entry;
034import org.opends.server.types.SubEntry;
035import org.opends.server.util.TimeThread;
036
037/**
038 * An abstract authentication policy.
039 */
040public abstract class AuthenticationPolicy
041{
042  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
043
044  /**
045   * Returns the authentication policy for the user provided user. The following
046   * algorithm is used in order to obtain the appropriate authentication policy:
047   * <ul>
048   * <li>if the user entry contains the {@code ds-pwp-password-policy-dn}
049   * attribute (whether real or virtual), then the referenced authentication
050   * policy will be returned
051   * <li>otherwise, a search is performed in order to find the nearest
052   * applicable password policy sub-entry to the user entry,
053   * <li>otherwise, the default password policy will be returned.
054   * </ul>
055   *
056   * @param userEntry
057   *          The user entry.
058   * @param useDefaultOnError
059   *          Indicates whether the server should fall back to using the default
060   *          password policy if there is a problem with the configured policy
061   *          for the user.
062   * @return The password policy for the user.
063   * @throws DirectoryException
064   *           If a problem occurs while attempting to determine the password
065   *           policy for the user.
066   */
067  public static AuthenticationPolicy forUser(Entry userEntry,
068      boolean useDefaultOnError) throws DirectoryException
069  {
070    // First check to see if the ds-pwp-password-policy-dn is present.
071    String userDNString = userEntry.getName().toString();
072    AttributeType type = DirectoryServer.getSchema().getAttributeType(OP_ATTR_PWPOLICY_POLICY_DN);
073    for (Attribute a : userEntry.getAttribute(type))
074    {
075      if (a.isEmpty())
076      {
077        continue;
078      }
079
080      ByteString v = a.iterator().next();
081      DN subentryDN;
082      try
083      {
084        subentryDN = DN.valueOf(v);
085      }
086      catch (LocalizedIllegalArgumentException e)
087      {
088        logger.traceException(e);
089
090        logger.trace("Could not parse password policy subentry DN %s for user %s",
091            v, userDNString, e);
092
093        if (useDefaultOnError)
094        {
095          logger.error(ERR_PWPSTATE_CANNOT_DECODE_SUBENTRY_VALUE_AS_DN,
096              v, userDNString, e.getMessage());
097          return DirectoryServer.getDefaultPasswordPolicy();
098        }
099        else
100        {
101          LocalizableMessage message = ERR_PWPSTATE_CANNOT_DECODE_SUBENTRY_VALUE_AS_DN
102              .get(v, userDNString, e.getMessage());
103          throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, e);
104        }
105      }
106
107      AuthenticationPolicy policy = DirectoryServer
108          .getAuthenticationPolicy(subentryDN);
109      if (policy == null)
110      {
111        logger.trace("Password policy subentry %s for user %s is not defined in the Directory Server.",
112                subentryDN, userDNString);
113
114        LocalizableMessage message = ERR_PWPSTATE_NO_SUCH_POLICY.get(userDNString, subentryDN);
115        if (useDefaultOnError)
116        {
117          logger.error(message);
118          return DirectoryServer.getDefaultPasswordPolicy();
119        }
120        else
121        {
122          throw new DirectoryException(
123              DirectoryServer.getServerErrorResultCode(), message);
124        }
125      }
126
127      logger.trace("Using password policy subentry %s for user %s.",
128            subentryDN, userDNString);
129
130      return policy;
131    }
132
133    // The ds-pwp-password-policy-dn attribute was not present, so instead
134    // search for the nearest applicable sub-entry.
135    List<SubEntry> pwpSubEntries = DirectoryServer.getSubentryManager()
136        .getSubentries(userEntry);
137    if (pwpSubEntries != null && !pwpSubEntries.isEmpty())
138    {
139      for (SubEntry subentry : pwpSubEntries)
140      {
141        try
142        {
143          if (subentry.getEntry().isPasswordPolicySubentry())
144          {
145            AuthenticationPolicy policy = DirectoryServer
146                .getAuthenticationPolicy(subentry.getDN());
147            if (policy == null)
148            {
149              // This shouldn't happen but if it does debug log
150              // this problem and fall back to default policy.
151              logger.trace("Found unknown password policy subentry DN %s for user %s",
152                  subentry.getDN(), userDNString);
153              break;
154            }
155            return policy;
156          }
157        }
158        catch (Exception e)
159        {
160          logger.traceException(e, "Could not parse password policy subentry DN %s for user %s",
161              subentry.getDN(), userDNString);
162        }
163      }
164    }
165
166    // No authentication policy found, so use the global default.
167    logger.trace("Using the default password policy for user %s", userDNString);
168
169    return DirectoryServer.getDefaultPasswordPolicy();
170  }
171
172
173
174  /**
175   * Creates a new abstract authentication policy.
176   */
177  protected AuthenticationPolicy()
178  {
179    // No implementation required.
180  }
181
182
183
184  /**
185   * Returns the name of the configuration entry associated with this
186   * authentication policy.
187   *
188   * @return The name of the configuration entry associated with this
189   *         authentication policy.
190   */
191  public abstract DN getDN();
192
193
194
195  /**
196   * Returns {@code true} if this authentication policy is a password policy and
197   * the methods {@link #createAuthenticationPolicyState(Entry)} and
198   * {@link #createAuthenticationPolicyState(Entry, long)} will return a
199   * {@code PasswordPolicyState}.
200   * <p>
201   * The default implementation is to return {@code false}.
202   *
203   * @return {@code true} if this authentication policy is a password policy,
204   *         otherwise {@code false}.
205   */
206  public boolean isPasswordPolicy()
207  {
208    return false;
209  }
210
211
212
213  /**
214   * Returns the authentication policy state object for the provided user using
215   * the current time as the basis for all time-based state logic (such as
216   * expiring passwords).
217   * <p>
218   * The default implementation is to call
219   * {@link #createAuthenticationPolicyState(Entry, long)} with the current
220   * time.
221   *
222   * @param userEntry
223   *          The user's entry.
224   * @return The authentication policy state object for the provided user.
225   * @throws DirectoryException
226   *           If a problem occurs while attempting to initialize the state
227   *           object from the provided user entry.
228   */
229  public AuthenticationPolicyState createAuthenticationPolicyState(
230      Entry userEntry) throws DirectoryException
231  {
232    return createAuthenticationPolicyState(userEntry, TimeThread.getTime());
233  }
234
235
236
237  /**
238   * Returns an authentication policy state object for the provided user using
239   * the specified time as the basis for all time-based state logic (such as
240   * expiring passwords).
241   *
242   * @param userEntry
243   *          The user's entry.
244   * @param time
245   *          The time since the epoch to use for all time-based state logic
246   *          (such as expiring passwords).
247   * @return The authentication policy state object for the provided user.
248   * @throws DirectoryException
249   *           If a problem occurs while attempting to initialize the state
250   *           object from the provided user entry.
251   */
252  public abstract AuthenticationPolicyState createAuthenticationPolicyState(
253      Entry userEntry, long time) throws DirectoryException;
254
255
256
257  /**
258   * Performs any necessary work to finalize this authentication policy.
259   * <p>
260   * The default implementation is to do nothing.
261   */
262  public void finalizeAuthenticationPolicy()
263  {
264    // Do nothing by default.
265  }
266}