package org.bubblecloud.ilves.ui.anonymous;
import com.vaadin.data.Property;
import com.vaadin.data.util.ObjectProperty;
import com.vaadin.data.util.PropertysetItem;
import com.vaadin.data.validator.StringLengthValidator;
import com.vaadin.server.VaadinService;
import com.vaadin.server.VaadinServletRequest;
import com.vaadin.shared.ui.MarginInfo;
import com.vaadin.shared.ui.label.ContentMode;
import com.vaadin.ui.*;
import org.apache.log4j.Logger;
import org.bubblecloud.ilves.component.grid.FieldDescriptor;
import org.bubblecloud.ilves.component.grid.ValidatingEditor;
import org.bubblecloud.ilves.component.grid.ValidatingEditorStateListener;
import org.bubblecloud.ilves.exception.SiteException;
import org.bubblecloud.ilves.model.Company;
import org.bubblecloud.ilves.model.EmailPasswordReset;
import org.bubblecloud.ilves.model.User;
import org.bubblecloud.ilves.security.PasswordLoginUtil;
import org.bubblecloud.ilves.security.UserDao;
import org.bubblecloud.ilves.site.AbstractViewlet;
import org.bubblecloud.ilves.util.StringUtil;
import javax.persistence.EntityManager;
import javax.servlet.http.HttpServletRequest;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* Viewlet for email validation.
*/
public class PasswordResetViewlet extends AbstractViewlet {
/** The logger. */
private static final Logger LOGGER = Logger.getLogger(PasswordResetViewlet.class);
/** Validating editor. */
private ValidatingEditor editor;
/** Password reset PIN property. */
private Property pinProperty;
/** Password property. */
private Property passwordProperty;
/** Password characters. */
private static final String PASSWORD_CHARACTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ!#%&,.-+*";
/** Random for password generation. */
private static SecureRandom random = new SecureRandom();
@Override
public final void enter(final String parameters) {
final HttpServletRequest request = ((VaadinServletRequest) VaadinService.getCurrentRequest())
.getHttpServletRequest();
final Company company = getSite().getSiteContext().getObject(Company.class);
if (!company.isEmailPasswordReset()) {
LOGGER.error("Password reset attempted but email password reset is disabled in company. "
+ "Email password reset ID: " + parameters
+ " (IP: " + request.getRemoteHost() + ":" + request.getRemotePort() + ")");
return;
}
final String emailPasswordResetId = parameters;
final EntityManager entityManager = getSite().getSiteContext().getObject(EntityManager.class);
final EmailPasswordReset emailPasswordReset
= UserDao.getEmailPasswordReset(entityManager, emailPasswordResetId);
if (emailPasswordReset != null) {
final User user = emailPasswordReset.getUser();
if (!user.getOwner().getCompanyId().equals(company.getCompanyId())) {
LOGGER.error("Password reset attempted through wrong company: " + user.getEmailAddress()
+ " (IP: " + request.getRemoteHost() + ":" + request.getRemotePort() + ")");
return;
}
final List<FieldDescriptor> fieldDescriptors = new ArrayList<FieldDescriptor>();
fieldDescriptors.add(new FieldDescriptor("pin", getSite().localize("input-password-reset-pin"),
TextField.class, null, 150, null, String.class, "",
false, true, true
).addValidator(new StringLengthValidator("Invalid PIN length.", 4, 4, false)));
fieldDescriptors.add(new FieldDescriptor("password", getSite().localize("input-password"),
TextField.class, null, 150, null, String.class, "",
true, true, false
));
editor = new ValidatingEditor(fieldDescriptors);
pinProperty = new ObjectProperty<String>(null, String.class);
passwordProperty = new ObjectProperty<String>(null, String.class);
reset();
final Button submitButton = new Button(getSite().localize("button-submit"));
submitButton.setEnabled(false);
submitButton.addClickListener(new Button.ClickListener() {
/** The default serial version ID. */
private static final long serialVersionUID = 1L;
@Override
public void buttonClick(final Button.ClickEvent event) {
editor.commit();
try {
final String pin = (String) pinProperty.getValue();
final byte[] pinAndSaltBytes = (user.getEmailAddress() + ":" + pin).getBytes("UTF-8");
final MessageDigest pinMd = MessageDigest.getInstance("SHA-256");
final byte[] pinAndSaltDigest = pinMd.digest(pinAndSaltBytes);
final String pinAndSaltHash = StringUtil.toHexString(pinAndSaltDigest);
if (emailPasswordReset.getPinHash().equals(pinAndSaltHash)) {
final String password = generatePassword();
PasswordLoginUtil.setUserPasswordHash(user.getOwner(), user, password.toCharArray());
passwordProperty.setValue(password);
submitButton.setEnabled(false);
fieldDescriptors.get(0).setReadOnly(true);
reset();
entityManager.getTransaction().begin();
try {
entityManager.remove(emailPasswordReset);
entityManager.persist(user);
entityManager.getTransaction().commit();
} catch (final Exception e) {
if (entityManager.getTransaction().isActive()) {
entityManager.getTransaction().rollback();
}
throw new SiteException("Error reseting password", e);
}
LOGGER.info("Password reset: " + user.getEmailAddress()
+ " (IP: " + request.getRemoteHost() + ":" + request.getRemotePort() + ")");
Notification.show(getSite().localize("message-password-reset-success"),
Notification.Type.HUMANIZED_MESSAGE);
} else {
entityManager.getTransaction().begin();
try {
entityManager.remove(emailPasswordReset);
entityManager.getTransaction().commit();
} catch (final Exception e) {
if (entityManager.getTransaction().isActive()) {
entityManager.getTransaction().rollback();
}
throw new SiteException("Error removing email reset password row.", e);
}
LOGGER.info("Password reset, invalid pin: " + user.getEmailAddress()
+ " (IP: " + request.getRemoteHost() + ":" + request.getRemotePort() + ")");
Notification.show(getSite().localize("message-invalid-password-reset-pin"),
Notification.Type.WARNING_MESSAGE);
final Company company = getSite().getSiteContext().getObject(Company.class);
//getUI().getPage().setLocation(company.getUrl() + "#!reset");
UI.getCurrent().getNavigator().navigateTo("reset");
getSession().close();
}
} catch (final Exception e) {
LOGGER.error("Error adding user: " + user.getEmailAddress()
+ " (IP: " + request.getRemoteHost() + ":" + request.getRemotePort() + ")", e);
Notification.show(getSite().localize("message-password-reset-error"),
Notification.Type.WARNING_MESSAGE);
}
}
});
editor.addListener(new ValidatingEditorStateListener() {
@Override
public void editorStateChanged(final ValidatingEditor source) {
if (source.isValid()) {
submitButton.setEnabled(true);
} else {
submitButton.setEnabled(false);
}
}
});
final HorizontalLayout titleLayout = new HorizontalLayout();
titleLayout.setMargin(new MarginInfo(true, false, true, false));
titleLayout.setSpacing(true);
final Embedded titleIcon = new Embedded(null, getSite().getIcon("view-icon-password-reset"));
titleIcon.setWidth(32, Unit.PIXELS);
titleIcon.setHeight(32, Unit.PIXELS);
titleLayout.addComponent(titleIcon);
final Label titleLabel = new Label(
"<h1>" + getSite().localize("view-password-reset") + "</h1>", ContentMode.HTML);
titleLayout.addComponent(titleLabel);
final VerticalLayout panel = new VerticalLayout();
panel.addComponent(titleLayout);
panel.addComponent(editor);
panel.addComponent(submitButton);
panel.setSpacing(true);
panel.setMargin(true);
setCompositionRoot(panel);
} else {
Notification.show(getSite().localize("message-password-reset-consumed"),
Notification.Type.WARNING_MESSAGE);
}
}
private PropertysetItem reset() {
final PropertysetItem item = new PropertysetItem();
item.addItemProperty("pin", pinProperty);
item.addItemProperty("password", passwordProperty);
editor.setItem(item, true);
return item;
}
/**
* Generates random password.
*
* @return the random password.
*/
private String generatePassword() {
StringBuilder sb = new StringBuilder();
synchronized (random) {
for( int i = 0; i < 10; i++ ) {
sb.append(PASSWORD_CHARACTERS.charAt(random.nextInt(PASSWORD_CHARACTERS.length())));
}
}
return sb.toString();
}
}