]> source.dussan.org Git - sonarqube.git/blob
97ba8d33aa4f0eb2a6950219f857ef5baa7872cd
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2018 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.Startable;
28 import org.sonar.api.config.Configuration;
29 import org.sonar.api.security.Authenticator;
30 import org.sonar.api.security.ExternalGroupsProvider;
31 import org.sonar.api.security.ExternalUsersProvider;
32 import org.sonar.api.security.SecurityRealm;
33 import org.sonar.api.security.UserDetails;
34 import org.sonar.api.server.authentication.Display;
35 import org.sonar.api.server.authentication.IdentityProvider;
36 import org.sonar.api.server.authentication.UserIdentity;
37 import org.sonar.api.utils.log.Logger;
38 import org.sonar.api.utils.log.Loggers;
39 import org.sonar.db.user.UserDto;
40 import org.sonar.server.authentication.UserRegistration.ExistingEmailStrategy;
41 import org.sonar.server.authentication.UserRegistration.UpdateLoginStrategy;
42 import org.sonar.server.authentication.event.AuthenticationEvent;
43 import org.sonar.server.authentication.event.AuthenticationEvent.Source;
44 import org.sonar.server.authentication.event.AuthenticationException;
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 = Loggers.get(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, HttpServletRequest 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, HttpServletRequest request, AuthenticationEvent.Method method) {
95     try {
96       ExternalUsersProvider.Context externalUsersProviderContext = new ExternalUsersProvider.Context(credentials.getLogin(), request);
97       UserDetails details = externalUsersProvider.doGetUserDetails(externalUsersProviderContext);
98       if (details == null) {
99         throw AuthenticationException.newBuilder()
100           .setSource(realmEventSource(method))
101           .setLogin(credentials.getLogin())
102           .setMessage("No user details")
103           .build();
104       }
105       Authenticator.Context authenticatorContext = new Authenticator.Context(credentials.getLogin(), credentials.getPassword(), request);
106       boolean status = authenticator.doAuthenticate(authenticatorContext);
107       if (!status) {
108         throw AuthenticationException.newBuilder()
109           .setSource(realmEventSource(method))
110           .setLogin(credentials.getLogin())
111           .setMessage("Realm returned authenticate=false")
112           .build();
113       }
114       UserDto userDto = synchronize(credentials.getLogin(), details, request, method);
115       authenticationEvent.loginSuccess(request, credentials.getLogin(), realmEventSource(method));
116       return userDto;
117     } catch (AuthenticationException e) {
118       throw e;
119     } catch (Exception e) {
120       // It seems that with Realm API it's expected to log the error and to not authenticate the user
121       LOG.error("Error during authentication", e);
122       throw AuthenticationException.newBuilder()
123         .setSource(realmEventSource(method))
124         .setLogin(credentials.getLogin())
125         .setMessage(e.getMessage())
126         .build();
127     }
128   }
129
130   private Source realmEventSource(AuthenticationEvent.Method method) {
131     return Source.realm(method, realm.getName());
132   }
133
134   private UserDto synchronize(String userLogin, UserDetails details, HttpServletRequest request, AuthenticationEvent.Method method) {
135     String name = details.getName();
136     UserIdentity.Builder userIdentityBuilder = UserIdentity.builder()
137       .setLogin(userLogin)
138       .setName(isEmpty(name) ? userLogin : name)
139       .setEmail(trimToNull(details.getEmail()))
140       .setProviderLogin(userLogin);
141     if (externalGroupsProvider != null) {
142       ExternalGroupsProvider.Context context = new ExternalGroupsProvider.Context(userLogin, request);
143       Collection<String> groups = externalGroupsProvider.doGetGroups(context);
144       userIdentityBuilder.setGroups(new HashSet<>(groups));
145     }
146     return userRegistrar.register(
147       UserRegistration.builder()
148         .setUserIdentity(userIdentityBuilder.build())
149         .setProvider(new ExternalIdentityProvider())
150         .setSource(realmEventSource(method))
151         .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
152         .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW)
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());
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 }