com.haulmont.cuba.web.security.ConnectionImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.haulmont.cuba.web.security.ConnectionImpl.java

Source

/*
 * Copyright (c) 2008-2017 Haulmont.
 *
 * 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 com.haulmont.cuba.web.security;

import com.haulmont.bali.events.EventRouter;
import com.haulmont.cuba.client.ClientUserSession;
import com.haulmont.cuba.core.global.ClientType;
import com.haulmont.cuba.core.global.Events;
import com.haulmont.cuba.core.global.GlobalConfig;
import com.haulmont.cuba.core.global.Messages;
import com.haulmont.cuba.core.sys.AppContext;
import com.haulmont.cuba.core.sys.SecurityContext;
import com.haulmont.cuba.gui.executors.BackgroundWorker;
import com.haulmont.cuba.security.app.UserSessionService;
import com.haulmont.cuba.security.auth.AbstractClientCredentials;
import com.haulmont.cuba.security.auth.AuthenticationDetails;
import com.haulmont.cuba.security.auth.AuthenticationService;
import com.haulmont.cuba.security.auth.Credentials;
import com.haulmont.cuba.security.entity.User;
import com.haulmont.cuba.security.global.*;
import com.haulmont.cuba.web.Connection;
import com.haulmont.cuba.web.security.events.*;
import com.haulmont.cuba.web.sys.VaadinSessionScope;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinService;
import com.vaadin.server.VaadinSession;
import com.vaadin.server.WebBrowser;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.List;
import java.util.TimeZone;

/**
 * Default {@link Connection} implementation for web-client.
 */
@Component(Connection.NAME)
@Scope(VaadinSessionScope.NAME)
public class ConnectionImpl extends EventRouter implements Connection {

    private static final Logger log = LoggerFactory.getLogger(ConnectionImpl.class);

    @Inject
    protected AuthenticationService authenticationService;
    @Inject
    protected UserSessionService userSessionService;
    @Inject
    protected List<LoginProvider> loginProviders;

    @Inject
    protected Events events;
    @Inject
    protected Messages messages;
    @Inject
    protected BackgroundWorker backgroundWorker;
    @Inject
    protected GlobalConfig globalConfig;

    // initial or used on login IP of the user
    protected String userRemoteAddress = null;

    @Override
    public void login(Credentials credentials) throws LoginException {
        backgroundWorker.checkUIAccess();

        preprocessCredentials(credentials);

        AuthenticationDetails authenticationDetails = loginInternal(credentials);

        ClientUserSession clientUserSession = createSession(authenticationDetails.getSession());
        if (credentials instanceof AnonymousUserCredentials) {
            clientUserSession.setAuthenticated(false);
        } else {
            clientUserSession.setAuthenticated(true);
        }

        UserSession previousSession = getSession();

        setSessionInternal(clientUserSession);

        publishUserConnectedEvent(credentials);

        fireStateChangeListeners(previousSession, clientUserSession);
    }

    protected ClientUserSession createSession(UserSession userSession) {
        return new ClientUserSession(userSession);
    }

    protected void preprocessCredentials(Credentials credentials) {
        if (credentials instanceof AbstractClientCredentials) {
            AbstractClientCredentials clientCredentials = (AbstractClientCredentials) credentials;
            clientCredentials.setClientType(ClientType.WEB);
            clientCredentials.setClientInfo(makeClientInfo());
            clientCredentials.setTimeZone(detectTimeZone());

            String currentUserRemoteAddress = getUserRemoteAddress();
            // update userRemoteAddress if current HTTP request is available
            if (currentUserRemoteAddress != null) {
                this.userRemoteAddress = currentUserRemoteAddress;
            }

            clientCredentials.setIpAddress(userRemoteAddress);
        }
    }

    @Nullable
    protected String getUserRemoteAddress() {
        VaadinRequest currentRequest = VaadinService.getCurrentRequest();

        String userRemoteAddress = null;

        if (currentRequest != null) {
            String xForwardedFor = currentRequest.getHeader("X_FORWARDED_FOR");
            if (StringUtils.isNotBlank(xForwardedFor)) {
                String[] strings = xForwardedFor.split(",");
                String userAddressFromHeader = StringUtils.trimToEmpty(strings[strings.length - 1]);

                if (StringUtils.isNotEmpty(userAddressFromHeader)) {
                    userRemoteAddress = userAddressFromHeader;
                } else {
                    userRemoteAddress = currentRequest.getRemoteAddr();
                }
            } else {
                userRemoteAddress = currentRequest.getRemoteAddr();
            }
        }

        return userRemoteAddress;
    }

    protected String makeClientInfo() {
        // timezone info is passed only on VaadinSession creation
        WebBrowser webBrowser = getWebBrowserDetails();

        //noinspection UnnecessaryLocalVariable
        String serverInfo = String.format("Web (%s:%s/%s) %s", globalConfig.getWebHostName(),
                globalConfig.getWebPort(), globalConfig.getWebContextName(), webBrowser.getBrowserApplication());

        return serverInfo;
    }

    protected TimeZone detectTimeZone() {
        WebBrowser webBrowser = getWebBrowserDetails();

        int offset = webBrowser.getTimezoneOffset() / 1000 / 60;
        char sign = offset >= 0 ? '+' : '-';
        int absOffset = Math.abs(offset);

        String hours = StringUtils.leftPad(String.valueOf(absOffset / 60), 2, '0');
        String minutes = StringUtils.leftPad(String.valueOf(absOffset % 60), 2, '0');

        return TimeZone.getTimeZone("GMT" + sign + hours + minutes);
    }

    protected WebBrowser getWebBrowserDetails() {
        // timezone info is passed only on VaadinSession creation
        WebBrowser webBrowser = VaadinSession.getCurrent().getBrowser();
        VaadinRequest currentRequest = VaadinService.getCurrentRequest();
        // update web browser instance if current request is not null
        // it can be null in case of background/async processing of login request
        if (currentRequest != null) {
            webBrowser.updateRequestDetails(currentRequest);
        }
        return webBrowser;
    }

    protected AuthenticationDetails loginInternal(Credentials credentials) throws LoginException {
        Class<? extends Credentials> credentialsClass = credentials.getClass();

        AuthenticationDetails details = null;
        try {
            publishBeforeLoginEvent(credentials);

            List<LoginProvider> providers = getProviders();

            for (LoginProvider provider : providers) {
                if (!provider.supports(credentialsClass)) {
                    continue;
                }

                log.trace("Login attempt using {}", provider.getClass().getName());

                try {
                    details = provider.login(credentials);

                    if (details != null) {
                        log.trace("Login successful for {}", credentials);

                        // publish login success
                        publishUserSessionStartedEvent(credentials, details);

                        return details;
                    }
                } catch (LoginException e) {
                    // publish auth fail
                    publishLoginFailed(credentials, provider, e);

                    throw e;
                } catch (RuntimeException re) {
                    InternalAuthenticationException ie = new InternalAuthenticationException(
                            "Exception is thrown by login provider", re);

                    // publish auth fail
                    publishLoginFailed(credentials, provider, ie);

                    throw ie;
                }
            }
        } finally {
            publishAfterLoginEvent(credentials, details);
        }

        throw new UnsupportedCredentialsException(
                "Unable to find login provider that supports credentials class " + credentialsClass.getName());
    }

    protected void fireStateChangeListeners(UserSession previousSession, UserSession newSession) {
        StateChangeEvent event = new StateChangeEvent(this, previousSession, newSession);
        fireEvent(StateChangeListener.class, StateChangeListener::connectionStateChanged, event);
    }

    protected void fireSubstitutionListeners() {
        UserSubstitutedEvent event = new UserSubstitutedEvent(this);
        fireEvent(UserSubstitutionListener.class, UserSubstitutionListener::userSubstituted, event);
    }

    protected void publishUserConnectedEvent(Credentials credentials) {
        events.publish(new UserConnectedEvent(this, credentials));
    }

    protected void publishBeforeLoginEvent(Credentials credentials) throws LoginException {
        try {
            events.publish(new BeforeLoginEvent(credentials));
        } catch (UndeclaredThrowableException e) {
            rethrowLoginException(e);
        }
    }

    protected void publishAfterLoginEvent(Credentials credentials, AuthenticationDetails authenticationDetails) {
        events.publish(new AfterLoginEvent(credentials, authenticationDetails));
    }

    protected void publishLoginFailed(Credentials credentials, LoginProvider provider, LoginException e)
            throws LoginException {
        try {
            events.publish(new LoginFailureEvent(credentials, provider, e));
        } catch (UndeclaredThrowableException re) {
            rethrowLoginException(re);
        }
    }

    protected void publishUserSessionStartedEvent(Credentials credentials,
            AuthenticationDetails authenticationDetails) {
        events.publish(new UserSessionStartedEvent(this, credentials, authenticationDetails));
    }

    protected void rethrowLoginException(RuntimeException e) throws LoginException {
        Throwable cause = e.getCause();
        if (cause instanceof LoginException) {
            throw (LoginException) cause;
        } else {
            throw e;
        }
    }

    protected ClientUserSession getSessionInternal() {
        return (ClientUserSession) VaadinSession.getCurrent().getAttribute(UserSession.class);
    }

    protected void setSessionInternal(ClientUserSession userSession) {
        VaadinSession.getCurrent().setAttribute(UserSession.class, userSession);
        if (userSession != null) {
            AppContext.setSecurityContext(new SecurityContext(userSession));
        } else {
            AppContext.setSecurityContext(null);
        }
    }

    @Override
    public void logout() {
        backgroundWorker.checkUIAccess();

        ClientUserSession session = getSessionInternal();

        if (session == null) {
            throw new IllegalStateException("There is no active session");
        }
        if (!session.isAuthenticated()) {
            throw new IllegalStateException("Active session is not authenticated");
        }

        if (session.isAuthenticated()) {
            authenticationService.logout();
        }

        publishUserSessionFinishedEvent(session);

        UserSession previousSession = getSession();

        setSessionInternal(null);

        removeListeners(UserSubstitutionListener.class);

        publishDisconnectedEvent(previousSession);

        fireStateChangeListeners(previousSession, null);
    }

    protected void publishUserSessionFinishedEvent(UserSession session) {
        events.publish(new UserSessionFinishedEvent(this, session));
    }

    protected void publishUserSessionSubstitutedEvent(UserSession previousSession, UserSession session) {
        events.publish(new UserSessionSubstitutedEvent(this, previousSession, session));
    }

    protected void publishDisconnectedEvent(UserSession previousSession) {
        events.publish(new UserDisconnectedEvent(this, previousSession));
    }

    @Override
    @Nullable
    public UserSession getSession() {
        return getSessionInternal();
    }

    @Override
    public void substituteUser(User substitutedUser) {
        UserSession previousSession = getSession();

        UserSession session = authenticationService.substituteUser(substitutedUser);

        ClientUserSession clientUserSession = createSession(session);
        clientUserSession.setAuthenticated(true);

        setSessionInternal(clientUserSession);

        publishUserSessionSubstitutedEvent(previousSession, clientUserSession);

        fireSubstitutionListeners();
    }

    @Override
    public boolean isConnected() {
        return getSessionInternal() != null;
    }

    @Override
    public boolean isAuthenticated() {
        ClientUserSession session = getSessionInternal();
        return session != null && session.isAuthenticated();
    }

    protected List<LoginProvider> getProviders() {
        return loginProviders;
    }

    @Override
    public boolean isAlive() {
        if (!isConnected()) {
            return false;
        }

        UserSession session = getSession();
        if (session == null) {
            return false;
        }

        try {
            userSessionService.getUserSession(session.getId());
        } catch (NoUserSessionException ignored) {
            return false;
        }

        return true;
    }

    @Override
    public void addStateChangeListener(StateChangeListener listener) {
        addListener(StateChangeListener.class, listener);
    }

    @Override
    public void removeStateChangeListener(StateChangeListener listener) {
        removeListener(StateChangeListener.class, listener);
    }

    @Override
    public void addUserSubstitutionListener(UserSubstitutionListener listener) {
        addListener(UserSubstitutionListener.class, listener);
    }

    @Override
    public void removeUserSubstitutionListener(UserSubstitutionListener listener) {
        removeListener(UserSubstitutionListener.class, listener);
    }

    @PostConstruct
    protected void init() {
        this.userRemoteAddress = getUserRemoteAddress();
    }
}