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}