]> source.dussan.org Git - sonarqube.git/blob
02661e1ff2a8c2b1da33e0dfceeadc0a8d5f60aa
[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.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;
35
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;
48
49 public class CredentialsExternalAuthenticationTest {
50
51   private static final String LOGIN = "LOGIN";
52   private static final String PASSWORD = "PASSWORD";
53
54   private static final String REALM_NAME = "realm name";
55
56   private MapSettings settings = new MapSettings();
57
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);
63
64   private TestUserRegistrar userIdentityAuthenticator = new TestUserRegistrar();
65   private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class);
66
67   private HttpServletRequest request = mock(HttpServletRequest.class);
68
69   private CredentialsExternalAuthentication underTest = new CredentialsExternalAuthentication(settings.asConfig(), securityRealmFactory, userIdentityAuthenticator, authenticationEvent);
70
71   @Before
72   public void setUp() throws Exception {
73     when(realm.getName()).thenReturn(REALM_NAME);
74   }
75
76   @Test
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);
84
85     underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC);
86
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));
94   }
95
96   @Test
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);
104
105     underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC);
106
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));
113   }
114
115   @Test
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);
122
123     underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC);
124
125     assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getProvider().getName()).isEqualTo("sonarqube");
126     verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, REALM_NAME));
127   }
128
129   @Test
130   public void authenticate_with_group_sync() {
131     when(externalGroupsProvider.doGetGroups(any(ExternalGroupsProvider.Context.class))).thenReturn(asList("group1", "group2"));
132     executeStartWithGroupSync();
133
134     executeAuthenticate();
135
136     assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue();
137     assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().shouldSyncGroups()).isTrue();
138     verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, REALM_NAME));
139   }
140
141   @Test
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);
148
149     underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC);
150
151     assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue();
152     assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getName()).isEqualTo(LOGIN);
153     verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, REALM_NAME));
154   }
155
156   @Test
157   public void use_downcase_login() {
158     settings.setProperty("sonar.authenticator.downcase", true);
159     executeStartWithoutGroupSync();
160
161     executeAuthenticate("LOGIN");
162
163     assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue();
164     assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getProviderLogin()).isEqualTo("login");
165     verify(authenticationEvent).loginSuccess(request, "login", Source.realm(BASIC, REALM_NAME));
166   }
167
168   @Test
169   public void does_not_user_downcase_login() {
170     settings.setProperty("sonar.authenticator.downcase", false);
171     executeStartWithoutGroupSync();
172
173     executeAuthenticate("LoGiN");
174
175     assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue();
176     assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getProviderLogin()).isEqualTo("LoGiN");
177     verify(authenticationEvent).loginSuccess(request, "LoGiN", Source.realm(BASIC, REALM_NAME));
178   }
179
180   @Test
181   public void fail_to_authenticate_when_user_details_are_null() {
182     executeStartWithoutGroupSync();
183     when(authenticator.doAuthenticate(any(Authenticator.Context.class))).thenReturn(true);
184
185     when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(null);
186
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);
192
193     verifyZeroInteractions(authenticationEvent);
194   }
195
196   @Test
197   public void fail_to_authenticate_when_external_authentication_fails() {
198     executeStartWithoutGroupSync();
199     when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(new UserDetails());
200
201     when(authenticator.doAuthenticate(any(Authenticator.Context.class))).thenReturn(false);
202
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);
208
209     verifyZeroInteractions(authenticationEvent);
210
211   }
212
213   @Test
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));
218
219     when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(new UserDetails());
220
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);
226
227     verifyZeroInteractions(authenticationEvent);
228   }
229
230   @Test
231   public void return_empty_user_when_no_realm() {
232     assertThat(underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC)).isEmpty();
233     verifyNoMoreInteractions(authenticationEvent);
234   }
235
236   @Test
237   public void fail_to_start_when_no_authenticator() {
238     when(realm.doGetAuthenticator()).thenReturn(null);
239     when(securityRealmFactory.getRealm()).thenReturn(realm);
240
241     assertThatThrownBy(() -> underTest.start())
242       .isInstanceOf(NullPointerException.class)
243       .hasMessage("No authenticator available");
244   }
245
246   @Test
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);
251
252     assertThatThrownBy(() -> underTest.start())
253       .isInstanceOf(NullPointerException.class)
254       .hasMessage("No users provider available");
255   }
256
257   private void executeStartWithoutGroupSync() {
258     when(realm.doGetAuthenticator()).thenReturn(authenticator);
259     when(realm.getUsersProvider()).thenReturn(externalUsersProvider);
260     when(securityRealmFactory.getRealm()).thenReturn(realm);
261     underTest.start();
262   }
263
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);
269     underTest.start();
270   }
271
272   private void executeAuthenticate() {
273     executeAuthenticate(LOGIN);
274   }
275
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);
282   }
283
284 }