]> source.dussan.org Git - sonarqube.git/blob
d3c1edbaf4aebfe5283eb6cbb5adee5aabcc0273
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2022 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 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;
41
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;
57
58 @RunWith(MockitoJUnitRunner.Silent.class)
59 public class LdapCredentialsAuthenticationTest {
60
61   private static final String LOGIN = "LOGIN";
62   private static final String PASSWORD = "PASSWORD";
63
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";
67
68   private static final LdapUserDetails LDAP_USER_DETAILS;
69
70   static {
71     LDAP_USER_DETAILS = new LdapUserDetails();
72     LDAP_USER_DETAILS.setName("name");
73   }
74
75   private static final LdapUserDetails LDAP_USER_DETAILS_WITH_EMAIL;
76
77   static {
78     LDAP_USER_DETAILS_WITH_EMAIL = new LdapUserDetails();
79     LDAP_USER_DETAILS_WITH_EMAIL.setName("name");
80     LDAP_USER_DETAILS_WITH_EMAIL.setEmail("email");
81   }
82
83   private final MapSettings settings = new MapSettings();
84   private final TestUserRegistrar userRegistrar = new TestUserRegistrar();
85
86   @Mock
87   private AuthenticationEvent authenticationEvent;
88
89   @Mock
90   private HttpServletRequest request = mock(HttpServletRequest.class);
91
92   @Mock
93   private LdapAuthenticator ldapAuthenticator;
94   @Mock
95   private LdapGroupsProvider ldapGroupsProvider;
96   @Mock
97   private LdapUsersProvider ldapUsersProvider;
98   @Mock
99   private LdapRealm ldapRealm;
100
101   private LdapCredentialsAuthentication underTest;
102
103   @Before
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);
110   }
111
112   @Test
113   public void authenticate_with_null_group_provider() {
114     reset(ldapRealm);
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);
119
120     LdapAuthenticator.Context authenticationContext = new LdapAuthenticator.Context(LOGIN, PASSWORD, request);
121     when(ldapAuthenticator.doAuthenticate(refEq(authenticationContext))).thenReturn(success(SERVER_KEY));
122
123     LdapUsersProvider.Context expectedUserContext = new LdapUsersProvider.Context(SERVER_KEY, LOGIN, request);
124     when(ldapUsersProvider.doGetUserDetails(refEq(expectedUserContext))).thenReturn(LDAP_USER_DETAILS_WITH_EMAIL);
125
126     underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC);
127
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();
135
136     verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, LDAP_SECURITY_REALM_NAME));
137     verify(ldapRealm).init();
138   }
139
140   @Test
141   public void authenticate_with_ldap() {
142     executeAuthenticate(LDAP_USER_DETAILS_WITH_EMAIL);
143
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();
152   }
153
154   @Test
155   public void login_is_used_when_no_name_provided() {
156     LdapUserDetails userDetails = new LdapUserDetails();
157     userDetails.setEmail("email");
158
159     executeAuthenticate(userDetails);
160
161     assertThat(userRegistrar.getAuthenticatorParameters().getProvider().getName()).isEqualTo(EXPECTED_EXTERNAL_PROVIDER_ID);
162     verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, LDAP_SECURITY_REALM_NAME));
163   }
164
165   @Test
166   public void authenticate_with_group_sync() {
167
168     LdapGroupsProvider.Context expectedGroupContext = new LdapGroupsProvider.Context(SERVER_KEY, LOGIN, request);
169     when(ldapGroupsProvider.doGetGroups(refEq(expectedGroupContext))).thenReturn(asList("group1", "group2"));
170
171     executeAuthenticate(LDAP_USER_DETAILS);
172
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));
176   }
177
178   @Test
179   public void use_login_if_user_details_contains_no_name() {
180
181     LdapUserDetails userDetails = new LdapUserDetails();
182     userDetails.setName(null);
183
184     executeAuthenticate(userDetails);
185
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));
189   }
190
191   @Test
192   public void use_downcase_login() {
193     settings.setProperty("sonar.authenticator.downcase", true);
194
195     mockLdapAuthentication(LOGIN.toLowerCase());
196     mockLdapUserDetailsRetrieval(LOGIN.toLowerCase(), LDAP_USER_DETAILS);
197
198     underTest.authenticate(new Credentials(LOGIN.toLowerCase(), PASSWORD), request, BASIC);
199
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));
203   }
204
205   @Test
206   public void does_not_user_downcase_login() {
207     settings.setProperty("sonar.authenticator.downcase", false);
208
209     mockLdapAuthentication("LoGiN");
210     mockLdapUserDetailsRetrieval("LoGiN", LDAP_USER_DETAILS);
211
212     underTest.authenticate(new Credentials("LoGiN", PASSWORD), request, BASIC);
213
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));
217   }
218
219   @Test
220   public void fail_to_authenticate_when_user_details_are_null() {
221
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);
227
228     verifyNoInteractions(authenticationEvent);
229   }
230
231   @Test
232   public void fail_to_authenticate_when_external_authentication_fails() {
233
234     LdapAuthenticator.Context authenticationContext = new LdapAuthenticator.Context(LOGIN, PASSWORD, request);
235     when(ldapAuthenticator.doAuthenticate(refEq(authenticationContext))).thenReturn(failed());
236
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);
243
244     verifyNoInteractions(ldapUsersProvider);
245     verifyNoInteractions(authenticationEvent);
246
247   }
248
249   @Test
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));
253
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);
260
261     verifyNoInteractions(ldapUsersProvider);
262     verifyNoInteractions(authenticationEvent);
263   }
264
265   @Test
266   public void return_empty_user_when_ldap_not_activated() {
267     reset(ldapRealm);
268     settings.clear();
269     underTest = new LdapCredentialsAuthentication(settings.asConfig(), userRegistrar, authenticationEvent, ldapRealm);
270
271     assertThat(underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC)).isEmpty();
272     verifyNoInteractions(authenticationEvent);
273     verifyNoInteractions(ldapRealm);
274   }
275
276   private void executeAuthenticate(@Nullable LdapUserDetails userDetails) {
277     mockLdapAuthentication(LOGIN);
278     mockLdapUserDetailsRetrieval(LOGIN, userDetails);
279     underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC);
280   }
281
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));
285   }
286
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);
290   }
291
292 }