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