3 * Copyright (C) 2009-2020 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 javax.servlet.http.Cookie;
23 import javax.servlet.http.HttpServletRequest;
24 import javax.servlet.http.HttpServletResponse;
25 import org.junit.Before;
26 import org.junit.Rule;
27 import org.junit.Test;
28 import org.mockito.ArgumentCaptor;
29 import org.sonar.api.config.internal.MapSettings;
30 import org.sonar.api.server.authentication.BaseIdentityProvider;
31 import org.sonar.api.utils.System2;
32 import org.sonar.db.DbTester;
33 import org.sonar.server.authentication.event.AuthenticationEvent;
34 import org.sonar.server.authentication.event.AuthenticationEvent.Method;
35 import org.sonar.server.authentication.event.AuthenticationEvent.Source;
36 import org.sonar.server.authentication.event.AuthenticationException;
37 import org.sonar.server.tester.AnonymousMockUserSession;
38 import org.sonar.server.tester.MockUserSession;
39 import org.sonar.server.user.ThreadLocalUserSession;
40 import org.sonar.server.user.UserSession;
42 import static org.assertj.core.api.Assertions.assertThat;
43 import static org.mockito.ArgumentMatchers.eq;
44 import static org.mockito.Mockito.doThrow;
45 import static org.mockito.Mockito.mock;
46 import static org.mockito.Mockito.reset;
47 import static org.mockito.Mockito.verify;
48 import static org.mockito.Mockito.verifyZeroInteractions;
49 import static org.mockito.Mockito.when;
51 public class UserSessionInitializerTest {
54 public DbTester dbTester = DbTester.create(System2.INSTANCE);
56 private ThreadLocalUserSession threadLocalSession = mock(ThreadLocalUserSession.class);
57 private HttpServletRequest request = mock(HttpServletRequest.class);
58 private HttpServletResponse response = mock(HttpServletResponse.class);
59 private RequestAuthenticator authenticator = mock(RequestAuthenticator.class);
60 private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class);
61 private MapSettings settings = new MapSettings();
62 private ArgumentCaptor<Cookie> cookieArgumentCaptor = ArgumentCaptor.forClass(Cookie.class);
64 private UserSessionInitializer underTest = new UserSessionInitializer(settings.asConfig(), threadLocalSession, authenticationEvent, authenticator);
68 when(request.getContextPath()).thenReturn("");
69 when(request.getRequestURI()).thenReturn("/measures");
73 public void check_urls() {
74 assertPathIsNotIgnored("/");
75 assertPathIsNotIgnored("/foo");
76 assertPathIsNotIgnored("/api/server_id/show");
78 assertPathIsIgnored("/api/authentication/login");
79 assertPathIsIgnored("/api/authentication/logout");
80 assertPathIsIgnored("/api/authentication/validate");
81 assertPathIsIgnored("/batch/index");
82 assertPathIsIgnored("/batch/file");
83 assertPathIsIgnored("/maintenance/index");
84 assertPathIsIgnored("/setup/index");
85 assertPathIsIgnored("/sessions/new");
86 assertPathIsIgnored("/sessions/logout");
87 assertPathIsIgnored("/sessions/unauthorized");
88 assertPathIsIgnored("/oauth2/callback/github");
89 assertPathIsIgnored("/oauth2/callback/foo");
90 assertPathIsIgnored("/api/system/db_migration_status");
91 assertPathIsIgnored("/api/system/status");
92 assertPathIsIgnored("/api/system/migrate_db");
93 assertPathIsIgnored("/api/server/version");
94 assertPathIsIgnored("/api/users/identity_providers");
95 assertPathIsIgnored("/api/l10n/index");
97 // exlude passcode urls
98 assertPathIsIgnoredWithAnonymousAccess("/api/ce/info");
99 assertPathIsIgnoredWithAnonymousAccess("/api/ce/pause");
100 assertPathIsIgnoredWithAnonymousAccess("/api/ce/resume");
101 assertPathIsIgnoredWithAnonymousAccess("/api/system/health");
103 // exclude static resources
104 assertPathIsIgnored("/css/style.css");
105 assertPathIsIgnored("/images/logo.png");
106 assertPathIsIgnored("/js/jquery.js");
110 public void return_code_401_when_not_authenticated_and_with_force_authentication() {
111 ArgumentCaptor<AuthenticationException> exceptionArgumentCaptor = ArgumentCaptor.forClass(AuthenticationException.class);
112 when(threadLocalSession.isLoggedIn()).thenReturn(false);
113 when(authenticator.authenticate(request, response)).thenReturn(new AnonymousMockUserSession());
114 settings.setProperty("sonar.forceAuthentication", true);
116 assertThat(underTest.initUserSession(request, response)).isTrue();
118 verifyZeroInteractions(response);
119 verify(authenticationEvent).loginFailure(eq(request), exceptionArgumentCaptor.capture());
120 verifyZeroInteractions(threadLocalSession);
121 AuthenticationException authenticationException = exceptionArgumentCaptor.getValue();
122 assertThat(authenticationException.getSource()).isEqualTo(Source.local(Method.BASIC));
123 assertThat(authenticationException.getLogin()).isNull();
124 assertThat(authenticationException.getMessage()).isEqualTo("User must be authenticated");
125 assertThat(authenticationException.getPublicMessage()).isNull();
129 public void return_401_and_stop_on_ws() {
130 when(request.getRequestURI()).thenReturn("/api/issues");
131 AuthenticationException authenticationException = AuthenticationException.newBuilder().setSource(Source.jwt()).setMessage("Token id hasn't been found").build();
132 doThrow(authenticationException).when(authenticator).authenticate(request, response);
134 assertThat(underTest.initUserSession(request, response)).isFalse();
136 verify(response).setStatus(401);
137 verify(authenticationEvent).loginFailure(request, authenticationException);
138 verifyZeroInteractions(threadLocalSession);
142 public void return_401_and_stop_on_batch_ws() {
143 when(request.getRequestURI()).thenReturn("/batch/global");
144 doThrow(AuthenticationException.newBuilder().setSource(Source.jwt()).setMessage("Token id hasn't been found").build())
145 .when(authenticator).authenticate(request, response);
147 assertThat(underTest.initUserSession(request, response)).isFalse();
149 verify(response).setStatus(401);
150 verifyZeroInteractions(threadLocalSession);
154 public void return_to_session_unauthorized_when_error_on_from_external_provider() throws Exception {
155 doThrow(AuthenticationException.newBuilder().setSource(Source.external(newBasicIdentityProvider("failing"))).setPublicMessage("Token id hasn't been found").build())
156 .when(authenticator).authenticate(request, response);
158 assertThat(underTest.initUserSession(request, response)).isFalse();
160 verify(response).sendRedirect("/sessions/unauthorized");
161 verify(response).addCookie(cookieArgumentCaptor.capture());
162 Cookie cookie = cookieArgumentCaptor.getValue();
163 assertThat(cookie.getName()).isEqualTo("AUTHENTICATION-ERROR");
164 assertThat(cookie.getValue()).isEqualTo("Token%20id%20hasn%27t%20been%20found");
165 assertThat(cookie.getPath()).isEqualTo("/");
166 assertThat(cookie.isHttpOnly()).isFalse();
167 assertThat(cookie.getMaxAge()).isEqualTo(300);
168 assertThat(cookie.getSecure()).isFalse();
172 public void return_to_session_unauthorized_when_error_on_from_external_provider_with_context_path() throws Exception {
173 when(request.getContextPath()).thenReturn("/sonarqube");
174 doThrow(AuthenticationException.newBuilder().setSource(Source.external(newBasicIdentityProvider("failing"))).setPublicMessage("Token id hasn't been found").build())
175 .when(authenticator).authenticate(request, response);
177 assertThat(underTest.initUserSession(request, response)).isFalse();
179 verify(response).sendRedirect("/sonarqube/sessions/unauthorized");
182 private void assertPathIsIgnored(String path) {
183 when(request.getRequestURI()).thenReturn(path);
185 assertThat(underTest.initUserSession(request, response)).isTrue();
187 verifyZeroInteractions(threadLocalSession, authenticator);
188 reset(threadLocalSession, authenticator);
191 private void assertPathIsIgnoredWithAnonymousAccess(String path) {
192 when(request.getRequestURI()).thenReturn(path);
193 UserSession session = new AnonymousMockUserSession();
194 when(authenticator.authenticate(request, response)).thenReturn(session);
196 assertThat(underTest.initUserSession(request, response)).isTrue();
198 verify(threadLocalSession).set(session);
199 reset(threadLocalSession, authenticator);
202 private void assertPathIsNotIgnored(String path) {
203 when(request.getRequestURI()).thenReturn(path);
204 UserSession session = new MockUserSession("foo");
205 when(authenticator.authenticate(request, response)).thenReturn(session);
207 assertThat(underTest.initUserSession(request, response)).isTrue();
209 verify(threadLocalSession).set(session);
210 reset(threadLocalSession, authenticator);
213 private static BaseIdentityProvider newBasicIdentityProvider(String name) {
214 BaseIdentityProvider mock = mock(BaseIdentityProvider.class);
215 when(mock.getName()).thenReturn(name);