You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

UserSessionInitializerTest.java 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2021 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. import javax.servlet.http.Cookie;
  22. import javax.servlet.http.HttpServletRequest;
  23. import javax.servlet.http.HttpServletResponse;
  24. import org.junit.Before;
  25. import org.junit.Rule;
  26. import org.junit.Test;
  27. import org.mockito.ArgumentCaptor;
  28. import org.sonar.api.config.internal.MapSettings;
  29. import org.sonar.api.server.authentication.BaseIdentityProvider;
  30. import org.sonar.api.utils.System2;
  31. import org.sonar.db.DbTester;
  32. import org.sonar.server.authentication.event.AuthenticationEvent;
  33. import org.sonar.server.authentication.event.AuthenticationEvent.Method;
  34. import org.sonar.server.authentication.event.AuthenticationEvent.Source;
  35. import org.sonar.server.authentication.event.AuthenticationException;
  36. import org.sonar.server.tester.AnonymousMockUserSession;
  37. import org.sonar.server.tester.MockUserSession;
  38. import org.sonar.server.user.ThreadLocalUserSession;
  39. import org.sonar.server.user.UserSession;
  40. import static org.assertj.core.api.Assertions.assertThat;
  41. import static org.mockito.ArgumentMatchers.eq;
  42. import static org.mockito.Mockito.doThrow;
  43. import static org.mockito.Mockito.mock;
  44. import static org.mockito.Mockito.reset;
  45. import static org.mockito.Mockito.verify;
  46. import static org.mockito.Mockito.verifyNoMoreInteractions;
  47. import static org.mockito.Mockito.when;
  48. public class UserSessionInitializerTest {
  49. @Rule
  50. public DbTester dbTester = DbTester.create(System2.INSTANCE);
  51. private ThreadLocalUserSession threadLocalSession = mock(ThreadLocalUserSession.class);
  52. private HttpServletRequest request = mock(HttpServletRequest.class);
  53. private HttpServletResponse response = mock(HttpServletResponse.class);
  54. private RequestAuthenticator authenticator = mock(RequestAuthenticator.class);
  55. private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class);
  56. private MapSettings settings = new MapSettings();
  57. private ArgumentCaptor<Cookie> cookieArgumentCaptor = ArgumentCaptor.forClass(Cookie.class);
  58. private UserSessionInitializer underTest = new UserSessionInitializer(settings.asConfig(), threadLocalSession, authenticationEvent, authenticator);
  59. @Before
  60. public void setUp() {
  61. when(request.getContextPath()).thenReturn("");
  62. when(request.getRequestURI()).thenReturn("/measures");
  63. }
  64. @Test
  65. public void check_urls() {
  66. assertPathIsNotIgnored("/");
  67. assertPathIsNotIgnored("/foo");
  68. assertPathIsNotIgnored("/api/server_id/show");
  69. assertPathIsIgnored("/api/authentication/login");
  70. assertPathIsIgnored("/api/authentication/logout");
  71. assertPathIsIgnored("/api/authentication/validate");
  72. assertPathIsIgnored("/batch/index");
  73. assertPathIsIgnored("/batch/file");
  74. assertPathIsIgnored("/maintenance/index");
  75. assertPathIsIgnored("/setup/index");
  76. assertPathIsIgnored("/sessions/new");
  77. assertPathIsIgnored("/sessions/logout");
  78. assertPathIsIgnored("/sessions/unauthorized");
  79. assertPathIsIgnored("/oauth2/callback/github");
  80. assertPathIsIgnored("/oauth2/callback/foo");
  81. assertPathIsIgnored("/api/system/db_migration_status");
  82. assertPathIsIgnored("/api/system/status");
  83. assertPathIsIgnored("/api/system/migrate_db");
  84. assertPathIsIgnored("/api/server/version");
  85. assertPathIsIgnored("/api/users/identity_providers");
  86. assertPathIsIgnored("/api/l10n/index");
  87. // exclude project_badge url, as they can be auth. by a token as queryparam
  88. assertPathIsIgnored("/api/project_badges/measure");
  89. assertPathIsIgnored("/api/project_badges/quality_gate");
  90. // exlude passcode urls
  91. assertPathIsIgnoredWithAnonymousAccess("/api/ce/info");
  92. assertPathIsIgnoredWithAnonymousAccess("/api/ce/pause");
  93. assertPathIsIgnoredWithAnonymousAccess("/api/ce/resume");
  94. assertPathIsIgnoredWithAnonymousAccess("/api/system/health");
  95. assertPathIsIgnoredWithAnonymousAccess("/api/system/liveness");
  96. assertPathIsIgnoredWithAnonymousAccess("/api/monitoring/metrics");
  97. // exclude static resources
  98. assertPathIsIgnored("/css/style.css");
  99. assertPathIsIgnored("/images/logo.png");
  100. assertPathIsIgnored("/js/jquery.js");
  101. }
  102. @Test
  103. public void return_code_401_when_not_authenticated_and_with_force_authentication() {
  104. ArgumentCaptor<AuthenticationException> exceptionArgumentCaptor = ArgumentCaptor.forClass(AuthenticationException.class);
  105. when(threadLocalSession.isLoggedIn()).thenReturn(false);
  106. when(authenticator.authenticate(request, response)).thenReturn(new AnonymousMockUserSession());
  107. assertThat(underTest.initUserSession(request, response)).isTrue();
  108. verifyNoMoreInteractions(response);
  109. verify(authenticationEvent).loginFailure(eq(request), exceptionArgumentCaptor.capture());
  110. verifyNoMoreInteractions(threadLocalSession);
  111. AuthenticationException authenticationException = exceptionArgumentCaptor.getValue();
  112. assertThat(authenticationException.getSource()).isEqualTo(Source.local(Method.BASIC));
  113. assertThat(authenticationException.getLogin()).isNull();
  114. assertThat(authenticationException.getMessage()).isEqualTo("User must be authenticated");
  115. assertThat(authenticationException.getPublicMessage()).isNull();
  116. }
  117. @Test
  118. public void does_not_return_code_401_when_not_authenticated_and_with_force_authentication_off() {
  119. when(threadLocalSession.isLoggedIn()).thenReturn(false);
  120. when(authenticator.authenticate(request, response)).thenReturn(new AnonymousMockUserSession());
  121. settings.setProperty("sonar.forceAuthentication", false);
  122. assertThat(underTest.initUserSession(request, response)).isTrue();
  123. verifyNoMoreInteractions(response);
  124. }
  125. @Test
  126. public void return_401_and_stop_on_ws() {
  127. when(request.getRequestURI()).thenReturn("/api/issues");
  128. AuthenticationException authenticationException = AuthenticationException.newBuilder().setSource(Source.jwt()).setMessage("Token id hasn't been found").build();
  129. doThrow(authenticationException).when(authenticator).authenticate(request, response);
  130. assertThat(underTest.initUserSession(request, response)).isFalse();
  131. verify(response).setStatus(401);
  132. verify(authenticationEvent).loginFailure(request, authenticationException);
  133. verifyNoMoreInteractions(threadLocalSession);
  134. }
  135. @Test
  136. public void return_401_and_stop_on_batch_ws() {
  137. when(request.getRequestURI()).thenReturn("/batch/global");
  138. doThrow(AuthenticationException.newBuilder().setSource(Source.jwt()).setMessage("Token id hasn't been found").build())
  139. .when(authenticator).authenticate(request, response);
  140. assertThat(underTest.initUserSession(request, response)).isFalse();
  141. verify(response).setStatus(401);
  142. verifyNoMoreInteractions(threadLocalSession);
  143. }
  144. @Test
  145. public void return_to_session_unauthorized_when_error_on_from_external_provider() throws Exception {
  146. doThrow(AuthenticationException.newBuilder().setSource(Source.external(newBasicIdentityProvider("failing"))).setPublicMessage("Token id hasn't been found").build())
  147. .when(authenticator).authenticate(request, response);
  148. assertThat(underTest.initUserSession(request, response)).isFalse();
  149. verify(response).sendRedirect("/sessions/unauthorized");
  150. verify(response).addCookie(cookieArgumentCaptor.capture());
  151. Cookie cookie = cookieArgumentCaptor.getValue();
  152. assertThat(cookie.getName()).isEqualTo("AUTHENTICATION-ERROR");
  153. assertThat(cookie.getValue()).isEqualTo("Token%20id%20hasn%27t%20been%20found");
  154. assertThat(cookie.getPath()).isEqualTo("/");
  155. assertThat(cookie.isHttpOnly()).isFalse();
  156. assertThat(cookie.getMaxAge()).isEqualTo(300);
  157. assertThat(cookie.getSecure()).isFalse();
  158. }
  159. @Test
  160. public void return_to_session_unauthorized_when_error_on_from_external_provider_with_context_path() throws Exception {
  161. when(request.getContextPath()).thenReturn("/sonarqube");
  162. doThrow(AuthenticationException.newBuilder().setSource(Source.external(newBasicIdentityProvider("failing"))).setPublicMessage("Token id hasn't been found").build())
  163. .when(authenticator).authenticate(request, response);
  164. assertThat(underTest.initUserSession(request, response)).isFalse();
  165. verify(response).sendRedirect("/sonarqube/sessions/unauthorized");
  166. }
  167. private void assertPathIsIgnored(String path) {
  168. when(request.getRequestURI()).thenReturn(path);
  169. assertThat(underTest.initUserSession(request, response)).isTrue();
  170. verifyNoMoreInteractions(threadLocalSession, authenticator);
  171. reset(threadLocalSession, authenticator);
  172. }
  173. private void assertPathIsIgnoredWithAnonymousAccess(String path) {
  174. when(request.getRequestURI()).thenReturn(path);
  175. UserSession session = new AnonymousMockUserSession();
  176. when(authenticator.authenticate(request, response)).thenReturn(session);
  177. assertThat(underTest.initUserSession(request, response)).isTrue();
  178. verify(threadLocalSession).set(session);
  179. reset(threadLocalSession, authenticator);
  180. }
  181. private void assertPathIsNotIgnored(String path) {
  182. when(request.getRequestURI()).thenReturn(path);
  183. UserSession session = new MockUserSession("foo");
  184. when(authenticator.authenticate(request, response)).thenReturn(session);
  185. assertThat(underTest.initUserSession(request, response)).isTrue();
  186. verify(threadLocalSession).set(session);
  187. reset(threadLocalSession, authenticator);
  188. }
  189. private static BaseIdentityProvider newBasicIdentityProvider(String name) {
  190. BaseIdentityProvider mock = mock(BaseIdentityProvider.class);
  191. when(mock.getName()).thenReturn(name);
  192. return mock;
  193. }
  194. }