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 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;
45 import static org.apache.commons.lang.StringUtils.isEmpty;
46 import static org.apache.commons.lang.StringUtils.trimToNull;
48 public class LdapCredentialsAuthentication {
50 private static final String LDAP_SECURITY_REALM = "LDAP";
52 private static final Logger LOG = Loggers.get(LdapCredentialsAuthentication.class);
54 private final Configuration configuration;
55 private final UserRegistrar userRegistrar;
56 private final AuthenticationEvent authenticationEvent;
58 private final LdapAuthenticator ldapAuthenticator;
59 private final LdapUsersProvider ldapUsersProvider;
60 private final LdapGroupsProvider ldapGroupsProvider;
61 private final boolean isLdapAuthActivated;
63 public LdapCredentialsAuthentication(Configuration configuration,
64 UserRegistrar userRegistrar, AuthenticationEvent authenticationEvent, LdapRealm ldapRealm) {
65 this.configuration = configuration;
66 this.userRegistrar = userRegistrar;
67 this.authenticationEvent = authenticationEvent;
69 String realmName = configuration.get(ProcessProperties.Property.SONAR_SECURITY_REALM.getKey()).orElse(null);
70 this.isLdapAuthActivated = LDAP_SECURITY_REALM.equals(realmName);
72 if (isLdapAuthActivated) {
74 this.ldapAuthenticator = ldapRealm.doGetAuthenticator();
75 this.ldapUsersProvider = ldapRealm.getUsersProvider();
76 this.ldapGroupsProvider = ldapRealm.getGroupsProvider();
78 this.ldapAuthenticator = null;
79 this.ldapUsersProvider = null;
80 this.ldapGroupsProvider = null;
84 public Optional<UserDto> authenticate(Credentials credentials, HttpServletRequest request, AuthenticationEvent.Method method) {
85 if (isLdapAuthActivated) {
86 return Optional.of(doAuthenticate(fixCase(credentials), request, method));
88 return Optional.empty();
91 private UserDto doAuthenticate(Credentials credentials, HttpServletRequest request, AuthenticationEvent.Method method) {
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")
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")
112 UserDto userDto = synchronize(credentials.getLogin(), authenticationResult.getServerKey(), ldapUserDetails, request, method);
113 authenticationEvent.loginSuccess(request, credentials.getLogin(), realmEventSource(method));
115 } catch (AuthenticationException 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())
128 private static Source realmEventSource(AuthenticationEvent.Method method) {
129 return Source.realm(method, "ldap");
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));
143 return userRegistrar.register(
144 UserRegistration.builder()
145 .setUserIdentity(userIdentityBuilder.build())
146 .setProvider(new LdapIdentityProvider(serverKey))
147 .setSource(realmEventSource(method))
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));
158 private static class LdapIdentityProvider implements IdentityProvider {
160 private final String key;
162 private LdapIdentityProvider(String ldapServerKey) {
163 this.key = LDAP_SECURITY_REALM + "_" + ldapServerKey;
167 public String getKey() {
172 public String getName() {
177 public Display getDisplay() {
182 public boolean isEnabled() {
187 public boolean allowsUsersToSignUp() {