]> source.dussan.org Git - sonarqube.git/blob
b41295d0562c8512b383a09d14d4831e59015685
[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 java.util.Collection;
23 import java.util.HashSet;
24 import java.util.Locale;
25 import java.util.Optional;
26 import javax.servlet.http.HttpServletRequest;
27 import org.sonar.api.config.Configuration;
28 import org.sonar.api.server.authentication.Display;
29 import org.sonar.api.server.authentication.IdentityProvider;
30 import org.sonar.api.server.authentication.UserIdentity;
31 import org.sonar.api.utils.log.Logger;
32 import org.sonar.api.utils.log.Loggers;
33 import org.sonar.auth.ldap.LdapAuthenticationResult;
34 import org.sonar.auth.ldap.LdapAuthenticator;
35 import org.sonar.auth.ldap.LdapGroupsProvider;
36 import org.sonar.auth.ldap.LdapRealm;
37 import org.sonar.auth.ldap.LdapUserDetails;
38 import org.sonar.auth.ldap.LdapUsersProvider;
39 import org.sonar.db.user.UserDto;
40 import org.sonar.process.ProcessProperties;
41 import org.sonar.server.authentication.event.AuthenticationEvent;
42 import org.sonar.server.authentication.event.AuthenticationEvent.Source;
43 import org.sonar.server.authentication.event.AuthenticationException;
44
45 import static org.apache.commons.lang.StringUtils.isEmpty;
46 import static org.apache.commons.lang.StringUtils.trimToNull;
47
48 public class LdapCredentialsAuthentication {
49
50   private static final String LDAP_SECURITY_REALM = "LDAP";
51
52   private static final Logger LOG = Loggers.get(LdapCredentialsAuthentication.class);
53
54   private final Configuration configuration;
55   private final UserRegistrar userRegistrar;
56   private final AuthenticationEvent authenticationEvent;
57
58   private final LdapAuthenticator ldapAuthenticator;
59   private final LdapUsersProvider ldapUsersProvider;
60   private final LdapGroupsProvider ldapGroupsProvider;
61   private final boolean isLdapAuthActivated;
62
63   public LdapCredentialsAuthentication(Configuration configuration,
64     UserRegistrar userRegistrar, AuthenticationEvent authenticationEvent, LdapRealm ldapRealm) {
65     this.configuration = configuration;
66     this.userRegistrar = userRegistrar;
67     this.authenticationEvent = authenticationEvent;
68
69     String realmName = configuration.get(ProcessProperties.Property.SONAR_SECURITY_REALM.getKey()).orElse(null);
70     this.isLdapAuthActivated = LDAP_SECURITY_REALM.equals(realmName);
71
72     if (isLdapAuthActivated) {
73       ldapRealm.init();
74       this.ldapAuthenticator = ldapRealm.doGetAuthenticator();
75       this.ldapUsersProvider = ldapRealm.getUsersProvider();
76       this.ldapGroupsProvider = ldapRealm.getGroupsProvider();
77     } else {
78       this.ldapAuthenticator = null;
79       this.ldapUsersProvider = null;
80       this.ldapGroupsProvider = null;
81     }
82   }
83
84   public Optional<UserDto> authenticate(Credentials credentials, HttpServletRequest request, AuthenticationEvent.Method method) {
85     if (isLdapAuthActivated) {
86       return Optional.of(doAuthenticate(fixCase(credentials), request, method));
87     }
88     return Optional.empty();
89   }
90
91   private UserDto doAuthenticate(Credentials credentials, HttpServletRequest request, AuthenticationEvent.Method method) {
92     try {
93       LdapAuthenticator.Context ldapAuthenticatorContext = new LdapAuthenticator.Context(credentials.getLogin(), credentials.getPassword().orElse(null), request);
94       LdapAuthenticationResult authenticationResult = ldapAuthenticator.doAuthenticate(ldapAuthenticatorContext);
95       if (!authenticationResult.isSuccess()) {
96         throw AuthenticationException.newBuilder()
97           .setSource(realmEventSource(method))
98           .setLogin(credentials.getLogin())
99           .setMessage("Realm returned authenticate=false")
100           .build();
101       }
102
103       LdapUsersProvider.Context ldapUsersProviderContext = new LdapUsersProvider.Context(authenticationResult.getServerKey(), credentials.getLogin(), request);
104       LdapUserDetails ldapUserDetails = ldapUsersProvider.doGetUserDetails(ldapUsersProviderContext);
105       if (ldapUserDetails == null) {
106         throw AuthenticationException.newBuilder()
107           .setSource(realmEventSource(method))
108           .setLogin(credentials.getLogin())
109           .setMessage("No user details")
110           .build();
111       }
112       UserDto userDto = synchronize(credentials.getLogin(), authenticationResult.getServerKey(), ldapUserDetails, request, method);
113       authenticationEvent.loginSuccess(request, credentials.getLogin(), realmEventSource(method));
114       return userDto;
115     } catch (AuthenticationException e) {
116       throw e;
117     } catch (Exception e) {
118       // It seems that with Realm API it's expected to log the error and to not authenticate the user
119       LOG.error("Error during authentication", e);
120       throw AuthenticationException.newBuilder()
121         .setSource(realmEventSource(method))
122         .setLogin(credentials.getLogin())
123         .setMessage(e.getMessage())
124         .build();
125     }
126   }
127
128   private static Source realmEventSource(AuthenticationEvent.Method method) {
129     return Source.realm(method, "ldap");
130   }
131
132   private UserDto synchronize(String userLogin, String serverKey, LdapUserDetails userDetails, HttpServletRequest request, AuthenticationEvent.Method method) {
133     String name = userDetails.getName();
134     UserIdentity.Builder userIdentityBuilder = UserIdentity.builder()
135       .setName(isEmpty(name) ? userLogin : name)
136       .setEmail(trimToNull(userDetails.getEmail()))
137       .setProviderLogin(userLogin);
138     if (ldapGroupsProvider != null) {
139       LdapGroupsProvider.Context context = new LdapGroupsProvider.Context(serverKey, userLogin, request);
140       Collection<String> groups = ldapGroupsProvider.doGetGroups(context);
141       userIdentityBuilder.setGroups(new HashSet<>(groups));
142     }
143     return userRegistrar.register(
144       UserRegistration.builder()
145         .setUserIdentity(userIdentityBuilder.build())
146         .setProvider(new LdapIdentityProvider(serverKey))
147         .setSource(realmEventSource(method))
148         .build());
149   }
150
151   private Credentials fixCase(Credentials credentials) {
152     if (configuration.getBoolean("sonar.authenticator.downcase").orElse(false)) {
153       return new Credentials(credentials.getLogin().toLowerCase(Locale.ENGLISH), credentials.getPassword().orElse(null));
154     }
155     return credentials;
156   }
157
158   private static class LdapIdentityProvider implements IdentityProvider {
159
160     private final String key;
161
162     private LdapIdentityProvider(String ldapServerKey) {
163       this.key = LDAP_SECURITY_REALM + "_" + ldapServerKey;
164     }
165
166     @Override
167     public String getKey() {
168       return key;
169     }
170
171     @Override
172     public String getName() {
173       return getKey();
174     }
175
176     @Override
177     public Display getDisplay() {
178       return null;
179     }
180
181     @Override
182     public boolean isEnabled() {
183       return true;
184     }
185
186     @Override
187     public boolean allowsUsersToSignUp() {
188       return true;
189     }
190   }
191
192 }