]> source.dussan.org Git - sonarqube.git/blob
57cffbc61f35c6844c4f01fabe3cb6ee2a7b7724
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2024 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.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29 import org.sonar.api.Startable;
30 import org.sonar.api.config.Configuration;
31 import org.sonar.api.security.Authenticator;
32 import org.sonar.api.security.ExternalGroupsProvider;
33 import org.sonar.api.security.ExternalUsersProvider;
34 import org.sonar.api.security.SecurityRealm;
35 import org.sonar.api.security.UserDetails;
36 import org.sonar.api.server.authentication.Display;
37 import org.sonar.api.server.authentication.IdentityProvider;
38 import org.sonar.api.server.authentication.UserIdentity;
39 import org.sonar.api.server.http.HttpRequest;
40 import org.sonar.db.user.UserDto;
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 import org.sonar.server.http.JavaxHttpRequest;
45 import org.sonar.server.user.SecurityRealmFactory;
46
47 import static java.util.Objects.requireNonNull;
48 import static org.apache.commons.lang.StringUtils.isEmpty;
49 import static org.apache.commons.lang.StringUtils.trimToNull;
50 import static org.sonar.server.user.ExternalIdentity.SQ_AUTHORITY;
51
52 /**
53  * Delegates the validation of credentials to an external system, e.g. LDAP.
54  */
55 public class CredentialsExternalAuthentication implements Startable {
56
57   private static final Logger LOG = LoggerFactory.getLogger(CredentialsExternalAuthentication.class);
58
59   private final Configuration config;
60   private final SecurityRealmFactory securityRealmFactory;
61   private final UserRegistrar userRegistrar;
62   private final AuthenticationEvent authenticationEvent;
63
64   private SecurityRealm realm;
65   private Authenticator authenticator;
66   private ExternalUsersProvider externalUsersProvider;
67   private ExternalGroupsProvider externalGroupsProvider;
68
69   public CredentialsExternalAuthentication(Configuration config, SecurityRealmFactory securityRealmFactory,
70     UserRegistrar userRegistrar, AuthenticationEvent authenticationEvent) {
71     this.config = config;
72     this.securityRealmFactory = securityRealmFactory;
73     this.userRegistrar = userRegistrar;
74     this.authenticationEvent = authenticationEvent;
75   }
76
77   @Override
78   public void start() {
79     realm = securityRealmFactory.getRealm();
80     if (realm != null) {
81       authenticator = requireNonNull(realm.doGetAuthenticator(), "No authenticator available");
82       externalUsersProvider = requireNonNull(realm.getUsersProvider(), "No users provider available");
83       externalGroupsProvider = realm.getGroupsProvider();
84     }
85   }
86
87   public Optional<UserDto> authenticate(Credentials credentials, HttpRequest request, AuthenticationEvent.Method method) {
88     if (realm == null) {
89       return Optional.empty();
90     }
91     return Optional.of(doAuthenticate(fixCase(credentials), request, method));
92   }
93
94   private UserDto doAuthenticate(Credentials credentials, HttpRequest request, AuthenticationEvent.Method method) {
95     try {
96       HttpServletRequest httpServletRequest = ((JavaxHttpRequest) request).getDelegate();
97       ExternalUsersProvider.Context externalUsersProviderContext = new ExternalUsersProvider.Context(credentials.getLogin(), request, httpServletRequest);
98       UserDetails details = externalUsersProvider.doGetUserDetails(externalUsersProviderContext);
99       if (details == null) {
100         throw AuthenticationException.newBuilder()
101           .setSource(realmEventSource(method))
102           .setLogin(credentials.getLogin())
103           .setMessage("No user details")
104           .build();
105       }
106
107       Authenticator.Context authenticatorContext = new Authenticator.Context(credentials.getLogin(), credentials.getPassword().orElse(null), request, httpServletRequest);
108       boolean status = authenticator.doAuthenticate(authenticatorContext);
109       if (!status) {
110         throw AuthenticationException.newBuilder()
111           .setSource(realmEventSource(method))
112           .setLogin(credentials.getLogin())
113           .setMessage("Realm returned authenticate=false")
114           .build();
115       }
116       UserDto userDto = synchronize(credentials.getLogin(), details, request, method);
117       authenticationEvent.loginSuccess(request, credentials.getLogin(), realmEventSource(method));
118       return userDto;
119     } catch (AuthenticationException e) {
120       throw e;
121     } catch (Exception e) {
122       // It seems that with Realm API it's expected to log the error and to not authenticate the user
123       LOG.error("Error during authentication", e);
124       throw AuthenticationException.newBuilder()
125         .setSource(realmEventSource(method))
126         .setLogin(credentials.getLogin())
127         .setMessage(e.getMessage())
128         .build();
129     }
130   }
131
132   private Source realmEventSource(AuthenticationEvent.Method method) {
133     return Source.realm(method, realm.getName());
134   }
135
136   private UserDto synchronize(String userLogin, UserDetails details, HttpRequest request, AuthenticationEvent.Method method) {
137     String name = details.getName();
138     UserIdentity.Builder userIdentityBuilder = UserIdentity.builder()
139       .setName(isEmpty(name) ? userLogin : name)
140       .setEmail(trimToNull(details.getEmail()))
141       .setProviderLogin(userLogin);
142     if (externalGroupsProvider != null) {
143       HttpServletRequest httpServletRequest = ((JavaxHttpRequest) request).getDelegate();
144       ExternalGroupsProvider.Context context = new ExternalGroupsProvider.Context(userLogin, request, httpServletRequest);
145       Collection<String> groups = externalGroupsProvider.doGetGroups(context);
146       userIdentityBuilder.setGroups(new HashSet<>(groups));
147     }
148     return userRegistrar.register(
149       UserRegistration.builder()
150         .setUserIdentity(userIdentityBuilder.build())
151         .setProvider(new ExternalIdentityProvider())
152         .setSource(realmEventSource(method))
153         .build());
154   }
155
156   private Credentials fixCase(Credentials credentials) {
157     if (config.getBoolean("sonar.authenticator.downcase").orElse(false)) {
158       return new Credentials(credentials.getLogin().toLowerCase(Locale.ENGLISH), credentials.getPassword().orElse(null));
159     }
160     return credentials;
161   }
162
163   private static class ExternalIdentityProvider implements IdentityProvider {
164     @Override
165     public String getKey() {
166       return SQ_AUTHORITY;
167     }
168
169     @Override
170     public String getName() {
171       return SQ_AUTHORITY;
172     }
173
174     @Override
175     public Display getDisplay() {
176       return null;
177     }
178
179     @Override
180     public boolean isEnabled() {
181       return true;
182     }
183
184     @Override
185     public boolean allowsUsersToSignUp() {
186       return true;
187     }
188   }
189
190   @Override
191   public void stop() {
192     // Nothing to do
193   }
194 }