org.sonar.server.authentication.SsoAuthenticator.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.server.authentication.SsoAuthenticator.java

Source

/*
 * SonarQube
 * Copyright (C) 2009-2017 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.sonar.server.authentication;

import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import javax.annotation.CheckForNull;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.sonar.api.Startable;
import org.sonar.api.config.Settings;
import org.sonar.api.server.authentication.Display;
import org.sonar.api.server.authentication.IdentityProvider;
import org.sonar.api.server.authentication.UserIdentity;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.db.user.UserDto;
import org.sonar.server.authentication.event.AuthenticationEvent;
import org.sonar.server.authentication.event.AuthenticationException;
import org.sonar.server.exceptions.BadRequestException;

import static org.apache.commons.lang.StringUtils.defaultIfBlank;
import static org.apache.commons.lang.time.DateUtils.addMinutes;
import static org.sonar.server.authentication.event.AuthenticationEvent.Source;
import static org.sonar.server.user.ExternalIdentity.SQ_AUTHORITY;

public class SsoAuthenticator implements Startable {

    private static final Logger LOG = Loggers.get(SsoAuthenticator.class);

    private static final Splitter COMA_SPLITTER = Splitter.on(",").trimResults().omitEmptyStrings();

    private static final String ENABLE_PARAM = "sonar.web.sso.enable";

    private static final String LOGIN_HEADER_PARAM = "sonar.web.sso.loginHeader";
    private static final String LOGIN_HEADER_DEFAULT_VALUE = "X-Forwarded-Login";

    private static final String NAME_HEADER_PARAM = "sonar.web.sso.nameHeader";
    private static final String NAME_HEADER_DEFAULT_VALUE = "X-Forwarded-Name";

    private static final String EMAIL_HEADER_PARAM = "sonar.web.sso.emailHeader";
    private static final String EMAIL_HEADER_DEFAULT_VALUE = "X-Forwarded-Email";

    private static final String GROUPS_HEADER_PARAM = "sonar.web.sso.groupsHeader";
    private static final String GROUPS_HEADER_DEFAULT_VALUE = "X-Forwarded-Groups";

    private static final String REFRESH_INTERVAL_PARAM = "sonar.web.sso.refreshIntervalInMinutes";
    private static final String REFRESH_INTERVAL_DEFAULT_VALUE = "5";

    private static final String LAST_REFRESH_TIME_TOKEN_PARAM = "ssoLastRefreshTime";

    private static final Map<String, String> DEFAULT_VALUES_BY_SETTING_KEYS = ImmutableMap.of(LOGIN_HEADER_PARAM,
            LOGIN_HEADER_DEFAULT_VALUE, NAME_HEADER_PARAM, NAME_HEADER_DEFAULT_VALUE, EMAIL_HEADER_PARAM,
            EMAIL_HEADER_DEFAULT_VALUE, GROUPS_HEADER_PARAM, GROUPS_HEADER_DEFAULT_VALUE, REFRESH_INTERVAL_PARAM,
            REFRESH_INTERVAL_DEFAULT_VALUE);

    private final System2 system2;
    private final Settings settings;
    private final UserIdentityAuthenticator userIdentityAuthenticator;
    private final JwtHttpHandler jwtHttpHandler;
    private final AuthenticationEvent authenticationEvent;

    private boolean enabled = false;
    private Map<String, String> settingsByKey = new HashMap<>();

    public SsoAuthenticator(System2 system2, Settings settings, UserIdentityAuthenticator userIdentityAuthenticator,
            JwtHttpHandler jwtHttpHandler, AuthenticationEvent authenticationEvent) {
        this.system2 = system2;
        this.settings = settings;
        this.userIdentityAuthenticator = userIdentityAuthenticator;
        this.jwtHttpHandler = jwtHttpHandler;
        this.authenticationEvent = authenticationEvent;
    }

    @Override
    public void start() {
        if (settings.getBoolean(ENABLE_PARAM)) {
            LOG.info("SSO Authentication enabled");
            enabled = true;
            DEFAULT_VALUES_BY_SETTING_KEYS.entrySet().forEach(
                    entry -> settingsByKey.put(entry.getKey(), defaultIfBlank(settings.getString(entry.getKey()),
                            DEFAULT_VALUES_BY_SETTING_KEYS.get(entry.getKey()))));
        }
    }

    @Override
    public void stop() {
        // Nothing to do
    }

    public Optional<UserDto> authenticate(HttpServletRequest request, HttpServletResponse response) {
        try {
            return doAuthenticate(request, response);
        } catch (BadRequestException e) {
            throw AuthenticationException.newBuilder().setSource(Source.sso()).setMessage(e.getMessage()).build();
        }
    }

    private Optional<UserDto> doAuthenticate(HttpServletRequest request, HttpServletResponse response) {
        if (!enabled) {
            return Optional.empty();
        }
        Map<String, String> headerValuesByNames = getHeaders(request);
        String login = getHeaderValue(headerValuesByNames, LOGIN_HEADER_PARAM);
        if (login == null) {
            return Optional.empty();
        }
        Optional<UserDto> user = getUserFromToken(request, response);
        if (user.isPresent() && login.equals(user.get().getLogin())) {
            return user;
        }

        UserDto userDto = doAuthenticate(headerValuesByNames, login);
        jwtHttpHandler.generateToken(userDto, ImmutableMap.of(LAST_REFRESH_TIME_TOKEN_PARAM, system2.now()),
                request, response);
        authenticationEvent.loginSuccess(request, userDto.getLogin(), Source.sso());
        return Optional.of(userDto);
    }

    private Optional<UserDto> getUserFromToken(HttpServletRequest request, HttpServletResponse response) {
        Optional<JwtHttpHandler.Token> token = jwtHttpHandler.getToken(request, response);
        if (!token.isPresent()) {
            return Optional.empty();
        }
        Date now = new Date(system2.now());
        int refreshIntervalInMinutes = Integer.parseInt(settingsByKey.get(REFRESH_INTERVAL_PARAM));
        Long lastFreshTime = (Long) token.get().getProperties().get(LAST_REFRESH_TIME_TOKEN_PARAM);
        if (lastFreshTime == null || now.after(addMinutes(new Date(lastFreshTime), refreshIntervalInMinutes))) {
            return Optional.empty();
        }
        return Optional.of(token.get().getUserDto());
    }

    private UserDto doAuthenticate(Map<String, String> headerValuesByNames, String login) {
        String name = getHeaderValue(headerValuesByNames, NAME_HEADER_PARAM);
        String email = getHeaderValue(headerValuesByNames, EMAIL_HEADER_PARAM);
        UserIdentity.Builder userIdentityBuilder = UserIdentity.builder().setLogin(login)
                .setName(name == null ? login : name).setEmail(email).setProviderLogin(login);
        if (hasHeader(headerValuesByNames, GROUPS_HEADER_PARAM)) {
            String groupsValue = getHeaderValue(headerValuesByNames, GROUPS_HEADER_PARAM);
            userIdentityBuilder.setGroups(groupsValue == null ? Collections.emptySet()
                    : new HashSet<>(COMA_SPLITTER.splitToList(groupsValue)));
        }
        return userIdentityAuthenticator.authenticate(userIdentityBuilder.build(), new SsoIdentityProvider(),
                Source.sso());
    }

    @CheckForNull
    private String getHeaderValue(Map<String, String> headerValuesByNames, String settingKey) {
        return headerValuesByNames.get(settingsByKey.get(settingKey).toLowerCase(Locale.ENGLISH));
    }

    private static Map<String, String> getHeaders(HttpServletRequest request) {
        Map<String, String> headers = new HashMap<>();
        Collections.list(request.getHeaderNames())
                .forEach(header -> headers.put(header.toLowerCase(Locale.ENGLISH), request.getHeader(header)));
        return headers;
    }

    private boolean hasHeader(Map<String, String> headerValuesByNames, String settingKey) {
        return headerValuesByNames.keySet().contains(settingsByKey.get(settingKey).toLowerCase(Locale.ENGLISH));
    }

    private static class SsoIdentityProvider implements IdentityProvider {
        @Override
        public String getKey() {
            return SQ_AUTHORITY;
        }

        @Override
        public String getName() {
            return getKey();
        }

        @Override
        public Display getDisplay() {
            return null;
        }

        @Override
        public boolean isEnabled() {
            return true;
        }

        @Override
        public boolean allowsUsersToSignUp() {
            return true;
        }
    }
}