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