3 * Copyright (C) 2009-2022 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 package org.sonar.server.authentication;
22 import com.google.common.base.Splitter;
23 import com.google.common.collect.ImmutableMap;
24 import java.util.Collections;
25 import java.util.Date;
26 import java.util.EnumSet;
27 import java.util.HashMap;
28 import java.util.HashSet;
29 import java.util.Locale;
31 import java.util.Optional;
32 import javax.annotation.CheckForNull;
33 import javax.servlet.http.HttpServletRequest;
34 import javax.servlet.http.HttpServletResponse;
35 import org.sonar.api.Startable;
36 import org.sonar.api.config.Configuration;
37 import org.sonar.api.server.authentication.Display;
38 import org.sonar.api.server.authentication.IdentityProvider;
39 import org.sonar.api.server.authentication.UserIdentity;
40 import org.sonar.api.utils.System2;
41 import org.sonar.api.utils.log.Logger;
42 import org.sonar.api.utils.log.Loggers;
43 import org.sonar.db.user.UserDto;
44 import org.sonar.process.ProcessProperties;
45 import org.sonar.server.authentication.event.AuthenticationEvent;
46 import org.sonar.server.authentication.event.AuthenticationEvent.Source;
47 import org.sonar.server.authentication.event.AuthenticationException;
48 import org.sonar.server.exceptions.BadRequestException;
50 import static org.apache.commons.lang.time.DateUtils.addMinutes;
51 import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_EMAIL_HEADER;
52 import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_ENABLE;
53 import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_GROUPS_HEADER;
54 import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_LOGIN_HEADER;
55 import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_NAME_HEADER;
56 import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_REFRESH_INTERVAL_IN_MINUTES;
57 import static org.sonar.server.user.ExternalIdentity.SQ_AUTHORITY;
60 * Authentication based on the HTTP request headers. The front proxy
61 * is responsible for validating user identity.
63 public class HttpHeadersAuthentication implements Startable {
65 private static final Logger LOG = Loggers.get(HttpHeadersAuthentication.class);
67 private static final Splitter COMA_SPLITTER = Splitter.on(",").trimResults().omitEmptyStrings();
69 private static final String LAST_REFRESH_TIME_TOKEN_PARAM = "ssoLastRefreshTime";
71 private static final EnumSet<ProcessProperties.Property> PROPERTIES = EnumSet.of(
72 SONAR_WEB_SSO_LOGIN_HEADER,
73 SONAR_WEB_SSO_NAME_HEADER,
74 SONAR_WEB_SSO_EMAIL_HEADER,
75 SONAR_WEB_SSO_GROUPS_HEADER,
76 SONAR_WEB_SSO_REFRESH_INTERVAL_IN_MINUTES);
78 private final System2 system2;
79 private final Configuration config;
80 private final UserRegistrar userRegistrar;
81 private final JwtHttpHandler jwtHttpHandler;
82 private final AuthenticationEvent authenticationEvent;
83 private final Map<String, String> settingsByKey = new HashMap<>();
85 private boolean enabled = false;
87 public HttpHeadersAuthentication(System2 system2, Configuration config, UserRegistrar userRegistrar,
88 JwtHttpHandler jwtHttpHandler, AuthenticationEvent authenticationEvent) {
89 this.system2 = system2;
91 this.userRegistrar = userRegistrar;
92 this.jwtHttpHandler = jwtHttpHandler;
93 this.authenticationEvent = authenticationEvent;
98 if (config.getBoolean(SONAR_WEB_SSO_ENABLE.getKey()).orElse(false)) {
99 LOG.info("HTTP headers authentication enabled");
101 PROPERTIES.forEach(entry -> settingsByKey.put(entry.getKey(), config.get(entry.getKey()).orElse(entry.getDefaultValue())));
110 public Optional<UserDto> authenticate(HttpServletRequest request, HttpServletResponse response) {
112 return doAuthenticate(request, response);
113 } catch (BadRequestException e) {
114 throw AuthenticationException.newBuilder()
115 .setSource(Source.sso())
116 .setMessage(e.getMessage())
121 private Optional<UserDto> doAuthenticate(HttpServletRequest request, HttpServletResponse response) {
123 return Optional.empty();
125 Map<String, String> headerValuesByNames = getHeaders(request);
126 String login = getHeaderValue(headerValuesByNames, SONAR_WEB_SSO_LOGIN_HEADER.getKey());
128 return Optional.empty();
130 Optional<UserDto> user = getUserFromToken(request, response);
131 if (user.isPresent() && login.equals(user.get().getLogin())) {
135 UserDto userDto = doAuthenticate(headerValuesByNames, login);
136 jwtHttpHandler.generateToken(userDto, ImmutableMap.of(LAST_REFRESH_TIME_TOKEN_PARAM, system2.now()), request, response);
137 authenticationEvent.loginSuccess(request, userDto.getLogin(), Source.sso());
138 return Optional.of(userDto);
141 private Optional<UserDto> getUserFromToken(HttpServletRequest request, HttpServletResponse response) {
142 Optional<JwtHttpHandler.Token> token = jwtHttpHandler.getToken(request, response);
143 if (!token.isPresent()) {
144 return Optional.empty();
146 Date now = new Date(system2.now());
147 int refreshIntervalInMinutes = Integer.parseInt(settingsByKey.get(SONAR_WEB_SSO_REFRESH_INTERVAL_IN_MINUTES.getKey()));
148 Long lastFreshTime = (Long) token.get().getProperties().get(LAST_REFRESH_TIME_TOKEN_PARAM);
149 if (lastFreshTime == null || now.after(addMinutes(new Date(lastFreshTime), refreshIntervalInMinutes))) {
150 return Optional.empty();
152 return Optional.of(token.get().getUserDto());
155 private UserDto doAuthenticate(Map<String, String> headerValuesByNames, String login) {
156 String name = getHeaderValue(headerValuesByNames, SONAR_WEB_SSO_NAME_HEADER.getKey());
157 String email = getHeaderValue(headerValuesByNames, SONAR_WEB_SSO_EMAIL_HEADER.getKey());
158 UserIdentity.Builder userIdentityBuilder = UserIdentity.builder()
159 .setName(name == null ? login : name)
161 .setProviderLogin(login);
162 if (hasHeader(headerValuesByNames, SONAR_WEB_SSO_GROUPS_HEADER.getKey())) {
163 String groupsValue = getHeaderValue(headerValuesByNames, SONAR_WEB_SSO_GROUPS_HEADER.getKey());
164 userIdentityBuilder.setGroups(groupsValue == null ? Collections.emptySet() : new HashSet<>(COMA_SPLITTER.splitToList(groupsValue)));
166 return userRegistrar.register(
167 UserRegistration.builder()
168 .setUserIdentity(userIdentityBuilder.build())
169 .setProvider(new SsoIdentityProvider())
170 .setSource(Source.sso())
175 private String getHeaderValue(Map<String, String> headerValuesByNames, String settingKey) {
176 return headerValuesByNames.get(settingsByKey.get(settingKey).toLowerCase(Locale.ENGLISH));
179 private static Map<String, String> getHeaders(HttpServletRequest request) {
180 Map<String, String> headers = new HashMap<>();
181 Collections.list(request.getHeaderNames()).forEach(header -> headers.put(header.toLowerCase(Locale.ENGLISH), request.getHeader(header)));
185 private boolean hasHeader(Map<String, String> headerValuesByNames, String settingKey) {
186 return headerValuesByNames.containsKey(settingsByKey.get(settingKey).toLowerCase(Locale.ENGLISH));
189 private static class SsoIdentityProvider implements IdentityProvider {
191 public String getKey() {
196 public String getName() {
201 public Display getDisplay() {
206 public boolean isEnabled() {
211 public boolean allowsUsersToSignUp() {