/**
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.waveprotocol.box.server.robots.agent.passwd;
import static org.waveprotocol.box.server.robots.agent.RobotAgentUtil.CANNOT_CHANGE_PASSWORD_FOR_USER;
import static org.waveprotocol.box.server.robots.agent.RobotAgentUtil.changeUserPassword;
import com.google.common.collect.ImmutableMap;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Singleton;
import org.apache.commons.cli.CommandLine;
import org.eclipse.jetty.util.MultiMap;
import org.waveprotocol.box.server.authentication.HttpRequestBasedCallbackHandler;
import org.waveprotocol.box.server.persistence.PersistenceException;
import org.waveprotocol.box.server.robots.agent.AbstractCliRobotAgent;
import org.waveprotocol.wave.model.wave.InvalidParticipantAddress;
import org.waveprotocol.wave.model.wave.ParticipantId;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
/**
* Robot agent that allows a user to change her own password. The userId should
* be from this wave domain.
*
* When the robot is added to a wave, it prints basic description and then
* inspects text entered by the user. When a new line char is entered it scans
* the last line of the text in the blip and parses it using Apache CLI command
* line interpreter. If the command found to be valid, the robot validates user
* credentials and then changes the password of the user to a new one.
*
* @author yurize@apache.org (Yuri Zelikov)
*/
@SuppressWarnings("serial")
@Singleton
public final class PasswordRobot extends AbstractCliRobotAgent {
private static final Logger LOG = Logger.getLogger(PasswordRobot.class.getName());
public static final String ROBOT_URI = AGENT_PREFIX_URI + "/passwd/user";
/** Configuration for the LoginContext. */
private final Configuration configuration;
@Inject
public PasswordRobot(Injector injector) {
super(injector);
configuration = injector.getInstance(Configuration.class);
}
@Override
protected String maybeExecuteCommand(CommandLine commandLine, String modifiedBy) {
String robotMessage = null;
// Get the user that wants to change her own password.
if (!modifiedBy.endsWith("@" + getWaveDomain())) {
// Can change passwords only for users on this wave domain.
robotMessage =
String.format("User %s does not belong to the @%s domain\n", modifiedBy,
getWaveDomain());
} else {
String[] args = commandLine.getArgs();
try {
ParticipantId participantId = ParticipantId.of(modifiedBy);
if (args.length == 2) {
// If current password is empty, i.e. "", then user should pass
// only the new password.
args = Arrays.copyOf(args, 3);
args[2] = args[1];
args[1] = "";
}
String oldPassword = args[1];
String newPassword = args[2];
verifyCredentials(oldPassword, participantId);
changeUserPassword(newPassword, participantId, getAccountStore());
robotMessage =
String.format("Changed password for user %s, the new password is: %s", modifiedBy,
newPassword);
LOG.info(modifiedBy + " changed password for user: " + modifiedBy);
} catch (IllegalArgumentException e) {
robotMessage = e.getMessage();
LOG.log(Level.SEVERE, "userId: " + modifiedBy, e);
} catch (PersistenceException e) {
robotMessage = CANNOT_CHANGE_PASSWORD_FOR_USER + modifiedBy;
LOG.log(Level.SEVERE, "userId: " + modifiedBy, e);
} catch (InvalidParticipantAddress e) {
robotMessage = CANNOT_CHANGE_PASSWORD_FOR_USER + modifiedBy;
LOG.log(Level.SEVERE, "userId: " + modifiedBy, e);
} catch (LoginException e) {
robotMessage =
CANNOT_CHANGE_PASSWORD_FOR_USER + modifiedBy
+ ". Please verify your old password";
LOG.log(Level.SEVERE, "userId: " + modifiedBy, e);
}
}
return robotMessage;
}
/**
* Verifies user credentials.
*
* @param oldPassword the password to verify.
* @param participantId the participantId of the user.
* @throws LoginException if the user provided incorrect password.
*/
private void verifyCredentials(String password, ParticipantId participantId)
throws LoginException {
MultiMap<String> parameters =
new MultiMap<String>(ImmutableMap.of("password", password, "address",
participantId.getAddress()));
CallbackHandler callbackHandler = new HttpRequestBasedCallbackHandler(parameters);
LoginContext context = new LoginContext("Wave", new Subject(), callbackHandler, configuration);
// If authentication fails, login() will throw a LoginException.
context.login();
}
@Override
public String getFullDescription() {
return getShortDescription() + " If your password is empty - enter only the new password.\n"
+ getUsage() + "\nExample: " + getCommandName() + " " + getExample();
}
@Override
public String getCmdLineSyntax() {
return "[OPTIONS] [OLD_PASSWORD] [NEW_PASSWORD]";
}
@Override
public String getExample() {
return "old_password new_password";
}
@Override
public String getShortDescription() {
return "The command allows users to change their own password. "
+ "Please make sure to use it in a wave without other participants. "
+ "It is also advised to remove yourself from the wave "
+ "when you finished changing the password.";
}
@Override
public String getCommandName() {
return "passwd";
}
@Override
public String getRobotName() {
return "Passwd-Bot";
}
@Override
public int getMinNumOfArguments() {
return 1;
}
@Override
public int getMaxNumOfArguments() {
return 2;
}
@Override
public String getRobotUri() {
return ROBOT_URI;
}
@Override
public String getRobotId() {
return "passwd-bot";
}
}