]> source.dussan.org Git - sonarqube.git/blob
29ed2e65195b94244627a33861f85449429a6921
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2019 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
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.
10  *
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.
15  *
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.
19  */
20 package org.sonar.server.authentication;
21
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;
30 import java.util.Map;
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.UserRegistration.ExistingEmailStrategy;
46 import org.sonar.server.authentication.event.AuthenticationEvent;
47 import org.sonar.server.authentication.event.AuthenticationEvent.Source;
48 import org.sonar.server.authentication.event.AuthenticationException;
49 import org.sonar.server.exceptions.BadRequestException;
50
51 import static org.apache.commons.lang.time.DateUtils.addMinutes;
52 import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_EMAIL_HEADER;
53 import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_ENABLE;
54 import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_GROUPS_HEADER;
55 import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_LOGIN_HEADER;
56 import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_NAME_HEADER;
57 import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_REFRESH_INTERVAL_IN_MINUTES;
58 import static org.sonar.server.user.ExternalIdentity.SQ_AUTHORITY;
59
60 /**
61  * Authentication based on the HTTP request headers. The front proxy
62  * is responsible for validating user identity.
63  */
64 public class HttpHeadersAuthentication implements Startable {
65
66   private static final Logger LOG = Loggers.get(HttpHeadersAuthentication.class);
67
68   private static final Splitter COMA_SPLITTER = Splitter.on(",").trimResults().omitEmptyStrings();
69
70   private static final String LAST_REFRESH_TIME_TOKEN_PARAM = "ssoLastRefreshTime";
71
72   private static final EnumSet<ProcessProperties.Property> PROPERTIES = EnumSet.of(
73     SONAR_WEB_SSO_LOGIN_HEADER,
74     SONAR_WEB_SSO_NAME_HEADER,
75     SONAR_WEB_SSO_EMAIL_HEADER,
76     SONAR_WEB_SSO_GROUPS_HEADER,
77     SONAR_WEB_SSO_REFRESH_INTERVAL_IN_MINUTES);
78
79   private final System2 system2;
80   private final Configuration config;
81   private final UserRegistrar userRegistrar;
82   private final JwtHttpHandler jwtHttpHandler;
83   private final AuthenticationEvent authenticationEvent;
84   private final Map<String, String> settingsByKey = new HashMap<>();
85
86   private boolean enabled = false;
87
88   public HttpHeadersAuthentication(System2 system2, Configuration config, UserRegistrar userRegistrar,
89     JwtHttpHandler jwtHttpHandler, AuthenticationEvent authenticationEvent) {
90     this.system2 = system2;
91     this.config = config;
92     this.userRegistrar = userRegistrar;
93     this.jwtHttpHandler = jwtHttpHandler;
94     this.authenticationEvent = authenticationEvent;
95   }
96
97   @Override
98   public void start() {
99     if (config.getBoolean(SONAR_WEB_SSO_ENABLE.getKey()).orElse(false)) {
100       LOG.info("HTTP headers authentication enabled");
101       enabled = true;
102       PROPERTIES.forEach(entry -> settingsByKey.put(entry.getKey(), config.get(entry.getKey()).orElse(entry.getDefaultValue())));
103     }
104   }
105
106   @Override
107   public void stop() {
108     // Nothing to do
109   }
110
111   public Optional<UserDto> authenticate(HttpServletRequest request, HttpServletResponse response) {
112     try {
113       return doAuthenticate(request, response);
114     } catch (BadRequestException e) {
115       throw AuthenticationException.newBuilder()
116         .setSource(Source.sso())
117         .setMessage(e.getMessage())
118         .build();
119     }
120   }
121
122   private Optional<UserDto> doAuthenticate(HttpServletRequest request, HttpServletResponse response) {
123     if (!enabled) {
124       return Optional.empty();
125     }
126     Map<String, String> headerValuesByNames = getHeaders(request);
127     String login = getHeaderValue(headerValuesByNames, SONAR_WEB_SSO_LOGIN_HEADER.getKey());
128     if (login == null) {
129       return Optional.empty();
130     }
131     Optional<UserDto> user = getUserFromToken(request, response);
132     if (user.isPresent() && login.equals(user.get().getLogin())) {
133       return user;
134     }
135
136     UserDto userDto = doAuthenticate(headerValuesByNames, login);
137     jwtHttpHandler.generateToken(userDto, ImmutableMap.of(LAST_REFRESH_TIME_TOKEN_PARAM, system2.now()), request, response);
138     authenticationEvent.loginSuccess(request, userDto.getLogin(), Source.sso());
139     return Optional.of(userDto);
140   }
141
142   private Optional<UserDto> getUserFromToken(HttpServletRequest request, HttpServletResponse response) {
143     Optional<JwtHttpHandler.Token> token = jwtHttpHandler.getToken(request, response);
144     if (!token.isPresent()) {
145       return Optional.empty();
146     }
147     Date now = new Date(system2.now());
148     int refreshIntervalInMinutes = Integer.parseInt(settingsByKey.get(SONAR_WEB_SSO_REFRESH_INTERVAL_IN_MINUTES.getKey()));
149     Long lastFreshTime = (Long) token.get().getProperties().get(LAST_REFRESH_TIME_TOKEN_PARAM);
150     if (lastFreshTime == null || now.after(addMinutes(new Date(lastFreshTime), refreshIntervalInMinutes))) {
151       return Optional.empty();
152     }
153     return Optional.of(token.get().getUserDto());
154   }
155
156   private UserDto doAuthenticate(Map<String, String> headerValuesByNames, String login) {
157     String name = getHeaderValue(headerValuesByNames, SONAR_WEB_SSO_NAME_HEADER.getKey());
158     String email = getHeaderValue(headerValuesByNames, SONAR_WEB_SSO_EMAIL_HEADER.getKey());
159     UserIdentity.Builder userIdentityBuilder = UserIdentity.builder()
160       .setLogin(login)
161       .setName(name == null ? login : name)
162       .setEmail(email)
163       .setProviderLogin(login);
164     if (hasHeader(headerValuesByNames, SONAR_WEB_SSO_GROUPS_HEADER.getKey())) {
165       String groupsValue = getHeaderValue(headerValuesByNames, SONAR_WEB_SSO_GROUPS_HEADER.getKey());
166       userIdentityBuilder.setGroups(groupsValue == null ? Collections.emptySet() : new HashSet<>(COMA_SPLITTER.splitToList(groupsValue)));
167     }
168     return userRegistrar.register(
169       UserRegistration.builder()
170         .setUserIdentity(userIdentityBuilder.build())
171         .setProvider(new SsoIdentityProvider())
172         .setSource(Source.sso())
173         .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
174         .build());
175   }
176
177   @CheckForNull
178   private String getHeaderValue(Map<String, String> headerValuesByNames, String settingKey) {
179     return headerValuesByNames.get(settingsByKey.get(settingKey).toLowerCase(Locale.ENGLISH));
180   }
181
182   private static Map<String, String> getHeaders(HttpServletRequest request) {
183     Map<String, String> headers = new HashMap<>();
184     Collections.list(request.getHeaderNames()).forEach(header -> headers.put(header.toLowerCase(Locale.ENGLISH), request.getHeader(header)));
185     return headers;
186   }
187
188   private boolean hasHeader(Map<String, String> headerValuesByNames, String settingKey) {
189     return headerValuesByNames.keySet().contains(settingsByKey.get(settingKey).toLowerCase(Locale.ENGLISH));
190   }
191
192   private static class SsoIdentityProvider implements IdentityProvider {
193     @Override
194     public String getKey() {
195       return SQ_AUTHORITY;
196     }
197
198     @Override
199     public String getName() {
200       return getKey();
201     }
202
203     @Override
204     public Display getDisplay() {
205       return null;
206     }
207
208     @Override
209     public boolean isEnabled() {
210       return true;
211     }
212
213     @Override
214     public boolean allowsUsersToSignUp() {
215       return true;
216     }
217   }
218 }