]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8416 add log (INFO) when user successfuly log in
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Fri, 25 Nov 2016 10:41:10 +0000 (11:41 +0100)
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Thu, 1 Dec 2016 15:55:10 +0000 (16:55 +0100)
19 files changed:
server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationModule.java
server/sonar-server/src/main/java/org/sonar/server/authentication/BasicAuthenticator.java
server/sonar-server/src/main/java/org/sonar/server/authentication/CredentialsAuthenticator.java
server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2CallbackFilter.java
server/sonar-server/src/main/java/org/sonar/server/authentication/RealmAuthenticator.java
server/sonar-server/src/main/java/org/sonar/server/authentication/SsoAuthenticator.java
server/sonar-server/src/main/java/org/sonar/server/authentication/event/AuthenticationEvent.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/authentication/event/AuthenticationEventImpl.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/authentication/event/package-info.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/authentication/ws/LoginAction.java
server/sonar-server/src/test/java/org/sonar/server/authentication/BasicAuthenticatorTest.java
server/sonar-server/src/test/java/org/sonar/server/authentication/CredentialsAuthenticatorTest.java
server/sonar-server/src/test/java/org/sonar/server/authentication/FakeOAuth2IdentityProvider.java
server/sonar-server/src/test/java/org/sonar/server/authentication/OAuth2CallbackFilterTest.java
server/sonar-server/src/test/java/org/sonar/server/authentication/RealmAuthenticatorTest.java
server/sonar-server/src/test/java/org/sonar/server/authentication/SsoAuthenticatorTest.java
server/sonar-server/src/test/java/org/sonar/server/authentication/event/AuthenticationEventImplTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/authentication/event/AuthenticationEventSourceTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/authentication/ws/LoginActionTest.java

index 4d810053907486f996a13e3d474d7565edec73db..639a9db840e952e5fce109667507e20cbff94e71 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.server.authentication;
 
 import org.sonar.core.platform.Module;
+import org.sonar.server.authentication.event.AuthenticationEventImpl;
 import org.sonar.server.authentication.ws.AuthenticationWs;
 import org.sonar.server.authentication.ws.LoginAction;
 import org.sonar.server.authentication.ws.ValidateAction;
@@ -28,6 +29,7 @@ public class AuthenticationModule extends Module {
   @Override
   protected void configureModule() {
     add(
+      AuthenticationEventImpl.class,
       AuthenticationWs.class,
       InitFilter.class,
       OAuth2CallbackFilter.class,
index d87da6dcca46fb2b22b05096652a801e56ebaac0..1e1b2bdffb9ee977f29f4809cb68360ac6a32d14 100644 (file)
@@ -27,11 +27,14 @@ import javax.servlet.http.HttpServletRequest;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.user.UserDto;
+import org.sonar.server.authentication.event.AuthenticationEvent;
 import org.sonar.server.exceptions.UnauthorizedException;
 import org.sonar.server.usertoken.UserTokenAuthenticator;
 
 import static java.util.Locale.ENGLISH;
 import static org.apache.commons.lang.StringUtils.isEmpty;
+import static org.sonar.server.authentication.event.AuthenticationEvent.Method;
+import static org.sonar.server.authentication.event.AuthenticationEvent.Source;
 
 public class BasicAuthenticator {
 
@@ -43,12 +46,14 @@ public class BasicAuthenticator {
   private final DbClient dbClient;
   private final CredentialsAuthenticator credentialsAuthenticator;
   private final UserTokenAuthenticator userTokenAuthenticator;
+  private final AuthenticationEvent authenticationEvent;
 
   public BasicAuthenticator(DbClient dbClient, CredentialsAuthenticator credentialsAuthenticator,
-    UserTokenAuthenticator userTokenAuthenticator) {
+    UserTokenAuthenticator userTokenAuthenticator, AuthenticationEvent authenticationEvent) {
     this.dbClient = dbClient;
     this.credentialsAuthenticator = credentialsAuthenticator;
     this.userTokenAuthenticator = userTokenAuthenticator;
+    this.authenticationEvent = authenticationEvent;
   }
 
   public Optional<UserDto> authenticate(HttpServletRequest request) {
@@ -60,7 +65,8 @@ public class BasicAuthenticator {
     String[] credentials = getCredentials(authorizationHeader);
     String login = credentials[0];
     String password = credentials[1];
-    return Optional.of(authenticate(login, password, request));
+    UserDto userDto = authenticate(login, password, request);
+    return Optional.of(userDto);
   }
 
   private static String[] getCredentials(String authorizationHeader) {
@@ -86,9 +92,11 @@ public class BasicAuthenticator {
 
   private UserDto authenticate(String login, String password, HttpServletRequest request) {
     if (isEmpty(password)) {
-      return authenticateFromUserToken(login);
+      UserDto userDto = authenticateFromUserToken(login);
+      authenticationEvent.login(request, userDto.getLogin(), Source.local(Method.BASIC_TOKEN));
+      return userDto;
     } else {
-      return credentialsAuthenticator.authenticate(login, password, request);
+      return credentialsAuthenticator.authenticate(login, password, request, Method.BASIC);
     }
   }
 
index 3706b8a072c01b60d28f68eb7457766632d7f5a1..7c2d168621b2ba50318df7cc745e1f15a5178b46 100644 (file)
 
 package org.sonar.server.authentication;
 
-import static org.sonar.db.user.UserDto.encryptPassword;
-
 import java.util.Optional;
 import javax.servlet.http.HttpServletRequest;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.user.UserDto;
+import org.sonar.server.authentication.event.AuthenticationEvent;
 import org.sonar.server.exceptions.UnauthorizedException;
 
+import static org.sonar.db.user.UserDto.encryptPassword;
+import static org.sonar.server.authentication.event.AuthenticationEvent.*;
+
 public class CredentialsAuthenticator {
 
   private final DbClient dbClient;
   private final RealmAuthenticator externalAuthenticator;
+  private final AuthenticationEvent authenticationEvent;
 
-  public CredentialsAuthenticator(DbClient dbClient, RealmAuthenticator externalAuthenticator) {
+  public CredentialsAuthenticator(DbClient dbClient, RealmAuthenticator externalAuthenticator, AuthenticationEvent authenticationEvent) {
     this.dbClient = dbClient;
     this.externalAuthenticator = externalAuthenticator;
+    this.authenticationEvent = authenticationEvent;
   }
 
-  public UserDto authenticate(String userLogin, String userPassword, HttpServletRequest request) {
+  public UserDto authenticate(String userLogin, String userPassword, HttpServletRequest request, Method method) {
     DbSession dbSession = dbClient.openSession(false);
     try {
-      return authenticate(dbSession, userLogin, userPassword, request);
+      return authenticate(dbSession, userLogin, userPassword, request, method);
     } finally {
       dbClient.closeSession(dbSession);
     }
   }
 
-  private UserDto authenticate(DbSession dbSession, String userLogin, String userPassword, HttpServletRequest request) {
+  private UserDto authenticate(DbSession dbSession, String userLogin, String userPassword, HttpServletRequest request, Method method) {
     UserDto user = dbClient.userDao().selectActiveUserByLogin(dbSession, userLogin);
     if (user != null && user.isLocal()) {
-      return authenticateFromDb(user, userPassword);
+      UserDto userDto = authenticateFromDb(user, userPassword);
+      authenticationEvent.login(request, userLogin, Source.local(method));
+      return userDto;
     }
-    Optional<UserDto> userDto = externalAuthenticator.authenticate(userLogin, userPassword, request);
+    Optional<UserDto> userDto = externalAuthenticator.authenticate(userLogin, userPassword, request, method);
     if (userDto.isPresent()) {
       return userDto.get();
     }
index f1ae0c5dcb9e2597c1988f56f1f5d8ed69313272..e38f63adfb6eb5eeffa02d29f4220c8939dbde45 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.server.authentication;
 
 import java.io.IOException;
+import javax.annotation.CheckForNull;
 import javax.servlet.FilterChain;
 import javax.servlet.FilterConfig;
 import javax.servlet.ServletException;
@@ -32,12 +33,15 @@ import org.sonar.api.platform.Server;
 import org.sonar.api.server.authentication.IdentityProvider;
 import org.sonar.api.server.authentication.OAuth2IdentityProvider;
 import org.sonar.api.server.authentication.UnauthorizedException;
+import org.sonar.api.server.authentication.UserIdentity;
 import org.sonar.api.web.ServletFilter;
+import org.sonar.server.authentication.event.AuthenticationEvent;
 
 import static com.google.common.base.Strings.isNullOrEmpty;
 import static java.lang.String.format;
 import static org.sonar.server.authentication.AuthenticationError.handleError;
 import static org.sonar.server.authentication.AuthenticationError.handleUnauthorizedError;
+import static org.sonar.server.authentication.event.AuthenticationEvent.Source;
 
 public class OAuth2CallbackFilter extends ServletFilter {
 
@@ -46,11 +50,14 @@ public class OAuth2CallbackFilter extends ServletFilter {
   private final IdentityProviderRepository identityProviderRepository;
   private final OAuth2ContextFactory oAuth2ContextFactory;
   private final Server server;
+  private final AuthenticationEvent authenticationEvent;
 
-  public OAuth2CallbackFilter(IdentityProviderRepository identityProviderRepository, OAuth2ContextFactory oAuth2ContextFactory, Server server) {
+  public OAuth2CallbackFilter(IdentityProviderRepository identityProviderRepository, OAuth2ContextFactory oAuth2ContextFactory,
+    Server server, AuthenticationEvent authenticationEvent) {
     this.identityProviderRepository = identityProviderRepository;
     this.oAuth2ContextFactory = oAuth2ContextFactory;
     this.server = server;
+    this.authenticationEvent = authenticationEvent;
   }
 
   @Override
@@ -68,7 +75,11 @@ public class OAuth2CallbackFilter extends ServletFilter {
       IdentityProvider provider = identityProviderRepository.getEnabledByKey(keyProvider);
       if (provider instanceof OAuth2IdentityProvider) {
         OAuth2IdentityProvider oauthProvider = (OAuth2IdentityProvider) provider;
-        oauthProvider.callback(oAuth2ContextFactory.newCallback(httpRequest, (HttpServletResponse) response, oauthProvider));
+        WrappedContext context = new WrappedContext(oAuth2ContextFactory.newCallback(httpRequest, (HttpServletResponse) response, oauthProvider));
+        oauthProvider.callback(context);
+        if (context.isAuthenticated()) {
+          authenticationEvent.login(httpRequest, context.getLogin(), Source.oauth2(provider.getName()));
+        }
       } else {
         handleError((HttpServletResponse) response, format("Not an OAuth2IdentityProvider: %s", provider.getClass()));
       }
@@ -76,12 +87,11 @@ public class OAuth2CallbackFilter extends ServletFilter {
       handleUnauthorizedError(e, (HttpServletResponse) response);
     } catch (Exception e) {
       handleError(e, (HttpServletResponse) response,
-        keyProvider.isEmpty() ? "Fail to callback authentication" :
-          format("Fail to callback authentication with '%s'", keyProvider));
+        keyProvider.isEmpty() ? "Fail to callback authentication" : format("Fail to callback authentication with '%s'", keyProvider));
     }
   }
 
-  public static String extractKeyProvider(String requestUri, String context) {
+  private static String extractKeyProvider(String requestUri, String context) {
     if (requestUri.contains(context)) {
       String key = requestUri.replace(context, "");
       if (!isNullOrEmpty(key)) {
@@ -100,4 +110,56 @@ public class OAuth2CallbackFilter extends ServletFilter {
   public void destroy() {
     // Nothing to do
   }
+
+  private static final class WrappedContext implements OAuth2IdentityProvider.CallbackContext {
+    private final OAuth2IdentityProvider.CallbackContext delegate;
+    private boolean authenticated = false;
+    @CheckForNull
+    private String login;
+
+    private WrappedContext(OAuth2IdentityProvider.CallbackContext delegate) {
+      this.delegate = delegate;
+    }
+
+    @Override
+    public String getCallbackUrl() {
+      return delegate.getCallbackUrl();
+    }
+
+    @Override
+    public HttpServletRequest getRequest() {
+      return delegate.getRequest();
+    }
+
+    @Override
+    public HttpServletResponse getResponse() {
+      return delegate.getResponse();
+    }
+
+    @Override
+    public void verifyCsrfState() {
+      delegate.verifyCsrfState();
+    }
+
+    @Override
+    public void redirectToRequestedPage() {
+      delegate.redirectToRequestedPage();
+    }
+
+    @Override
+    public void authenticate(UserIdentity userIdentity) {
+      delegate.authenticate(userIdentity);
+      this.authenticated = true;
+      this.login = userIdentity.getLogin();
+    }
+
+    public boolean isAuthenticated() {
+      return authenticated;
+    }
+
+    @CheckForNull
+    public String getLogin() {
+      return login;
+    }
+  }
 }
index 76ccd170e91f14888752f7dfea11f6eafc234776..f2ab92b748285cb52874fa6e278f1609377fbf7e 100644 (file)
@@ -38,6 +38,7 @@ import org.sonar.api.server.authentication.UserIdentity;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
 import org.sonar.db.user.UserDto;
+import org.sonar.server.authentication.event.AuthenticationEvent;
 import org.sonar.server.exceptions.UnauthorizedException;
 import org.sonar.server.user.SecurityRealmFactory;
 
@@ -45,6 +46,7 @@ import static java.util.Objects.requireNonNull;
 import static org.apache.commons.lang.StringUtils.isEmpty;
 import static org.apache.commons.lang.StringUtils.trimToNull;
 import static org.sonar.api.CoreProperties.CORE_AUTHENTICATOR_CREATE_USERS;
+import static org.sonar.server.authentication.event.AuthenticationEvent.Source;
 import static org.sonar.server.user.UserUpdater.SQ_AUTHORITY;
 
 public class RealmAuthenticator implements Startable {
@@ -54,16 +56,19 @@ public class RealmAuthenticator implements Startable {
   private final Settings settings;
   private final SecurityRealmFactory securityRealmFactory;
   private final UserIdentityAuthenticator userIdentityAuthenticator;
+  private final AuthenticationEvent authenticationEvent;
 
   private SecurityRealm realm;
   private Authenticator authenticator;
   private ExternalUsersProvider externalUsersProvider;
   private ExternalGroupsProvider externalGroupsProvider;
 
-  public RealmAuthenticator(Settings settings, SecurityRealmFactory securityRealmFactory, UserIdentityAuthenticator userIdentityAuthenticator) {
+  public RealmAuthenticator(Settings settings, SecurityRealmFactory securityRealmFactory,
+    UserIdentityAuthenticator userIdentityAuthenticator, AuthenticationEvent authenticationEvent) {
     this.settings = settings;
     this.securityRealmFactory = securityRealmFactory;
     this.userIdentityAuthenticator = userIdentityAuthenticator;
+    this.authenticationEvent = authenticationEvent;
   }
 
   @Override
@@ -76,14 +81,14 @@ public class RealmAuthenticator implements Startable {
     }
   }
 
-  public Optional<UserDto> authenticate(String userLogin, String userPassword, HttpServletRequest request) {
+  public Optional<UserDto> authenticate(String userLogin, String userPassword, HttpServletRequest request, AuthenticationEvent.Method method) {
     if (realm == null) {
       return Optional.empty();
     }
-    return Optional.of(doAuthenticate(getLogin(userLogin), userPassword, request));
+    return Optional.of(doAuthenticate(getLogin(userLogin), userPassword, request, method));
   }
 
-  private UserDto doAuthenticate(String userLogin, String userPassword, HttpServletRequest request) {
+  private UserDto doAuthenticate(String userLogin, String userPassword, HttpServletRequest request, AuthenticationEvent.Method method) {
     try {
       ExternalUsersProvider.Context externalUsersProviderContext = new ExternalUsersProvider.Context(userLogin, request);
       UserDetails details = externalUsersProvider.doGetUserDetails(externalUsersProviderContext);
@@ -95,7 +100,9 @@ public class RealmAuthenticator implements Startable {
       if (!status) {
         throw new UnauthorizedException("Fail to authenticate from external provider");
       }
-      return synchronize(userLogin, details, request);
+      UserDto userDto = synchronize(userLogin, details, request);
+      authenticationEvent.login(request, userLogin, Source.realm(method, realm.getName()));
+      return userDto;
     } catch (Exception e) {
       // It seems that with Realm API it's expected to log the error and to not authenticate the user
       LOG.error("Error during authentication", e);
index a1d3608fc78eaac80334d50c99e3122f6ed83f74..292e101cf9c58be415030705f4d4baedc38c4a49 100644 (file)
@@ -42,10 +42,12 @@ import org.sonar.api.utils.System2;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
 import org.sonar.db.user.UserDto;
+import org.sonar.server.authentication.event.AuthenticationEvent;
 import org.sonar.server.exceptions.BadRequestException;
 
 import static org.apache.commons.lang.StringUtils.defaultIfBlank;
 import static org.apache.commons.lang.time.DateUtils.addMinutes;
+import static org.sonar.server.authentication.event.AuthenticationEvent.Source;
 import static org.sonar.server.user.UserUpdater.SQ_AUTHORITY;
 
 public class SsoAuthenticator implements Startable {
@@ -84,15 +86,18 @@ public class SsoAuthenticator implements Startable {
   private final Settings settings;
   private final UserIdentityAuthenticator userIdentityAuthenticator;
   private final JwtHttpHandler jwtHttpHandler;
+  private final AuthenticationEvent authenticationEvent;
 
   private boolean enabled = false;
   private Map<String, String> settingsByKey = new HashMap<>();
 
-  public SsoAuthenticator(System2 system2, Settings settings, UserIdentityAuthenticator userIdentityAuthenticator, JwtHttpHandler jwtHttpHandler) {
+  public SsoAuthenticator(System2 system2, Settings settings, UserIdentityAuthenticator userIdentityAuthenticator,
+    JwtHttpHandler jwtHttpHandler, AuthenticationEvent authenticationEvent) {
     this.system2 = system2;
     this.settings = settings;
     this.userIdentityAuthenticator = userIdentityAuthenticator;
     this.jwtHttpHandler = jwtHttpHandler;
+    this.authenticationEvent = authenticationEvent;
   }
 
   @Override
@@ -134,6 +139,7 @@ public class SsoAuthenticator implements Startable {
 
     UserDto userDto = doAuthenticate(headerValuesByNames, login);
     jwtHttpHandler.generateToken(userDto, ImmutableMap.of(LAST_REFRESH_TIME_TOKEN_PARAM, system2.now()), request, response);
+    authenticationEvent.login(request, userDto.getLogin(), Source.sso());
     return Optional.of(userDto);
   }
 
diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/event/AuthenticationEvent.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/event/AuthenticationEvent.java
new file mode 100644 (file)
index 0000000..a66e0e0
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.authentication.event;
+
+import java.util.Objects;
+import javax.annotation.Nullable;
+import javax.servlet.http.HttpServletRequest;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+public interface AuthenticationEvent {
+
+  void login(HttpServletRequest request, String login, Source source);
+
+  enum Method {
+    BASIC, BASIC_TOKEN, FORM, FORM_TOKEN, SSO, OAUTH2, EXTERNAL
+  }
+
+  enum Provider {
+    LOCAL, SSO, REALM, EXTERNAL
+  }
+
+  class Source {
+    private static final String LOCAL_PROVIDER_NAME = "local";
+    private static final Source SSO_INSTANCE = new Source(Method.SSO, Provider.SSO, "sso");
+
+    private final Method method;
+    private final Provider provider;
+    private final String providerName;
+
+    private Source(Method method, Provider provider, String providerName) {
+      this.method = requireNonNull(method, "method can't be null");
+      this.provider = requireNonNull(provider, "provider can't be null");
+      this.providerName = requireNonNull(providerName, "provider name can't be null");
+      checkArgument(!providerName.isEmpty(), "provider name can't be empty");
+    }
+
+    public static Source local(Method method) {
+      return new Source(method, Provider.LOCAL, LOCAL_PROVIDER_NAME);
+    }
+
+    public static Source oauth2(String providerName) {
+      return new Source(Method.OAUTH2, Provider.EXTERNAL, providerName);
+    }
+
+    public static Source realm(Method method, String providerName) {
+      return new Source(method, Provider.REALM, providerName);
+    }
+
+    public static Source sso() {
+      return SSO_INSTANCE;
+    }
+
+    public Method getMethod() {
+      return method;
+    }
+
+    public Provider getProvider() {
+      return provider;
+    }
+
+    public String getProviderName() {
+      return providerName;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      Source source = (Source) o;
+      return method == source.method &&
+        provider == source.provider &&
+        providerName.equals(source.providerName);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(method, provider, providerName);
+    }
+
+    @Override
+    public String toString() {
+      return "Source{" +
+        "method=" + method +
+        ", provider=" + provider +
+        ", providerName='" + providerName + '\'' +
+        '}';
+    }
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/event/AuthenticationEventImpl.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/event/AuthenticationEventImpl.java
new file mode 100644 (file)
index 0000000..5a96961
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.authentication.event;
+
+import com.google.common.base.Joiner;
+import java.util.Collections;
+import javax.annotation.Nullable;
+import javax.servlet.http.HttpServletRequest;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.core.util.stream.Collectors;
+
+public class AuthenticationEventImpl implements AuthenticationEvent {
+  private static final Logger LOGGER = Loggers.get("auth.event");
+
+  @Override
+  public void login(HttpServletRequest request, @Nullable String login, Source source) {
+    LOGGER.info("login success [method|{}][provider|{}|{}][IP|{}|{}][login|{}]",
+        source.getMethod(), source.getProvider(), source.getProviderName(), request.getRemoteAddr(), getAllIps(request),
+        login == null ? "" : login);
+  }
+
+  private static String getAllIps(HttpServletRequest request) {
+    return Collections.list(request.getHeaders("X-Forwarded-For")).stream().collect(Collectors.join(Joiner.on(",")));
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/event/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/event/package-info.java
new file mode 100644 (file)
index 0000000..be2ac2f
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.authentication.event;
+
+import javax.annotation.ParametersAreNonnullByDefault;
index 3a959c4fd122414f68db982e03dbfb9e152e338c..ab057b0827157fd0950b1502863054092f34d861 100644 (file)
@@ -39,6 +39,7 @@ import org.sonar.server.user.ThreadLocalUserSession;
 
 import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
 import static org.apache.commons.lang.StringUtils.isEmpty;
+import static org.sonar.server.authentication.event.AuthenticationEvent.Method;
 
 public class LoginAction extends ServletFilter {
 
@@ -88,7 +89,7 @@ public class LoginAction extends ServletFilter {
     if (isEmpty(login) || isEmpty(password)) {
       throw new UnauthorizedException();
     }
-    return credentialsAuthenticator.authenticate(login, password, request);
+    return credentialsAuthenticator.authenticate(login, password, request, Method.FORM);
   }
 
   @Override
index a5098b5a942107fd2036e2f7212f9e1c30b9b308..0a0a30c92586d79f2564ba25c50c8fc2c9435c74 100644 (file)
@@ -23,7 +23,6 @@ package org.sonar.server.authentication;
 import java.util.Base64;
 import java.util.Optional;
 import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -33,6 +32,7 @@ import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
 import org.sonar.db.user.UserDto;
 import org.sonar.db.user.UserTesting;
+import org.sonar.server.authentication.event.AuthenticationEvent;
 import org.sonar.server.exceptions.UnauthorizedException;
 import org.sonar.server.usertoken.UserTokenAuthenticator;
 
@@ -41,18 +41,22 @@ import static org.assertj.core.api.Java6Assertions.assertThat;
 import static org.junit.rules.ExpectedException.none;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
+import static org.sonar.server.authentication.event.AuthenticationEvent.Source;
+import static org.sonar.server.authentication.event.AuthenticationEvent.Method.BASIC;
+import static org.sonar.server.authentication.event.AuthenticationEvent.Method.BASIC_TOKEN;
 
 public class BasicAuthenticatorTest {
 
   private static final Base64.Encoder BASE64_ENCODER = Base64.getEncoder();
 
-  static final String LOGIN = "login";
-  static final String PASSWORD = "password";
-  static final String CREDENTIALS_IN_BASE64 = toBase64(LOGIN + ":" + PASSWORD);
+  private static final String LOGIN = "login";
+  private static final String PASSWORD = "password";
+  private static final String CREDENTIALS_IN_BASE64 = toBase64(LOGIN + ":" + PASSWORD);
 
-  static final UserDto USER = UserTesting.newUserDto().setLogin(LOGIN);
+  private static final UserDto USER = UserTesting.newUserDto().setLogin(LOGIN);
 
   @Rule
   public ExpectedException expectedException = none();
@@ -60,44 +64,47 @@ public class BasicAuthenticatorTest {
   @Rule
   public DbTester dbTester = DbTester.create(System2.INSTANCE);
 
-  DbClient dbClient = dbTester.getDbClient();
+  private DbClient dbClient = dbTester.getDbClient();
 
-  DbSession dbSession = dbTester.getSession();
+  private DbSession dbSession = dbTester.getSession();
 
-  CredentialsAuthenticator credentialsAuthenticator = mock(CredentialsAuthenticator.class);
-  UserTokenAuthenticator userTokenAuthenticator = mock(UserTokenAuthenticator.class);
+  private CredentialsAuthenticator credentialsAuthenticator = mock(CredentialsAuthenticator.class);
+  private UserTokenAuthenticator userTokenAuthenticator = mock(UserTokenAuthenticator.class);
 
-  HttpServletRequest request = mock(HttpServletRequest.class);
-  HttpServletResponse response = mock(HttpServletResponse.class);
+  private HttpServletRequest request = mock(HttpServletRequest.class);
 
-  BasicAuthenticator underTest = new BasicAuthenticator(dbClient, credentialsAuthenticator, userTokenAuthenticator);
+  private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class);
+
+  private BasicAuthenticator underTest = new BasicAuthenticator(dbClient, credentialsAuthenticator, userTokenAuthenticator, authenticationEvent);
 
   @Test
   public void authenticate_from_basic_http_header() throws Exception {
     when(request.getHeader("Authorization")).thenReturn("Basic " + CREDENTIALS_IN_BASE64);
-    when(credentialsAuthenticator.authenticate(LOGIN, PASSWORD, request)).thenReturn(USER);
+    when(credentialsAuthenticator.authenticate(LOGIN, PASSWORD, request, BASIC)).thenReturn(USER);
 
     underTest.authenticate(request);
 
-    verify(credentialsAuthenticator).authenticate(LOGIN, PASSWORD, request);
+    verify(credentialsAuthenticator).authenticate(LOGIN, PASSWORD, request, BASIC);
+    verifyNoMoreInteractions(authenticationEvent);
   }
 
   @Test
   public void authenticate_from_basic_http_header_with_password_containing_semi_colon() throws Exception {
     String password = "!ascii-only:-)@";
     when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64(LOGIN + ":" + password));
-    when(credentialsAuthenticator.authenticate(LOGIN, password, request)).thenReturn(USER);
+    when(credentialsAuthenticator.authenticate(LOGIN, password, request, BASIC)).thenReturn(USER);
 
     underTest.authenticate(request);
 
-    verify(credentialsAuthenticator).authenticate(LOGIN, password, request);
+    verify(credentialsAuthenticator).authenticate(LOGIN, password, request, BASIC);
+    verifyNoMoreInteractions(authenticationEvent);
   }
 
   @Test
   public void does_not_authenticate_when_no_authorization_header() throws Exception {
     underTest.authenticate(request);
 
-    verifyZeroInteractions(credentialsAuthenticator);
+    verifyZeroInteractions(credentialsAuthenticator, authenticationEvent);
   }
 
   @Test
@@ -106,7 +113,7 @@ public class BasicAuthenticatorTest {
 
     underTest.authenticate(request);
 
-    verifyZeroInteractions(credentialsAuthenticator);
+    verifyZeroInteractions(credentialsAuthenticator, authenticationEvent);
   }
 
   @Test
@@ -114,7 +121,11 @@ public class BasicAuthenticatorTest {
     when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64(":" + PASSWORD));
 
     expectedException.expect(UnauthorizedException.class);
-    underTest.authenticate(request);
+    try {
+      underTest.authenticate(request);
+    } finally {
+      verifyZeroInteractions(authenticationEvent);
+    }
   }
 
   @Test
@@ -136,6 +147,7 @@ public class BasicAuthenticatorTest {
 
     assertThat(userDto.isPresent()).isTrue();
     assertThat(userDto.get().getLogin()).isEqualTo(LOGIN);
+    verify(authenticationEvent).login(request, LOGIN, Source.local(BASIC_TOKEN));
   }
 
   @Test
@@ -145,7 +157,11 @@ public class BasicAuthenticatorTest {
     when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64("token:"));
 
     expectedException.expect(UnauthorizedException.class);
-    underTest.authenticate(request);
+    try {
+      underTest.authenticate(request);
+    } finally {
+      verifyZeroInteractions(authenticationEvent);
+    }
   }
 
   @Test
@@ -155,16 +171,20 @@ public class BasicAuthenticatorTest {
     when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64("token:"));
 
     expectedException.expect(UnauthorizedException.class);
-    underTest.authenticate(request);
+    try {
+      underTest.authenticate(request);
+    } finally {
+      verifyZeroInteractions(authenticationEvent);
+    }
   }
 
-  private UserDto insertUser(UserDto userDto){
+  private UserDto insertUser(UserDto userDto) {
     dbClient.userDao().insert(dbSession, userDto);
     dbSession.commit();
     return userDto;
   }
 
-  private static String toBase64(String text){
+  private static String toBase64(String text) {
     return new String(BASE64_ENCODER.encode(text.getBytes(UTF_8)));
   }
 
index e4686805f2b2086683e830170ac8c3d8de8bb99b..5b900f56c67285eeda412bc86b449b48a3db3016 100644 (file)
 
 package org.sonar.server.authentication;
 
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.rules.ExpectedException.none;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.sonar.db.user.UserTesting.newUserDto;
-
 import java.util.Optional;
 import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -38,30 +30,40 @@ import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
 import org.sonar.db.user.UserDto;
+import org.sonar.server.authentication.event.AuthenticationEvent;
 import org.sonar.server.exceptions.UnauthorizedException;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.rules.ExpectedException.none;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+import static org.sonar.db.user.UserTesting.newUserDto;
+import static org.sonar.server.authentication.event.AuthenticationEvent.Source;
+import static org.sonar.server.authentication.event.AuthenticationEvent.Method.BASIC;
+
 public class CredentialsAuthenticatorTest {
 
-  static final String LOGIN = "LOGIN";
-  static final String PASSWORD = "PASSWORD";
-  static final String SALT = "0242b0b4c0a93ddfe09dd886de50bc25ba000b51";
-  static final String CRYPTED_PASSWORD = "540e4fc4be4e047db995bc76d18374a5b5db08cc";
+  private static final String LOGIN = "LOGIN";
+  private static final String PASSWORD = "PASSWORD";
+  private static final String SALT = "0242b0b4c0a93ddfe09dd886de50bc25ba000b51";
+  private static final String CRYPTED_PASSWORD = "540e4fc4be4e047db995bc76d18374a5b5db08cc";
 
   @Rule
   public ExpectedException expectedException = none();
-
   @Rule
   public DbTester dbTester = DbTester.create(System2.INSTANCE);
 
-  DbClient dbClient = dbTester.getDbClient();
+  private DbClient dbClient = dbTester.getDbClient();
 
-  DbSession dbSession = dbTester.getSession();
+  private DbSession dbSession = dbTester.getSession();
 
-  RealmAuthenticator externalAuthenticator = mock(RealmAuthenticator.class);
-  HttpServletRequest request = mock(HttpServletRequest.class);
-  HttpServletResponse response = mock(HttpServletResponse.class);
+  private RealmAuthenticator externalAuthenticator = mock(RealmAuthenticator.class);
+  private HttpServletRequest request = mock(HttpServletRequest.class);
+  private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class);
 
-  CredentialsAuthenticator underTest = new CredentialsAuthenticator(dbClient, externalAuthenticator);
+  private CredentialsAuthenticator underTest = new CredentialsAuthenticator(dbClient, externalAuthenticator, authenticationEvent);
 
   @Test
   public void authenticate_local_user() throws Exception {
@@ -73,6 +75,7 @@ public class CredentialsAuthenticatorTest {
 
     UserDto userDto = executeAuthenticate();
     assertThat(userDto.getLogin()).isEqualTo(LOGIN);
+    verify(authenticationEvent).login(request, LOGIN, Source.local(BASIC));
   }
 
   @Test
@@ -84,30 +87,39 @@ public class CredentialsAuthenticatorTest {
       .setLocal(true));
 
     expectedException.expect(UnauthorizedException.class);
-    executeAuthenticate();
+    try {
+      executeAuthenticate();
+    } finally {
+      verifyZeroInteractions(authenticationEvent);
+    }
   }
 
   @Test
   public void authenticate_external_user() throws Exception {
-    when(externalAuthenticator.authenticate(LOGIN, PASSWORD, request)).thenReturn(Optional.of(newUserDto()));
+    when(externalAuthenticator.authenticate(LOGIN, PASSWORD, request, BASIC)).thenReturn(Optional.of(newUserDto()));
     insertUser(newUserDto()
       .setLogin(LOGIN)
       .setLocal(false));
 
     executeAuthenticate();
 
-    verify(externalAuthenticator).authenticate(LOGIN, PASSWORD, request);
+    verify(externalAuthenticator).authenticate(LOGIN, PASSWORD, request, BASIC);
+    verifyZeroInteractions(authenticationEvent);
   }
 
   @Test
   public void fail_to_authenticate_authenticate_external_user_when_no_external_authentication() throws Exception {
-    when(externalAuthenticator.authenticate(LOGIN, PASSWORD, request)).thenReturn(Optional.empty());
+    when(externalAuthenticator.authenticate(LOGIN, PASSWORD, request, BASIC)).thenReturn(Optional.empty());
     insertUser(newUserDto()
       .setLogin(LOGIN)
       .setLocal(false));
 
     expectedException.expect(UnauthorizedException.class);
-    executeAuthenticate();
+    try {
+      executeAuthenticate();
+    } finally {
+      verifyZeroInteractions(authenticationEvent);
+    }
   }
 
   @Test
@@ -119,7 +131,11 @@ public class CredentialsAuthenticatorTest {
       .setLocal(true));
 
     expectedException.expect(UnauthorizedException.class);
-    executeAuthenticate();
+    try {
+      executeAuthenticate();
+    } finally {
+      verifyZeroInteractions(authenticationEvent);
+    }
   }
 
   @Test
@@ -131,14 +147,18 @@ public class CredentialsAuthenticatorTest {
       .setLocal(true));
 
     expectedException.expect(UnauthorizedException.class);
-    executeAuthenticate();
+    try {
+      executeAuthenticate();
+    } finally {
+      verifyZeroInteractions(authenticationEvent);
+    }
   }
 
-  private UserDto executeAuthenticate(){
-    return underTest.authenticate(LOGIN, PASSWORD, request);
+  private UserDto executeAuthenticate() {
+    return underTest.authenticate(LOGIN, PASSWORD, request, BASIC);
   }
 
-  private UserDto insertUser(UserDto userDto){
+  private UserDto insertUser(UserDto userDto) {
     dbClient.userDao().insert(dbSession, userDto);
     dbSession.commit();
     return userDto;
index b174a2988f9e1acd8661f7e7896096a47aae24e4..7a8aef6fc1e4df168d42715ef213a58c178e7b2e 100644 (file)
@@ -28,6 +28,7 @@ class FakeOAuth2IdentityProvider extends TestIdentityProvider implements OAuth2I
 
   public FakeOAuth2IdentityProvider(String key, boolean enabled) {
     setKey(key);
+    setName("name of " + key);
     setEnabled(enabled);
   }
 
index b0d6635464c3f7b2182fd8a42347cd79d4f66d9f..6ff937e9c609064745d665c16b176789217b62d1 100644 (file)
  */
 package org.sonar.server.authentication;
 
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
 import javax.servlet.FilterChain;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -34,37 +29,45 @@ import org.junit.rules.ExpectedException;
 import org.sonar.api.platform.Server;
 import org.sonar.api.server.authentication.OAuth2IdentityProvider;
 import org.sonar.api.server.authentication.UnauthorizedException;
+import org.sonar.api.server.authentication.UserIdentity;
 import org.sonar.api.utils.log.LogTester;
 import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.server.authentication.event.AuthenticationEvent;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+import static org.sonar.server.authentication.event.AuthenticationEvent.Source;
 
 public class OAuth2CallbackFilterTest {
 
-  static String OAUTH2_PROVIDER_KEY = "github";
+  private static final String OAUTH2_PROVIDER_KEY = "github";
+  private static final String LOGIN = "foo";
 
   @Rule
   public LogTester logTester = new LogTester();
-
   @Rule
   public ExpectedException thrown = ExpectedException.none();
-
   @Rule
   public IdentityProviderRepositoryRule identityProviderRepository = new IdentityProviderRepositoryRule();
 
-  OAuth2ContextFactory oAuth2ContextFactory = mock(OAuth2ContextFactory.class);
+  private OAuth2ContextFactory oAuth2ContextFactory = mock(OAuth2ContextFactory.class);
 
-  HttpServletRequest request = mock(HttpServletRequest.class);
-  HttpServletResponse response = mock(HttpServletResponse.class);
-  Server server = mock(Server.class);
-  FilterChain chain = mock(FilterChain.class);
+  private HttpServletRequest request = mock(HttpServletRequest.class);
+  private HttpServletResponse response = mock(HttpServletResponse.class);
+  private Server server = mock(Server.class);
+  private FilterChain chain = mock(FilterChain.class);
 
-  FakeOAuth2IdentityProvider oAuth2IdentityProvider = new FakeOAuth2IdentityProvider(OAUTH2_PROVIDER_KEY, true);
-  OAuth2IdentityProvider.InitContext oauth2Context = mock(OAuth2IdentityProvider.InitContext.class);
+  private FakeOAuth2IdentityProvider oAuth2IdentityProvider = new WellbehaveFakeOAuth2IdentityProvider(OAUTH2_PROVIDER_KEY, true, LOGIN);
+  private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class);
 
-  OAuth2CallbackFilter underTest = new OAuth2CallbackFilter(identityProviderRepository, oAuth2ContextFactory, server);
+  private OAuth2CallbackFilter underTest = new OAuth2CallbackFilter(identityProviderRepository, oAuth2ContextFactory, server, authenticationEvent);
 
   @Before
   public void setUp() throws Exception {
-    when(oAuth2ContextFactory.newContext(request, response, oAuth2IdentityProvider)).thenReturn(oauth2Context);
+    when(oAuth2ContextFactory.newCallback(request, response, oAuth2IdentityProvider)).thenReturn(mock(OAuth2IdentityProvider.CallbackContext.class));
     when(server.getContextPath()).thenReturn("");
   }
 
@@ -81,7 +84,19 @@ public class OAuth2CallbackFilterTest {
 
     underTest.doFilter(request, response, chain);
 
-    assertCallbackCalled();
+    assertCallbackCalled(oAuth2IdentityProvider, true);
+  }
+
+  @Test
+  public void do_filter_with_context_no_log_if_provider_did_not_call_authenticate_on_context() throws Exception {
+    when(server.getContextPath()).thenReturn("/sonarqube");
+    when(request.getRequestURI()).thenReturn("/sonarqube/oauth2/callback/" + OAUTH2_PROVIDER_KEY);
+    FakeOAuth2IdentityProvider identityProvider = new FakeOAuth2IdentityProvider(OAUTH2_PROVIDER_KEY, true);
+    identityProviderRepository.addIdentityProvider(identityProvider);
+
+    underTest.doFilter(request, response, chain);
+
+    assertCallbackCalled(identityProvider, false);
   }
 
   @Test
@@ -91,7 +106,7 @@ public class OAuth2CallbackFilterTest {
 
     underTest.doFilter(request, response, chain);
 
-    assertCallbackCalled();
+    assertCallbackCalled(oAuth2IdentityProvider, true);
   }
 
   @Test
@@ -103,6 +118,7 @@ public class OAuth2CallbackFilterTest {
     underTest.doFilter(request, response, chain);
 
     assertError("Not an OAuth2IdentityProvider: class org.sonar.server.authentication.FakeBasicIdentityProvider");
+    verifyZeroInteractions(authenticationEvent);
   }
 
   @Test
@@ -113,6 +129,7 @@ public class OAuth2CallbackFilterTest {
     underTest.doFilter(request, response, chain);
 
     assertError("Fail to callback authentication with 'github'");
+    verifyZeroInteractions(authenticationEvent);
   }
 
   @Test
@@ -126,6 +143,7 @@ public class OAuth2CallbackFilterTest {
     underTest.doFilter(request, response, chain);
 
     verify(response).sendRedirect("/sessions/unauthorized?message=Email+john%40email.com+is+already+used");
+    verifyZeroInteractions(authenticationEvent);
   }
 
   @Test
@@ -137,11 +155,17 @@ public class OAuth2CallbackFilterTest {
     underTest.doFilter(request, response, chain);
 
     assertError("Fail to callback authentication");
+    verifyZeroInteractions(authenticationEvent);
   }
 
-  private void assertCallbackCalled() {
+  private void assertCallbackCalled(FakeOAuth2IdentityProvider oAuth2IdentityProvider, boolean expectLoginLog) {
     assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty();
     assertThat(oAuth2IdentityProvider.isCallbackCalled()).isTrue();
+    if (expectLoginLog) {
+      verify(authenticationEvent).login(request, LOGIN, Source.oauth2(oAuth2IdentityProvider.getName()));
+    } else {
+      verifyZeroInteractions(authenticationEvent);
+    }
   }
 
   private void assertError(String expectedError) throws Exception {
@@ -163,4 +187,26 @@ public class OAuth2CallbackFilterTest {
     }
   }
 
+  /**
+   * An extension of {@link FakeOAuth2IdentityProvider} that actually call {@link org.sonar.api.server.authentication.OAuth2IdentityProvider.CallbackContext#authenticate(UserIdentity)}.
+   */
+  private static class WellbehaveFakeOAuth2IdentityProvider extends FakeOAuth2IdentityProvider {
+    private final String login;
+
+    public WellbehaveFakeOAuth2IdentityProvider(String key, boolean enabled, String login) {
+      super(key, enabled);
+      this.login = login;
+    }
+
+    @Override
+    public void callback(CallbackContext context) {
+      super.callback(context);
+      context.authenticate(UserIdentity.builder()
+        .setLogin(login)
+        .setProviderLogin(login)
+        .setEmail(login + "@toto.com")
+        .setName("name of " + login)
+        .build());
+    }
+  }
 }
index 36682e422706f2e66348fb2e25ca70d1cf5f5971..1d5ac67de480ca9bc38e3375e7c9c047ff5ce622 100644 (file)
@@ -21,7 +21,7 @@
 package org.sonar.server.authentication;
 
 import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -36,6 +36,7 @@ import org.sonar.api.security.UserDetails;
 import org.sonar.api.server.authentication.IdentityProvider;
 import org.sonar.api.server.authentication.UserIdentity;
 import org.sonar.db.user.UserDto;
+import org.sonar.server.authentication.event.AuthenticationEvent;
 import org.sonar.server.exceptions.UnauthorizedException;
 import org.sonar.server.user.SecurityRealmFactory;
 
@@ -46,36 +47,45 @@ import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 import static org.sonar.db.user.UserTesting.newUserDto;
+import static org.sonar.server.authentication.event.AuthenticationEvent.Method.BASIC;
 
 public class RealmAuthenticatorTest {
 
+  private static final String LOGIN = "LOGIN";
+  private static final String PASSWORD = "PASSWORD";
+
+  private static final UserDto USER = newUserDto();
+  private static final String REALM_NAME = "realm name";
+
   @Rule
   public ExpectedException expectedException = none();
 
-  static final String LOGIN = "LOGIN";
-  static final String PASSWORD = "PASSWORD";
+  private ArgumentCaptor<UserIdentity> userIdentityArgumentCaptor = ArgumentCaptor.forClass(UserIdentity.class);
+  private ArgumentCaptor<IdentityProvider> identityProviderArgumentCaptor = ArgumentCaptor.forClass(IdentityProvider.class);
 
-  static final UserDto USER = newUserDto();
+  private Settings settings = new MapSettings();
 
-  ArgumentCaptor<UserIdentity> userIdentityArgumentCaptor = ArgumentCaptor.forClass(UserIdentity.class);
-  ArgumentCaptor<IdentityProvider> identityProviderArgumentCaptor = ArgumentCaptor.forClass(IdentityProvider.class);
+  private SecurityRealmFactory securityRealmFactory = mock(SecurityRealmFactory.class);
+  private SecurityRealm realm = mock(SecurityRealm.class);
+  private Authenticator authenticator = mock(Authenticator.class);
+  private ExternalUsersProvider externalUsersProvider = mock(ExternalUsersProvider.class);
+  private ExternalGroupsProvider externalGroupsProvider = mock(ExternalGroupsProvider.class);
 
-  Settings settings = new MapSettings();
+  private UserIdentityAuthenticator userIdentityAuthenticator = mock(UserIdentityAuthenticator.class);
+  private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class);
 
-  SecurityRealmFactory securityRealmFactory = mock(SecurityRealmFactory.class);
-  SecurityRealm realm = mock(SecurityRealm.class);
-  Authenticator authenticator = mock(Authenticator.class);
-  ExternalUsersProvider externalUsersProvider = mock(ExternalUsersProvider.class);
-  ExternalGroupsProvider externalGroupsProvider = mock(ExternalGroupsProvider.class);
+  private HttpServletRequest request = mock(HttpServletRequest.class);
 
-  UserIdentityAuthenticator userIdentityAuthenticator = mock(UserIdentityAuthenticator.class);
+  private RealmAuthenticator underTest = new RealmAuthenticator(settings, securityRealmFactory, userIdentityAuthenticator, authenticationEvent);
 
-  HttpServletRequest request = mock(HttpServletRequest.class);
-  HttpServletResponse response = mock(HttpServletResponse.class);
-
-  RealmAuthenticator underTest = new RealmAuthenticator(settings, securityRealmFactory, userIdentityAuthenticator);
+  @Before
+  public void setUp() throws Exception {
+    when(realm.getName()).thenReturn(REALM_NAME);
+  }
 
   @Test
   public void authenticate() throws Exception {
@@ -87,7 +97,7 @@ public class RealmAuthenticatorTest {
     when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(userDetails);
     when(userIdentityAuthenticator.authenticate(any(UserIdentity.class), any(IdentityProvider.class))).thenReturn(USER);
 
-    underTest.authenticate(LOGIN, PASSWORD, request);
+    underTest.authenticate(LOGIN, PASSWORD, request, BASIC);
 
     verify(userIdentityAuthenticator).authenticate(userIdentityArgumentCaptor.capture(), identityProviderArgumentCaptor.capture());
     UserIdentity userIdentity = userIdentityArgumentCaptor.getValue();
@@ -96,6 +106,7 @@ public class RealmAuthenticatorTest {
     assertThat(userIdentity.getName()).isEqualTo("name");
     assertThat(userIdentity.getEmail()).isEqualTo("email");
     assertThat(userIdentity.shouldSyncGroups()).isFalse();
+    verify(authenticationEvent).login(request, LOGIN, AuthenticationEvent.Source.realm(BASIC, REALM_NAME));
   }
 
   @Test
@@ -108,7 +119,7 @@ public class RealmAuthenticatorTest {
     when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(userDetails);
     when(userIdentityAuthenticator.authenticate(any(UserIdentity.class), any(IdentityProvider.class))).thenReturn(USER);
 
-    underTest.authenticate(LOGIN, PASSWORD, request);
+    underTest.authenticate(LOGIN, PASSWORD, request, BASIC);
 
     verify(userIdentityAuthenticator).authenticate(userIdentityArgumentCaptor.capture(), identityProviderArgumentCaptor.capture());
 
@@ -116,6 +127,7 @@ public class RealmAuthenticatorTest {
     assertThat(identityProviderArgumentCaptor.getValue().getName()).isEqualTo("sonarqube");
     assertThat(identityProviderArgumentCaptor.getValue().getDisplay()).isNull();
     assertThat(identityProviderArgumentCaptor.getValue().isEnabled()).isTrue();
+    verify(authenticationEvent).login(request, LOGIN, AuthenticationEvent.Source.realm(BASIC, REALM_NAME));
   }
 
   @Test
@@ -127,10 +139,11 @@ public class RealmAuthenticatorTest {
     when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(userDetails);
     when(userIdentityAuthenticator.authenticate(any(UserIdentity.class), any(IdentityProvider.class))).thenReturn(USER);
 
-    underTest.authenticate(LOGIN, PASSWORD, request);
+    underTest.authenticate(LOGIN, PASSWORD, request, BASIC);
 
     verify(userIdentityAuthenticator).authenticate(userIdentityArgumentCaptor.capture(), identityProviderArgumentCaptor.capture());
     assertThat(identityProviderArgumentCaptor.getValue().getName()).isEqualTo("sonarqube");
+    verify(authenticationEvent).login(request, LOGIN, AuthenticationEvent.Source.realm(BASIC, REALM_NAME));
   }
 
   @Test
@@ -145,6 +158,7 @@ public class RealmAuthenticatorTest {
     UserIdentity userIdentity = userIdentityArgumentCaptor.getValue();
     assertThat(userIdentity.shouldSyncGroups()).isTrue();
     assertThat(userIdentity.getGroups()).containsOnly("group1", "group2");
+    verify(authenticationEvent).login(request, LOGIN, AuthenticationEvent.Source.realm(BASIC, REALM_NAME));
   }
 
   @Test
@@ -156,10 +170,11 @@ public class RealmAuthenticatorTest {
     when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(userDetails);
     when(userIdentityAuthenticator.authenticate(any(UserIdentity.class), any(IdentityProvider.class))).thenReturn(USER);
 
-    underTest.authenticate(LOGIN, PASSWORD, request);
+    underTest.authenticate(LOGIN, PASSWORD, request, BASIC);
 
     verify(userIdentityAuthenticator).authenticate(userIdentityArgumentCaptor.capture(), identityProviderArgumentCaptor.capture());
     assertThat(userIdentityArgumentCaptor.getValue().getName()).isEqualTo(LOGIN);
+    verify(authenticationEvent).login(request, LOGIN, AuthenticationEvent.Source.realm(BASIC, REALM_NAME));
   }
 
   @Test
@@ -171,6 +186,7 @@ public class RealmAuthenticatorTest {
 
     verify(userIdentityAuthenticator).authenticate(userIdentityArgumentCaptor.capture(), identityProviderArgumentCaptor.capture());
     assertThat(identityProviderArgumentCaptor.getValue().allowsUsersToSignUp()).isTrue();
+    verify(authenticationEvent).login(request, LOGIN, AuthenticationEvent.Source.realm(BASIC, REALM_NAME));
   }
 
   @Test
@@ -182,6 +198,7 @@ public class RealmAuthenticatorTest {
 
     verify(userIdentityAuthenticator).authenticate(userIdentityArgumentCaptor.capture(), identityProviderArgumentCaptor.capture());
     assertThat(identityProviderArgumentCaptor.getValue().allowsUsersToSignUp()).isFalse();
+    verify(authenticationEvent).login(request, LOGIN, AuthenticationEvent.Source.realm(BASIC, REALM_NAME));
   }
 
   @Test
@@ -195,6 +212,7 @@ public class RealmAuthenticatorTest {
     UserIdentity userIdentity = userIdentityArgumentCaptor.getValue();
     assertThat(userIdentity.getLogin()).isEqualTo("login");
     assertThat(userIdentity.getProviderLogin()).isEqualTo("login");
+    verify(authenticationEvent).login(request, "login", AuthenticationEvent.Source.realm(BASIC, REALM_NAME));
   }
 
   @Test
@@ -208,6 +226,7 @@ public class RealmAuthenticatorTest {
     UserIdentity userIdentity = userIdentityArgumentCaptor.getValue();
     assertThat(userIdentity.getLogin()).isEqualTo("LoGiN");
     assertThat(userIdentity.getProviderLogin()).isEqualTo("LoGiN");
+    verify(authenticationEvent).login(request, "LoGiN", AuthenticationEvent.Source.realm(BASIC, REALM_NAME));
   }
 
   @Test
@@ -218,7 +237,11 @@ public class RealmAuthenticatorTest {
     when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(null);
 
     expectedException.expect(UnauthorizedException.class);
-    underTest.authenticate(LOGIN, PASSWORD, request);
+    try {
+      underTest.authenticate(LOGIN, PASSWORD, request, BASIC);
+    } finally {
+      verifyZeroInteractions(authenticationEvent);
+    }
   }
 
   @Test
@@ -229,7 +252,11 @@ public class RealmAuthenticatorTest {
     when(authenticator.doAuthenticate(any(Authenticator.Context.class))).thenReturn(false);
 
     expectedException.expect(UnauthorizedException.class);
-    underTest.authenticate(LOGIN, PASSWORD, request);
+    try {
+      underTest.authenticate(LOGIN, PASSWORD, request, BASIC);
+    } finally {
+      verifyZeroInteractions(authenticationEvent);
+    }
   }
 
   @Test
@@ -240,12 +267,17 @@ public class RealmAuthenticatorTest {
     when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(null);
 
     expectedException.expect(UnauthorizedException.class);
-    underTest.authenticate(LOGIN, PASSWORD, request);
+    try {
+      underTest.authenticate(LOGIN, PASSWORD, request, BASIC);
+    } finally {
+      verifyZeroInteractions(authenticationEvent);
+    }
   }
 
   @Test
   public void return_empty_user_when_no_realm() throws Exception {
-    assertThat(underTest.authenticate(LOGIN, PASSWORD, request)).isEmpty();
+    assertThat(underTest.authenticate(LOGIN, PASSWORD, request, BASIC)).isEmpty();
+    verifyNoMoreInteractions(authenticationEvent);
   }
 
   @Test
@@ -293,7 +325,7 @@ public class RealmAuthenticatorTest {
     UserDetails userDetails = new UserDetails();
     userDetails.setName("name");
     when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(userDetails);
-    underTest.authenticate(login, PASSWORD, request);
+    underTest.authenticate(login, PASSWORD, request, BASIC);
   }
 
 }
index 9ed6449b1dbc59b5c3dce69db750a495b379ba9c..ab9749155565d6a99d77f210fdc175f4f9e0a6cd 100644 (file)
@@ -41,6 +41,7 @@ import org.sonar.core.util.stream.Collectors;
 import org.sonar.db.DbTester;
 import org.sonar.db.user.GroupDto;
 import org.sonar.db.user.UserDto;
+import org.sonar.server.authentication.event.AuthenticationEvent;
 import org.sonar.server.organization.DefaultOrganizationProvider;
 import org.sonar.server.organization.TestDefaultOrganizationProvider;
 import org.sonar.server.user.NewUserNotifier;
@@ -59,6 +60,7 @@ import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 import static org.sonar.db.user.UserTesting.newUserDto;
+import static org.sonar.server.authentication.event.AuthenticationEvent.Source;
 
 public class SsoAuthenticatorTest {
 
@@ -99,8 +101,9 @@ public class SsoAuthenticatorTest {
 
   private HttpServletResponse response = mock(HttpServletResponse.class);
   private JwtHttpHandler jwtHttpHandler = mock(JwtHttpHandler.class);
+  private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class);
 
-  private SsoAuthenticator underTest = new SsoAuthenticator(system2, settings, userIdentityAuthenticator, jwtHttpHandler);
+  private SsoAuthenticator underTest = new SsoAuthenticator(system2, settings, userIdentityAuthenticator, jwtHttpHandler, authenticationEvent);
 
   @Before
   public void setUp() throws Exception {
@@ -120,6 +123,7 @@ public class SsoAuthenticatorTest {
 
     verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group1, group2);
     verifyTokenIsUpdated(NOW);
+    verify(authenticationEvent).login(request, DEFAULT_LOGIN, Source.sso());
   }
 
   @Test
@@ -127,9 +131,11 @@ public class SsoAuthenticatorTest {
     startWithSso();
     setNotUserInToken();
 
-    underTest.authenticate(createRequest(DEFAULT_LOGIN, null, null, null), response);
+    HttpServletRequest request = createRequest(DEFAULT_LOGIN, null, null, null);
+    underTest.authenticate(request, response);
 
     verifyUserInDb(DEFAULT_LOGIN, DEFAULT_LOGIN, null);
+    verify(authenticationEvent).login(request, DEFAULT_LOGIN, Source.sso());
   }
 
   @Test
@@ -144,6 +150,7 @@ public class SsoAuthenticatorTest {
 
     verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group2);
     verifyTokenIsUpdated(NOW);
+    verify(authenticationEvent).login(request, DEFAULT_LOGIN, Source.sso());
   }
 
   @Test
@@ -151,10 +158,12 @@ public class SsoAuthenticatorTest {
     startWithSso();
     setNotUserInToken();
     insertUser(DEFAULT_USER, group1);
+    HttpServletRequest request = createRequest(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, "");
 
-    underTest.authenticate(createRequest(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, ""), response);
+    underTest.authenticate(request, response);
 
     verityUserHasNoGroup(DEFAULT_LOGIN);
+    verify(authenticationEvent).login(request, DEFAULT_LOGIN, Source.sso());
   }
 
   @Test
@@ -165,10 +174,12 @@ public class SsoAuthenticatorTest {
     Map<String, String> headerValuesByName = new HashMap<>();
     headerValuesByName.put("X-Forwarded-Login", DEFAULT_LOGIN);
     headerValuesByName.put("X-Forwarded-Groups", null);
+    HttpServletRequest request = createRequest(headerValuesByName);
 
-    underTest.authenticate(createRequest(headerValuesByName), response);
+    underTest.authenticate(request, response);
 
     verityUserHasNoGroup(DEFAULT_LOGIN);
+    verify(authenticationEvent).login(request, DEFAULT_LOGIN, Source.sso());
   }
 
   @Test
@@ -176,10 +187,12 @@ public class SsoAuthenticatorTest {
     startWithSso();
     setNotUserInToken();
     insertUser(DEFAULT_USER, group1);
+    HttpServletRequest request = createRequest(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, null);
 
-    underTest.authenticate(createRequest(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, null), response);
+    underTest.authenticate(request, response);
 
     verityUserGroups(DEFAULT_LOGIN, group1);
+    verify(authenticationEvent).login(request, DEFAULT_LOGIN, Source.sso());
   }
 
   @Test
@@ -194,6 +207,7 @@ public class SsoAuthenticatorTest {
     // User is not updated
     verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group1);
     verifyTokenIsNotUpdated();
+    verifyZeroInteractions(authenticationEvent);
   }
 
   @Test
@@ -209,6 +223,7 @@ public class SsoAuthenticatorTest {
     // User is updated
     verifyUserInDb(DEFAULT_LOGIN, "new name", "new email", group2);
     verifyTokenIsUpdated(NOW);
+    verify(authenticationEvent).login(request, DEFAULT_LOGIN, Source.sso());
   }
 
   @Test
@@ -223,6 +238,7 @@ public class SsoAuthenticatorTest {
     // User is updated
     verifyUserInDb(DEFAULT_LOGIN, "new name", "new email", group2);
     verifyTokenIsUpdated(NOW);
+    verify(authenticationEvent).login(request, DEFAULT_LOGIN, Source.sso());
   }
 
   @Test
@@ -239,6 +255,7 @@ public class SsoAuthenticatorTest {
     // User is not updated
     verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group1);
     verifyTokenIsNotUpdated();
+    verifyZeroInteractions(authenticationEvent);
   }
 
   @Test
@@ -252,6 +269,7 @@ public class SsoAuthenticatorTest {
 
     verifyUserInDb("AnotherLogin", "Another name", "Another email", group2);
     verifyTokenIsUpdated(NOW);
+    verify(authenticationEvent).login(request, "AnotherLogin", Source.sso());
   }
 
   @Test
@@ -267,6 +285,7 @@ public class SsoAuthenticatorTest {
     underTest.authenticate(request, response);
 
     verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group1, group2);
+    verify(authenticationEvent).login(request, DEFAULT_LOGIN, Source.sso());
   }
 
   @Test
@@ -282,6 +301,7 @@ public class SsoAuthenticatorTest {
     underTest.authenticate(request, response);
 
     verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group1, group2);
+    verify(authenticationEvent).login(request, DEFAULT_LOGIN, Source.sso());
   }
 
   @Test
@@ -293,6 +313,7 @@ public class SsoAuthenticatorTest {
     underTest.authenticate(request, response);
 
     verifyUserInDb(DEFAULT_LOGIN, DEFAULT_LOGIN, null, group1, group2);
+    verify(authenticationEvent).login(request, DEFAULT_LOGIN, Source.sso());
   }
 
   @Test
@@ -304,6 +325,7 @@ public class SsoAuthenticatorTest {
 
     verifyUserNotAuthenticated();
     verifyTokenIsNotUpdated();
+    verifyZeroInteractions(authenticationEvent);
   }
 
   @Test
@@ -313,7 +335,7 @@ public class SsoAuthenticatorTest {
     underTest.authenticate(createRequest(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, GROUPS), response);
 
     verifyUserNotAuthenticated();
-    verifyZeroInteractions(jwtHttpHandler);
+    verifyZeroInteractions(jwtHttpHandler, authenticationEvent);
   }
 
   @Test
@@ -323,7 +345,11 @@ public class SsoAuthenticatorTest {
 
     expectedException.expect(UnauthorizedException.class);
     expectedException.expectMessage("user.bad_login");
-    underTest.authenticate(createRequest("invalid login", DEFAULT_NAME, DEFAULT_EMAIL, GROUPS), response);
+    try {
+      underTest.authenticate(createRequest("invalid login", DEFAULT_NAME, DEFAULT_EMAIL, GROUPS), response);
+    } finally {
+      verifyZeroInteractions(authenticationEvent);
+    }
   }
 
   private void startWithSso() {
diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/event/AuthenticationEventImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/event/AuthenticationEventImplTest.java
new file mode 100644 (file)
index 0000000..3cff532
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.authentication.event;
+
+import com.google.common.base.Joiner;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.servlet.http.HttpServletRequest;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.server.authentication.event.AuthenticationEvent.Method;
+import static org.sonar.server.authentication.event.AuthenticationEvent.Source;
+
+public class AuthenticationEventImplTest {
+  @Rule
+  public LogTester logTester = new LogTester();
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private AuthenticationEventImpl underTest = new AuthenticationEventImpl();
+
+  @Test
+  public void login_fails_with_NPE_if_request_is_null() {
+    expectedException.expect(NullPointerException.class);
+
+    underTest.login(null, "login", Source.sso());
+  }
+
+  @Test
+  public void login_fails_with_NPE_if_source_is_null() {
+    expectedException.expect(NullPointerException.class);
+
+    underTest.login(mock(HttpServletRequest.class), "login", null);
+  }
+
+  @Test
+  public void login_creates_INFO_log_with_empty_login_if_login_argument_is_null() {
+    underTest.login(mockRequest(), null, Source.sso());
+
+    verifyLog("login success [method|SSO][provider|SSO|sso][IP||][login|]");
+  }
+
+  @Test
+  public void login_creates_INFO_log_with_method_provider_and_login() {
+    underTest.login(mockRequest(), "foo", Source.realm(Method.BASIC, "some provider name"));
+
+    verifyLog("login success [method|BASIC][provider|REALM|some provider name][IP||][login|foo]");
+  }
+
+  @Test
+  public void login_logs_remote_ip_from_request() {
+    underTest.login(mockRequest("1.2.3.4"), "foo", Source.realm(Method.EXTERNAL, "bar"));
+
+    verifyLog("login success [method|EXTERNAL][provider|REALM|bar][IP|1.2.3.4|][login|foo]");
+  }
+
+  @Test
+  public void login_logs_X_Forwarded_For_header_from_request() {
+    HttpServletRequest request = mockRequest("1.2.3.4", asList("2.3.4.5"));
+    underTest.login(request, "foo", Source.realm(Method.EXTERNAL, "bar"));
+
+    verifyLog("login success [method|EXTERNAL][provider|REALM|bar][IP|1.2.3.4|2.3.4.5][login|foo]");
+  }
+
+  @Test
+  public void login_logs_X_Forwarded_For_header_from_request_and_supports_multiple_headers() {
+    HttpServletRequest request = mockRequest("1.2.3.4", asList("2.3.4.5", "6.5.4.3"), asList("9.5.6.7"), asList("6.3.2.4"));
+    underTest.login(request, "foo", Source.realm(Method.EXTERNAL, "bar"));
+
+    verifyLog("login success [method|EXTERNAL][provider|REALM|bar][IP|1.2.3.4|2.3.4.5,6.5.4.3,9.5.6.7,6.3.2.4][login|foo]");
+  }
+
+  private void verifyLog(String expected) {
+    assertThat(logTester.logs()).hasSize(1);
+    assertThat(logTester.logs(LoggerLevel.INFO))
+      .containsOnly(expected);
+  }
+
+  private static HttpServletRequest mockRequest() {
+    return mockRequest("");
+  }
+
+  private static HttpServletRequest mockRequest(String remoteAddr, List<String>... remoteIps) {
+    HttpServletRequest res = mock(HttpServletRequest.class);
+    when(res.getRemoteAddr()).thenReturn(remoteAddr);
+    when(res.getHeaders("X-Forwarded-For"))
+      .thenReturn(Collections.enumeration(
+        Arrays.stream(remoteIps)
+          .map(Joiner.on(",")::join)
+          .collect(Collectors.toList())));
+    return res;
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/event/AuthenticationEventSourceTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/event/AuthenticationEventSourceTest.java
new file mode 100644 (file)
index 0000000..95e6b22
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.authentication.event;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+import static org.sonar.server.authentication.event.AuthenticationEvent.Method;
+import static org.sonar.server.authentication.event.AuthenticationEvent.Provider;
+import static org.sonar.server.authentication.event.AuthenticationEvent.Source;
+
+public class AuthenticationEventSourceTest {
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Test
+  public void local_fails_with_NPE_if_method_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("method can't be null");
+
+    Source.local(null);
+  }
+
+  @Test
+  public void local_creates_source_instance_with_specified_method_and_hardcoded_provider_and_provider_name() {
+    Source underTest = Source.local(Method.BASIC_TOKEN);
+
+    assertThat(underTest.getMethod()).isEqualTo(Method.BASIC_TOKEN);
+    assertThat(underTest.getProvider()).isEqualTo(Provider.LOCAL);
+    assertThat(underTest.getProviderName()).isEqualTo("local");
+  }
+
+  @Test
+  public void oauth2_fails_with_NPE_if_providerName_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("provider name can't be null");
+
+    Source.oauth2(null);
+  }
+
+  @Test
+  public void oauth2_creates_source_instance_with_specified_provider_name_and_hardcoded_provider_and_method() {
+    Source underTest = Source.oauth2("some name");
+
+    assertThat(underTest.getMethod()).isEqualTo(Method.OAUTH2);
+    assertThat(underTest.getProvider()).isEqualTo(Provider.EXTERNAL);
+    assertThat(underTest.getProviderName()).isEqualTo("some name");
+  }
+
+  @Test
+  public void oauth2_fails_with_IAE_if_providerName_is_empty() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("provider name can't be empty");
+
+    Source.oauth2("");
+  }
+
+  @Test
+  public void realm_fails_with_NPE_if_method_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("method can't be null");
+
+    Source.realm(null, "name");
+  }
+
+  @Test
+  public void realm_fails_with_NPE_if_providerName_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("provider name can't be null");
+
+    Source.realm(Method.BASIC, null);
+  }
+
+  @Test
+  public void realm_fails_with_IAE_if_providerName_is_empty() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("provider name can't be empty");
+
+    Source.realm(Method.BASIC, "");
+  }
+
+  @Test
+  public void realm_creates_source_instance_with_specified_method_and_provider_name_and_hardcoded_provider() {
+    Source underTest = Source.realm(Method.BASIC, "some name");
+
+    assertThat(underTest.getMethod()).isEqualTo(Method.BASIC);
+    assertThat(underTest.getProvider()).isEqualTo(Provider.REALM);
+    assertThat(underTest.getProviderName()).isEqualTo("some name");
+  }
+
+  @Test
+  public void sso_returns_source_instance_with_hardcoded_method_provider_and_providerName() {
+    Source underTest = Source.sso();
+
+    assertThat(underTest.getMethod()).isEqualTo(Method.SSO);
+    assertThat(underTest.getProvider()).isEqualTo(Provider.SSO);
+    assertThat(underTest.getProviderName()).isEqualTo("sso");
+
+    assertThat(underTest).isSameAs(Source.sso());
+  }
+}
index e37274ce3ceef540d539cd865f94c7ad6704824c..7120cd89dfccd65c21bd97659eabfb45ba1db7f9 100644 (file)
@@ -45,31 +45,32 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
+import static org.sonar.server.authentication.event.AuthenticationEvent.Method.FORM;
 
 public class LoginActionTest {
 
-  static final String LOGIN = "LOGIN";
-  static final String PASSWORD = "PASSWORD";
+  private static final String LOGIN = "LOGIN";
+  private static final String PASSWORD = "PASSWORD";
 
   @Rule
   public DbTester dbTester = DbTester.create(System2.INSTANCE);
 
-  DbClient dbClient = dbTester.getDbClient();
+  private DbClient dbClient = dbTester.getDbClient();
 
-  DbSession dbSession = dbTester.getSession();
+  private DbSession dbSession = dbTester.getSession();
 
-  ThreadLocalUserSession threadLocalUserSession = new ThreadLocalUserSession();
+  private ThreadLocalUserSession threadLocalUserSession = new ThreadLocalUserSession();
 
-  HttpServletRequest request = mock(HttpServletRequest.class);
-  HttpServletResponse response = mock(HttpServletResponse.class);
-  FilterChain chain = mock(FilterChain.class);
+  private HttpServletRequest request = mock(HttpServletRequest.class);
+  private HttpServletResponse response = mock(HttpServletResponse.class);
+  private FilterChain chain = mock(FilterChain.class);
 
-  CredentialsAuthenticator credentialsAuthenticator = mock(CredentialsAuthenticator.class);
-  JwtHttpHandler jwtHttpHandler = mock(JwtHttpHandler.class);
+  private CredentialsAuthenticator credentialsAuthenticator = mock(CredentialsAuthenticator.class);
+  private JwtHttpHandler jwtHttpHandler = mock(JwtHttpHandler.class);
 
-  UserDto user = UserTesting.newUserDto().setLogin(LOGIN);
+  private UserDto user = UserTesting.newUserDto().setLogin(LOGIN);
 
-  LoginAction underTest  = new LoginAction(dbClient, credentialsAuthenticator, jwtHttpHandler, threadLocalUserSession);
+  private LoginAction underTest  = new LoginAction(dbClient, credentialsAuthenticator, jwtHttpHandler, threadLocalUserSession);
 
   @Before
   public void setUp() throws Exception {
@@ -87,12 +88,12 @@ public class LoginActionTest {
 
   @Test
   public void do_authenticate() throws Exception {
-    when(credentialsAuthenticator.authenticate(LOGIN, PASSWORD, request)).thenReturn(user);
+    when(credentialsAuthenticator.authenticate(LOGIN, PASSWORD, request, FORM)).thenReturn(user);
 
     executeRequest(LOGIN, PASSWORD);
 
     assertThat(threadLocalUserSession.isLoggedIn()).isTrue();
-    verify(credentialsAuthenticator).authenticate(LOGIN, PASSWORD, request);
+    verify(credentialsAuthenticator).authenticate(LOGIN, PASSWORD, request, FORM);
     verify(jwtHttpHandler).generateToken(user, request, response);
     verifyZeroInteractions(chain);
   }
@@ -108,7 +109,7 @@ public class LoginActionTest {
 
   @Test
   public void return_authorized_code_when_unauthorized_exception_is_thrown() throws Exception {
-    doThrow(new UnauthorizedException()).when(credentialsAuthenticator).authenticate(LOGIN, PASSWORD, request);
+    doThrow(new UnauthorizedException()).when(credentialsAuthenticator).authenticate(LOGIN, PASSWORD, request, FORM);
 
     executeRequest(LOGIN, PASSWORD);