*/
package eu.etaxonomy.cdm.vaadin.view;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+
+import javax.mail.internet.AddressException;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.ApplicationEventPublisher;
-import org.springframework.context.event.EventListener;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.core.env.Environment;
+import org.springframework.mail.MailException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
+import org.springframework.util.concurrent.ListenableFuture;
+import org.vaadin.spring.events.Event;
+import org.vaadin.spring.events.EventBus;
+import org.vaadin.spring.events.EventBusListener;
+import org.vaadin.spring.events.annotation.EventBusListenerMethod;
+import com.vaadin.data.validator.AbstractStringValidator;
import com.vaadin.spring.annotation.SpringComponent;
-import com.vaadin.spring.annotation.UIScope;
+import com.vaadin.spring.annotation.ViewScope;
+import com.vaadin.ui.themes.ValoTheme;
+import eu.etaxonomy.cdm.api.application.ICdmRepository;
+import eu.etaxonomy.cdm.api.config.CdmConfigurationKeys;
+import eu.etaxonomy.cdm.api.service.security.AccountSelfManagementException;
+import eu.etaxonomy.cdm.api.service.security.EmailAddressNotFoundException;
import eu.etaxonomy.cdm.vaadin.event.AuthenticationAttemptEvent;
import eu.etaxonomy.cdm.vaadin.event.AuthenticationSuccessEvent;
+import eu.etaxonomy.cdm.vaadin.event.UserAccountEvent;
+import eu.etaxonomy.cdm.vaadin.ui.UserAccountSelfManagementUI;
+import eu.etaxonomy.cdm.vaadin.util.VaadinServletUtilities;
import eu.etaxonomy.vaadin.mvp.AbstractPresenter;
import eu.etaxonomy.vaadin.ui.navigation.NavigationEvent;
import eu.etaxonomy.vaadin.ui.navigation.NavigationManager;
*
*/
@SpringComponent
-@UIScope // DO NOT CHANGE !!! LoginPresenter must be in UIScope so that the LoginPresenter is available to all Views.
-public class LoginPresenter extends AbstractPresenter<LoginView> {
+@ViewScope
+public class LoginPresenter extends AbstractPresenter<LoginView> implements EventBusListener<AuthenticationAttemptEvent> {
private static final long serialVersionUID = 4020699735656994791L;
private final static String PROPNAME_PASSWORD = "cdm-vaadin.login.pwd";
+ private String redirectToState;
+
+ protected EventBus.UIEventBus uiEventBus;
+
@Autowired
- protected ApplicationEventPublisher eventBus;
+ @Qualifier("cdmRepository")
+ private ICdmRepository repo;
- private String redirectToState;
+ @Autowired
+ protected Environment env;
+
+// @Override
+// protected void eventViewBusSubscription(ViewEventBus viewEventBus) {
+// viewEventBus.subscribe(this);
+// }
+
+ @Autowired
+ protected void setUIEventBus(EventBus.UIEventBus uiEventBus){
+ this.uiEventBus = uiEventBus;
+ uiEventBus.subscribe(this);
+ }
public boolean authenticate(String userName, String password) {
log.debug("user '" + userName + "' authenticated");
currentSecurityContext().setAuthentication(authentication);
if(NavigationManager.class.isAssignableFrom(getNavigationManager().getClass())){
- eventBus.publishEvent(new AuthenticationSuccessEvent(userName));
+ uiEventBus.publish(this, new AuthenticationSuccessEvent(userName));
log.debug("redirecting to " + redirectToState);
- eventBus.publishEvent(new NavigationEvent(redirectToState));
+ uiEventBus.publish(this, new NavigationEvent(redirectToState));
}
}
} catch (AuthenticationException e){
return false;
}
-
-
- /**
- * {@inheritDoc}
- */
@Override
public void handleViewEntered() {
redirectToState = String.join("/", redirectToStateTokens);
}
+ getView().getLoginDialog().getEmail().addValidator(new AbstractStringValidator("An account for this email address already exits. You may want to use the \"Password Revovery\" tab intsead?") {
+ private static final long serialVersionUID = 1L;
+ @Override
+ protected boolean isValidValue(String value) {
+ return !repo.getAccountRegistrationService().emailAddressExists(value);
+ }
+ });
+
// attempt to auto login
if(StringUtils.isNotEmpty(System.getProperty(PROPNAME_USER)) && StringUtils.isNotEmpty(System.getProperty(PROPNAME_PASSWORD))){
log.warn("Performing autologin with user " + System.getProperty(PROPNAME_USER));
authenticate(System.getProperty(PROPNAME_USER), System.getProperty(PROPNAME_PASSWORD));
}
+
}
- @EventListener
- protected void onLoginEvent(AuthenticationAttemptEvent e){
- authenticate(e.getUserName(), getView().getLoginDialog().getPassword().getValue());
+ @Override
+ public void onEvent(Event<AuthenticationAttemptEvent> event) {
+ if(getView()!= null){
+ authenticate(event.getPayload().getUserName(), getView().getLoginDialog().getPassword().getValue());
+ } else {
+ log.info("view is NULL, not yet disposed LoginPresenter?");
+ }
}
+ @EventBusListenerMethod
+ public void onPasswordRevoveryEvent(UserAccountEvent event) throws MalformedURLException, ExecutionException, MailException, AddressException, AccountSelfManagementException {
+
+ if(event.getAction().equals(UserAccountEvent.UserAccountAction.REQUEST_PASSWORD_RESET)) {
+ requestPasswordReset();
+ } else if(event.getAction().equals(UserAccountEvent.UserAccountAction.REGISTER_ACCOUNT)) {
+ requestAccountCreation();
+ }
+ }
+ private void requestPasswordReset() throws MalformedURLException, ExecutionException {
+ String userNameOrEmail = getView().getLoginDialog().getUserNameOrEmail().getValue();
+ URL servletBaseUrl = VaadinServletUtilities.getServletBaseUrl();
+ logger.debug("UserAccountAction.REQUEST_PASSWORD_RESET for " + servletBaseUrl + ", userNameOrEmail:" + userNameOrEmail);
+ // Implementation note: UI modifications allied in the below callback methods will not affect the UI
+ // immediately, therefore we use a CountDownLatch
+ CountDownLatch finshedSignal = new CountDownLatch(1);
+ List<Throwable> asyncException = new ArrayList<>(1);
+ ListenableFuture<Boolean> futureResult = repo.getPasswordResetService().emailResetToken(
+ userNameOrEmail,
+ servletBaseUrl.toString() + "/app/" + UserAccountSelfManagementUI.NAME + "#!" + PasswordResetViewBean.NAME + "/%s");
+ futureResult.addCallback(
+ successFuture -> {
+ finshedSignal.countDown();
+ },
+ exception -> {
+ // possible MailException
+ asyncException.add(exception);
+ finshedSignal.countDown();
+ }
+ );
+ boolean asyncTimeout = false;
+ Boolean result = false;
+ try {
+ finshedSignal.await(2, TimeUnit.SECONDS);
+ result = futureResult.get();
+ } catch (InterruptedException e) {
+ asyncTimeout = true;
+ } catch (Exception e) {
+ // in case executing getUserNameOrEmail() causes an exception faster
+ // than futureResult.addCallback( can be processed, the exception
+ // can not be caught asynchronously
+ // so we are adding all these exceptions here
+ asyncException.add(e);
+ }
+ if(!asyncException.isEmpty()) {
+ String messageText = "An unknown error has occurred.";
+ if(asyncException.get(0) instanceof MailException) {
+ String supportText = "the support";
+ String supportEmail = env.getProperty(CdmConfigurationKeys.MAIL_ADDRESS_SUPPORT);
+ if(supportEmail != null) {
+ supportText = "<a href=\"mailto:" + supportEmail +"\">" + supportEmail + "</a>";
+ }
+ messageText = "Sending the password reset email to you has failed. Please try again later or contact " + supportText + " in case this error persists.";
+ }
+ if(asyncException.get(0) instanceof EmailAddressNotFoundException) {
+ messageText = "There is no user accout for this email address.";
+ }
+ getView().getLoginDialog().getMessageSendRecoveryEmailLabel().setValue(messageText);
+ getView().getLoginDialog().getMessageSendRecoveryEmailLabel().setStyleName(ValoTheme.LABEL_FAILURE);
+ } else {
+ if(!asyncTimeout && result) {
+ getView().getLoginDialog().getMessageSendRecoveryEmailLabel().setValue("An email with a password reset link has been sent to you.");
+ getView().getLoginDialog().getMessageSendRecoveryEmailLabel().setStyleName(ValoTheme.LABEL_SUCCESS);
+ getView().getLoginDialog().getSendOnetimeLogin().setEnabled(false);
+ getView().getLoginDialog().getUserNameOrEmail().setEnabled(false);
+ getView().getLoginDialog().getUserNameOrEmail().setReadOnly(true);
+
+ } else {
+ getView().getLoginDialog().getMessageSendRecoveryEmailLabel().setValue("A timeout has occured, please try again.");
+ getView().getLoginDialog().getMessageSendRecoveryEmailLabel().setStyleName(ValoTheme.LABEL_FAILURE);
+ }
+ }
+ }
+
+ private void requestAccountCreation() throws MalformedURLException, MailException, AddressException, AccountSelfManagementException, ExecutionException {
+ String emailAddress = getView().getLoginDialog().getEmail().getValue();
+ URL servletBaseUrl = VaadinServletUtilities.getServletBaseUrl();
+
+ logger.debug("UserAccountAction.REGISTER_ACCOUNT for " + servletBaseUrl + ", emailAddress:" + emailAddress);
+
+ CountDownLatch finshedSignal = new CountDownLatch(1);
+ List<Throwable> asyncException = new ArrayList<>(1);
+ ListenableFuture<Boolean> futureResult = repo.getAccountRegistrationService().emailAccountRegistrationRequest(emailAddress,
+ servletBaseUrl.toString() + "/app/" + UserAccountSelfManagementUI.NAME + "#!" + AccountRegistrationViewBean.NAME + "/%s");
+ futureResult.addCallback(
+ successFuture -> {
+ finshedSignal.countDown();
+ },
+ exception -> {
+ // possible MailException
+ asyncException.add(exception);
+ finshedSignal.countDown();
+ }
+ );
+ boolean asyncTimeout = false;
+ Boolean result = false;
+ try {
+ finshedSignal.await(2, TimeUnit.SECONDS);
+ result = futureResult.get();
+ } catch (InterruptedException e) {
+ asyncTimeout = true;
+ } catch (Exception e) {
+ // in case executing emailAccountRegistrationRequest() causes an exception faster
+ // than futureResult.addCallback( can be processed, the exception
+ // can not be caught asynchronously
+ // so we are adding all these exceptions here
+ asyncException.add(e);
+ }
+ if(!asyncException.isEmpty()) {
+ getView().getLoginDialog().getRegisterMessageLabel()
+ .setValue("Sending the account resitration email to you has failed. Please try again later or contect the support in case this error persists.");
+ getView().getLoginDialog().getRegisterMessageLabel().setStyleName(ValoTheme.LABEL_FAILURE);
+ } else {
+ if(!asyncTimeout && result) {
+ getView().getLoginDialog().getRegisterMessageLabel().setValue("An email with with further instructions has been sent to you.");
+ getView().getLoginDialog().getRegisterMessageLabel().setStyleName(ValoTheme.LABEL_SUCCESS);
+ getView().getLoginDialog().getEmail().setEnabled(false);
+ getView().getLoginDialog().getRegisterButton().setEnabled(false);
+
+ } else {
+ getView().getLoginDialog().getRegisterMessageLabel().setValue("A timeout has occured, please try again.");
+ getView().getLoginDialog().getRegisterMessageLabel().setStyleName(ValoTheme.LABEL_FAILURE);
+ }
+ }
+ }
}