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 javax.servlet.http.HttpServletRequest;
23 import org.junit.Before;
24 import org.junit.Test;
25 import org.sonar.api.config.internal.MapSettings;
26 import org.sonar.api.security.Authenticator;
27 import org.sonar.api.security.ExternalGroupsProvider;
28 import org.sonar.api.security.ExternalUsersProvider;
29 import org.sonar.api.security.SecurityRealm;
30 import org.sonar.api.security.UserDetails;
31 import org.sonar.server.authentication.event.AuthenticationEvent;
32 import org.sonar.server.authentication.event.AuthenticationEvent.Source;
33 import org.sonar.server.authentication.event.AuthenticationException;
34 import org.sonar.server.user.SecurityRealmFactory;
36 import static java.util.Arrays.asList;
37 import static org.assertj.core.api.Assertions.assertThat;
38 import static org.assertj.core.api.Assertions.assertThatThrownBy;
39 import static org.mockito.ArgumentMatchers.any;
40 import static org.mockito.Mockito.doThrow;
41 import static org.mockito.Mockito.mock;
42 import static org.mockito.Mockito.verify;
43 import static org.mockito.Mockito.verifyNoMoreInteractions;
44 import static org.mockito.Mockito.verifyZeroInteractions;
45 import static org.mockito.Mockito.when;
46 import static org.sonar.server.authentication.event.AuthenticationEvent.Method.BASIC;
47 import static org.sonar.server.authentication.event.AuthenticationEvent.Method.BASIC_TOKEN;
49 public class CredentialsExternalAuthenticationTest {
51 private static final String LOGIN = "LOGIN";
52 private static final String PASSWORD = "PASSWORD";
54 private static final String REALM_NAME = "realm name";
56 private MapSettings settings = new MapSettings();
58 private SecurityRealmFactory securityRealmFactory = mock(SecurityRealmFactory.class);
59 private SecurityRealm realm = mock(SecurityRealm.class);
60 private Authenticator authenticator = mock(Authenticator.class);
61 private ExternalUsersProvider externalUsersProvider = mock(ExternalUsersProvider.class);
62 private ExternalGroupsProvider externalGroupsProvider = mock(ExternalGroupsProvider.class);
64 private TestUserRegistrar userIdentityAuthenticator = new TestUserRegistrar();
65 private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class);
67 private HttpServletRequest request = mock(HttpServletRequest.class);
69 private CredentialsExternalAuthentication underTest = new CredentialsExternalAuthentication(settings.asConfig(), securityRealmFactory, userIdentityAuthenticator, authenticationEvent);
72 public void setUp() throws Exception {
73 when(realm.getName()).thenReturn(REALM_NAME);
77 public void authenticate() {
78 executeStartWithoutGroupSync();
79 when(authenticator.doAuthenticate(any(Authenticator.Context.class))).thenReturn(true);
80 UserDetails userDetails = new UserDetails();
81 userDetails.setName("name");
82 userDetails.setEmail("email");
83 when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(userDetails);
85 underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC);
87 assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue();
88 assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getProviderLogin()).isEqualTo(LOGIN);
89 assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getProviderId()).isNull();
90 assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getName()).isEqualTo("name");
91 assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getEmail()).isEqualTo("email");
92 assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().shouldSyncGroups()).isFalse();
93 verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, REALM_NAME));
97 public void authenticate_with_sonarqube_identity_provider() {
98 executeStartWithoutGroupSync();
99 when(authenticator.doAuthenticate(any(Authenticator.Context.class))).thenReturn(true);
100 UserDetails userDetails = new UserDetails();
101 userDetails.setName("name");
102 userDetails.setEmail("email");
103 when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(userDetails);
105 underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC);
107 assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue();
108 assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getProvider().getKey()).isEqualTo("sonarqube");
109 assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getProvider().getName()).isEqualTo("sonarqube");
110 assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getProvider().getDisplay()).isNull();
111 assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getProvider().isEnabled()).isTrue();
112 verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, REALM_NAME));
116 public void login_is_used_when_no_name_provided() {
117 executeStartWithoutGroupSync();
118 when(authenticator.doAuthenticate(any(Authenticator.Context.class))).thenReturn(true);
119 UserDetails userDetails = new UserDetails();
120 userDetails.setEmail("email");
121 when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(userDetails);
123 underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC);
125 assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getProvider().getName()).isEqualTo("sonarqube");
126 verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, REALM_NAME));
130 public void authenticate_with_group_sync() {
131 when(externalGroupsProvider.doGetGroups(any(ExternalGroupsProvider.Context.class))).thenReturn(asList("group1", "group2"));
132 executeStartWithGroupSync();
134 executeAuthenticate();
136 assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue();
137 assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().shouldSyncGroups()).isTrue();
138 verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, REALM_NAME));
142 public void use_login_if_user_details_contains_no_name() {
143 executeStartWithoutGroupSync();
144 when(authenticator.doAuthenticate(any(Authenticator.Context.class))).thenReturn(true);
145 UserDetails userDetails = new UserDetails();
146 userDetails.setName(null);
147 when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(userDetails);
149 underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC);
151 assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue();
152 assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getName()).isEqualTo(LOGIN);
153 verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, REALM_NAME));
157 public void use_downcase_login() {
158 settings.setProperty("sonar.authenticator.downcase", true);
159 executeStartWithoutGroupSync();
161 executeAuthenticate("LOGIN");
163 assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue();
164 assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getProviderLogin()).isEqualTo("login");
165 verify(authenticationEvent).loginSuccess(request, "login", Source.realm(BASIC, REALM_NAME));
169 public void does_not_user_downcase_login() {
170 settings.setProperty("sonar.authenticator.downcase", false);
171 executeStartWithoutGroupSync();
173 executeAuthenticate("LoGiN");
175 assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue();
176 assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getProviderLogin()).isEqualTo("LoGiN");
177 verify(authenticationEvent).loginSuccess(request, "LoGiN", Source.realm(BASIC, REALM_NAME));
181 public void fail_to_authenticate_when_user_details_are_null() {
182 executeStartWithoutGroupSync();
183 when(authenticator.doAuthenticate(any(Authenticator.Context.class))).thenReturn(true);
185 when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(null);
187 assertThatThrownBy(() -> underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC))
188 .hasMessage("No user details")
189 .isInstanceOf(AuthenticationException.class)
190 .hasFieldOrPropertyWithValue("source", Source.realm(BASIC, REALM_NAME))
191 .hasFieldOrPropertyWithValue("login", LOGIN);
193 verifyZeroInteractions(authenticationEvent);
197 public void fail_to_authenticate_when_external_authentication_fails() {
198 executeStartWithoutGroupSync();
199 when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(new UserDetails());
201 when(authenticator.doAuthenticate(any(Authenticator.Context.class))).thenReturn(false);
203 assertThatThrownBy(() -> underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC))
204 .hasMessage("Realm returned authenticate=false")
205 .isInstanceOf(AuthenticationException.class)
206 .hasFieldOrPropertyWithValue("source", Source.realm(BASIC, REALM_NAME))
207 .hasFieldOrPropertyWithValue("login", LOGIN);
209 verifyZeroInteractions(authenticationEvent);
214 public void fail_to_authenticate_when_any_exception_is_thrown() {
215 executeStartWithoutGroupSync();
216 String expectedMessage = "emulating exception in doAuthenticate";
217 doThrow(new IllegalArgumentException(expectedMessage)).when(authenticator).doAuthenticate(any(Authenticator.Context.class));
219 when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(new UserDetails());
221 assertThatThrownBy(() -> underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC_TOKEN))
222 .hasMessage(expectedMessage)
223 .isInstanceOf(AuthenticationException.class)
224 .hasFieldOrPropertyWithValue("source", Source.realm(BASIC_TOKEN, REALM_NAME))
225 .hasFieldOrPropertyWithValue("login", LOGIN);
227 verifyZeroInteractions(authenticationEvent);
231 public void return_empty_user_when_no_realm() {
232 assertThat(underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC)).isEmpty();
233 verifyNoMoreInteractions(authenticationEvent);
237 public void fail_to_start_when_no_authenticator() {
238 when(realm.doGetAuthenticator()).thenReturn(null);
239 when(securityRealmFactory.getRealm()).thenReturn(realm);
241 assertThatThrownBy(() -> underTest.start())
242 .isInstanceOf(NullPointerException.class)
243 .hasMessage("No authenticator available");
247 public void fail_to_start_when_no_user_provider() {
248 when(realm.doGetAuthenticator()).thenReturn(authenticator);
249 when(realm.getUsersProvider()).thenReturn(null);
250 when(securityRealmFactory.getRealm()).thenReturn(realm);
252 assertThatThrownBy(() -> underTest.start())
253 .isInstanceOf(NullPointerException.class)
254 .hasMessage("No users provider available");
257 private void executeStartWithoutGroupSync() {
258 when(realm.doGetAuthenticator()).thenReturn(authenticator);
259 when(realm.getUsersProvider()).thenReturn(externalUsersProvider);
260 when(securityRealmFactory.getRealm()).thenReturn(realm);
264 private void executeStartWithGroupSync() {
265 when(realm.doGetAuthenticator()).thenReturn(authenticator);
266 when(realm.getUsersProvider()).thenReturn(externalUsersProvider);
267 when(realm.getGroupsProvider()).thenReturn(externalGroupsProvider);
268 when(securityRealmFactory.getRealm()).thenReturn(realm);
272 private void executeAuthenticate() {
273 executeAuthenticate(LOGIN);
276 private void executeAuthenticate(String login) {
277 when(authenticator.doAuthenticate(any(Authenticator.Context.class))).thenReturn(true);
278 UserDetails userDetails = new UserDetails();
279 userDetails.setName("name");
280 when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(userDetails);
281 underTest.authenticate(new Credentials(login, PASSWORD), request, BASIC);