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 2008-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2014-2015 ForgeRock AS
026 */
027
028package org.opends.guitools.controlpanel.task;
029
030import static org.opends.messages.AdminToolMessages.*;
031
032import java.util.ArrayList;
033import java.util.Collection;
034import java.util.HashSet;
035import java.util.Set;
036import java.util.TreeSet;
037
038import javax.naming.Context;
039import javax.naming.NamingEnumeration;
040import javax.naming.directory.SearchControls;
041import javax.naming.directory.SearchResult;
042import javax.naming.ldap.InitialLdapContext;
043
044import org.opends.admin.ads.util.ConnectionUtils;
045import org.opends.guitools.controlpanel.browser.BrowserController;
046import org.opends.guitools.controlpanel.datamodel.BackendDescriptor;
047import org.opends.guitools.controlpanel.datamodel.BaseDNDescriptor;
048import org.opends.guitools.controlpanel.datamodel.ControlPanelInfo;
049import org.opends.guitools.controlpanel.ui.ProgressDialog;
050import org.opends.guitools.controlpanel.ui.nodes.BasicNode;
051import org.opends.guitools.controlpanel.util.Utilities;
052import org.forgerock.i18n.LocalizableMessage;
053import org.opends.server.config.ConfigConstants;
054import org.opends.server.tools.LDAPPasswordModify;
055import org.opends.server.types.DN;
056import org.opends.server.types.OpenDsException;
057
058/**
059 * The task called when we want to reset the password of the user.
060 *
061 */
062public class ResetUserPasswordTask extends Task
063{
064  private Set<String> backendSet;
065  private BasicNode node;
066  private char[] currentPassword;
067  private char[] newPassword;
068  private DN dn;
069  private boolean useAdminCtx;
070
071  /**
072   * Constructor of the task.
073   * @param info the control panel information.
074   * @param dlg the progress dialog where the task progress will be displayed.
075   * @param node the node corresponding to the entry whose password is going
076   * to be reset.
077   * @param controller the BrowserController.
078   * @param pwd the new password.
079   */
080  public ResetUserPasswordTask(ControlPanelInfo info, ProgressDialog dlg,
081      BasicNode node, BrowserController controller, char[] pwd)
082  {
083    super(info, dlg);
084    backendSet = new HashSet<>();
085    this.node = node;
086    this.newPassword = pwd;
087    try
088    {
089      dn = DN.valueOf(node.getDN());
090      for (BackendDescriptor backend : info.getServerDescriptor().getBackends())
091      {
092        for (BaseDNDescriptor baseDN : backend.getBaseDns())
093        {
094          if (dn.isDescendantOf(baseDN.getDn()))
095          {
096            backendSet.add(backend.getBackendID());
097          }
098        }
099      }
100    }
101    catch (OpenDsException ode)
102    {
103      throw new RuntimeException("Could not parse DN: "+node.getDN(), ode);
104    }
105    try
106    {
107      InitialLdapContext ctx =
108        controller.findConnectionForDisplayedEntry(node);
109      if (ctx != null && isBoundAs(dn, ctx))
110      {
111        currentPassword = ConnectionUtils.getBindPassword(ctx).toCharArray();
112      }
113    }
114    catch (Throwable t)
115    {
116    }
117    useAdminCtx = controller.isConfigurationNode(node);
118  }
119
120  /** {@inheritDoc} */
121  public Type getType()
122  {
123    return Type.MODIFY_ENTRY;
124  }
125
126  /** {@inheritDoc} */
127  public Set<String> getBackends()
128  {
129    return backendSet;
130  }
131
132  /** {@inheritDoc} */
133  public LocalizableMessage getTaskDescription()
134  {
135    return INFO_CTRL_PANEL_RESET_USER_PASSWORD_TASK_DESCRIPTION.get(
136        node.getDN());
137  }
138
139  /** {@inheritDoc} */
140  public boolean regenerateDescriptor()
141  {
142    return false;
143  }
144
145  /** {@inheritDoc} */
146  protected String getCommandLinePath()
147  {
148    return getCommandLinePath("ldappasswordmodify");
149  }
150
151  /** {@inheritDoc} */
152  protected ArrayList<String> getCommandLineArguments()
153  {
154    ArrayList<String> args = new ArrayList<>();
155    if (currentPassword == null)
156    {
157      args.add("--authzID");
158      args.add("dn:"+dn);
159    }
160    else
161    {
162      args.add("--currentPassword");
163      args.add(String.valueOf(currentPassword));
164    }
165    args.add("--newPassword");
166    args.add(String.valueOf(newPassword));
167    args.addAll(getConnectionCommandLineArguments(useAdminCtx, true));
168    args.add(getNoPropertiesFileArgument());
169    return args;
170  }
171
172  /** {@inheritDoc} */
173  public boolean canLaunch(Task taskToBeLaunched,
174      Collection<LocalizableMessage> incompatibilityReasons)
175  {
176    if (!isServerRunning()
177        && state == State.RUNNING
178        && runningOnSameServer(taskToBeLaunched))
179    {
180      // All the operations are incompatible if they apply to this
181      // backend for safety.  This is a short operation so the limitation
182      // has not a lot of impact.
183      Set<String> backends = new TreeSet<>(taskToBeLaunched.getBackends());
184      backends.retainAll(getBackends());
185      if (!backends.isEmpty())
186      {
187        incompatibilityReasons.add(getIncompatibilityMessage(this, taskToBeLaunched));
188        return false;
189      }
190    }
191    return true;
192  }
193
194  /** {@inheritDoc} */
195  public void runTask()
196  {
197    state = State.RUNNING;
198    lastException = null;
199    try
200    {
201      ArrayList<String> arguments = getCommandLineArguments();
202      String[] args = new String[arguments.size()];
203      arguments.toArray(args);
204
205      returnCode = LDAPPasswordModify.mainPasswordModify(args, false,
206            outPrintStream, errorPrintStream);
207
208      if (returnCode != 0)
209      {
210        state = State.FINISHED_WITH_ERROR;
211      }
212      else
213      {
214        if (lastException == null && currentPassword != null)
215        {
216          // The connections must be updated, just update the environment, which
217          // is what we use to clone connections and to launch scripts.
218          // The environment will also be used if we want to reconnect.
219          getInfo().getDirContext().addToEnvironment(
220              Context.SECURITY_CREDENTIALS,
221              String.valueOf(newPassword));
222          if (getInfo().getUserDataDirContext() != null)
223          {
224            getInfo().getUserDataDirContext().addToEnvironment(
225                Context.SECURITY_CREDENTIALS,
226                String.valueOf(newPassword));
227          }
228        }
229        state = State.FINISHED_SUCCESSFULLY;
230      }
231    }
232    catch (Throwable t)
233    {
234      lastException = t;
235      state = State.FINISHED_WITH_ERROR;
236    }
237  }
238
239  /**
240   * Returns <CODE>true</CODE> if we are bound using the provided entry.  In
241   * the case of root entries this is not necessarily the same as using that
242   * particular DN (we might be binding using a value specified in
243   * ds-cfg-alternate-bind-dn).
244   * @param dn the DN.
245   * @param ctx the connection that we are using to modify the password.
246   * @return <CODE>true</CODE> if we are bound using the provided entry.
247   */
248  private boolean isBoundAs(DN dn, InitialLdapContext ctx)
249  {
250    boolean isBoundAs = false;
251    DN bindDN = DN.rootDN();
252    try
253    {
254      String b = ConnectionUtils.getBindDN(ctx);
255      bindDN = DN.valueOf(b);
256      isBoundAs = dn.equals(bindDN);
257    }
258    catch (Throwable t)
259    {
260      // Ignore
261    }
262    if (!isBoundAs)
263    {
264      try
265      {
266        SearchControls ctls = new SearchControls();
267        ctls.setSearchScope(SearchControls.OBJECT_SCOPE);
268        String filter =
269          "(|(objectClass=*)(objectclass=ldapsubentry))";
270        String attrName = ConfigConstants.ATTR_ROOTDN_ALTERNATE_BIND_DN;
271        ctls.setReturningAttributes(new String[] {attrName});
272        NamingEnumeration<SearchResult> entries =
273          ctx.search(Utilities.getJNDIName(dn.toString()), filter, ctls);
274
275        try
276        {
277          while (entries.hasMore())
278          {
279            SearchResult sr = entries.next();
280            Set<String> dns = ConnectionUtils.getValues(sr, attrName);
281            for (String sDn : dns)
282            {
283              if (bindDN.equals(DN.valueOf(sDn)))
284              {
285                isBoundAs = true;
286                break;
287              }
288            }
289          }
290        }
291        finally
292        {
293          entries.close();
294        }
295      }
296      catch (Throwable t)
297      {
298      }
299    }
300    return isBoundAs;
301  }
302}