Project

General

Profile

Download (13.2 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2017 EDIT
3
* European Distributed Institute of Taxonomy
4
* http://www.e-taxonomy.eu
5
*
6
* The contents of this file are subject to the Mozilla Public License Version 1.1
7
* See LICENSE.TXT at the top of this package for the full license terms.
8
*/
9
package eu.etaxonomy.cdm.vaadin.view;
10

    
11
import java.net.MalformedURLException;
12
import java.net.URL;
13
import java.util.ArrayList;
14
import java.util.List;
15
import java.util.concurrent.CountDownLatch;
16
import java.util.concurrent.ExecutionException;
17
import java.util.concurrent.TimeUnit;
18

    
19
import javax.mail.internet.AddressException;
20

    
21
import org.apache.commons.lang.StringUtils;
22
import org.apache.log4j.Logger;
23
import org.springframework.beans.factory.annotation.Autowired;
24
import org.springframework.beans.factory.annotation.Qualifier;
25
import org.springframework.mail.MailException;
26
import org.springframework.security.authentication.AuthenticationManager;
27
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
28
import org.springframework.security.core.Authentication;
29
import org.springframework.security.core.AuthenticationException;
30
import org.springframework.util.concurrent.ListenableFuture;
31
import org.vaadin.spring.events.Event;
32
import org.vaadin.spring.events.EventBus;
33
import org.vaadin.spring.events.EventBusListener;
34
import org.vaadin.spring.events.annotation.EventBusListenerMethod;
35

    
36
import com.vaadin.data.validator.AbstractStringValidator;
37
import com.vaadin.spring.annotation.SpringComponent;
38
import com.vaadin.spring.annotation.ViewScope;
39
import com.vaadin.ui.themes.ValoTheme;
40

    
41
import eu.etaxonomy.cdm.api.application.ICdmRepository;
42
import eu.etaxonomy.cdm.api.service.security.AccountSelfManagementException;
43
import eu.etaxonomy.cdm.api.service.security.EmailAddressNotFoundException;
44
import eu.etaxonomy.cdm.vaadin.event.AuthenticationAttemptEvent;
45
import eu.etaxonomy.cdm.vaadin.event.AuthenticationSuccessEvent;
46
import eu.etaxonomy.cdm.vaadin.event.UserAccountEvent;
47
import eu.etaxonomy.cdm.vaadin.ui.UserAccountSelfManagementUI;
48
import eu.etaxonomy.cdm.vaadin.util.VaadinServletUtilities;
49
import eu.etaxonomy.vaadin.mvp.AbstractPresenter;
50
import eu.etaxonomy.vaadin.ui.navigation.NavigationEvent;
51
import eu.etaxonomy.vaadin.ui.navigation.NavigationManager;
52

    
53
/**
54
 * The {@link LoginView} is used as replacement view in the scope of other views.
55
 * Therefore the LoginPresenter must be in <b>UIScope</b> so that the LoginPresenter
56
 * is available to all Views.
57
 * <p>
58
 * The LoginPresenter offers a <b>auto login feature for developers</b>. To activate the auto login
59
 * you need to provide the <code>user name</code> and <code>password</code> using the environment variables
60
 * <code>cdm-vaadin.login.usr</code> and <code>cdm-vaadin.login.pwd</code>, e.g.:
61
 * <pre>
62
 * -Dcdm-vaadin.login.usr=admin -Dcdm-vaadin.login.pwd=00000
63
 * </pre>
64
 *
65
 * @author a.kohlbecker
66
 * @since Apr 25, 2017
67
 *
68
 */
69
@SpringComponent
70
@ViewScope
71
public class LoginPresenter extends AbstractPresenter<LoginView> implements EventBusListener<AuthenticationAttemptEvent> {
72

    
73
    private static final long serialVersionUID = 4020699735656994791L;
74

    
75
    private static final Logger log = Logger.getLogger(LoginPresenter.class);
76

    
77
    private final static String PROPNAME_USER = "cdm-vaadin.login.usr";
78

    
79
    private final static String PROPNAME_PASSWORD = "cdm-vaadin.login.pwd";
80

    
81
    private String redirectToState;
82

    
83
    protected EventBus.UIEventBus uiEventBus;
84

    
85
    @Autowired
86
    @Qualifier("cdmRepository")
87
    private ICdmRepository repo;
88

    
89
//    @Override
90
//    protected void eventViewBusSubscription(ViewEventBus viewEventBus) {
91
//        viewEventBus.subscribe(this);
92
//    }
93

    
94
    @Autowired
95
    protected void setUIEventBus(EventBus.UIEventBus uiEventBus){
96
        this.uiEventBus = uiEventBus;
97
        uiEventBus.subscribe(this);
98
    }
99

    
100
    public boolean authenticate(String userName, String password) {
101

    
102
        getView().clearMessage();
103

    
104
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userName, password);
105
        AuthenticationManager authenticationManager = getRepo().getAuthenticationManager();
106
        try {
107
            Authentication authentication = authenticationManager.authenticate(token);
108
            if(authentication != null && authentication.isAuthenticated()) {
109
                log.debug("user '" + userName + "' authenticated");
110
                currentSecurityContext().setAuthentication(authentication);
111
                if(NavigationManager.class.isAssignableFrom(getNavigationManager().getClass())){
112
                    uiEventBus.publish(this, new AuthenticationSuccessEvent(userName));
113
                    log.debug("redirecting to " + redirectToState);
114
                    uiEventBus.publish(this, new NavigationEvent(redirectToState));
115
                }
116
            }
117
        } catch (AuthenticationException e){
118
            getView().showErrorMessage("Login failed! Please check your username and password.");
119
        }
120
        return false;
121
    }
122

    
123
    @Override
124
    public void handleViewEntered() {
125

    
126
        List<String> redirectToStateTokens = getNavigationManager().getCurrentViewParameters();
127
        String currentViewName = getNavigationManager().getCurrentViewName();
128

    
129
        if(currentViewName.equals(LoginViewBean.NAME) && redirectToStateTokens.isEmpty()){
130
            // login view is shown in turn to an explicit login request of the user (e.g. login button pressed)
131
            // use the redirectToStateTokens 1-n as redirectToState
132
            //FIXME implement : redirectToState = UserView.NAME
133

    
134
        } else {
135
            // the login view is shown instead of the requested view for which the user needs to login
136
            redirectToState = String.join("/", redirectToStateTokens);
137
        }
138

    
139
        getView().getLoginDialog().getEmail().addValidator(new AbstractStringValidator("An account for this email address already exits. You may want to use the \"Password Revovery\" tab intsead?") {
140
            private static final long serialVersionUID = 1L;
141
            @Override
142
            protected boolean isValidValue(String value) {
143
                return !repo.getAccountRegistrationService().emailAddressExists(value);
144
            }
145
        });
146

    
147
        // attempt to auto login
148
        if(StringUtils.isNotEmpty(System.getProperty(PROPNAME_USER)) && StringUtils.isNotEmpty(System.getProperty(PROPNAME_PASSWORD))){
149
            log.warn("Performing autologin with user " + System.getProperty(PROPNAME_USER));
150
            authenticate(System.getProperty(PROPNAME_USER), System.getProperty(PROPNAME_PASSWORD));
151
        }
152

    
153
    }
154

    
155
    @Override
156
    public void onEvent(Event<AuthenticationAttemptEvent> event) {
157
        if(getView()!= null){
158
            authenticate(event.getPayload().getUserName(), getView().getLoginDialog().getPassword().getValue());
159
        } else {
160
            log.info("view is NULL, not yet disposed LoginPresenter?");
161
        }
162
    }
163

    
164
    @EventBusListenerMethod
165
    public void onPasswordRevoveryEvent(UserAccountEvent event) throws MalformedURLException, ExecutionException, MailException, AddressException, AccountSelfManagementException {
166

    
167
        if(event.getAction().equals(UserAccountEvent.UserAccountAction.REQUEST_PASSWORD_RESET)) {
168
            requestPasswordReset();
169
        } else if(event.getAction().equals(UserAccountEvent.UserAccountAction.REGISTER_ACCOUNT)) {
170
            requestAccountCreation();
171
        }
172
    }
173

    
174
    private void requestPasswordReset() throws MalformedURLException, ExecutionException {
175
        String userNameOrEmail = getView().getLoginDialog().getUserNameOrEmail().getValue();
176
        URL servletBaseUrl = VaadinServletUtilities.getServletBaseUrl();
177
        logger.debug("UserAccountAction.REQUEST_PASSWORD_RESET for " + servletBaseUrl + ", userNameOrEmail:" + userNameOrEmail);
178
        // Implementation note: UI modifications allied in the below callback methods will not affect the UI
179
        // immediately, therefore we use a CountDownLatch
180
        CountDownLatch finshedSignal = new CountDownLatch(1);
181
        List<Throwable> asyncException = new ArrayList<>(1);
182
        ListenableFuture<Boolean> futureResult = repo.getPasswordResetService().emailResetToken(
183
                userNameOrEmail,
184
                servletBaseUrl.toString() + "/app/" + UserAccountSelfManagementUI.NAME + "#!" + PasswordResetViewBean.NAME + "/%s");
185
        futureResult.addCallback(
186
                    successFuture -> {
187
                        finshedSignal.countDown();
188
                    },
189
                    exception -> {
190
                        // possible MailException
191
                        asyncException.add(exception);
192
                        finshedSignal.countDown();
193
                    }
194
                );
195
        boolean asyncTimeout = false;
196
        Boolean result = false;
197
        try {
198
            finshedSignal.await(2, TimeUnit.SECONDS);
199
            result = futureResult.get();
200
        } catch (InterruptedException e) {
201
            asyncTimeout = true;
202
        } catch (Exception e) {
203
            // in case executing getUserNameOrEmail() causes an exeption faster
204
            // than futureResult.addCallback( can be processed, the execption
205
            // can not be caught asynchronously
206
            // so we are adding all these exceptions here
207
            asyncException.add(e);
208
        }
209
        if(!asyncException.isEmpty()) {
210
            String messageText = "An unknown error has occurred.";
211
            if(asyncException.get(0) instanceof MailException) {
212
                messageText = "Sending the password reset email to you has failed. Please try again later or contect the support in case this error persists.";
213
            }
214
            if(asyncException.get(0) instanceof EmailAddressNotFoundException) {
215
                messageText = "There is no user accout for this email address.";
216
            }
217
            getView().getLoginDialog().getMessageSendRecoveryEmailLabel().setValue(messageText);
218
            getView().getLoginDialog().getMessageSendRecoveryEmailLabel().setStyleName(ValoTheme.LABEL_FAILURE);
219
        } else {
220
            if(!asyncTimeout && result) {
221
                getView().getLoginDialog().getMessageSendRecoveryEmailLabel().setValue("An email with a password reset link has been sent to you.");
222
                getView().getLoginDialog().getMessageSendRecoveryEmailLabel().setStyleName(ValoTheme.LABEL_SUCCESS);
223
                getView().getLoginDialog().getSendOnetimeLogin().setEnabled(false);
224
                getView().getLoginDialog().getUserNameOrEmail().setEnabled(false);
225
                getView().getLoginDialog().getUserNameOrEmail().setReadOnly(true);
226

    
227
            } else {
228
                getView().getLoginDialog().getMessageSendRecoveryEmailLabel().setValue("A timeout has occured, please try again.");
229
                getView().getLoginDialog().getMessageSendRecoveryEmailLabel().setStyleName(ValoTheme.LABEL_FAILURE);
230
            }
231
        }
232
    }
233

    
234
    private void requestAccountCreation() throws MalformedURLException, MailException, AddressException, AccountSelfManagementException, ExecutionException {
235
        String emailAddress = getView().getLoginDialog().getEmail().getValue();
236
        URL servletBaseUrl = VaadinServletUtilities.getServletBaseUrl();
237

    
238
        logger.debug("UserAccountAction.REGISTER_ACCOUNT for " + servletBaseUrl + ", emailAddress:" + emailAddress);
239

    
240
        CountDownLatch finshedSignal = new CountDownLatch(1);
241
        List<Throwable> asyncException = new ArrayList<>(1);
242
        ListenableFuture<Boolean> futureResult = repo.getAccountRegistrationService().emailAccountRegistrationRequest(emailAddress,
243
                servletBaseUrl.toString() + "/app/" + UserAccountSelfManagementUI.NAME + "#!" + AccountRegistrationViewBean.NAME + "/%s");
244
        futureResult.addCallback(
245
                    successFuture -> {
246
                        finshedSignal.countDown();
247
                    },
248
                    exception -> {
249
                        // possible MailException
250
                        asyncException.add(exception);
251
                        finshedSignal.countDown();
252
                    }
253
                );
254
        boolean asyncTimeout = false;
255
        Boolean result = false;
256
        try {
257
            finshedSignal.await(2, TimeUnit.SECONDS);
258
            result = futureResult.get();
259
        } catch (InterruptedException e) {
260
            asyncTimeout = true;
261
        }
262
        if(!asyncException.isEmpty()) {
263
            getView().getLoginDialog().getRegisterMessageLabel()
264
            .setValue("Sending the account resitration email to you has failed. Please try again later or contect the support in case this error persists.");
265
            getView().getLoginDialog().getRegisterMessageLabel().setStyleName(ValoTheme.LABEL_FAILURE);
266
        } else {
267
            if(!asyncTimeout && result) {
268
                getView().getLoginDialog().getRegisterMessageLabel().setValue("An email with with further instructions has been sent to you.");
269
                getView().getLoginDialog().getRegisterMessageLabel().setStyleName(ValoTheme.LABEL_SUCCESS);
270
                getView().getLoginDialog().getEmail().setEnabled(false);
271
                getView().getLoginDialog().getRegisterButton().setEnabled(false);
272

    
273
            } else {
274
                getView().getLoginDialog().getRegisterMessageLabel().setValue("A timeout has occured, please try again.");
275
                getView().getLoginDialog().getRegisterMessageLabel().setStyleName(ValoTheme.LABEL_FAILURE);
276
            }
277
        }
278
    }
279

    
280
}
(11-11/18)