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.annotation.Nullable;
23 import javax.servlet.http.HttpServletRequest;
24 import org.junit.Before;
25 import org.junit.Test;
26 import org.junit.runner.RunWith;
27 import org.mockito.Mock;
28 import org.mockito.junit.MockitoJUnitRunner;
29 import org.sonar.api.config.internal.MapSettings;
30 import org.sonar.api.server.authentication.IdentityProvider;
31 import org.sonar.api.server.authentication.UserIdentity;
32 import org.sonar.auth.ldap.LdapAuthenticator;
33 import org.sonar.auth.ldap.LdapGroupsProvider;
34 import org.sonar.auth.ldap.LdapRealm;
35 import org.sonar.auth.ldap.LdapUserDetails;
36 import org.sonar.auth.ldap.LdapUsersProvider;
37 import org.sonar.process.ProcessProperties;
38 import org.sonar.server.authentication.event.AuthenticationEvent;
39 import org.sonar.server.authentication.event.AuthenticationEvent.Source;
40 import org.sonar.server.authentication.event.AuthenticationException;
42 import static java.util.Arrays.asList;
43 import static org.assertj.core.api.Assertions.assertThat;
44 import static org.assertj.core.api.Assertions.assertThatThrownBy;
45 import static org.mockito.ArgumentMatchers.any;
46 import static org.mockito.ArgumentMatchers.refEq;
47 import static org.mockito.Mockito.doThrow;
48 import static org.mockito.Mockito.mock;
49 import static org.mockito.Mockito.reset;
50 import static org.mockito.Mockito.verify;
51 import static org.mockito.Mockito.verifyNoInteractions;
52 import static org.mockito.Mockito.when;
53 import static org.sonar.auth.ldap.LdapAuthenticationResult.failed;
54 import static org.sonar.auth.ldap.LdapAuthenticationResult.success;
55 import static org.sonar.server.authentication.event.AuthenticationEvent.Method.BASIC;
56 import static org.sonar.server.authentication.event.AuthenticationEvent.Method.BASIC_TOKEN;
58 @RunWith(MockitoJUnitRunner.Silent.class)
59 public class LdapCredentialsAuthenticationTest {
61 private static final String LOGIN = "LOGIN";
62 private static final String PASSWORD = "PASSWORD";
64 private static final String LDAP_SECURITY_REALM_NAME = "ldap";
65 private static final String SERVER_KEY = "superServerKey";
66 private static final String EXPECTED_EXTERNAL_PROVIDER_ID = "LDAP_superServerKey";
68 private static final LdapUserDetails LDAP_USER_DETAILS;
71 LDAP_USER_DETAILS = new LdapUserDetails();
72 LDAP_USER_DETAILS.setName("name");
75 private static final LdapUserDetails LDAP_USER_DETAILS_WITH_EMAIL;
78 LDAP_USER_DETAILS_WITH_EMAIL = new LdapUserDetails();
79 LDAP_USER_DETAILS_WITH_EMAIL.setName("name");
80 LDAP_USER_DETAILS_WITH_EMAIL.setEmail("email");
83 private final MapSettings settings = new MapSettings();
84 private final TestUserRegistrar userRegistrar = new TestUserRegistrar();
87 private AuthenticationEvent authenticationEvent;
90 private HttpServletRequest request = mock(HttpServletRequest.class);
93 private LdapAuthenticator ldapAuthenticator;
95 private LdapGroupsProvider ldapGroupsProvider;
97 private LdapUsersProvider ldapUsersProvider;
99 private LdapRealm ldapRealm;
101 private LdapCredentialsAuthentication underTest;
104 public void setUp() throws Exception {
105 settings.setProperty(ProcessProperties.Property.SONAR_SECURITY_REALM.getKey(), "LDAP");
106 when(ldapRealm.doGetAuthenticator()).thenReturn(ldapAuthenticator);
107 when(ldapRealm.getUsersProvider()).thenReturn(ldapUsersProvider);
108 when(ldapRealm.getGroupsProvider()).thenReturn(ldapGroupsProvider);
109 underTest = new LdapCredentialsAuthentication(settings.asConfig(), userRegistrar, authenticationEvent, ldapRealm);
113 public void authenticate_with_null_group_provider() {
115 when(ldapRealm.doGetAuthenticator()).thenReturn(ldapAuthenticator);
116 when(ldapRealm.getUsersProvider()).thenReturn(ldapUsersProvider);
117 when(ldapRealm.getGroupsProvider()).thenReturn(null);
118 underTest = new LdapCredentialsAuthentication(settings.asConfig(), userRegistrar, authenticationEvent, ldapRealm);
120 LdapAuthenticator.Context authenticationContext = new LdapAuthenticator.Context(LOGIN, PASSWORD, request);
121 when(ldapAuthenticator.doAuthenticate(refEq(authenticationContext))).thenReturn(success(SERVER_KEY));
123 LdapUsersProvider.Context expectedUserContext = new LdapUsersProvider.Context(SERVER_KEY, LOGIN, request);
124 when(ldapUsersProvider.doGetUserDetails(refEq(expectedUserContext))).thenReturn(LDAP_USER_DETAILS_WITH_EMAIL);
126 underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC);
128 UserIdentity identity = userRegistrar.getAuthenticatorParameters().getUserIdentity();
129 assertThat(userRegistrar.isAuthenticated()).isTrue();
130 assertThat(identity.getProviderLogin()).isEqualTo(LOGIN);
131 assertThat(identity.getProviderId()).isNull();
132 assertThat(identity.getName()).isEqualTo("name");
133 assertThat(identity.getEmail()).isEqualTo("email");
134 assertThat(identity.shouldSyncGroups()).isFalse();
136 verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, LDAP_SECURITY_REALM_NAME));
137 verify(ldapRealm).init();
141 public void authenticate_with_ldap() {
142 executeAuthenticate(LDAP_USER_DETAILS_WITH_EMAIL);
144 IdentityProvider provider = userRegistrar.getAuthenticatorParameters().getProvider();
145 assertThat(userRegistrar.isAuthenticated()).isTrue();
146 assertThat(provider.getKey()).isEqualTo(EXPECTED_EXTERNAL_PROVIDER_ID);
147 assertThat(provider.getName()).isEqualTo(EXPECTED_EXTERNAL_PROVIDER_ID);
148 assertThat(provider.getDisplay()).isNull();
149 assertThat(provider.isEnabled()).isTrue();
150 verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, LDAP_SECURITY_REALM_NAME));
151 verify(ldapRealm).init();
155 public void login_is_used_when_no_name_provided() {
156 LdapUserDetails userDetails = new LdapUserDetails();
157 userDetails.setEmail("email");
159 executeAuthenticate(userDetails);
161 assertThat(userRegistrar.getAuthenticatorParameters().getProvider().getName()).isEqualTo(EXPECTED_EXTERNAL_PROVIDER_ID);
162 verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, LDAP_SECURITY_REALM_NAME));
166 public void authenticate_with_group_sync() {
168 LdapGroupsProvider.Context expectedGroupContext = new LdapGroupsProvider.Context(SERVER_KEY, LOGIN, request);
169 when(ldapGroupsProvider.doGetGroups(refEq(expectedGroupContext))).thenReturn(asList("group1", "group2"));
171 executeAuthenticate(LDAP_USER_DETAILS);
173 assertThat(userRegistrar.isAuthenticated()).isTrue();
174 assertThat(userRegistrar.getAuthenticatorParameters().getUserIdentity().shouldSyncGroups()).isTrue();
175 verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, LDAP_SECURITY_REALM_NAME));
179 public void use_login_if_user_details_contains_no_name() {
181 LdapUserDetails userDetails = new LdapUserDetails();
182 userDetails.setName(null);
184 executeAuthenticate(userDetails);
186 assertThat(userRegistrar.isAuthenticated()).isTrue();
187 assertThat(userRegistrar.getAuthenticatorParameters().getUserIdentity().getName()).isEqualTo(LOGIN);
188 verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, LDAP_SECURITY_REALM_NAME));
192 public void use_downcase_login() {
193 settings.setProperty("sonar.authenticator.downcase", true);
195 mockLdapAuthentication(LOGIN.toLowerCase());
196 mockLdapUserDetailsRetrieval(LOGIN.toLowerCase(), LDAP_USER_DETAILS);
198 underTest.authenticate(new Credentials(LOGIN.toLowerCase(), PASSWORD), request, BASIC);
200 assertThat(userRegistrar.isAuthenticated()).isTrue();
201 assertThat(userRegistrar.getAuthenticatorParameters().getUserIdentity().getProviderLogin()).isEqualTo("login");
202 verify(authenticationEvent).loginSuccess(request, "login", Source.realm(BASIC, LDAP_SECURITY_REALM_NAME));
206 public void does_not_user_downcase_login() {
207 settings.setProperty("sonar.authenticator.downcase", false);
209 mockLdapAuthentication("LoGiN");
210 mockLdapUserDetailsRetrieval("LoGiN", LDAP_USER_DETAILS);
212 underTest.authenticate(new Credentials("LoGiN", PASSWORD), request, BASIC);
214 assertThat(userRegistrar.isAuthenticated()).isTrue();
215 assertThat(userRegistrar.getAuthenticatorParameters().getUserIdentity().getProviderLogin()).isEqualTo("LoGiN");
216 verify(authenticationEvent).loginSuccess(request, "LoGiN", Source.realm(BASIC, LDAP_SECURITY_REALM_NAME));
220 public void fail_to_authenticate_when_user_details_are_null() {
222 assertThatThrownBy(() -> executeAuthenticate(null))
223 .hasMessage("No user details")
224 .isInstanceOf(AuthenticationException.class)
225 .hasFieldOrPropertyWithValue("source", Source.realm(BASIC, LDAP_SECURITY_REALM_NAME))
226 .hasFieldOrPropertyWithValue("login", LOGIN);
228 verifyNoInteractions(authenticationEvent);
232 public void fail_to_authenticate_when_external_authentication_fails() {
234 LdapAuthenticator.Context authenticationContext = new LdapAuthenticator.Context(LOGIN, PASSWORD, request);
235 when(ldapAuthenticator.doAuthenticate(refEq(authenticationContext))).thenReturn(failed());
237 Credentials credentials = new Credentials(LOGIN, PASSWORD);
238 assertThatThrownBy(() -> underTest.authenticate(credentials, request, BASIC))
239 .hasMessage("Realm returned authenticate=false")
240 .isInstanceOf(AuthenticationException.class)
241 .hasFieldOrPropertyWithValue("source", Source.realm(BASIC, LDAP_SECURITY_REALM_NAME))
242 .hasFieldOrPropertyWithValue("login", LOGIN);
244 verifyNoInteractions(ldapUsersProvider);
245 verifyNoInteractions(authenticationEvent);
250 public void fail_to_authenticate_when_any_exception_is_thrown() {
251 String expectedMessage = "emulating exception in doAuthenticate";
252 doThrow(new IllegalArgumentException(expectedMessage)).when(ldapAuthenticator).doAuthenticate(any(LdapAuthenticator.Context.class));
254 Credentials credentials = new Credentials(LOGIN, PASSWORD);
255 assertThatThrownBy(() -> underTest.authenticate(credentials, request, BASIC_TOKEN))
256 .hasMessage(expectedMessage)
257 .isInstanceOf(AuthenticationException.class)
258 .hasFieldOrPropertyWithValue("source", Source.realm(BASIC_TOKEN, LDAP_SECURITY_REALM_NAME))
259 .hasFieldOrPropertyWithValue("login", LOGIN);
261 verifyNoInteractions(ldapUsersProvider);
262 verifyNoInteractions(authenticationEvent);
266 public void return_empty_user_when_ldap_not_activated() {
269 underTest = new LdapCredentialsAuthentication(settings.asConfig(), userRegistrar, authenticationEvent, ldapRealm);
271 assertThat(underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC)).isEmpty();
272 verifyNoInteractions(authenticationEvent);
273 verifyNoInteractions(ldapRealm);
276 private void executeAuthenticate(@Nullable LdapUserDetails userDetails) {
277 mockLdapAuthentication(LOGIN);
278 mockLdapUserDetailsRetrieval(LOGIN, userDetails);
279 underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC);
282 private void mockLdapAuthentication(String login) {
283 LdapAuthenticator.Context authenticationContext = new LdapAuthenticator.Context(login, PASSWORD, request);
284 when(ldapAuthenticator.doAuthenticate(refEq(authenticationContext))).thenReturn(success(SERVER_KEY));
287 private void mockLdapUserDetailsRetrieval(String login, @Nullable LdapUserDetails userDetails) {
288 LdapUsersProvider.Context expectedUserContext = new LdapUsersProvider.Context(SERVER_KEY, login, request);
289 when(ldapUsersProvider.doGetUserDetails(refEq(expectedUserContext))).thenReturn(userDetails);