]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7732 Authentication is now done in Java
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Fri, 17 Jun 2016 16:01:48 +0000 (18:01 +0200)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Wed, 29 Jun 2016 06:39:32 +0000 (08:39 +0200)
41 files changed:
it/it-plugins/security-plugin/src/main/java/FakeAuthenticator.java
it/it-tests/src/test/java/it/user/RailsExternalAuthenticationTest.java
server/sonar-server/src/main/java/org/sonar/server/authentication/AuthLoginAction.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationModule.java
server/sonar-server/src/main/java/org/sonar/server/authentication/BaseContextFactory.java
server/sonar-server/src/main/java/org/sonar/server/authentication/CredentialsAuthenticator.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/authentication/GenerateJwtTokenFilter.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/authentication/JwtHttpHandler.java
server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2ContextFactory.java
server/sonar-server/src/main/java/org/sonar/server/authentication/RealmAuthenticator.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/authentication/UserIdentityAuthenticator.java
server/sonar-server/src/main/java/org/sonar/server/authentication/ValidateJwtTokenFilter.java
server/sonar-server/src/main/java/org/sonar/server/authentication/ws/AuthenticationWs.java
server/sonar-server/src/main/java/org/sonar/server/user/AbstractUserSession.java
server/sonar-server/src/main/java/org/sonar/server/user/RubyUserSession.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/user/ServerUserSession.java
server/sonar-server/src/main/java/org/sonar/server/user/UpdateUser.java
server/sonar-server/src/main/java/org/sonar/server/user/UserUpdater.java
server/sonar-server/src/main/java/org/sonar/server/ws/ServletFilterHandler.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/authentication/AuthLoginActionTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/authentication/BaseContextFactoryTest.java
server/sonar-server/src/test/java/org/sonar/server/authentication/CredentialsAuthenticatorTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/authentication/GenerateJwtTokenFilterTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/authentication/InitFilterTest.java
server/sonar-server/src/test/java/org/sonar/server/authentication/JwtHttpHandlerTest.java
server/sonar-server/src/test/java/org/sonar/server/authentication/OAuth2CallbackFilterTest.java
server/sonar-server/src/test/java/org/sonar/server/authentication/OAuth2ContextFactoryTest.java
server/sonar-server/src/test/java/org/sonar/server/authentication/RealmAuthenticatorTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/authentication/UserIdentityAuthenticatorTest.java
server/sonar-server/src/test/java/org/sonar/server/authentication/ValidateJwtTokenFilterTest.java
server/sonar-server/src/test/java/org/sonar/server/authentication/ws/AuthenticationWsTest.java
server/sonar-server/src/test/java/org/sonar/server/user/RubyUserSessionTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/user/ServerUserSessionTest.java
server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterTest.java
server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/authentication_controller.rb
server/sonar-web/src/main/webapp/WEB-INF/app/controllers/application_controller.rb
server/sonar-web/src/main/webapp/WEB-INF/lib/authenticated_system.rb
sonar-db/src/main/java/org/sonar/db/user/UserDto.java
sonar-db/src/test/java/org/sonar/db/user/UserDtoTest.java
sonar-plugin-api/src/main/java/org/sonar/api/server/authentication/UserIdentity.java
sonar-plugin-api/src/test/java/org/sonar/api/server/authentication/UserIdentityTest.java

index 6455d260b894e46541b847e454527e2ea2d3fd5c..8e75d9c1fcf496a89f2bc7ee75fec26198e3daa4 100644 (file)
@@ -90,7 +90,7 @@ public class FakeAuthenticator implements LoginPasswordAuthenticator {
 
   private void checkExistence(String username) {
     if (!data.containsKey(username + ".password")) {
-      throw new RuntimeException("No such user");
+      throw new IllegalArgumentException("No such user : " + username);
     }
   }
 
index 6ce7c2ec88f88431da0d7c2e7fc8a885353de064..c0692cd7820debc7fd1e672ac9194bbcc905d652 100644 (file)
  */
 package it.user;
 
+import static java.net.HttpURLConnection.HTTP_OK;
+import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+import static util.ItUtils.pluginArtifact;
+import static util.ItUtils.setServerProperty;
+
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
 import com.sonar.orchestrator.Orchestrator;
@@ -51,13 +58,6 @@ import org.sonarqube.ws.client.WsResponse;
 import util.QaOnly;
 import util.selenium.SeleneseTest;
 
-import static java.net.HttpURLConnection.HTTP_OK;
-import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.fail;
-import static util.ItUtils.pluginArtifact;
-import static util.ItUtils.setServerProperty;
-
 /**
  * Test deprecated authentication done by Rails. It's kept has every features has not bee migrated to java yet.
  *
diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthLoginAction.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthLoginAction.java
new file mode 100644 (file)
index 0000000..770cd63
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * 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;
+
+import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
+import static org.elasticsearch.common.Strings.isNullOrEmpty;
+
+import java.io.IOException;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.sonar.api.web.ServletFilter;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.exceptions.UnauthorizedException;
+
+public class AuthLoginAction extends ServletFilter {
+
+  static final String AUTH_LOGIN_URL = "/api/authentication/login";
+
+  private static final String POST = "POST";
+
+  private final CredentialsAuthenticator credentialsAuthenticator;
+  private final JwtHttpHandler jwtHttpHandler;
+
+  public AuthLoginAction(CredentialsAuthenticator credentialsAuthenticator, JwtHttpHandler jwtHttpHandler) {
+    this.credentialsAuthenticator = credentialsAuthenticator;
+    this.jwtHttpHandler = jwtHttpHandler;
+  }
+
+  @Override
+  public UrlPattern doGetPattern() {
+    return UrlPattern.create(AUTH_LOGIN_URL);
+  }
+
+  @Override
+  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
+    HttpServletRequest request = (HttpServletRequest) servletRequest;
+    HttpServletResponse response = (HttpServletResponse) servletResponse;
+
+    if (!request.getMethod().equals(POST)) {
+      response.setStatus(HTTP_BAD_REQUEST);
+      return;
+    }
+    try {
+      UserDto userDto = authenticate(request);
+      jwtHttpHandler.generateToken(userDto, response);
+      // TODO add chain.doFilter when Rack filter will not be executed after this filter (or use a Servlet)
+    } catch (UnauthorizedException e) {
+      response.setStatus(e.httpCode());
+    }
+  }
+
+  private UserDto authenticate(HttpServletRequest request) {
+    String login = request.getParameter("login");
+    String password = request.getParameter("password");
+    if (isNullOrEmpty(login) || isNullOrEmpty(password)) {
+      throw new UnauthorizedException();
+    }
+    return credentialsAuthenticator.authenticate(login, password, request);
+  }
+
+  @Override
+  public void init(FilterConfig filterConfig) throws ServletException {
+    // Nothing to do
+  }
+
+  @Override
+  public void destroy() {
+    // Nothing to do
+  }
+}
index b2740cc6b9b9236cb14273339c8a72de68c2216d..a55033f0e7eeacec2ec70b1703e9ffed4e1982bc 100644 (file)
@@ -34,10 +34,12 @@ public class AuthenticationModule extends Module {
       OAuth2ContextFactory.class,
       UserIdentityAuthenticator.class,
       OAuthCsrfVerifier.class,
-      GenerateJwtTokenFilter.class,
       ValidateJwtTokenFilter.class,
       JwtSerializer.class,
       JwtHttpHandler.class,
-      JwtCsrfVerifier.class);
+      JwtCsrfVerifier.class,
+      AuthLoginAction.class,
+      CredentialsAuthenticator.class,
+      RealmAuthenticator.class);
   }
 }
index 9261f37edfd8561c483438b4302fed84fc24397c..ea7df897a90b89ae05d6b132d2b50e7fe8873ada 100644 (file)
@@ -24,15 +24,18 @@ import javax.servlet.http.HttpServletResponse;
 import org.sonar.api.platform.Server;
 import org.sonar.api.server.authentication.BaseIdentityProvider;
 import org.sonar.api.server.authentication.UserIdentity;
+import org.sonar.db.user.UserDto;
 
 public class BaseContextFactory {
 
   private final UserIdentityAuthenticator userIdentityAuthenticator;
   private final Server server;
+  private final JwtHttpHandler jwtHttpHandler;
 
-  public BaseContextFactory(UserIdentityAuthenticator userIdentityAuthenticator, Server server) {
+  public BaseContextFactory(UserIdentityAuthenticator userIdentityAuthenticator, Server server, JwtHttpHandler jwtHttpHandler) {
     this.userIdentityAuthenticator = userIdentityAuthenticator;
     this.server = server;
+    this.jwtHttpHandler = jwtHttpHandler;
   }
 
   public BaseIdentityProvider.Context newContext(HttpServletRequest request, HttpServletResponse response, BaseIdentityProvider identityProvider) {
@@ -67,7 +70,8 @@ public class BaseContextFactory {
 
     @Override
     public void authenticate(UserIdentity userIdentity) {
-      userIdentityAuthenticator.authenticate(userIdentity, identityProvider, request, response);
+      UserDto userDto = userIdentityAuthenticator.authenticate(userIdentity, identityProvider);
+      jwtHttpHandler.generateToken(userDto, response);
     }
   }
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/CredentialsAuthenticator.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/CredentialsAuthenticator.java
new file mode 100644 (file)
index 0000000..3706b8a
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+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.exceptions.UnauthorizedException;
+
+public class CredentialsAuthenticator {
+
+  private final DbClient dbClient;
+  private final RealmAuthenticator externalAuthenticator;
+
+  public CredentialsAuthenticator(DbClient dbClient, RealmAuthenticator externalAuthenticator) {
+    this.dbClient = dbClient;
+    this.externalAuthenticator = externalAuthenticator;
+  }
+
+  public UserDto authenticate(String userLogin, String userPassword, HttpServletRequest request) {
+    DbSession dbSession = dbClient.openSession(false);
+    try {
+      return authenticate(dbSession, userLogin, userPassword, request);
+    } finally {
+      dbClient.closeSession(dbSession);
+    }
+  }
+
+  private UserDto authenticate(DbSession dbSession, String userLogin, String userPassword, HttpServletRequest request) {
+    UserDto user = dbClient.userDao().selectActiveUserByLogin(dbSession, userLogin);
+    if (user != null && user.isLocal()) {
+      return authenticateFromDb(user, userPassword);
+    }
+    Optional<UserDto> userDto = externalAuthenticator.authenticate(userLogin, userPassword, request);
+    if (userDto.isPresent()) {
+      return userDto.get();
+    }
+    throw new UnauthorizedException();
+  }
+
+  private static UserDto authenticateFromDb(UserDto userDto, String userPassword) {
+    String cryptedPassword = userDto.getCryptedPassword();
+    String salt = userDto.getSalt();
+    if (cryptedPassword == null || salt == null
+      || !cryptedPassword.equals(encryptPassword(userPassword, salt))) {
+      throw new UnauthorizedException();
+    }
+    return userDto;
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/GenerateJwtTokenFilter.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/GenerateJwtTokenFilter.java
deleted file mode 100644 (file)
index 24f206b..0000000
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * 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;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.PrintWriter;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletOutputStream;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.WriteListener;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpServletResponseWrapper;
-import org.sonar.api.web.ServletFilter;
-import org.sonar.server.user.UserSession;
-
-/**
- * This filter creates web session when user authenticates on URL "/sessions/login".
- * The generated session is server stateless.
- */
-public class GenerateJwtTokenFilter extends ServletFilter {
-
-  private static final String POST = "POST";
-
-  private final JwtHttpHandler jwtHttpHandler;
-  private final UserSession userSession;
-
-  public GenerateJwtTokenFilter(JwtHttpHandler jwtHttpHandler, UserSession userSession) {
-    this.jwtHttpHandler = jwtHttpHandler;
-    this.userSession = userSession;
-  }
-
-  @Override
-  public UrlPattern doGetPattern() {
-    return UrlPattern.create("/sessions/login");
-  }
-
-  @Override
-  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
-    HttpServletRequest request = (HttpServletRequest) servletRequest;
-    HttpServletResponse response = (HttpServletResponse) servletResponse;
-
-    if (request.getMethod().equals(POST)) {
-      BufferResponseWrapper wrapper = new BufferResponseWrapper(response);
-      chain.doFilter(request, wrapper);
-      if (userSession.isLoggedIn()) {
-        jwtHttpHandler.generateToken(userSession.getLogin(), response);
-      }
-      response.getOutputStream().write(wrapper.getWrapperBytes());
-    } else {
-      chain.doFilter(request, response);
-    }
-  }
-
-  @Override
-  public void init(FilterConfig filterConfig) throws ServletException {
-    // Nothing to do
-  }
-
-  @Override
-  public void destroy() {
-    // Nothing to do
-  }
-
-  /**
-   * As the RackFilter is executed before this filter, the reponse is commited and it's not possible anymore to add cookie.
-   * So we're create a buffer response wrapper that will buffer the dat that should be send to the browser in order to not commit the response.
-   * It's then possible to add cookie before flushing data to the browser.
-   *
-   * See <a href="http://stackoverflow.com/questions/11025605/response-is-committing-and-dofilter-chain-is-broken">
-   *
-   * Note : this must be removed when authentication will not use rails anymore
-   */
-  private static final class BufferResponseWrapper extends HttpServletResponseWrapper {
-
-    private BufferServletOutputStream stream = new BufferServletOutputStream();
-
-    BufferResponseWrapper(HttpServletResponse httpServletResponse) {
-      super(httpServletResponse);
-    }
-
-    @Override
-    public ServletOutputStream getOutputStream() throws IOException {
-      return stream;
-    }
-
-    @Override
-    public PrintWriter getWriter() throws IOException {
-      return new PrintWriter(stream);
-    }
-
-    byte[] getWrapperBytes() {
-      return stream.getBytes();
-    }
-  }
-
-  private static final class BufferServletOutputStream extends ServletOutputStream {
-    private ByteArrayOutputStream out = new ByteArrayOutputStream();
-
-    @Override
-    public void write(int b) throws IOException {
-      out.write(b);
-    }
-
-    byte[] getBytes() {
-      return out.toByteArray();
-    }
-
-    @Override
-    public boolean isReady() {
-      return false;
-    }
-
-    @Override
-    public void setWriteListener(WriteListener listener) {
-      // Nothing to do
-    }
-  }
-}
index 4f77ffe8c07c76500721ff50234664b6723749cb..d42349b5f6ed74efe1ace8e11149aeedd89de827 100644 (file)
@@ -23,6 +23,7 @@ package org.sonar.server.authentication;
 import static java.util.Objects.requireNonNull;
 import static org.elasticsearch.common.Strings.isNullOrEmpty;
 import static org.sonar.server.authentication.CookieUtils.findCookie;
+import static org.sonar.server.user.ServerUserSession.createForUser;
 
 import com.google.common.collect.ImmutableMap;
 import io.jsonwebtoken.Claims;
@@ -40,6 +41,9 @@ import org.sonar.api.utils.System2;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.user.UserDto;
+import org.sonar.server.exceptions.UnauthorizedException;
+import org.sonar.server.user.ServerUserSession;
+import org.sonar.server.user.ThreadLocalUserSession;
 
 @ServerSide
 public class JwtHttpHandler {
@@ -59,8 +63,6 @@ public class JwtHttpHandler {
   // The value must be lower than sessionTimeoutInSeconds
   private static final int SESSION_REFRESH_IN_SECONDS = 5 * 60;
 
-  private static final String RAILS_USER_ID_SESSION = "user_id";
-
   private final System2 system2;
   private final DbClient dbClient;
   private final Server server;
@@ -69,65 +71,85 @@ public class JwtHttpHandler {
   // This timeout is used to disconnect the user we he has not browse any page for a while
   private final int sessionTimeoutInSeconds;
   private final JwtCsrfVerifier jwtCsrfVerifier;
+  private final ThreadLocalUserSession threadLocalUserSession;
 
-  public JwtHttpHandler(System2 system2, DbClient dbClient, Server server, Settings settings, JwtSerializer jwtSerializer, JwtCsrfVerifier jwtCsrfVerifier) {
+  public JwtHttpHandler(System2 system2, DbClient dbClient, Server server, Settings settings, JwtSerializer jwtSerializer, JwtCsrfVerifier jwtCsrfVerifier,
+    ThreadLocalUserSession threadLocalUserSession) {
     this.jwtSerializer = jwtSerializer;
     this.server = server;
     this.dbClient = dbClient;
     this.system2 = system2;
     this.sessionTimeoutInSeconds = getSessionTimeoutInSeconds(settings);
     this.jwtCsrfVerifier = jwtCsrfVerifier;
+    this.threadLocalUserSession = threadLocalUserSession;
   }
 
-  void generateToken(String userLogin, HttpServletResponse response) {
+  void generateToken(UserDto user, HttpServletResponse response) {
     String csrfState = jwtCsrfVerifier.generateState(response, sessionTimeoutInSeconds);
 
     String token = jwtSerializer.encode(new JwtSerializer.JwtSession(
-      userLogin,
+      user.getLogin(),
       sessionTimeoutInSeconds,
       ImmutableMap.of(
         LAST_REFRESH_TIME_PARAM, system2.now(),
         CSRF_JWT_PARAM, csrfState)));
     response.addCookie(createCookie(JWT_COOKIE, token, sessionTimeoutInSeconds));
+    threadLocalUserSession.set(createForUser(dbClient, user));
   }
 
   void validateToken(HttpServletRequest request, HttpServletResponse response) {
+    validate(request, response);
+    if (!threadLocalUserSession.isLoggedIn()) {
+      threadLocalUserSession.set(ServerUserSession.createForAnonymous(dbClient));
+    }
+  }
+
+  private void validate(HttpServletRequest request, HttpServletResponse response) {
+    Optional<String> token = getTokenFromCookie(request);
+    if (!token.isPresent()) {
+      return;
+    }
+    validateToken(token.get(), request, response);
+  }
+
+  private static Optional<String> getTokenFromCookie(HttpServletRequest request) {
     Optional<Cookie> jwtCookie = findCookie(JWT_COOKIE, request);
-    if (jwtCookie.isPresent()) {
-      Cookie cookie = jwtCookie.get();
-      String token = cookie.getValue();
-      if (!isNullOrEmpty(token)) {
-        validateToken(token, request, response);
-      }
+    if (!jwtCookie.isPresent()) {
+      return Optional.empty();
+    }
+    Cookie cookie = jwtCookie.get();
+    String token = cookie.getValue();
+    if (isNullOrEmpty(token)) {
+      return Optional.empty();
     }
+    return Optional.of(token);
   }
 
   private void validateToken(String tokenEncoded, HttpServletRequest request, HttpServletResponse response) {
     Optional<Claims> claims = jwtSerializer.decode(tokenEncoded);
     if (!claims.isPresent()) {
-      removeSession(request, response);
+      removeToken(response);
       return;
     }
 
     Date now = new Date(system2.now());
-
     Claims token = claims.get();
     if (now.after(DateUtils.addSeconds(token.getIssuedAt(), SESSION_DISCONNECT_IN_SECONDS))) {
-      removeSession(request, response);
+      removeToken(response);
       return;
     }
-
-    Optional<UserDto> user = selectUserFromDb(token.getSubject());
-    if (!user.isPresent()) {
-      removeSession(request, response);
-      return;
-    }
-
     jwtCsrfVerifier.verifyState(request, (String) token.get(CSRF_JWT_PARAM));
-    request.getSession().setAttribute(RAILS_USER_ID_SESSION, user.get().getId());
+
     if (now.after(DateUtils.addSeconds(getLastRefreshDate(token), SESSION_REFRESH_IN_SECONDS))) {
       refreshToken(token, response);
     }
+
+    Optional<UserDto> user = selectUserFromDb(token.getSubject());
+    if (!user.isPresent()) {
+      removeToken(response);
+      throw new UnauthorizedException("User does not exist");
+    }
+    threadLocalUserSession.set(createForUser(dbClient, user.get()));
   }
 
   private static Date getLastRefreshDate(Claims token) {
@@ -142,10 +164,10 @@ public class JwtHttpHandler {
     jwtCsrfVerifier.refreshState(response, (String) token.get(CSRF_JWT_PARAM), sessionTimeoutInSeconds);
   }
 
-  private void removeSession(HttpServletRequest request, HttpServletResponse response) {
-    request.getSession().removeAttribute(RAILS_USER_ID_SESSION);
+  void removeToken(HttpServletResponse response) {
     response.addCookie(createCookie(JWT_COOKIE, null, 0));
     jwtCsrfVerifier.removeState(response);
+    threadLocalUserSession.remove();
   }
 
   private Cookie createCookie(String name, @Nullable String value, int expirationInSeconds) {
index 6135d6aff491977259fbbb2c5f599e28278b0a4e..6f5948cc6161f799603e5690f24d19477871dca5 100644 (file)
@@ -30,17 +30,20 @@ import org.sonar.api.platform.Server;
 import org.sonar.api.server.authentication.OAuth2IdentityProvider;
 import org.sonar.api.server.authentication.UserIdentity;
 import org.sonar.api.utils.MessageException;
+import org.sonar.db.user.UserDto;
 
 public class OAuth2ContextFactory {
 
   private final UserIdentityAuthenticator userIdentityAuthenticator;
   private final Server server;
   private final OAuthCsrfVerifier csrfVerifier;
+  private final JwtHttpHandler jwtHttpHandler;
 
-  public OAuth2ContextFactory(UserIdentityAuthenticator userIdentityAuthenticator, Server server, OAuthCsrfVerifier csrfVerifier) {
+  public OAuth2ContextFactory(UserIdentityAuthenticator userIdentityAuthenticator, Server server, OAuthCsrfVerifier csrfVerifier, JwtHttpHandler jwtHttpHandler) {
     this.userIdentityAuthenticator = userIdentityAuthenticator;
     this.server = server;
     this.csrfVerifier = csrfVerifier;
+    this.jwtHttpHandler = jwtHttpHandler;
   }
 
   public OAuth2IdentityProvider.InitContext newContext(HttpServletRequest request, HttpServletResponse response, OAuth2IdentityProvider identityProvider) {
@@ -112,7 +115,8 @@ public class OAuth2ContextFactory {
 
     @Override
     public void authenticate(UserIdentity userIdentity) {
-      userIdentityAuthenticator.authenticate(userIdentity, identityProvider, request, response);
+      UserDto userDto = userIdentityAuthenticator.authenticate(userIdentity, identityProvider);
+      jwtHttpHandler.generateToken(userDto, response);
     }
   }
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/RealmAuthenticator.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/RealmAuthenticator.java
new file mode 100644 (file)
index 0000000..d2ee042
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * 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;
+
+import static java.util.Objects.requireNonNull;
+import static org.apache.commons.lang.StringUtils.trimToNull;
+import static org.elasticsearch.common.Strings.isNullOrEmpty;
+import static org.sonar.api.CoreProperties.CORE_AUTHENTICATOR_CREATE_USERS;
+import static org.sonar.server.user.UserUpdater.SQ_AUTHORITY;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Optional;
+import javax.servlet.http.HttpServletRequest;
+import org.sonar.api.Startable;
+import org.sonar.api.config.Settings;
+import org.sonar.api.security.Authenticator;
+import org.sonar.api.security.ExternalGroupsProvider;
+import org.sonar.api.security.ExternalUsersProvider;
+import org.sonar.api.security.SecurityRealm;
+import org.sonar.api.security.UserDetails;
+import org.sonar.api.server.authentication.Display;
+import org.sonar.api.server.authentication.IdentityProvider;
+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.exceptions.UnauthorizedException;
+import org.sonar.server.user.SecurityRealmFactory;
+
+public class RealmAuthenticator implements Startable {
+
+  private static final Logger LOG = Loggers.get(RealmAuthenticator.class);
+
+  private final Settings settings;
+  private final SecurityRealmFactory securityRealmFactory;
+  private final UserIdentityAuthenticator userIdentityAuthenticator;
+
+  private SecurityRealm realm;
+  private Authenticator authenticator;
+  private ExternalUsersProvider externalUsersProvider;
+  private ExternalGroupsProvider externalGroupsProvider;
+
+  public RealmAuthenticator(Settings settings, SecurityRealmFactory securityRealmFactory, UserIdentityAuthenticator userIdentityAuthenticator) {
+    this.settings = settings;
+    this.securityRealmFactory = securityRealmFactory;
+    this.userIdentityAuthenticator = userIdentityAuthenticator;
+  }
+
+  @Override
+  public void start() {
+    realm = securityRealmFactory.getRealm();
+    if (realm != null) {
+      authenticator = requireNonNull(realm.doGetAuthenticator(), "No authenticator available");
+      externalUsersProvider = requireNonNull(realm.getUsersProvider(), "No users provider available");
+      externalGroupsProvider = realm.getGroupsProvider();
+    }
+  }
+
+  public Optional<UserDto> authenticate(String userLogin, String userPassword, HttpServletRequest request) {
+    if (realm == null) {
+      return Optional.empty();
+    }
+    return Optional.of(doAuthenticate(getLogin(userLogin), userPassword, request));
+  }
+
+  private UserDto doAuthenticate(String userLogin, String userPassword, HttpServletRequest request) {
+    try {
+      ExternalUsersProvider.Context externalUsersProviderContext = new ExternalUsersProvider.Context(userLogin, request);
+      UserDetails details = externalUsersProvider.doGetUserDetails(externalUsersProviderContext);
+      if (details == null) {
+        throw new UnauthorizedException("No user details");
+      }
+      Authenticator.Context authenticatorContext = new Authenticator.Context(userLogin, userPassword, request);
+      boolean status = authenticator.doAuthenticate(authenticatorContext);
+      if (!status) {
+        throw new UnauthorizedException("Fail to authenticate from external provider");
+      }
+      return synchronize(userLogin, details, request);
+    } 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);
+      throw new UnauthorizedException();
+    }
+  }
+
+  private UserDto synchronize(String userLogin, UserDetails details, HttpServletRequest request) {
+    String name = details.getName();
+    UserIdentity.Builder userIdentityBuilder = UserIdentity.builder()
+      .setLogin(userLogin)
+      .setName(isNullOrEmpty(name) ? userLogin : name)
+      .setEmail(trimToNull(details.getEmail()))
+      .setProviderLogin(userLogin);
+    if (externalGroupsProvider != null) {
+      ExternalGroupsProvider.Context context = new ExternalGroupsProvider.Context(userLogin, request);
+      Collection<String> groups = externalGroupsProvider.doGetGroups(context);
+      userIdentityBuilder.setGroups(new HashSet<>(groups));
+    }
+    return userIdentityAuthenticator.authenticate(userIdentityBuilder.build(), new ExternalIdentityProvider());
+  }
+
+  private String getLogin(String userLogin) {
+    if (settings.getBoolean("sonar.authenticator.downcase")) {
+      return userLogin.toLowerCase(Locale.ENGLISH);
+    }
+    return userLogin;
+  }
+
+  private class ExternalIdentityProvider implements IdentityProvider {
+    @Override
+    public String getKey() {
+      return SQ_AUTHORITY;
+    }
+
+    @Override
+    public String getName() {
+      return SQ_AUTHORITY;
+    }
+
+    @Override
+    public Display getDisplay() {
+      return null;
+    }
+
+    @Override
+    public boolean isEnabled() {
+      return true;
+    }
+
+    @Override
+    public boolean allowsUsersToSignUp() {
+      return settings.getBoolean(CORE_AUTHENTICATOR_CREATE_USERS);
+    }
+  }
+
+  @Override
+  public void stop() {
+    // Nothing to do
+  }
+}
index 0e8069926d7e7bd8362b94484a6ccc759c5edfd7..76a71966d90ecff330329d405b01ac3fababfed4 100644 (file)
@@ -31,8 +31,6 @@ import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import javax.annotation.Nonnull;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
 import org.sonar.api.server.authentication.IdentityProvider;
 import org.sonar.api.server.authentication.UnauthorizedException;
 import org.sonar.api.server.authentication.UserIdentity;
@@ -54,20 +52,14 @@ public class UserIdentityAuthenticator {
 
   private final DbClient dbClient;
   private final UserUpdater userUpdater;
-  private final JwtHttpHandler jwtHttpHandler;
 
-  public UserIdentityAuthenticator(DbClient dbClient, UserUpdater userUpdater, JwtHttpHandler jwtHttpHandler) {
+  public UserIdentityAuthenticator(DbClient dbClient, UserUpdater userUpdater) {
     this.dbClient = dbClient;
     this.userUpdater = userUpdater;
-    this.jwtHttpHandler = jwtHttpHandler;
   }
 
-  public void authenticate(UserIdentity user, IdentityProvider provider, HttpServletRequest request, HttpServletResponse response) {
-    UserDto userDb = register(user, provider);
-
-    // hack to disable Ruby on Rails authentication
-    request.getSession().setAttribute("user_id", userDb.getId());
-    jwtHttpHandler.generateToken(userDb.getLogin(), response);
+  public UserDto authenticate(UserIdentity user, IdentityProvider provider) {
+    return register(user, provider);
   }
 
   private UserDto register(UserIdentity user, IdentityProvider provider) {
@@ -101,8 +93,7 @@ public class UserIdentityAuthenticator {
       .setLogin(userLogin)
       .setEmail(user.getEmail())
       .setName(user.getName())
-      .setExternalIdentity(new ExternalIdentity(provider.getKey(), user.getProviderLogin()))
-      );
+      .setExternalIdentity(new ExternalIdentity(provider.getKey(), user.getProviderLogin())));
     UserDto newUser = dbClient.userDao().selectOrFailByLogin(dbSession, userLogin);
     syncGroups(dbSession, user, newUser);
     return newUser;
@@ -138,27 +129,19 @@ public class UserIdentityAuthenticator {
   }
 
   private void addGroups(DbSession dbSession, UserDto userDto, Collection<String> groupsToAdd, Map<String, GroupDto> groupsByName) {
-    if (!groupsToAdd.isEmpty()) {
-      for (String groupToAdd : groupsToAdd) {
-        GroupDto groupDto = groupsByName.get(groupToAdd);
-        if (groupDto != null) {
-          LOGGER.debug("Adding group '{}' to user '{}'", groupDto.getName(), userDto.getLogin());
-          dbClient.userGroupDao().insert(dbSession, new UserGroupDto().setGroupId(groupDto.getId()).setUserId(userDto.getId()));
-        }
-      }
-    }
+    groupsToAdd.stream().map(groupsByName::get).filter(groupDto -> groupDto != null).forEach(
+      groupDto -> {
+        LOGGER.debug("Adding group '{}' to user '{}'", groupDto.getName(), userDto.getLogin());
+        dbClient.userGroupDao().insert(dbSession, new UserGroupDto().setGroupId(groupDto.getId()).setUserId(userDto.getId()));
+      });
   }
 
   private void removeGroups(DbSession dbSession, UserDto userDto, Collection<String> groupsToRemove, Map<String, GroupDto> groupsByName) {
-    if (!groupsToRemove.isEmpty()) {
-      for (String groupToRemove : groupsToRemove) {
-        GroupDto groupDto = groupsByName.get(groupToRemove);
-        if (groupDto != null) {
-          LOGGER.debug("Removing group '{}' from user '{}'", groupDto.getName(), userDto.getLogin());
-          dbClient.userGroupDao().delete(dbSession, new UserGroupDto().setGroupId(groupDto.getId()).setUserId(userDto.getId()));
-        }
-      }
-    }
+    groupsToRemove.stream().map(groupsByName::get).filter(groupDto -> groupDto != null).forEach(
+      groupDto -> {
+        LOGGER.debug("Removing group '{}' from user '{}'", groupDto.getName(), userDto.getLogin());
+        dbClient.userGroupDao().delete(dbSession, new UserGroupDto().setGroupId(groupDto.getId()).setUserId(userDto.getId()));
+      });
   }
 
   private enum GroupDtoToName implements Function<GroupDto, String> {
index 42ad84f697e17fa1249dfa5340cff201c667f6f5..fd3878b665ecbba4e22b9f53539f573740369ba6 100644 (file)
 
 package org.sonar.server.authentication;
 
+import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
+import static org.sonar.api.CoreProperties.CORE_FORCE_AUTHENTICATION_PROPERTY;
 import static org.sonar.api.web.ServletFilter.UrlPattern.Builder.staticResourcePatterns;
+import static org.sonar.server.authentication.AuthLoginAction.AUTH_LOGIN_URL;
 
+import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
+import java.util.Set;
 import javax.servlet.FilterChain;
 import javax.servlet.FilterConfig;
 import javax.servlet.ServletException;
@@ -30,17 +35,34 @@ import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import org.sonar.api.config.Settings;
 import org.sonar.api.server.ServerSide;
 import org.sonar.api.web.ServletFilter;
 import org.sonar.server.exceptions.UnauthorizedException;
+import org.sonar.server.user.UserSession;
 
 @ServerSide
 public class ValidateJwtTokenFilter extends ServletFilter {
 
+  // SONAR-6546 these urls should be get from WebService
+  private static final Set<String> SKIPPED_URLS = ImmutableSet.of(
+    "/batch/index", "/batch/file", "/batch_bootstrap/index",
+    "/maintenance/*",
+    "/setup/*",
+    "/sessions/*",
+    "/api/system/db_migration_status", "/api/system/status", "/api/system/migrate_db",
+    "/api/server/*",
+    AUTH_LOGIN_URL
+  );
+
+  private final Settings settings;
   private final JwtHttpHandler jwtHttpHandler;
+  private final UserSession userSession;
 
-  public ValidateJwtTokenFilter(JwtHttpHandler jwtHttpHandler) {
+  public ValidateJwtTokenFilter(Settings settings, JwtHttpHandler jwtHttpHandler, UserSession userSession) {
+    this.settings = settings;
     this.jwtHttpHandler = jwtHttpHandler;
+    this.userSession = userSession;
   }
 
   @Override
@@ -48,6 +70,7 @@ public class ValidateJwtTokenFilter extends ServletFilter {
     return UrlPattern.builder()
       .includes("/*")
       .excludes(staticResourcePatterns())
+      .excludes(SKIPPED_URLS)
       .build();
   }
 
@@ -55,15 +78,41 @@ public class ValidateJwtTokenFilter extends ServletFilter {
   public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
     HttpServletRequest request = (HttpServletRequest) servletRequest;
     HttpServletResponse response = (HttpServletResponse) servletResponse;
+    String path = request.getRequestURI().replaceFirst(request.getContextPath(), "");
 
     try {
+      if (isDeprecatedBatchWs(path)) {
+        chain.doFilter(request, response);
+        return;
+      }
+
       jwtHttpHandler.validateToken(request, response);
+      // TODO handle basic authentication
+      if (!userSession.isLoggedIn() && settings.getBoolean(CORE_FORCE_AUTHENTICATION_PROPERTY)) {
+        throw new UnauthorizedException("User must be authenticated");
+      }
       chain.doFilter(request, response);
     } catch (UnauthorizedException e) {
-      response.setStatus(e.httpCode());
+      jwtHttpHandler.removeToken(response);
+      response.setStatus(HTTP_UNAUTHORIZED);
+
+      if (isWsUrl(path)) {
+        return;
+      }
+      // WS should stop here. Rails page should continue in order to deal with redirection
+      chain.doFilter(request, response);
     }
   }
 
+  // Scanner is still using deprecated /batch/<File name>.jar WS
+  private static boolean isDeprecatedBatchWs(String path){
+    return path.startsWith("/batch/") && path.endsWith(".jar");
+  }
+
+  private static boolean isWsUrl(String path){
+    return path.startsWith("/batch/") || path.startsWith("/api/");
+  }
+
   @Override
   public void init(FilterConfig filterConfig) throws ServletException {
     // Nothing to do
index 79600e01b5ef6cfc305d189699a2d1ec33107c2d..f35a25e1b0dfa5762fd73261bd59b5ee2056062e 100644 (file)
@@ -22,14 +22,16 @@ package org.sonar.server.authentication.ws;
 import com.google.common.io.Resources;
 import org.sonar.api.server.ws.RailsHandler;
 import org.sonar.api.server.ws.WebService;
+import org.sonar.server.ws.ServletFilterHandler;
 
 public class AuthenticationWs implements WebService {
 
   @Override
   public void define(Context context) {
     NewController controller = context.createController("api/authentication");
-    controller.setDescription("Check authentication credentials.");
+    controller.setDescription("Handle authentication.");
 
+    defineLoginAction(controller);
     defineValidateAction(controller);
 
     controller.done();
@@ -37,7 +39,7 @@ public class AuthenticationWs implements WebService {
 
   private void defineValidateAction(NewController controller) {
     NewAction action = controller.createAction("validate")
-      .setDescription("Check credentials")
+      .setDescription("Check credentials.")
       .setSince("3.3")
       .setHandler(RailsHandler.INSTANCE)
       .setResponseExample(Resources.getResource(this.getClass(), "example-validate.json"));
@@ -45,4 +47,18 @@ public class AuthenticationWs implements WebService {
     RailsHandler.addFormatParam(action);
   }
 
+  private static void defineLoginAction(NewController controller) {
+    NewAction action = controller.createAction("login")
+      .setDescription("Authenticate a user.")
+      .setSince("6.0")
+      .setPost(true)
+      .setHandler(ServletFilterHandler.INSTANCE);
+    action.createParam("login")
+      .setDescription("Login of the user")
+      .setRequired(true);
+    action.createParam("password")
+      .setDescription("Password of the user")
+      .setRequired(true);
+  }
+
 }
index 00d2e2a9208a10401a0113118cb73de0b32e0d32..0dcb44b377e1b5741afcac8df395dcc837766435 100644 (file)
@@ -19,6 +19,9 @@
  */
 package org.sonar.server.user;
 
+import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Maps.newHashMap;
+
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Strings;
 import com.google.common.collect.HashMultimap;
@@ -33,18 +36,19 @@ import java.util.Set;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 import org.sonar.api.security.DefaultGroups;
+import org.sonar.db.user.UserDto;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.exceptions.UnauthorizedException;
 
-import static com.google.common.collect.Lists.newArrayList;
-import static com.google.common.collect.Maps.newHashMap;
-
 public abstract class AbstractUserSession<T extends AbstractUserSession> implements UserSession {
   protected static final String INSUFFICIENT_PRIVILEGES_MESSAGE = "Insufficient privileges";
   private static final ForbiddenException INSUFFICIENT_PRIVILEGES_EXCEPTION = new ForbiddenException(INSUFFICIENT_PRIVILEGES_MESSAGE);
 
+  protected UserDto userDto;
   protected Integer userId;
   protected String login;
+  protected String name;
+
   protected Set<String> userGroups = Sets.newHashSet(DefaultGroups.ANYONE);
   protected List<String> globalPermissions = Collections.emptyList();
   protected HashMultimap<String, String> projectKeyByPermission = HashMultimap.create();
@@ -52,7 +56,7 @@ public abstract class AbstractUserSession<T extends AbstractUserSession> impleme
   protected Map<String, String> projectUuidByComponentUuid = newHashMap();
   protected List<String> projectPermissionsCheckedByKey = newArrayList();
   protected List<String> projectPermissionsCheckedByUuid = newArrayList();
-  protected String name;
+
   protected Locale locale = Locale.ENGLISH;
 
   private final Class<T> clazz;
@@ -61,13 +65,14 @@ public abstract class AbstractUserSession<T extends AbstractUserSession> impleme
     this.clazz = clazz;
   }
 
+
   @Override
   @CheckForNull
   public String getLogin() {
     return login;
   }
 
-  protected T setLogin(@Nullable String s) {
+  public T setLogin(@Nullable String s) {
     this.login = Strings.emptyToNull(s);
     return clazz.cast(this);
   }
@@ -78,7 +83,7 @@ public abstract class AbstractUserSession<T extends AbstractUserSession> impleme
     return name;
   }
 
-  protected T setName(@Nullable String s) {
+  public T setName(@Nullable String s) {
     this.name = Strings.emptyToNull(s);
     return clazz.cast(this);
   }
@@ -89,7 +94,7 @@ public abstract class AbstractUserSession<T extends AbstractUserSession> impleme
     return userId;
   }
 
-  protected T setUserId(@Nullable Integer userId) {
+  public T setUserId(@Nullable Integer userId) {
     this.userId = userId;
     return clazz.cast(this);
   }
@@ -99,7 +104,7 @@ public abstract class AbstractUserSession<T extends AbstractUserSession> impleme
     return userGroups;
   }
 
-  protected T setUserGroups(@Nullable String... userGroups) {
+  public T setUserGroups(@Nullable String... userGroups) {
     if (userGroups != null) {
       this.userGroups.addAll(Arrays.asList(userGroups));
     }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/RubyUserSession.java b/server/sonar-server/src/main/java/org/sonar/server/user/RubyUserSession.java
deleted file mode 100644 (file)
index 92da6ed..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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.user;
-
-import java.util.List;
-
-import javax.annotation.Nullable;
-
-import org.sonar.core.platform.ComponentContainer;
-import org.sonar.db.component.ResourceDao;
-import org.sonar.db.user.AuthorizationDao;
-import org.sonar.server.platform.Platform;
-import org.sonar.server.ui.JRubyI18n;
-
-import com.google.common.annotations.VisibleForTesting;
-
-public class RubyUserSession {
-
-  private static RubyUserSession instance;
-
-  private static RubyUserSession getInstance() {
-    if (instance == null) {
-      instance = new RubyUserSession(Platform.getInstance());
-    }
-    return instance;
-  }
-
-  private final Platform platform;
-
-  /**
-   * Invoked by Ruby code - see application_controller.rb
-   */
-  public static void setSession(@Nullable Integer userId, @Nullable String login, @Nullable String name, @Nullable List<String> userGroups, @Nullable String localeRubyKey) {
-    getInstance().setSessionImpl(userId, login, name, userGroups, localeRubyKey);
-  }
-
-  @VisibleForTesting
-  RubyUserSession(Platform platform) {
-    // Utility class
-    this.platform = platform;
-  }
-
-  public void setSessionImpl(@Nullable Integer userId, @Nullable String login, @Nullable String name, @Nullable List<String> userGroups, @Nullable String localeRubyKey) {
-    ComponentContainer container = platform.getContainer();
-    ThreadLocalUserSession threadLocalUserSession = container.getComponentByType(ThreadLocalUserSession.class);
-
-    UserSession session = new ServerUserSession(container.getComponentByType(AuthorizationDao.class),
-      container.getComponentByType(ResourceDao.class))
-      .setLogin(login)
-      .setName(name)
-      .setUserId(userId)
-      .setUserGroups(userGroups != null ? userGroups.toArray(new String[userGroups.size()]) : null)
-      .setLocale(JRubyI18n.toLocale(localeRubyKey));
-    threadLocalUserSession.set(session);
-  }
-
-}
index 3fe9aaaca2626c629dc589b7ff640000660d4235..b4792f6b1680a04403f70f107aa9bd38988992ef 100644 (file)
  */
 package org.sonar.server.user;
 
+import static com.google.common.collect.Maps.newHashMap;
+import static java.util.Objects.requireNonNull;
+
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import org.sonar.api.security.DefaultGroups;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
 import org.sonar.db.component.ResourceDao;
 import org.sonar.db.component.ResourceDto;
 import org.sonar.db.user.AuthorizationDao;
-
-import static com.google.common.collect.Maps.newHashMap;
-import static com.google.common.collect.Sets.newHashSet;
+import org.sonar.db.user.GroupDto;
+import org.sonar.db.user.UserDto;
 
 /**
  * Part of the current HTTP session
  */
 public class ServerUserSession extends AbstractUserSession<ServerUserSession> {
-
   private Map<String, String> projectKeyByComponentKey = newHashMap();
 
+  private final DbClient dbClient;
   private final AuthorizationDao authorizationDao;
   private final ResourceDao resourceDao;
 
-  ServerUserSession(AuthorizationDao authorizationDao, ResourceDao resourceDao) {
+  private ServerUserSession(DbClient dbClient, @Nullable UserDto userDto) {
     super(ServerUserSession.class);
+    this.dbClient = dbClient;
+    this.authorizationDao = dbClient.authorizationDao();
+    this.resourceDao = dbClient.resourceDao();
     this.globalPermissions = null;
-    this.authorizationDao = authorizationDao;
-    this.resourceDao = resourceDao;
-    // Do not forget that when forceAuthentication is set to true, the Anyone group should not be set (but this will be check when
-    // authentication will be done in Java)
-    this.userGroups = newHashSet(DefaultGroups.ANYONE);
+    if(userDto != null){
+      this.setLogin(userDto.getLogin());
+      this.setName(userDto.getName());
+      this.setUserId(userDto.getId().intValue());
+      this.userGroups.addAll(getUserGroups(userDto.getLogin()));
+    }
+  }
+
+  public static ServerUserSession createForUser(DbClient dbClient, UserDto userDto){
+    requireNonNull(userDto, "UserDto must not be null");
+    return new ServerUserSession(dbClient, userDto);
+  }
+
+  public static ServerUserSession createForAnonymous(DbClient dbClient){
+    return new ServerUserSession(dbClient, null);
+  }
+
+  private Set<String> getUserGroups(String userLogin) {
+    DbSession dbSession = dbClient.openSession(false);
+    try {
+      return new HashSet<>(dbClient.groupDao().selectByUserLogin(dbSession, userLogin).stream().map(GroupDto::getName).collect(Collectors.toSet()));
+    } finally {
+      dbClient.closeSession(dbSession);
+    }
   }
 
   @Override
index 363001b6cf1389f25143d4a22e62c7008afab627..2598aaa307bd307d07c7cb10ab198ba5c699c729 100644 (file)
  */
 package org.sonar.server.user;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+
 import java.util.List;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-
 public class UpdateUser {
 
   private String login;
@@ -34,11 +34,11 @@ public class UpdateUser {
   private String password;
   private ExternalIdentity externalIdentity;
 
-  boolean nameChanged;
-  boolean emailChanged;
-  boolean scmAccountsChanged;
-  boolean passwordChanged;
-  boolean externalIdentityChanged;
+  private boolean nameChanged;
+  private boolean emailChanged;
+  private boolean scmAccountsChanged;
+  private boolean passwordChanged;
+  private boolean externalIdentityChanged;
 
   private UpdateUser(String login) {
     // No direct call to this constructor
@@ -98,6 +98,9 @@ public class UpdateUser {
     return externalIdentity;
   }
 
+  /**
+   * This method should only be used when updating a none local user
+   */
   public UpdateUser setExternalIdentity(@Nullable ExternalIdentity externalIdentity) {
     this.externalIdentity = externalIdentity;
     externalIdentityChanged = true;
index d63d0b66c535d03e86e4113ead46d0bd353f45b1..2e869f5cdd33cda9fc950fcfcd4fc2b1e43ccf27 100644 (file)
  */
 package org.sonar.server.user;
 
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static com.google.common.collect.Lists.newArrayList;
+import static org.sonar.db.user.UserDto.encryptPassword;
+
 import com.google.common.base.Joiner;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Iterables;
@@ -47,9 +51,6 @@ import org.sonar.server.exceptions.ServerException;
 import org.sonar.server.user.index.UserIndexer;
 import org.sonar.server.util.Validation;
 
-import static com.google.common.base.Strings.isNullOrEmpty;
-import static com.google.common.collect.Lists.newArrayList;
-
 @ServerSide
 public class UserUpdater {
 
@@ -115,11 +116,13 @@ public class UserUpdater {
     UpdateUser updateUser = UpdateUser.create(login)
       .setName(newUser.name())
       .setEmail(newUser.email())
-      .setScmAccounts(newUser.scmAccounts())
-      .setExternalIdentity(newUser.externalIdentity());
+      .setScmAccounts(newUser.scmAccounts());
     if (newUser.password() != null) {
       updateUser.setPassword(newUser.password());
     }
+    if (newUser.externalIdentity() != null) {
+      updateUser.setExternalIdentity(newUser.externalIdentity());
+    }
     // Hack to allow to change the password of the user
     existingUser.setLocal(true);
     updateUserDto(dbSession, updateUser, existingUser);
@@ -178,22 +181,22 @@ public class UserUpdater {
     List<Message> messages = newArrayList();
 
     String login = newUser.login();
-    validateLoginFormat(login, messages);
-    userDto.setLogin(login);
+    if (validateLoginFormat(login, messages)) {
+      userDto.setLogin(login);
+    }
 
     String name = newUser.name();
-    validateNameFormat(name, messages);
-    userDto.setName(name);
+    if (validateNameFormat(name, messages)) {
+      userDto.setName(name);
+    }
 
     String email = newUser.email();
-    if (email != null) {
-      validateEmailFormat(email, messages);
+    if (email != null && validateEmailFormat(email, messages)) {
       userDto.setEmail(email);
     }
 
     String password = newUser.password();
-    if (password != null) {
-      validatePasswords(password, messages);
+    if (password != null && validatePasswords(password, messages)) {
       setEncryptedPassWord(password, userDto);
     }
 
@@ -215,26 +218,22 @@ public class UserUpdater {
     List<Message> messages = newArrayList();
 
     String name = updateUser.name();
-    if (updateUser.isNameChanged()) {
-      validateNameFormat(name, messages);
+    if (updateUser.isNameChanged() && validateNameFormat(name, messages)) {
       userDto.setName(name);
     }
 
     String email = updateUser.email();
-    if (updateUser.isEmailChanged()) {
-      validateEmailFormat(email, messages);
+    if (updateUser.isEmailChanged() && validateEmailFormat(email, messages)) {
       userDto.setEmail(email);
     }
 
-    if (isNewExternalIdentityNotEqualsToSonaQube(updateUser)) {
+    if (updateUser.isExternalIdentityChanged()) {
       setExternalIdentity(userDto, updateUser.externalIdentity());
       userDto.setSalt(null);
       userDto.setCryptedPassword(null);
     } else {
       String password = updateUser.password();
-      if (updateUser.isPasswordChanged()) {
-        validatePasswords(password, messages);
-        checkPasswordChangeAllowed(userDto, messages);
+      if (updateUser.isPasswordChanged() && validatePasswords(password, messages) && checkPasswordChangeAllowed(userDto, messages)) {
         setEncryptedPassWord(password, userDto);
       }
     }
@@ -242,17 +241,15 @@ public class UserUpdater {
     if (updateUser.isScmAccountsChanged()) {
       List<String> scmAccounts = sanitizeScmAccounts(updateUser.scmAccounts());
       if (scmAccounts != null && !scmAccounts.isEmpty()) {
-        validateScmAccounts(dbSession, scmAccounts, userDto.getLogin(), email != null ? email : userDto.getEmail(), userDto, messages);
-        userDto.setScmAccounts(scmAccounts);
+        String newOrOldEmail = email != null ? email : userDto.getEmail();
+        if (validateScmAccounts(dbSession, scmAccounts, userDto.getLogin(), newOrOldEmail, userDto, messages)) {
+          userDto.setScmAccounts(scmAccounts);
+        }
       } else {
         userDto.setScmAccounts((String) null);
       }
     }
 
-    if (updateUser.isExternalIdentityChanged()) {
-      setExternalIdentity(userDto, updateUser.externalIdentity());
-    }
-
     if (!messages.isEmpty()) {
       throw new BadRequestException(messages);
     }
@@ -270,64 +267,71 @@ public class UserUpdater {
     }
   }
 
-  private static void checkNotEmptyParam(@Nullable String value, String param, List<Message> messages) {
+  private static boolean checkNotEmptyParam(@Nullable String value, String param, List<Message> messages) {
     if (isNullOrEmpty(value)) {
       messages.add(Message.of(Validation.CANT_BE_EMPTY_MESSAGE, param));
+      return false;
     }
+    return true;
   }
 
-  private static String validateLoginFormat(@Nullable String login, List<Message> messages) {
-    checkNotEmptyParam(login, LOGIN_PARAM, messages);
+  private static boolean validateLoginFormat(@Nullable String login, List<Message> messages) {
+    boolean isValid = checkNotEmptyParam(login, LOGIN_PARAM, messages);
     if (!isNullOrEmpty(login)) {
       if (login.length() < LOGIN_MIN_LENGTH) {
         messages.add(Message.of(Validation.IS_TOO_SHORT_MESSAGE, LOGIN_PARAM, LOGIN_MIN_LENGTH));
+        return false;
       } else if (login.length() > LOGIN_MAX_LENGTH) {
         messages.add(Message.of(Validation.IS_TOO_LONG_MESSAGE, LOGIN_PARAM, LOGIN_MAX_LENGTH));
+        return false;
       } else if (!login.matches("\\A\\w[\\w\\.\\-_@]+\\z")) {
         messages.add(Message.of("user.bad_login"));
+        return false;
       }
     }
-    return login;
+    return isValid;
   }
 
-  private static void validateNameFormat(@Nullable String name, List<Message> messages) {
-    checkNotEmptyParam(name, NAME_PARAM, messages);
+  private static boolean validateNameFormat(@Nullable String name, List<Message> messages) {
+    boolean isValid = checkNotEmptyParam(name, NAME_PARAM, messages);
     if (name != null && name.length() > NAME_MAX_LENGTH) {
       messages.add(Message.of(Validation.IS_TOO_LONG_MESSAGE, NAME_PARAM, 200));
+      return false;
     }
+    return isValid;
   }
 
-  private static void validateEmailFormat(@Nullable String email, List<Message> messages) {
+  private static boolean validateEmailFormat(@Nullable String email, List<Message> messages) {
     if (email != null && email.length() > EMAIL_MAX_LENGTH) {
       messages.add(Message.of(Validation.IS_TOO_LONG_MESSAGE, EMAIL_PARAM, 100));
+      return false;
     }
+    return true;
   }
 
-  private static void checkPasswordChangeAllowed(UserDto userDto, List<Message> messages) {
+  private static boolean checkPasswordChangeAllowed(UserDto userDto, List<Message> messages) {
     if (!userDto.isLocal()) {
       messages.add(Message.of("user.password_cant_be_changed_on_external_auth"));
+      return false;
     }
+    return true;
   }
 
-  private static boolean isNewExternalIdentityNotEqualsToSonaQube(UpdateUser updateUser) {
-    ExternalIdentity externalIdentity = updateUser.externalIdentity();
-    if (updateUser.isExternalIdentityChanged() && externalIdentity != null) {
-      return !externalIdentity.getProvider().equals(SQ_AUTHORITY);
-    }
-    return false;
-  }
-
-  private static void validatePasswords(@Nullable String password, List<Message> messages) {
+  private static boolean validatePasswords(@Nullable String password, List<Message> messages) {
     if (password == null || password.length() == 0) {
       messages.add(Message.of(Validation.CANT_BE_EMPTY_MESSAGE, PASSWORD_PARAM));
+      return false;
     }
+    return true;
   }
 
-  private void validateScmAccounts(DbSession dbSession, List<String> scmAccounts, @Nullable String login, @Nullable String email, @Nullable UserDto existingUser,
+  private boolean validateScmAccounts(DbSession dbSession, List<String> scmAccounts, @Nullable String login, @Nullable String email, @Nullable UserDto existingUser,
     List<Message> messages) {
+    boolean isValid = true;
     for (String scmAccount : scmAccounts) {
       if (scmAccount.equals(login) || scmAccount.equals(email)) {
         messages.add(Message.of("user.login_or_email_used_as_scm_account"));
+        isValid = false;
       } else {
         List<UserDto> matchingUsers = dbClient.userDao().selectByScmAccountOrLoginOrEmail(dbSession, scmAccount);
         List<String> matchingUsersWithoutExistingUser = newArrayList();
@@ -338,9 +342,11 @@ public class UserUpdater {
         }
         if (!matchingUsersWithoutExistingUser.isEmpty()) {
           messages.add(Message.of("user.scm_account_already_used", scmAccount, Joiner.on(", ").join(matchingUsersWithoutExistingUser)));
+          isValid = false;
         }
       }
     }
+    return isValid;
   }
 
   @CheckForNull
@@ -373,10 +379,6 @@ public class UserUpdater {
     userDto.setCryptedPassword(encryptPassword(password, saltHex));
   }
 
-  private static String encryptPassword(String password, String salt) {
-    return DigestUtils.sha1Hex("--" + salt + "--" + password + "--");
-  }
-
   private void notifyNewUser(String login, String name, String email) {
     newUserNotifier.onNewUser(NewUserHandler.Context.builder()
       .setLogin(login)
diff --git a/server/sonar-server/src/main/java/org/sonar/server/ws/ServletFilterHandler.java b/server/sonar-server/src/main/java/org/sonar/server/ws/ServletFilterHandler.java
new file mode 100644 (file)
index 0000000..3f1f7b8
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * 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.ws;
+
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.RequestHandler;
+import org.sonar.api.server.ws.Response;
+
+/**
+ * Used to declare web services that are implemented by a servlet filter.
+ */
+public class ServletFilterHandler implements RequestHandler {
+
+  public static final RequestHandler INSTANCE = new ServletFilterHandler();
+
+  private ServletFilterHandler() {
+    // Nothing
+  }
+
+  @Override
+  public void handle(Request request, Response response) {
+    throw new UnsupportedOperationException("This web service is implemented as a servlet filter");
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/AuthLoginActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/AuthLoginActionTest.java
new file mode 100644 (file)
index 0000000..cbd7f12
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * 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;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.doThrow;
+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 java.io.IOException;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.junit.Test;
+import org.sonar.db.user.UserDto;
+import org.sonar.db.user.UserTesting;
+import org.sonar.server.exceptions.UnauthorizedException;
+
+public class AuthLoginActionTest {
+
+  static final String LOGIN = "LOGIN";
+  static final String PASSWORD = "PASSWORD";
+
+  static final UserDto USER = UserTesting.newUserDto().setLogin(LOGIN);
+
+  HttpServletRequest request = mock(HttpServletRequest.class);
+  HttpServletResponse response = mock(HttpServletResponse.class);
+  FilterChain chain = mock(FilterChain.class);
+
+  CredentialsAuthenticator credentialsAuthenticator = mock(CredentialsAuthenticator.class);
+  JwtHttpHandler jwtHttpHandler = mock(JwtHttpHandler.class);
+
+  AuthLoginAction underTest  = new AuthLoginAction(credentialsAuthenticator, jwtHttpHandler);
+
+  @Test
+  public void do_get_pattern() throws Exception {
+    assertThat(underTest.doGetPattern().matches("/api/authentication/login")).isTrue();
+    assertThat(underTest.doGetPattern().matches("/api/authentication/logout")).isFalse();
+    assertThat(underTest.doGetPattern().matches("/foo")).isFalse();
+  }
+
+  @Test
+  public void do_authenticate() throws Exception {
+    when(credentialsAuthenticator.authenticate(LOGIN, PASSWORD, request)).thenReturn(USER);
+
+    executeRequest(LOGIN, PASSWORD);
+
+    verify(credentialsAuthenticator).authenticate(LOGIN, PASSWORD, request);
+    verify(jwtHttpHandler).generateToken(USER, response);
+    verifyZeroInteractions(chain);
+  }
+
+  @Test
+  public void ignore_get_request() throws Exception {
+    when(request.getMethod()).thenReturn("GET");
+
+    underTest.doFilter(request, response, chain);
+
+    verifyZeroInteractions(credentialsAuthenticator, jwtHttpHandler, chain);
+  }
+
+  @Test
+  public void return_authorized_code_when_unauthorized_exception_is_thrown() throws Exception {
+    doThrow(new UnauthorizedException()).when(credentialsAuthenticator).authenticate(LOGIN, PASSWORD, request);
+
+    executeRequest(LOGIN, PASSWORD);
+
+    verify(response).setStatus(401);
+  }
+
+  @Test
+  public void return_unauthorized_code_when_no_login() throws Exception {
+    executeRequest(null, PASSWORD);
+    verify(response).setStatus(401);
+  }
+
+  @Test
+  public void return_unauthorized_code_when_empty_login() throws Exception {
+    executeRequest("", PASSWORD);
+    verify(response).setStatus(401);
+  }
+
+  @Test
+  public void return_unauthorized_code_when_no_password() throws Exception {
+    executeRequest(LOGIN, null);
+    verify(response).setStatus(401);
+  }
+
+  @Test
+  public void return_unauthorized_code_when_empty_password() throws Exception {
+    executeRequest(LOGIN, "");
+    verify(response).setStatus(401);
+  }
+
+  private void executeRequest(String login, String password) throws IOException, ServletException {
+    when(request.getMethod()).thenReturn("POST");
+    when(request.getParameter("login")).thenReturn(login);
+    when(request.getParameter("password")).thenReturn(password);
+    underTest.doFilter(request, response, chain);
+  }
+}
index b74ccaf628cdf6f909c5a5a9d3ed96e4e96a1b10..8128b3ac55ead9b375e88810a630b1ebb3947310 100644 (file)
@@ -20,6 +20,8 @@
 package org.sonar.server.authentication;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -32,6 +34,7 @@ import org.junit.Test;
 import org.sonar.api.platform.Server;
 import org.sonar.api.server.authentication.BaseIdentityProvider;
 import org.sonar.api.server.authentication.UserIdentity;
+import org.sonar.db.user.UserDto;
 
 public class BaseContextFactoryTest {
 
@@ -50,8 +53,9 @@ public class BaseContextFactoryTest {
   HttpServletRequest request = mock(HttpServletRequest.class);
   HttpServletResponse response = mock(HttpServletResponse.class);
   BaseIdentityProvider identityProvider = mock(BaseIdentityProvider.class);
+  JwtHttpHandler jwtHttpHandler = mock(JwtHttpHandler.class);
 
-  BaseContextFactory underTest = new BaseContextFactory(userIdentityAuthenticator, server);
+  BaseContextFactory underTest = new BaseContextFactory(userIdentityAuthenticator, server, jwtHttpHandler);
 
   @Before
   public void setUp() throws Exception {
@@ -74,6 +78,7 @@ public class BaseContextFactoryTest {
     when(request.getSession()).thenReturn(session);
 
     context.authenticate(USER_IDENTITY);
-    verify(userIdentityAuthenticator).authenticate(USER_IDENTITY, identityProvider, request, response);
+    verify(userIdentityAuthenticator).authenticate(USER_IDENTITY, identityProvider);
+    verify(jwtHttpHandler).generateToken(any(UserDto.class), eq(response));
   }
 }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/CredentialsAuthenticatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/CredentialsAuthenticatorTest.java
new file mode 100644 (file)
index 0000000..e468680
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * 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;
+
+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;
+import org.sonar.api.utils.System2;
+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.exceptions.UnauthorizedException;
+
+public class CredentialsAuthenticatorTest {
+
+  static final String LOGIN = "LOGIN";
+  static final String PASSWORD = "PASSWORD";
+  static final String SALT = "0242b0b4c0a93ddfe09dd886de50bc25ba000b51";
+  static final String CRYPTED_PASSWORD = "540e4fc4be4e047db995bc76d18374a5b5db08cc";
+
+  @Rule
+  public ExpectedException expectedException = none();
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  DbClient dbClient = dbTester.getDbClient();
+
+  DbSession dbSession = dbTester.getSession();
+
+  RealmAuthenticator externalAuthenticator = mock(RealmAuthenticator.class);
+  HttpServletRequest request = mock(HttpServletRequest.class);
+  HttpServletResponse response = mock(HttpServletResponse.class);
+
+  CredentialsAuthenticator underTest = new CredentialsAuthenticator(dbClient, externalAuthenticator);
+
+  @Test
+  public void authenticate_local_user() throws Exception {
+    insertUser(newUserDto()
+      .setLogin(LOGIN)
+      .setCryptedPassword(CRYPTED_PASSWORD)
+      .setSalt(SALT)
+      .setLocal(true));
+
+    UserDto userDto = executeAuthenticate();
+    assertThat(userDto.getLogin()).isEqualTo(LOGIN);
+  }
+
+  @Test
+  public void fail_to_authenticate_local_user_when_password_is_wrong() throws Exception {
+    insertUser(newUserDto()
+      .setLogin(LOGIN)
+      .setCryptedPassword("Wrong password")
+      .setSalt("Wrong salt")
+      .setLocal(true));
+
+    expectedException.expect(UnauthorizedException.class);
+    executeAuthenticate();
+  }
+
+  @Test
+  public void authenticate_external_user() throws Exception {
+    when(externalAuthenticator.authenticate(LOGIN, PASSWORD, request)).thenReturn(Optional.of(newUserDto()));
+    insertUser(newUserDto()
+      .setLogin(LOGIN)
+      .setLocal(false));
+
+    executeAuthenticate();
+
+    verify(externalAuthenticator).authenticate(LOGIN, PASSWORD, request);
+  }
+
+  @Test
+  public void fail_to_authenticate_authenticate_external_user_when_no_external_authentication() throws Exception {
+    when(externalAuthenticator.authenticate(LOGIN, PASSWORD, request)).thenReturn(Optional.empty());
+    insertUser(newUserDto()
+      .setLogin(LOGIN)
+      .setLocal(false));
+
+    expectedException.expect(UnauthorizedException.class);
+    executeAuthenticate();
+  }
+
+  @Test
+  public void fail_to_authenticate_local_user_that_have_no_password() throws Exception {
+    insertUser(newUserDto()
+      .setLogin(LOGIN)
+      .setCryptedPassword(null)
+      .setSalt(SALT)
+      .setLocal(true));
+
+    expectedException.expect(UnauthorizedException.class);
+    executeAuthenticate();
+  }
+
+  @Test
+  public void fail_to_authenticate_local_user_that_have_no_salt() throws Exception {
+    insertUser(newUserDto()
+      .setLogin(LOGIN)
+      .setCryptedPassword(CRYPTED_PASSWORD)
+      .setSalt(null)
+      .setLocal(true));
+
+    expectedException.expect(UnauthorizedException.class);
+    executeAuthenticate();
+  }
+
+  private UserDto executeAuthenticate(){
+    return underTest.authenticate(LOGIN, PASSWORD, request);
+  }
+
+  private UserDto insertUser(UserDto userDto){
+    dbClient.userDao().insert(dbSession, userDto);
+    dbSession.commit();
+    return userDto;
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/GenerateJwtTokenFilterTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/GenerateJwtTokenFilterTest.java
deleted file mode 100644 (file)
index b6f6c10..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * 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;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.jboss.netty.handler.codec.http.HttpMethod.GET;
-import static org.jboss.netty.handler.codec.http.HttpMethod.POST;
-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 javax.servlet.FilterChain;
-import javax.servlet.ServletOutputStream;
-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;
-import org.sonar.server.tester.UserSessionRule;
-
-public class GenerateJwtTokenFilterTest {
-
-  @Rule
-  public ExpectedException thrown = ExpectedException.none();
-
-  @Rule
-  public UserSessionRule userSession = UserSessionRule.standalone();
-
-  HttpServletRequest request = mock(HttpServletRequest.class);
-  HttpServletResponse response = mock(HttpServletResponse.class);
-  FilterChain chain = mock(FilterChain.class);
-
-  JwtHttpHandler jwtHttpHandler = mock(JwtHttpHandler.class);
-
-  GenerateJwtTokenFilter underTest = new GenerateJwtTokenFilter(jwtHttpHandler, userSession);
-
-  @Before
-  public void setUp() throws Exception {
-    when(response.getOutputStream()).thenReturn(mock(ServletOutputStream.class));
-  }
-
-  @Test
-  public void do_get_pattern() throws Exception {
-    assertThat(underTest.doGetPattern().matches("/sessions/login")).isTrue();
-    assertThat(underTest.doGetPattern().matches("/")).isFalse();
-  }
-
-  @Test
-  public void create_session_when_post_request_and_user_is_authenticated() throws Exception {
-    executePostRequest();
-    userSession.login("john");
-
-    underTest.doFilter(request, response, chain);
-
-    verify(jwtHttpHandler).generateToken("john", response);
-  }
-
-  @Test
-  public void does_nothing_on_get_request() throws Exception {
-    executeGetRequest();
-    userSession.login("john");
-
-    underTest.doFilter(request, response, chain);
-
-    verifyZeroInteractions(jwtHttpHandler);
-  }
-
-  @Test
-  public void does_nothing_when_user_is_not_authenticated() throws Exception {
-    executePostRequest();
-    userSession.anonymous();
-
-    underTest.doFilter(request, response, chain);
-
-    verifyZeroInteractions(jwtHttpHandler);
-  }
-
-  private void executePostRequest() {
-    when(request.getMethod()).thenReturn(POST.getName());
-  }
-
-  private void executeGetRequest() {
-    when(request.getMethod()).thenReturn(GET.getName());
-  }
-
-}
index 5fb48bb9ec29e12830f55a29bc4ac1ef6365884a..e4b147416fe7577928259f5356df53e05a8c998d 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;
@@ -35,11 +40,6 @@ import org.sonar.api.server.authentication.UnauthorizedException;
 import org.sonar.api.utils.log.LogTester;
 import org.sonar.api.utils.log.LoggerLevel;
 
-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;
-
 public class InitFilterTest {
 
   static String OAUTH2_PROVIDER_KEY = "github";
index 4523c24bf93360b9db0d9ad486f4e57b0482ebff..2f67f52021a53838568a7203e937cb28e1fb3cd5 100644 (file)
@@ -31,6 +31,7 @@ import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 import static org.sonar.api.utils.System2.INSTANCE;
+import static org.sonar.db.user.UserTesting.newUserDto;
 
 import io.jsonwebtoken.Claims;
 import io.jsonwebtoken.impl.DefaultClaims;
@@ -53,14 +54,15 @@ import org.sonar.db.DbClient;
 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.tester.UserSessionRule;
+import org.sonar.server.exceptions.UnauthorizedException;
+import org.sonar.server.user.ServerUserSession;
+import org.sonar.server.user.ThreadLocalUserSession;
 
 public class JwtHttpHandlerTest {
 
   static final String JWT_TOKEN = "TOKEN";
-  static final String USER_LOGIN = "john";
   static final String CSRF_STATE = "CSRF_STATE";
+  static final String USER_LOGIN = "john";
 
   static final long NOW = 10_000_000_000L;
   static final long FOUR_MINUTES_AGO = NOW - 4 * 60 * 1000L;
@@ -70,12 +72,11 @@ public class JwtHttpHandlerTest {
   @Rule
   public ExpectedException thrown = ExpectedException.none();
 
-  @Rule
-  public UserSessionRule userSession = UserSessionRule.standalone();
-
   @Rule
   public DbTester dbTester = DbTester.create(INSTANCE);
 
+  ThreadLocalUserSession threadLocalUserSession = new ThreadLocalUserSession();
+
   DbClient dbClient = dbTester.getDbClient();
 
   DbSession dbSession = dbTester.getSession();
@@ -93,20 +94,25 @@ public class JwtHttpHandlerTest {
   JwtSerializer jwtSerializer = mock(JwtSerializer.class);
   JwtCsrfVerifier jwtCsrfVerifier = mock(JwtCsrfVerifier.class);
 
-  JwtHttpHandler underTest = new JwtHttpHandler(system2, dbClient, server, settings, jwtSerializer, jwtCsrfVerifier);
+  UserDto userDto = newUserDto().setLogin(USER_LOGIN);
+
+  JwtHttpHandler underTest = new JwtHttpHandler(system2, dbClient, server, settings, jwtSerializer, jwtCsrfVerifier, threadLocalUserSession);
 
   @Before
   public void setUp() throws Exception {
+    threadLocalUserSession.remove();
     when(system2.now()).thenReturn(NOW);
     when(server.isSecured()).thenReturn(true);
     when(request.getSession()).thenReturn(httpSession);
     when(jwtSerializer.encode(any(JwtSerializer.JwtSession.class))).thenReturn(JWT_TOKEN);
     when(jwtCsrfVerifier.generateState(eq(response), anyInt())).thenReturn(CSRF_STATE);
+    dbClient.userDao().insert(dbSession, userDto);
+    dbSession.commit();
   }
 
   @Test
-  public void create_session() throws Exception {
-    underTest.generateToken(USER_LOGIN, response);
+  public void create_token() throws Exception {
+    underTest.generateToken(userDto, response);
 
     Optional<Cookie> jwtCookie = findCookie("JWT-SESSION");
     assertThat(jwtCookie).isPresent();
@@ -114,11 +120,12 @@ public class JwtHttpHandlerTest {
 
     verify(jwtSerializer).encode(jwtArgumentCaptor.capture());
     verifyToken(jwtArgumentCaptor.getValue(), 3 * 24 * 60 * 60, NOW);
+    assertThat(threadLocalUserSession.get().isLoggedIn()).isTrue();
   }
 
   @Test
-  public void generate_csrf_state() throws Exception {
-    underTest.generateToken(USER_LOGIN, response);
+  public void generate_csrf_state_when_creating_token() throws Exception {
+    underTest.generateToken(userDto, response);
 
     verify(jwtCsrfVerifier).generateState(response, 3 * 24 * 60 * 60);
 
@@ -128,26 +135,12 @@ public class JwtHttpHandlerTest {
   }
 
   @Test
-  public void validate_session() throws Exception {
-    addJwtCookie();
-    UserDto user = addUser();
-
-    Claims claims = createToken(NOW);
-    when(jwtSerializer.decode(JWT_TOKEN)).thenReturn(Optional.of(claims));
-
-    underTest.validateToken(request, response);
-
-    verify(httpSession).setAttribute("user_id", user.getId());
-    verify(jwtSerializer, never()).encode(any(JwtSerializer.JwtSession.class));
-  }
-
-  @Test
-  public void use_session_timeout_from_settings() throws Exception {
+  public void generate_token_is_using_session_timeout_from_settings() throws Exception {
     int sessionTimeoutInHours = 10;
     settings.setProperty("sonar.auth.sessionTimeoutInHours", sessionTimeoutInHours);
 
-    underTest = new JwtHttpHandler(system2, dbClient, server, settings, jwtSerializer, jwtCsrfVerifier);
-    underTest.generateToken(USER_LOGIN, response);
+    underTest = new JwtHttpHandler(system2, dbClient, server, settings, jwtSerializer, jwtCsrfVerifier, threadLocalUserSession);
+    underTest.generateToken(userDto, response);
 
     verify(jwtSerializer).encode(jwtArgumentCaptor.capture());
     verifyToken(jwtArgumentCaptor.getValue(), sessionTimeoutInHours * 60 * 60, NOW);
@@ -158,113 +151,122 @@ public class JwtHttpHandlerTest {
     int firstSessionTimeoutInHours = 10;
     settings.setProperty("sonar.auth.sessionTimeoutInHours", firstSessionTimeoutInHours);
 
-    underTest = new JwtHttpHandler(system2, dbClient, server, settings, jwtSerializer, jwtCsrfVerifier);
-    underTest.generateToken(USER_LOGIN, response);
+    underTest = new JwtHttpHandler(system2, dbClient, server, settings, jwtSerializer, jwtCsrfVerifier, threadLocalUserSession);
+    underTest.generateToken(userDto, response);
 
     // The property is updated, but it won't be taking into account
     settings.setProperty("sonar.auth.sessionTimeoutInHours", 15);
-    underTest.generateToken(USER_LOGIN, response);
+    underTest.generateToken(userDto, response);
     verify(jwtSerializer, times(2)).encode(jwtArgumentCaptor.capture());
     verifyToken(jwtArgumentCaptor.getAllValues().get(0), firstSessionTimeoutInHours * 60 * 60, NOW);
     verifyToken(jwtArgumentCaptor.getAllValues().get(1), firstSessionTimeoutInHours * 60 * 60, NOW);
   }
 
   @Test
-  public void refresh_session_when_refresh_time_is_reached() throws Exception {
+  public void validate_token() throws Exception {
+    addJwtCookie();
+
+    Claims claims = createToken(USER_LOGIN, NOW);
+    when(jwtSerializer.decode(JWT_TOKEN)).thenReturn(Optional.of(claims));
+
+    underTest.validateToken(request, response);
+
+    verify(jwtSerializer, never()).encode(any(JwtSerializer.JwtSession.class));
+    assertThat(threadLocalUserSession.get().isLoggedIn()).isTrue();
+  }
+
+  @Test
+  public void validate_token_refresh_session_when_refresh_time_is_reached() throws Exception {
     addJwtCookie();
-    UserDto user = addUser();
 
     // Token was created 10 days ago and refreshed 6 minutes ago
-    Claims claims = createToken(TEN_DAYS_AGO);
+    Claims claims = createToken(USER_LOGIN, TEN_DAYS_AGO);
     claims.put("lastRefreshTime", SIX_MINUTES_AGO);
     when(jwtSerializer.decode(JWT_TOKEN)).thenReturn(Optional.of(claims));
 
     underTest.validateToken(request, response);
 
-    verify(httpSession).setAttribute("user_id", user.getId());
     verify(jwtSerializer).refresh(any(Claims.class), eq(3 * 24 * 60 * 60));
+    assertThat(threadLocalUserSession.get().isLoggedIn()).isTrue();
   }
 
   @Test
-  public void does_not_refresh_session_when_refresh_time_is_not_reached() throws Exception {
+  public void validate_token_does_not_refresh_session_when_refresh_time_is_not_reached() throws Exception {
     addJwtCookie();
-    UserDto user = addUser();
 
     // Token was created 10 days ago and refreshed 4 minutes ago
-    Claims claims = createToken(TEN_DAYS_AGO);
+    Claims claims = createToken(USER_LOGIN, TEN_DAYS_AGO);
     claims.put("lastRefreshTime", FOUR_MINUTES_AGO);
     when(jwtSerializer.decode(JWT_TOKEN)).thenReturn(Optional.of(claims));
 
     underTest.validateToken(request, response);
 
-    verify(httpSession).setAttribute("user_id", user.getId());
     verify(jwtSerializer, never()).refresh(any(Claims.class), anyInt());
+    assertThat(threadLocalUserSession.get().isLoggedIn()).isTrue();
   }
 
   @Test
-  public void remove_session_when_disconnected_timeout_is_reached() throws Exception {
+  public void validate_token_removes_session_when_disconnected_timeout_is_reached() throws Exception {
     addJwtCookie();
-    addUser();
 
     // Token was created 4 months ago, refreshed 4 minutes ago, and it expired in 5 minutes
-    Claims claims = createToken(NOW - (4L * 30 * 24 * 60 * 60 * 1000));
+    Claims claims = createToken(USER_LOGIN, NOW - (4L * 30 * 24 * 60 * 60 * 1000));
     claims.setExpiration(new Date(NOW + 5 * 60 * 1000));
     claims.put("lastRefreshTime", FOUR_MINUTES_AGO);
     when(jwtSerializer.decode(JWT_TOKEN)).thenReturn(Optional.of(claims));
 
     underTest.validateToken(request, response);
 
-    verify(httpSession).removeAttribute("user_id");
     verifyCookie(findCookie("JWT-SESSION").get(), null, 0);
+    assertThat(threadLocalUserSession.get().isLoggedIn()).isFalse();
   }
 
   @Test
-  public void remove_session_when_user_is_disabled() throws Exception {
+  public void validate_token_fails_with_unauthorized_when_user_is_disabled() throws Exception {
     addJwtCookie();
-    addUser(false);
+    UserDto user = addUser(false);
 
-    Claims claims = createToken(NOW);
+    Claims claims = createToken(user.getLogin(), NOW);
     when(jwtSerializer.decode(JWT_TOKEN)).thenReturn(Optional.of(claims));
 
+    thrown.expect(UnauthorizedException.class);
     underTest.validateToken(request, response);
-
-    verify(httpSession).removeAttribute("user_id");
-    verifyCookie(findCookie("JWT-SESSION").get(), null, 0);
   }
 
   @Test
-  public void remove_session_when_token_is_no_more_valid() throws Exception {
+  public void validate_token_removes_session_when_token_is_no_more_valid() throws Exception {
     addJwtCookie();
 
     when(jwtSerializer.decode(JWT_TOKEN)).thenReturn(Optional.empty());
 
     underTest.validateToken(request, response);
 
-    verify(httpSession).removeAttribute("user_id");
     verifyCookie(findCookie("JWT-SESSION").get(), null, 0);
+    assertThat(threadLocalUserSession.get().isLoggedIn()).isFalse();
   }
 
   @Test
-  public void does_nothing_when_no_jwt_cookie() throws Exception {
+  public void validate_token_does_nothing_when_no_jwt_cookie() throws Exception {
     underTest.validateToken(request, response);
 
     verifyZeroInteractions(httpSession, jwtSerializer);
+    assertThat(threadLocalUserSession.get().isLoggedIn()).isFalse();
   }
 
   @Test
-  public void does_nothing_when_empty_value_in_jwt_cookie() throws Exception {
+  public void validate_token_does_nothing_when_empty_value_in_jwt_cookie() throws Exception {
     when(request.getCookies()).thenReturn(new Cookie[] {new Cookie("JWT-SESSION", "")});
 
     underTest.validateToken(request, response);
 
     verifyZeroInteractions(httpSession, jwtSerializer);
+    assertThat(threadLocalUserSession.get().isLoggedIn()).isFalse();
   }
 
   @Test
-  public void verify_csrf_state() throws Exception {
+  public void validate_token_verify_csrf_state() throws Exception {
     addJwtCookie();
-    addUser();
-    Claims claims = createToken(NOW);
+    Claims claims = createToken(USER_LOGIN, NOW);
     claims.put("xsrfToken", CSRF_STATE);
     when(jwtSerializer.decode(JWT_TOKEN)).thenReturn(Optional.of(claims));
 
@@ -274,12 +276,11 @@ public class JwtHttpHandlerTest {
   }
 
   @Test
-  public void refresh_state_when_refreshing_token() throws Exception {
+  public void validate_token_refresh_state_when_refreshing_token() throws Exception {
     addJwtCookie();
-    addUser();
 
     // Token was created 10 days ago and refreshed 6 minutes ago
-    Claims claims = createToken(TEN_DAYS_AGO);
+    Claims claims = createToken(USER_LOGIN, TEN_DAYS_AGO);
     claims.put("xsrfToken", "CSRF_STATE");
     when(jwtSerializer.decode(JWT_TOKEN)).thenReturn(Optional.of(claims));
 
@@ -290,7 +291,7 @@ public class JwtHttpHandlerTest {
   }
 
   @Test
-  public void remove_state_when_removing_token() throws Exception {
+  public void validate_token_remove_state_when_removing_token() throws Exception {
     addJwtCookie();
     // Token is invalid => it will be removed
     when(jwtSerializer.decode(JWT_TOKEN)).thenReturn(Optional.empty());
@@ -301,6 +302,24 @@ public class JwtHttpHandlerTest {
     verify(jwtCsrfVerifier).removeState(response);
   }
 
+  @Test
+  public void remove_token() throws Exception {
+    underTest.removeToken(response);
+
+    verifyCookie(findCookie("JWT-SESSION").get(), null, 0);
+    verify(jwtCsrfVerifier).removeState(response);
+    assertThat(threadLocalUserSession.get().isLoggedIn()).isFalse();
+  }
+
+  @Test
+  public void remove_token_is_removing_user_session() throws Exception {
+    threadLocalUserSession.set(ServerUserSession.createForUser(dbClient, userDto));
+
+    underTest.removeToken(response);
+
+    assertThat(threadLocalUserSession.get().isLoggedIn()).isFalse();
+  }
+
   private void verifyToken(JwtSerializer.JwtSession token, int expectedExpirationTime, long expectedRefreshTime) {
     assertThat(token.getExpirationTimeInSeconds()).isEqualTo(expectedExpirationTime);
     assertThat(token.getUserLogin()).isEqualTo(USER_LOGIN);
@@ -322,13 +341,8 @@ public class JwtHttpHandlerTest {
     assertThat(cookie.getValue()).isEqualTo(value);
   }
 
-  private UserDto addUser() {
-    return addUser(true);
-  }
-
   private UserDto addUser(boolean active) {
-    UserDto user = UserTesting.newUserDto()
-      .setLogin(USER_LOGIN)
+    UserDto user = newUserDto()
       .setActive(active);
     dbClient.userDao().insert(dbSession, user);
     dbSession.commit();
@@ -341,15 +355,15 @@ public class JwtHttpHandlerTest {
     return cookie;
   }
 
-  private Claims createToken(long createdAt) {
+  private Claims createToken(String userLogin, long createdAt) {
     // Expired in 5 minutes by default
-    return createToken(createdAt, NOW + 5 * 60 * 1000);
+    return createToken(userLogin, createdAt, NOW + 5 * 60 * 1000);
   }
 
-  private Claims createToken(long createdAt, long expiredAt) {
+  private Claims createToken(String userLogin, long createdAt, long expiredAt) {
     DefaultClaims claims = new DefaultClaims();
     claims.setId("ID");
-    claims.setSubject(USER_LOGIN);
+    claims.setSubject(userLogin);
     claims.setIssuedAt(new Date(createdAt));
     claims.setExpiration(new Date(expiredAt));
     claims.put("lastRefreshTime", createdAt);
index e07443f4c29e22426a576f01a651f72da9078ce1..b0d6635464c3f7b2182fd8a42347cd79d4f66d9f 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;
@@ -32,11 +37,6 @@ import org.sonar.api.server.authentication.UnauthorizedException;
 import org.sonar.api.utils.log.LogTester;
 import org.sonar.api.utils.log.LoggerLevel;
 
-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;
-
 public class OAuth2CallbackFilterTest {
 
   static String OAUTH2_PROVIDER_KEY = "github";
@@ -139,7 +139,7 @@ public class OAuth2CallbackFilterTest {
     assertError("Fail to callback authentication");
   }
 
-  private void assertCallbackCalled(){
+  private void assertCallbackCalled() {
     assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty();
     assertThat(oAuth2IdentityProvider.isCallbackCalled()).isTrue();
   }
index 677bfaf7dfbc3345fec685e3265dff9fbc59cbcb..9afb606bf41f579f7d4c188fae2ad4cfe47147fa 100644 (file)
@@ -20,6 +20,8 @@
 package org.sonar.server.authentication;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -35,6 +37,7 @@ import org.sonar.api.platform.Server;
 import org.sonar.api.server.authentication.OAuth2IdentityProvider;
 import org.sonar.api.server.authentication.UserIdentity;
 import org.sonar.api.utils.MessageException;
+import org.sonar.db.user.UserDto;
 
 public class OAuth2ContextFactoryTest {
 
@@ -56,13 +59,14 @@ public class OAuth2ContextFactoryTest {
   UserIdentityAuthenticator userIdentityAuthenticator = mock(UserIdentityAuthenticator.class);
   Server server = mock(Server.class);
   OAuthCsrfVerifier csrfVerifier = mock(OAuthCsrfVerifier.class);
+  JwtHttpHandler jwtHttpHandler = mock(JwtHttpHandler.class);
 
   HttpServletRequest request = mock(HttpServletRequest.class);
   HttpServletResponse response = mock(HttpServletResponse.class);
   HttpSession session = mock(HttpSession.class);
   OAuth2IdentityProvider identityProvider = mock(OAuth2IdentityProvider.class);
 
-  OAuth2ContextFactory underTest = new OAuth2ContextFactory(userIdentityAuthenticator, server, csrfVerifier);
+  OAuth2ContextFactory underTest = new OAuth2ContextFactory(userIdentityAuthenticator, server, csrfVerifier, jwtHttpHandler);
 
   @Before
   public void setUp() throws Exception {
@@ -74,7 +78,7 @@ public class OAuth2ContextFactoryTest {
   public void create_context() throws Exception {
     when(server.getPublicRootUrl()).thenReturn(SECURED_PUBLIC_ROOT_URL);
 
-    OAuth2IdentityProvider.InitContext context = underTest.newContext(request, response, identityProvider);
+    OAuth2IdentityProvider.InitContext context = newInitContext();
 
     assertThat(context.getRequest()).isEqualTo(request);
     assertThat(context.getResponse()).isEqualTo(response);
@@ -83,7 +87,7 @@ public class OAuth2ContextFactoryTest {
 
   @Test
   public void generate_csrf_state() throws Exception {
-    OAuth2IdentityProvider.InitContext context = underTest.newContext(request, response, identityProvider);
+    OAuth2IdentityProvider.InitContext context = newInitContext();
 
     context.generateCsrfState();
 
@@ -92,7 +96,7 @@ public class OAuth2ContextFactoryTest {
 
   @Test
   public void redirect_to() throws Exception {
-    OAuth2IdentityProvider.InitContext context = underTest.newContext(request, response, identityProvider);
+    OAuth2IdentityProvider.InitContext context = newInitContext();
 
     context.redirectTo("/test");
 
@@ -103,7 +107,7 @@ public class OAuth2ContextFactoryTest {
   public void fail_to_get_callback_url_on_not_secured_server() throws Exception {
     when(server.getPublicRootUrl()).thenReturn(NOT_SECURED_PUBLIC_URL);
 
-    OAuth2IdentityProvider.InitContext context = underTest.newContext(request, response, identityProvider);
+    OAuth2IdentityProvider.InitContext context = newInitContext();
 
     thrown.expect(MessageException.class);
     thrown.expectMessage("The server url should be configured in https, please update the property 'sonar.core.serverBaseURL'");
@@ -114,7 +118,7 @@ public class OAuth2ContextFactoryTest {
   public void create_callback() throws Exception {
     when(server.getPublicRootUrl()).thenReturn(SECURED_PUBLIC_ROOT_URL);
 
-    OAuth2IdentityProvider.CallbackContext callback = underTest.newCallback(request, response, identityProvider);
+    OAuth2IdentityProvider.CallbackContext callback = newCallbackContext();
 
     assertThat(callback.getRequest()).isEqualTo(request);
     assertThat(callback.getResponse()).isEqualTo(response);
@@ -123,17 +127,18 @@ public class OAuth2ContextFactoryTest {
 
   @Test
   public void authenticate() throws Exception {
-    OAuth2IdentityProvider.CallbackContext callback = underTest.newCallback(request, response, identityProvider);
+    OAuth2IdentityProvider.CallbackContext callback = newCallbackContext();
 
     callback.authenticate(USER_IDENTITY);
 
-    verify(userIdentityAuthenticator).authenticate(USER_IDENTITY, identityProvider, request, response);
+    verify(userIdentityAuthenticator).authenticate(USER_IDENTITY, identityProvider);
+    verify(jwtHttpHandler).generateToken(any(UserDto.class), eq(response));
   }
 
   @Test
   public void redirect_to_requested_page() throws Exception {
     when(server.getContextPath()).thenReturn("");
-    OAuth2IdentityProvider.CallbackContext callback = underTest.newCallback(request, response, identityProvider);
+    OAuth2IdentityProvider.CallbackContext callback = newCallbackContext();
 
     callback.redirectToRequestedPage();
 
@@ -143,7 +148,7 @@ public class OAuth2ContextFactoryTest {
   @Test
   public void redirect_to_requested_page_with_context() throws Exception {
     when(server.getContextPath()).thenReturn("/sonarqube");
-    OAuth2IdentityProvider.CallbackContext callback = underTest.newCallback(request, response, identityProvider);
+    OAuth2IdentityProvider.CallbackContext callback = newCallbackContext();
 
     callback.redirectToRequestedPage();
 
@@ -152,10 +157,19 @@ public class OAuth2ContextFactoryTest {
 
   @Test
   public void verify_csrf_state() throws Exception {
-    OAuth2IdentityProvider.CallbackContext callback = underTest.newCallback(request, response, identityProvider);
+    OAuth2IdentityProvider.CallbackContext callback = newCallbackContext();
 
     callback.verifyCsrfState();
 
     verify(csrfVerifier).verifyState(request, response);
   }
+
+  private OAuth2IdentityProvider.InitContext newInitContext() {
+    return underTest.newContext(request, response, identityProvider);
+  }
+
+  private OAuth2IdentityProvider.CallbackContext newCallbackContext() {
+    return underTest.newCallback(request, response, identityProvider);
+  }
+
 }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/RealmAuthenticatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/RealmAuthenticatorTest.java
new file mode 100644 (file)
index 0000000..4543360
--- /dev/null
@@ -0,0 +1,298 @@
+/*
+ * 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;
+
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.rules.ExpectedException.none;
+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.when;
+import static org.sonar.db.user.UserTesting.newUserDto;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mockito.ArgumentCaptor;
+import org.sonar.api.config.Settings;
+import org.sonar.api.security.Authenticator;
+import org.sonar.api.security.ExternalGroupsProvider;
+import org.sonar.api.security.ExternalUsersProvider;
+import org.sonar.api.security.SecurityRealm;
+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.exceptions.UnauthorizedException;
+import org.sonar.server.user.SecurityRealmFactory;
+
+public class RealmAuthenticatorTest {
+
+  @Rule
+  public ExpectedException expectedException = none();
+
+  static final String LOGIN = "LOGIN";
+  static final String PASSWORD = "PASSWORD";
+
+  static final UserDto USER = newUserDto();
+
+  ArgumentCaptor<UserIdentity> userIdentityArgumentCaptor = ArgumentCaptor.forClass(UserIdentity.class);
+  ArgumentCaptor<IdentityProvider> identityProviderArgumentCaptor = ArgumentCaptor.forClass(IdentityProvider.class);
+
+  Settings settings = new Settings();
+
+  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);
+
+  UserIdentityAuthenticator userIdentityAuthenticator = mock(UserIdentityAuthenticator.class);
+
+  HttpServletRequest request = mock(HttpServletRequest.class);
+  HttpServletResponse response = mock(HttpServletResponse.class);
+
+  RealmAuthenticator underTest = new RealmAuthenticator(settings, securityRealmFactory, userIdentityAuthenticator);
+
+  @Test
+  public void authenticate() throws Exception {
+    executeStartWithoutGroupSync();
+    when(authenticator.doAuthenticate(any(Authenticator.Context.class))).thenReturn(true);
+    UserDetails userDetails = new UserDetails();
+    userDetails.setName("name");
+    userDetails.setEmail("email");
+    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);
+
+    verify(userIdentityAuthenticator).authenticate(userIdentityArgumentCaptor.capture(), identityProviderArgumentCaptor.capture());
+    UserIdentity userIdentity = userIdentityArgumentCaptor.getValue();
+    assertThat(userIdentity.getLogin()).isEqualTo(LOGIN);
+    assertThat(userIdentity.getProviderLogin()).isEqualTo(LOGIN);
+    assertThat(userIdentity.getName()).isEqualTo("name");
+    assertThat(userIdentity.getEmail()).isEqualTo("email");
+    assertThat(userIdentity.shouldSyncGroups()).isFalse();
+  }
+
+  @Test
+  public void authenticate_with_sonarqube_identity_provider() throws Exception {
+    executeStartWithoutGroupSync();
+    when(authenticator.doAuthenticate(any(Authenticator.Context.class))).thenReturn(true);
+    UserDetails userDetails = new UserDetails();
+    userDetails.setName("name");
+    userDetails.setEmail("email");
+    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);
+
+    verify(userIdentityAuthenticator).authenticate(userIdentityArgumentCaptor.capture(), identityProviderArgumentCaptor.capture());
+
+    assertThat(identityProviderArgumentCaptor.getValue().getKey()).isEqualTo("sonarqube");
+    assertThat(identityProviderArgumentCaptor.getValue().getName()).isEqualTo("sonarqube");
+    assertThat(identityProviderArgumentCaptor.getValue().getDisplay()).isNull();
+    assertThat(identityProviderArgumentCaptor.getValue().isEnabled()).isTrue();
+  }
+
+  @Test
+  public void login_is_used_when_no_name_provided() throws Exception {
+    executeStartWithoutGroupSync();
+    when(authenticator.doAuthenticate(any(Authenticator.Context.class))).thenReturn(true);
+    UserDetails userDetails = new UserDetails();
+    userDetails.setEmail("email");
+    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);
+
+    verify(userIdentityAuthenticator).authenticate(userIdentityArgumentCaptor.capture(), identityProviderArgumentCaptor.capture());
+    assertThat(identityProviderArgumentCaptor.getValue().getName()).isEqualTo("sonarqube");
+  }
+
+  @Test
+  public void authenticate_with_group_sync() throws Exception {
+    when(externalGroupsProvider.doGetGroups(any(ExternalGroupsProvider.Context.class))).thenReturn(asList("group1", "group2"));
+    when(userIdentityAuthenticator.authenticate(any(UserIdentity.class), any(IdentityProvider.class))).thenReturn(USER);
+    executeStartWithGroupSync();
+    executeAuthenticate();
+
+    verify(userIdentityAuthenticator).authenticate(userIdentityArgumentCaptor.capture(), identityProviderArgumentCaptor.capture());
+
+    UserIdentity userIdentity = userIdentityArgumentCaptor.getValue();
+    assertThat(userIdentity.shouldSyncGroups()).isTrue();
+    assertThat(userIdentity.getGroups()).containsOnly("group1", "group2");
+  }
+
+  @Test
+  public void use_login_if_user_details_contains_no_name() throws Exception {
+    executeStartWithoutGroupSync();
+    when(authenticator.doAuthenticate(any(Authenticator.Context.class))).thenReturn(true);
+    UserDetails userDetails = new UserDetails();
+    userDetails.setName(null);
+    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);
+
+    verify(userIdentityAuthenticator).authenticate(userIdentityArgumentCaptor.capture(), identityProviderArgumentCaptor.capture());
+    assertThat(userIdentityArgumentCaptor.getValue().getName()).isEqualTo(LOGIN);
+  }
+
+  @Test
+  public void allow_to_sign_up_property() throws Exception {
+    settings.setProperty("sonar.authenticator.createUsers", true);
+    when(userIdentityAuthenticator.authenticate(any(UserIdentity.class), any(IdentityProvider.class))).thenReturn(USER);
+    executeStartWithoutGroupSync();
+    executeAuthenticate();
+
+    verify(userIdentityAuthenticator).authenticate(userIdentityArgumentCaptor.capture(), identityProviderArgumentCaptor.capture());
+    assertThat(identityProviderArgumentCaptor.getValue().allowsUsersToSignUp()).isTrue();
+  }
+
+  @Test
+  public void does_not_allow_to_sign_up_property() throws Exception {
+    settings.setProperty("sonar.authenticator.createUsers", false);
+    when(userIdentityAuthenticator.authenticate(any(UserIdentity.class), any(IdentityProvider.class))).thenReturn(USER);
+    executeStartWithoutGroupSync();
+    executeAuthenticate();
+
+    verify(userIdentityAuthenticator).authenticate(userIdentityArgumentCaptor.capture(), identityProviderArgumentCaptor.capture());
+    assertThat(identityProviderArgumentCaptor.getValue().allowsUsersToSignUp()).isFalse();
+  }
+
+  @Test
+  public void use_downcase_login() throws Exception {
+    settings.setProperty("sonar.authenticator.downcase", true);
+    when(userIdentityAuthenticator.authenticate(any(UserIdentity.class), any(IdentityProvider.class))).thenReturn(USER);
+    executeStartWithoutGroupSync();
+    executeAuthenticate("LOGIN");
+
+    verify(userIdentityAuthenticator).authenticate(userIdentityArgumentCaptor.capture(), identityProviderArgumentCaptor.capture());
+    UserIdentity userIdentity = userIdentityArgumentCaptor.getValue();
+    assertThat(userIdentity.getLogin()).isEqualTo("login");
+    assertThat(userIdentity.getProviderLogin()).isEqualTo("login");
+  }
+
+  @Test
+  public void does_not_user_downcase_login() throws Exception {
+    settings.setProperty("sonar.authenticator.downcase", false);
+    when(userIdentityAuthenticator.authenticate(any(UserIdentity.class), any(IdentityProvider.class))).thenReturn(USER);
+    executeStartWithoutGroupSync();
+    executeAuthenticate("LoGiN");
+
+    verify(userIdentityAuthenticator).authenticate(userIdentityArgumentCaptor.capture(), identityProviderArgumentCaptor.capture());
+    UserIdentity userIdentity = userIdentityArgumentCaptor.getValue();
+    assertThat(userIdentity.getLogin()).isEqualTo("LoGiN");
+    assertThat(userIdentity.getProviderLogin()).isEqualTo("LoGiN");
+  }
+
+  @Test
+  public void fail_to_authenticate_when_user_details_are_null() throws Exception {
+    executeStartWithoutGroupSync();
+    when(authenticator.doAuthenticate(any(Authenticator.Context.class))).thenReturn(true);
+
+    when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(null);
+
+    expectedException.expect(UnauthorizedException.class);
+    underTest.authenticate(LOGIN, PASSWORD, request);
+  }
+
+  @Test
+  public void fail_to_authenticate_when_external_authentication_fails() throws Exception {
+    executeStartWithoutGroupSync();
+    when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(new UserDetails());
+
+    when(authenticator.doAuthenticate(any(Authenticator.Context.class))).thenReturn(false);
+
+    expectedException.expect(UnauthorizedException.class);
+    underTest.authenticate(LOGIN, PASSWORD, request);
+  }
+
+  @Test
+  public void fail_to_authenticate_when_any_exception_is_thrown() throws Exception {
+    executeStartWithoutGroupSync();
+    doThrow(IllegalArgumentException.class).when(authenticator).doAuthenticate(any(Authenticator.Context.class));
+
+    when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(null);
+
+    expectedException.expect(UnauthorizedException.class);
+    underTest.authenticate(LOGIN, PASSWORD, request);
+  }
+
+  @Test
+  public void return_empty_user_when_no_realm() throws Exception {
+    assertThat(underTest.authenticate(LOGIN, PASSWORD, request)).isEmpty();
+  }
+
+  @Test
+  public void fail_to_start_when_no_authenticator() throws Exception {
+    when(realm.doGetAuthenticator()).thenReturn(null);
+    when(securityRealmFactory.getRealm()).thenReturn(realm);
+
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("No authenticator available");
+    underTest.start();
+  }
+
+  @Test
+  public void fail_to_start_when_no_user_provider() throws Exception {
+    when(realm.doGetAuthenticator()).thenReturn(authenticator);
+    when(realm.getUsersProvider()).thenReturn(null);
+    when(securityRealmFactory.getRealm()).thenReturn(realm);
+
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("No users provider available");
+    underTest.start();
+  }
+
+  private void executeStartWithoutGroupSync() {
+    when(realm.doGetAuthenticator()).thenReturn(authenticator);
+    when(realm.getUsersProvider()).thenReturn(externalUsersProvider);
+    when(securityRealmFactory.getRealm()).thenReturn(realm);
+    underTest.start();
+  }
+
+  private void executeStartWithGroupSync() {
+    when(realm.doGetAuthenticator()).thenReturn(authenticator);
+    when(realm.getUsersProvider()).thenReturn(externalUsersProvider);
+    when(realm.getGroupsProvider()).thenReturn(externalGroupsProvider);
+    when(securityRealmFactory.getRealm()).thenReturn(realm);
+    underTest.start();
+  }
+
+  private void executeAuthenticate() {
+    executeAuthenticate(LOGIN);
+  }
+
+  private void executeAuthenticate(String login) {
+    when(authenticator.doAuthenticate(any(Authenticator.Context.class))).thenReturn(true);
+    UserDetails userDetails = new UserDetails();
+    userDetails.setName("name");
+    when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(userDetails);
+    underTest.authenticate(login, PASSWORD, request);
+  }
+
+}
index 645c468fc4f9d01be3e9ceff2a68ba466ad44123..51d31dc2baf4b6951d0acbba10b5617105fedb9e 100644 (file)
@@ -23,15 +23,12 @@ import static com.google.common.collect.Sets.newHashSet;
 import static java.util.Collections.singletonList;
 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 java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -84,32 +81,28 @@ public class UserIdentityAuthenticatorTest {
   UserDao userDao = dbClient.userDao();
   GroupDao groupDao = dbClient.groupDao();
   Settings settings = new Settings();
-  JwtHttpHandler jwtHttpHandler = mock(JwtHttpHandler.class);
 
   HttpServletRequest request = mock(HttpServletRequest.class);
   HttpServletResponse response = mock(HttpServletResponse.class);
-  HttpSession httpSession = mock(HttpSession.class);
 
   UserUpdater userUpdater = new UserUpdater(
     mock(NewUserNotifier.class),
     settings,
     dbClient,
     mock(UserIndexer.class),
-    system2
-    );
+    system2);
 
-  UserIdentityAuthenticator underTest = new UserIdentityAuthenticator(dbClient, userUpdater, jwtHttpHandler);
+  UserIdentityAuthenticator underTest = new UserIdentityAuthenticator(dbClient, userUpdater);
 
   @Before
   public void setUp() throws Exception {
     settings.setProperty("sonar.defaultGroup", DEFAULT_GROUP);
     addGroup(DEFAULT_GROUP);
-    when(request.getSession()).thenReturn(httpSession);
   }
 
   @Test
   public void authenticate_new_user() throws Exception {
-    underTest.authenticate(USER_IDENTITY, IDENTITY_PROVIDER, request, response);
+    underTest.authenticate(USER_IDENTITY, IDENTITY_PROVIDER);
     dbSession.commit();
 
     UserDto userDto = userDao.selectByLogin(dbSession, USER_LOGIN);
@@ -134,7 +127,7 @@ public class UserIdentityAuthenticatorTest {
       .setName("John")
       // group3 doesn't exist in db, it will be ignored
       .setGroups(newHashSet("group1", "group2", "group3"))
-      .build(), IDENTITY_PROVIDER, request, response);
+      .build(), IDENTITY_PROVIDER);
     dbSession.commit();
 
     UserDto userDto = userDao.selectByLogin(dbSession, USER_LOGIN);
@@ -151,11 +144,10 @@ public class UserIdentityAuthenticatorTest {
       .setName("Old name")
       .setEmail("Old email")
       .setExternalIdentity("old identity")
-      .setExternalIdentityProvider("old provide")
-      );
+      .setExternalIdentityProvider("old provide"));
     dbSession.commit();
 
-    underTest.authenticate(USER_IDENTITY, IDENTITY_PROVIDER, request, response);
+    underTest.authenticate(USER_IDENTITY, IDENTITY_PROVIDER);
     dbSession.commit();
 
     UserDto userDto = userDao.selectByLogin(dbSession, USER_LOGIN);
@@ -175,11 +167,10 @@ public class UserIdentityAuthenticatorTest {
       .setName("Old name")
       .setEmail("Old email")
       .setExternalIdentity("old identity")
-      .setExternalIdentityProvider("old provide")
-      );
+      .setExternalIdentityProvider("old provide"));
     dbSession.commit();
 
-    underTest.authenticate(USER_IDENTITY, IDENTITY_PROVIDER, request, response);
+    underTest.authenticate(USER_IDENTITY, IDENTITY_PROVIDER);
     dbSession.commit();
 
     UserDto userDto = userDao.selectByLogin(dbSession, USER_LOGIN);
@@ -196,8 +187,7 @@ public class UserIdentityAuthenticatorTest {
     userDao.insert(dbSession, new UserDto()
       .setLogin(USER_LOGIN)
       .setActive(true)
-      .setName("John")
-      );
+      .setName("John"));
     addGroup("group1");
     addGroup("group2");
     dbSession.commit();
@@ -208,7 +198,7 @@ public class UserIdentityAuthenticatorTest {
       .setName("John")
       // group3 doesn't exist in db, it will be ignored
       .setGroups(newHashSet("group1", "group2", "group3"))
-      .build(), IDENTITY_PROVIDER, request, response);
+      .build(), IDENTITY_PROVIDER);
     dbSession.commit();
 
     Set<String> userGroups = new HashSet<>(dbClient.groupMembershipDao().selectGroupsByLogins(dbSession, singletonList(USER_LOGIN)).get(USER_LOGIN));
@@ -238,7 +228,7 @@ public class UserIdentityAuthenticatorTest {
       .setName("John")
       // Only group1 is returned by the id provider => group2 will be removed
       .setGroups(newHashSet("group1"))
-      .build(), IDENTITY_PROVIDER, request, response);
+      .build(), IDENTITY_PROVIDER);
     dbSession.commit();
 
     verifyUserGroups(USER_LOGIN, "group1");
@@ -267,35 +257,12 @@ public class UserIdentityAuthenticatorTest {
       .setName("John")
       // No group => group1 and group2 will be removed
       .setGroups(Collections.<String>emptySet())
-      .build(), IDENTITY_PROVIDER, request, response);
+      .build(), IDENTITY_PROVIDER);
     dbSession.commit();
 
     verifyNoUserGroups(USER_LOGIN);
   }
 
-  @Test
-  public void update_session_for_rails() throws Exception {
-    UserDto userDto = UserTesting.newUserDto().setLogin(USER_LOGIN);
-    userDao.insert(dbSession, userDto);
-    dbSession.commit();
-
-    underTest.authenticate(USER_IDENTITY, IDENTITY_PROVIDER, request, response);
-
-    verify(httpSession).setAttribute("user_id", userDto.getId());
-  }
-
-  @Test
-  public void create_jwt_token() throws Exception {
-    UserDto userDto = UserTesting.newUserDto().setLogin(USER_LOGIN);
-    userDao.insert(dbSession, userDto);
-    dbSession.commit();
-
-    underTest.authenticate(USER_IDENTITY, IDENTITY_PROVIDER, request, response);
-
-    verify(httpSession).setAttribute("user_id", userDto.getId());
-    verify(jwtHttpHandler).generateToken(USER_LOGIN, response);
-  }
-
   @Test
   public void fail_to_authenticate_new_user_when_allow_users_to_signup_is_false() throws Exception {
     TestIdentityProvider identityProvider = new TestIdentityProvider()
@@ -306,7 +273,7 @@ public class UserIdentityAuthenticatorTest {
 
     thrown.expect(UnauthorizedException.class);
     thrown.expectMessage("'github' users are not allowed to sign up");
-    underTest.authenticate(USER_IDENTITY, identityProvider, request, response);
+    underTest.authenticate(USER_IDENTITY, identityProvider);
   }
 
   @Test
@@ -321,7 +288,7 @@ public class UserIdentityAuthenticatorTest {
     thrown.expect(UnauthorizedException.class);
     thrown.expectMessage("You can't sign up because email 'john@email.com' is already used by an existing user. " +
       "This means that you probably already registered with another account.");
-    underTest.authenticate(USER_IDENTITY, IDENTITY_PROVIDER, request, response);
+    underTest.authenticate(USER_IDENTITY, IDENTITY_PROVIDER);
   }
 
   private void verifyUserGroups(String userLogin, String... groups) {
index 03c1ea71e04c6341fec745c73f7079c4e5861002..210d940833d2dcd7c99c3153df98c88935410723 100644 (file)
@@ -31,23 +31,31 @@ import javax.servlet.FilterChain;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.sonar.api.config.Settings;
 import org.sonar.server.exceptions.UnauthorizedException;
+import org.sonar.server.tester.UserSessionRule;
 
 public class ValidateJwtTokenFilterTest {
 
+  @Rule
+  public UserSessionRule userSession = UserSessionRule.standalone();
+
   HttpServletRequest request = mock(HttpServletRequest.class);
   HttpServletResponse response = mock(HttpServletResponse.class);
   FilterChain chain = mock(FilterChain.class);
 
   JwtHttpHandler jwtHttpHandler = mock(JwtHttpHandler.class);
 
-  ValidateJwtTokenFilter underTest = new ValidateJwtTokenFilter(jwtHttpHandler);
+  Settings settings = new Settings();
+
+  ValidateJwtTokenFilter underTest = new ValidateJwtTokenFilter(settings, jwtHttpHandler, userSession);
 
   @Before
   public void setUp() throws Exception {
     when(request.getContextPath()).thenReturn("");
-    when(request.getRequestURI()).thenReturn("/test");
+    when(request.getRequestURI()).thenReturn("/measures");
   }
 
   @Test
@@ -55,6 +63,20 @@ public class ValidateJwtTokenFilterTest {
     assertThat(underTest.doGetPattern().matches("/")).isTrue();
     assertThat(underTest.doGetPattern().matches("/foo")).isTrue();
 
+    assertThat(underTest.doGetPattern().matches("/api/authentication/login")).isFalse();
+    assertThat(underTest.doGetPattern().matches("/batch/index")).isFalse();
+    assertThat(underTest.doGetPattern().matches("/batch/file")).isFalse();
+    assertThat(underTest.doGetPattern().matches("/batch_bootstrap/index")).isFalse();
+    assertThat(underTest.doGetPattern().matches("/maintenance/index")).isFalse();
+    assertThat(underTest.doGetPattern().matches("/setup/index")).isFalse();
+    assertThat(underTest.doGetPattern().matches("/sessions/new")).isFalse();
+    assertThat(underTest.doGetPattern().matches("/sessions/logout")).isFalse();
+    assertThat(underTest.doGetPattern().matches("/api/system/db_migration_status")).isFalse();
+    assertThat(underTest.doGetPattern().matches("/api/system/status")).isFalse();
+    assertThat(underTest.doGetPattern().matches("/api/system/status")).isFalse();
+    assertThat(underTest.doGetPattern().matches("/api/system/migrate_db")).isFalse();
+    assertThat(underTest.doGetPattern().matches("/api/server/index")).isFalse();
+
     // exclude static resources
     assertThat(underTest.doGetPattern().matches("/css/style.css")).isFalse();
     assertThat(underTest.doGetPattern().matches("/fonts/font.ttf")).isFalse();
@@ -64,6 +86,7 @@ public class ValidateJwtTokenFilterTest {
 
   @Test
   public void validate_session() throws Exception {
+    userSession.login("john");
     underTest.doFilter(request, response, chain);
 
     verify(jwtHttpHandler).validateToken(request, response);
@@ -72,6 +95,29 @@ public class ValidateJwtTokenFilterTest {
 
   @Test
   public void return_code_401_when_invalid_token_exception() throws Exception {
+    userSession.login("john");
+    doThrow(new UnauthorizedException("invalid token")).when(jwtHttpHandler).validateToken(request, response);
+
+    underTest.doFilter(request, response, chain);
+
+    verify(response).setStatus(401);
+    verify(chain).doFilter(request, response);
+  }
+
+  @Test
+  public void return_code_401_when_not_authenticated_and_with_force_authentication() throws Exception {
+    settings.setProperty("sonar.forceAuthentication", true);
+    userSession.anonymous();
+
+    underTest.doFilter(request, response, chain);
+
+    verify(response).setStatus(401);
+    verify(chain).doFilter(request, response);
+  }
+
+  @Test
+  public void return_401_and_stop_on_ws() throws Exception {
+    when(request.getRequestURI()).thenReturn("/api/issues");
     doThrow(new UnauthorizedException("invalid token")).when(jwtHttpHandler).validateToken(request, response);
 
     underTest.doFilter(request, response, chain);
@@ -80,4 +126,24 @@ public class ValidateJwtTokenFilterTest {
     verifyZeroInteractions(chain);
   }
 
+  @Test
+  public void return_401_and_stop_on_batch_ws() throws Exception {
+    when(request.getRequestURI()).thenReturn("/batch/index");
+    doThrow(new UnauthorizedException("invalid token")).when(jwtHttpHandler).validateToken(request, response);
+
+    underTest.doFilter(request, response, chain);
+
+    verify(response).setStatus(401);
+    verifyZeroInteractions(chain);
+  }
+
+  @Test
+  public void ignore_old_batch_ws() throws Exception {
+    when(request.getRequestURI()).thenReturn("/batch/name.jar");
+
+    underTest.doFilter(request, response, chain);
+
+    verify(chain).doFilter(request, response);
+    verifyZeroInteractions(jwtHttpHandler, response);
+  }
 }
index 4bb0ed6209f1659acaf2c7bffa1522282c61a69d..728a1e61975667f55bd7373d7feccebf3927452b 100644 (file)
  */
 package org.sonar.server.authentication.ws;
 
+import static org.assertj.core.api.Assertions.assertThat;
+
 import org.junit.Test;
 import org.sonar.api.server.ws.RailsHandler;
 import org.sonar.api.server.ws.WebService;
+import org.sonar.server.ws.ServletFilterHandler;
 import org.sonar.server.ws.WsTester;
 
-import static org.assertj.core.api.Assertions.assertThat;
-
 public class AuthenticationWsTest {
 
   WsTester tester = new WsTester(new AuthenticationWs());
@@ -35,12 +36,18 @@ public class AuthenticationWsTest {
     WebService.Controller controller = tester.controller("api/authentication");
     assertThat(controller).isNotNull();
     assertThat(controller.description()).isNotEmpty();
-    assertThat(controller.actions()).hasSize(1);
+    assertThat(controller.actions()).hasSize(2);
 
     WebService.Action validate = controller.action("validate");
     assertThat(validate).isNotNull();
     assertThat(validate.handler()).isInstanceOf(RailsHandler.class);
     assertThat(validate.responseExampleAsString()).isNotEmpty();
     assertThat(validate.params()).hasSize(1);
+
+    WebService.Action login = controller.action("login");
+    assertThat(login).isNotNull();
+    assertThat(login.handler()).isInstanceOf(ServletFilterHandler.class);
+    assertThat(login.isPost()).isTrue();
+    assertThat(login.params()).hasSize(2);
   }
 }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/RubyUserSessionTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/RubyUserSessionTest.java
deleted file mode 100644 (file)
index b5b5886..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * 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.user;
-
-import java.util.Locale;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.core.platform.ComponentContainer;
-import org.sonar.server.platform.Platform;
-
-import static com.google.common.collect.Lists.newArrayList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class RubyUserSessionTest {
-  Platform platform = mock(Platform.class);
-  ComponentContainer componentContainer = mock(ComponentContainer.class);
-  ThreadLocalUserSession threadLocalUserSession = new ThreadLocalUserSession();
-  RubyUserSession underTest = new RubyUserSession(platform);
-
-  @Before
-  public void setUp() {
-    // for test isolation
-    threadLocalUserSession.remove();
-
-    when(platform.getContainer()).thenReturn(componentContainer);
-    when(componentContainer.getComponentByType(ThreadLocalUserSession.class)).thenReturn(threadLocalUserSession);
-  }
-
-  @After
-  public void tearDown() {
-    // clean up for next test
-    threadLocalUserSession.remove();
-  }
-
-  @Test
-  public void should_set_session() {
-    underTest.setSessionImpl(123, "karadoc", "Karadoc", newArrayList("sonar-users"), "fr");
-
-    UserSession session = threadLocalUserSession.get();
-
-    assertThat(session).isNotNull();
-    assertThat(session.getLogin()).isEqualTo("karadoc");
-    assertThat(session.getName()).isEqualTo("Karadoc");
-    assertThat(session.getUserId()).isEqualTo(123);
-    assertThat(session.getUserGroups()).containsOnly("sonar-users", "Anyone");
-    assertThat(session.isLoggedIn()).isTrue();
-    assertThat(session.locale()).isEqualTo(Locale.FRENCH);
-  }
-
-  @Test
-  public void should_set_anonymous_session() {
-    underTest.setSessionImpl(null, null, null, null, "fr");
-
-    UserSession session = threadLocalUserSession.get();
-
-    assertThat(session).isNotNull();
-    assertThat(session.getLogin()).isNull();
-    assertThat(session.getName()).isNull();
-    assertThat(session.getUserId()).isNull();
-    assertThat(session.getUserGroups()).containsOnly("Anyone");
-    assertThat(session.isLoggedIn()).isFalse();
-    assertThat(session.locale()).isEqualTo(Locale.FRENCH);
-  }
-
-}
index 504136e065cd746fcfee5b99eb477f6cc162410a..111d9c5460ce753ee27e71e9aeb0e5493e5db63a 100644 (file)
  */
 package org.sonar.server.user;
 
-import java.util.Arrays;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.core.permission.GlobalPermissions.DASHBOARD_SHARING;
+import static org.sonar.core.permission.GlobalPermissions.QUALITY_PROFILE_ADMIN;
+import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN;
+import static org.sonar.db.user.UserTesting.newUserDto;
+import static org.sonar.server.user.ServerUserSession.createForAnonymous;
+import static org.sonar.server.user.ServerUserSession.createForUser;
+
+import javax.annotation.Nullable;
+import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.System2;
 import org.sonar.api.web.UserRole;
 import org.sonar.core.permission.GlobalPermissions;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDbTester;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.component.ComponentTesting;
-import org.sonar.db.component.ResourceDao;
-import org.sonar.db.component.ResourceDto;
-import org.sonar.db.user.AuthorizationDao;
+import org.sonar.db.user.GroupRoleDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.db.user.UserRoleDto;
 import org.sonar.server.exceptions.ForbiddenException;
 
-import static com.google.common.collect.Lists.newArrayList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
 public class ServerUserSessionTest {
   static final String LOGIN = "marius";
-  static final String PROJECT_KEY = "com.foo:Bar";
+
   static final String PROJECT_UUID = "ABCD";
   static final String FILE_KEY = "com.foo:Bar:BarFile.xoo";
   static final String FILE_UUID = "BCDE";
 
-  AuthorizationDao authorizationDao = mock(AuthorizationDao.class);
-  ResourceDao resourceDao = mock(ResourceDao.class);
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
 
-  @Test
-  public void login_should_not_be_empty() {
-    UserSession session = newServerUserSession().setLogin("");
-    assertThat(session.getLogin()).isNull();
-    assertThat(session.isLoggedIn()).isFalse();
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  ComponentDbTester componentDbTester = new ComponentDbTester(dbTester);
+
+  DbClient dbClient = dbTester.getDbClient();
+
+  DbSession dbSession = dbTester.getSession();
+
+  UserDto userDto = newUserDto().setLogin(LOGIN);
+  ComponentDto project, file;
+
+  @Before
+  public void setUp() throws Exception {
+    project = componentDbTester.insertComponent(ComponentTesting.newProjectDto(PROJECT_UUID));
+    file = componentDbTester.insertComponent(ComponentTesting.newFileDto(project, FILE_UUID).setKey(FILE_KEY));
+    dbClient.userDao().insert(dbSession, userDto);
+    dbSession.commit();
   }
 
   @Test
   public void has_global_permission() {
-    UserSession session = newServerUserSession().setLogin(LOGIN);
+    addGlobalPermissions("admin", "profileadmin");
+    UserSession session = newUserSession(userDto);
 
-    when(authorizationDao.selectGlobalPermissions(LOGIN)).thenReturn(Arrays.asList("profileadmin", "admin"));
-
-    assertThat(session.hasPermission(GlobalPermissions.QUALITY_PROFILE_ADMIN)).isTrue();
-    assertThat(session.hasPermission(GlobalPermissions.SYSTEM_ADMIN)).isTrue();
-    assertThat(session.hasPermission(GlobalPermissions.DASHBOARD_SHARING)).isFalse();
+    assertThat(session.hasPermission(QUALITY_PROFILE_ADMIN)).isTrue();
+    assertThat(session.hasPermission(SYSTEM_ADMIN)).isTrue();
+    assertThat(session.hasPermission(DASHBOARD_SHARING)).isFalse();
   }
 
   @Test
   public void check_global_Permission_ok() {
-    UserSession session = newServerUserSession().setLogin(LOGIN);
+    addGlobalPermissions("admin", "profileadmin");
+    UserSession session = newUserSession(userDto);
 
-    when(authorizationDao.selectGlobalPermissions(LOGIN)).thenReturn(Arrays.asList("profileadmin", "admin"));
-
-    session.checkPermission(GlobalPermissions.QUALITY_PROFILE_ADMIN);
+    session.checkPermission(QUALITY_PROFILE_ADMIN);
   }
 
-  @Test(expected = ForbiddenException.class)
+  @Test
   public void check_global_Permission_ko() {
-    UserSession session = newServerUserSession().setLogin(LOGIN);
+    addGlobalPermissions("admin", "profileadmin");
+    UserSession session = newUserSession(userDto);
 
-    when(authorizationDao.selectGlobalPermissions(LOGIN)).thenReturn(Arrays.asList("profileadmin", "admin"));
-
-    session.checkPermission(GlobalPermissions.DASHBOARD_SHARING);
+    expectedException.expect(ForbiddenException.class);
+    session.checkPermission(DASHBOARD_SHARING);
   }
 
   @Test
   public void has_component_permission() {
-    UserSession session = newServerUserSession().setLogin(LOGIN).setUserId(1);
-
-    String componentKey = FILE_KEY;
-    when(resourceDao.getRootProjectByComponentKey(componentKey)).thenReturn(new ResourceDto().setKey(componentKey));
-    when(authorizationDao.selectAuthorizedRootProjectsKeys(1, UserRole.USER)).thenReturn(newArrayList(componentKey));
+    addProjectPermissions(project, UserRole.USER);
+    UserSession session = newUserSession(userDto);
 
-    assertThat(session.hasComponentPermission(UserRole.USER, componentKey)).isTrue();
-    assertThat(session.hasComponentPermission(UserRole.CODEVIEWER, componentKey)).isFalse();
-    assertThat(session.hasComponentPermission(UserRole.ADMIN, componentKey)).isFalse();
+    assertThat(session.hasComponentPermission(UserRole.USER, FILE_KEY)).isTrue();
+    assertThat(session.hasComponentPermission(UserRole.CODEVIEWER, FILE_KEY)).isFalse();
+    assertThat(session.hasComponentPermission(UserRole.ADMIN, FILE_KEY)).isFalse();
   }
 
   @Test
   public void has_component_uuid_permission() {
-    UserSession session = newServerUserSession().setLogin(LOGIN).setUserId(1);
-
-    String componentUuid = FILE_UUID;
-    when(resourceDao.selectResource(componentUuid)).thenReturn(new ResourceDto().setUuid(componentUuid).setProjectUuid(PROJECT_UUID));
-    when(authorizationDao.selectAuthorizedRootProjectsUuids(1, UserRole.USER)).thenReturn(newArrayList(PROJECT_UUID));
+    addProjectPermissions(project, UserRole.USER);
+    UserSession session = newUserSession(userDto);
 
-    assertThat(session.hasComponentUuidPermission(UserRole.USER, componentUuid)).isTrue();
-    assertThat(session.hasComponentUuidPermission(UserRole.CODEVIEWER, componentUuid)).isFalse();
-    assertThat(session.hasComponentUuidPermission(UserRole.ADMIN, componentUuid)).isFalse();
+    assertThat(session.hasComponentUuidPermission(UserRole.USER, FILE_UUID)).isTrue();
+    assertThat(session.hasComponentUuidPermission(UserRole.CODEVIEWER, FILE_UUID)).isFalse();
+    assertThat(session.hasComponentUuidPermission(UserRole.ADMIN, FILE_UUID)).isFalse();
   }
 
   @Test
   public void has_component_permission_with_only_global_permission() {
-    UserSession session = newServerUserSession().setLogin(LOGIN).setUserId(1);
+    addGlobalPermissions(UserRole.USER);
+    UserSession session = newUserSession(userDto);
 
-    String componentKey = FILE_KEY;
-    when(resourceDao.getRootProjectByComponentKey(componentKey)).thenReturn(new ResourceDto().setKey(componentKey));
-    when(authorizationDao.selectGlobalPermissions(LOGIN)).thenReturn(Arrays.asList(UserRole.USER));
-
-    assertThat(session.hasComponentPermission(UserRole.USER, componentKey)).isTrue();
-    assertThat(session.hasComponentPermission(UserRole.CODEVIEWER, componentKey)).isFalse();
-    assertThat(session.hasComponentPermission(UserRole.ADMIN, componentKey)).isFalse();
+    assertThat(session.hasComponentPermission(UserRole.USER, FILE_KEY)).isTrue();
+    assertThat(session.hasComponentPermission(UserRole.CODEVIEWER, FILE_KEY)).isFalse();
+    assertThat(session.hasComponentPermission(UserRole.ADMIN, FILE_KEY)).isFalse();
   }
 
   @Test
   public void has_component_uuid_permission_with_only_global_permission() {
-    UserSession session = newServerUserSession().setLogin(LOGIN).setUserId(1);
-
-    String componentUuid = FILE_UUID;
-    when(resourceDao.selectResource(componentUuid)).thenReturn(new ResourceDto().setUuid(componentUuid).setProjectUuid(PROJECT_UUID));
-    when(authorizationDao.selectGlobalPermissions(LOGIN)).thenReturn(Arrays.asList(UserRole.USER));
+    addGlobalPermissions(UserRole.USER);
+    UserSession session = newUserSession(userDto);
 
-    assertThat(session.hasComponentUuidPermission(UserRole.USER, componentUuid)).isTrue();
-    assertThat(session.hasComponentUuidPermission(UserRole.CODEVIEWER, componentUuid)).isFalse();
-    assertThat(session.hasComponentUuidPermission(UserRole.ADMIN, componentUuid)).isFalse();
+    assertThat(session.hasComponentUuidPermission(UserRole.USER, FILE_UUID)).isTrue();
+    assertThat(session.hasComponentUuidPermission(UserRole.CODEVIEWER, FILE_UUID)).isFalse();
+    assertThat(session.hasComponentUuidPermission(UserRole.ADMIN, FILE_UUID)).isFalse();
   }
 
   @Test
   public void check_component_key_permission_ok() {
-    UserSession session = newServerUserSession().setLogin(LOGIN).setUserId(1);
-
-    when(resourceDao.getRootProjectByComponentKey(FILE_KEY)).thenReturn(new ResourceDto().setKey(PROJECT_KEY));
-    when(authorizationDao.selectAuthorizedRootProjectsKeys(1, UserRole.USER)).thenReturn(newArrayList(PROJECT_KEY));
+    addProjectPermissions(project, UserRole.USER);
+    UserSession session = newUserSession(userDto);
 
     session.checkComponentPermission(UserRole.USER, FILE_KEY);
   }
 
   @Test
   public void check_component_key_permission_with_only_global_permission_ok() {
-    UserSession session = newServerUserSession().setLogin(LOGIN).setUserId(1);
-
-    when(resourceDao.getRootProjectByComponentKey(FILE_KEY)).thenReturn(new ResourceDto().setKey(PROJECT_KEY));
-    when(authorizationDao.selectGlobalPermissions(LOGIN)).thenReturn(Arrays.asList(UserRole.USER));
+    addGlobalPermissions(UserRole.USER);
+    UserSession session = newUserSession(userDto);
 
     session.checkComponentPermission(UserRole.USER, FILE_KEY);
   }
 
-  @Test(expected = ForbiddenException.class)
+  @Test
   public void check_component_key_permission_ko() {
-    UserSession session = newServerUserSession().setLogin(LOGIN).setUserId(1);
-
-    when(resourceDao.getRootProjectByComponentKey(FILE_KEY)).thenReturn(new ResourceDto().setKey("com.foo:Bar2"));
-    when(authorizationDao.selectAuthorizedRootProjectsKeys(1, UserRole.USER)).thenReturn(newArrayList(PROJECT_KEY));
+    ComponentDto project2 = componentDbTester.insertComponent(ComponentTesting.newProjectDto());
+    ComponentDto file2 = componentDbTester.insertComponent(ComponentTesting.newFileDto(project2));
+    addProjectPermissions(project, UserRole.USER);
+    UserSession session = newUserSession(userDto);
 
-    session.checkComponentPermission(UserRole.USER, FILE_KEY);
+    expectedException.expect(ForbiddenException.class);
+    session.checkComponentPermission(UserRole.USER, file2.getKey());
   }
 
   @Test
   public void check_component_uuid_permission_ok() {
-    UserSession session = newServerUserSession().setLogin(LOGIN).setUserId(1);
-
-    ComponentDto project = ComponentTesting.newProjectDto();
-    ComponentDto file = ComponentTesting.newFileDto(project, "file-uuid");
-    when(resourceDao.selectResource("file-uuid")).thenReturn(new ResourceDto().setProjectUuid(project.uuid()));
-    when(authorizationDao.selectAuthorizedRootProjectsUuids(1, UserRole.USER)).thenReturn(newArrayList(project.uuid()));
+    addProjectPermissions(project, UserRole.USER);
+    UserSession session = newUserSession(userDto);
 
-    session.checkComponentUuidPermission(UserRole.USER, file.uuid());
+    session.checkComponentUuidPermission(UserRole.USER, FILE_UUID);
   }
 
-  @Test(expected = ForbiddenException.class)
+  @Test
   public void check_component_uuid_permission_ko() {
-    UserSession session = newServerUserSession().setLogin(LOGIN).setUserId(1);
-
-    ComponentDto project = ComponentTesting.newProjectDto();
-    when(resourceDao.selectResource("file-uuid")).thenReturn(new ResourceDto().setProjectUuid(project.uuid()));
-    when(authorizationDao.selectAuthorizedRootProjectsUuids(1, UserRole.USER)).thenReturn(newArrayList(project.uuid()));
+    addProjectPermissions(project, UserRole.USER);
+    UserSession session = newUserSession(userDto);
 
+    expectedException.expect(ForbiddenException.class);
     session.checkComponentUuidPermission(UserRole.USER, "another-uuid");
   }
 
-  @Test(expected = ForbiddenException.class)
+  @Test
   public void check_component_key_permission_when_project_not_found() {
-    UserSession session = newServerUserSession().setLogin(LOGIN).setUserId(1);
-
-    when(resourceDao.getRootProjectByComponentKey(FILE_KEY)).thenReturn(null);
-
-    session.checkComponentPermission(UserRole.USER, FILE_KEY);
+    ComponentDto project2 = componentDbTester.insertComponent(ComponentTesting.newProjectDto());
+    ComponentDto file2 = componentDbTester.insertComponent(ComponentTesting.newFileDto(project2)
+      // Simulate file is linked to an invalid project
+      .setProjectUuid("INVALID"));
+    addProjectPermissions(project, UserRole.USER);
+    UserSession session = newUserSession(userDto);
+
+    expectedException.expect(ForbiddenException.class);
+    session.checkComponentPermission(UserRole.USER, file2.getKey());
   }
 
-  @Test(expected = ForbiddenException.class)
+  @Test
   public void check_component_dto_permission_ko() {
-    UserSession session = newServerUserSession().setLogin(LOGIN).setUserId(1);
-
-    ComponentDto project = ComponentTesting.newProjectDto();
-    when(authorizationDao.selectAuthorizedRootProjectsKeys(1, UserRole.USER)).thenReturn(newArrayList(project.uuid()));
+    addProjectPermissions(project, UserRole.USER);
+    UserSession session = newUserSession(userDto);
 
+    expectedException.expect(ForbiddenException.class);
     session.checkComponentPermission(UserRole.USER, "another");
   }
 
   @Test
   public void deprecated_has_global_permission() throws Exception {
-    UserSession session = newServerUserSession().setLogin(LOGIN);
-
-    when(authorizationDao.selectGlobalPermissions(LOGIN)).thenReturn(Arrays.asList("profileadmin", "admin"));
+    addGlobalPermissions("profileadmin", "admin");
+    UserSession session = newUserSession(userDto);
 
-    assertThat(session.hasGlobalPermission(GlobalPermissions.QUALITY_PROFILE_ADMIN)).isTrue();
-    assertThat(session.hasGlobalPermission(GlobalPermissions.SYSTEM_ADMIN)).isTrue();
-    assertThat(session.hasGlobalPermission(GlobalPermissions.DASHBOARD_SHARING)).isFalse();
+    assertThat(session.hasGlobalPermission(QUALITY_PROFILE_ADMIN)).isTrue();
+    assertThat(session.hasGlobalPermission(SYSTEM_ADMIN)).isTrue();
+    assertThat(session.hasGlobalPermission(DASHBOARD_SHARING)).isFalse();
   }
 
   @Test
   public void deprecated_check_global_permission() throws Exception {
-    UserSession session = newServerUserSession().setLogin(LOGIN);
+    addGlobalPermissions("profileadmin", "admin");
+    UserSession session = newUserSession(userDto);
 
-    when(authorizationDao.selectGlobalPermissions(LOGIN)).thenReturn(Arrays.asList("profileadmin", "admin"));
+    session.checkGlobalPermission(QUALITY_PROFILE_ADMIN);
+  }
 
-    session.checkGlobalPermission(GlobalPermissions.QUALITY_PROFILE_ADMIN);
+  @Test
+  public void fail_if_user_dto_is_null() throws Exception {
+    expectedException.expect(NullPointerException.class);
+    newUserSession(null);
+  }
+
+  @Test
+  public void anonymous_user() throws Exception {
+    UserSession session = newAnonymousSession();
+
+    assertThat(session.getLogin()).isNull();
+    assertThat(session.isLoggedIn()).isFalse();
   }
 
-  private ServerUserSession newServerUserSession() {
-    return new ServerUserSession(authorizationDao, resourceDao);
+  @Test
+  public void has_global_permission_for_anonymous() throws Exception {
+    addAnonymousPermissions(null, "profileadmin", "admin");
+    UserSession session = newAnonymousSession();
+
+    assertThat(session.getLogin()).isNull();
+    assertThat(session.isLoggedIn()).isFalse();
+
+    assertThat(session.hasPermission(GlobalPermissions.QUALITY_PROFILE_ADMIN)).isTrue();
+    assertThat(session.hasPermission(GlobalPermissions.SYSTEM_ADMIN)).isTrue();
+    assertThat(session.hasPermission(GlobalPermissions.DASHBOARD_SHARING)).isFalse();
   }
 
+  @Test
+  public void has_project_permission_for_anonymous() throws Exception {
+    addAnonymousPermissions(project, UserRole.USER);
+    UserSession session = newAnonymousSession();
+
+    assertThat(session.hasComponentPermission(UserRole.USER, FILE_KEY)).isTrue();
+    assertThat(session.hasComponentPermission(UserRole.CODEVIEWER, FILE_KEY)).isFalse();
+    assertThat(session.hasComponentPermission(UserRole.ADMIN, FILE_KEY)).isFalse();
+  }
+
+  private ServerUserSession newUserSession(UserDto userDto) {
+    return createForUser(dbClient, userDto);
+  }
+
+  private ServerUserSession newAnonymousSession() {
+    return createForAnonymous(dbClient);
+  }
+
+  private void addGlobalPermissions(String... permissions) {
+    addPermissions(null, permissions);
+  }
+
+  private void addProjectPermissions(ComponentDto component, String... permissions) {
+    addPermissions(component, permissions);
+  }
+
+  private void addPermissions( @Nullable ComponentDto component, String... permissions) {
+    for (String permission : permissions) {
+      dbClient.roleDao().insertUserRole(dbSession, new UserRoleDto()
+        .setRole(permission)
+        .setResourceId(component == null ? null : component.getId())
+        .setUserId(userDto.getId()));
+    }
+    dbSession.commit();
+  }
+
+  private void addAnonymousPermissions(@Nullable ComponentDto component, String... permissions) {
+    for (String permission : permissions) {
+      dbClient.roleDao().insertGroupRole(dbSession, new GroupRoleDto()
+        .setRole(permission)
+        .setResourceId(component == null ? null : component.getId()));
+    }
+    dbSession.commit();
+  }
+
+
 }
index 88968af76e9acbead7086a23cd124594ec8d28ad..2820f85d3f7af0312981c5970c44a2a67c5fe2eb 100644 (file)
  */
 package org.sonar.server.user;
 
+import static com.google.common.collect.Lists.newArrayList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.data.MapEntry.entry;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.sonar.api.CoreProperties.CORE_DEFAULT_GROUP;
+import static org.sonar.db.user.UserTesting.newDisabledUser;
+import static org.sonar.db.user.UserTesting.newUserDto;
+
 import com.google.common.base.Strings;
 import java.util.List;
 import org.elasticsearch.search.SearchHit;
@@ -46,17 +57,6 @@ import org.sonar.server.user.index.UserIndexDefinition;
 import org.sonar.server.user.index.UserIndexer;
 import org.sonar.server.util.Validation;
 
-import static com.google.common.collect.Lists.newArrayList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.data.MapEntry.entry;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.sonar.api.CoreProperties.CORE_DEFAULT_GROUP;
-import static org.sonar.db.user.UserTesting.newDisabledUser;
-import static org.sonar.db.user.UserTesting.newUserDto;
-
 public class UserUpdaterTest {
 
   static final long NOW = 1418215735482L;
@@ -104,7 +104,7 @@ public class UserUpdaterTest {
       .setLogin("user")
       .setName("User")
       .setEmail("user@mail.com")
-      .setPassword("password")
+      .setPassword("PASSWORD")
       .setScmAccounts(newArrayList("u1", "u_1", "User 1")));
 
     UserDto dto = userDao.selectByLogin(session, "user");
@@ -502,8 +502,8 @@ public class UserUpdaterTest {
     assertThat(dto.getScmAccounts()).isNull();
     assertThat(dto.isLocal()).isTrue();
 
-    assertThat(dto.getSalt()).isNotEqualTo("79bd6a8e79fb8c76ac8b121cc7e8e11ad1af8365");
-    assertThat(dto.getCryptedPassword()).isNotEqualTo("650d2261c98361e2f67f90ce5c65a95e7d8ea2fg");
+    assertThat(dto.getSalt()).isNotNull().isNotEqualTo("79bd6a8e79fb8c76ac8b121cc7e8e11ad1af8365");
+    assertThat(dto.getCryptedPassword()).isNotNull().isNotEqualTo("650d2261c98361e2f67f90ce5c65a95e7d8ea2fg");
     assertThat(dto.getCreatedAt()).isEqualTo(PAST);
     assertThat(dto.getUpdatedAt()).isEqualTo(NOW);
 
index d8194ad7ef60e0a290f38e347a854e837dac2ea5..a55e5363ac9ff767c919928a7ec4498e42dc845c 100644 (file)
@@ -58,7 +58,7 @@ class Api::AuthenticationController < Api::ApiController
   end
 
   def anonymous?
-    !session.has_key?('user_id')
+    current_user.nil?
   end
 
   def set_cache_buster
index d1c1682b30e6f4b01a18f611e4eb7380a4f4f353..83b7a6ebff8746051f9a5592c179385d7131b264 100644 (file)
@@ -22,7 +22,7 @@ class ApplicationController < ActionController::Base
   include AuthenticatedSystem
   include NeedAuthorization::Helper
 
-  before_filter :check_database_version, :set_user_session, :check_authentication
+  before_filter :check_database_version, :set_i18n, :check_authentication
 
   # Required for JRuby 1.7
   rescue_from 'Java::JavaLang::Exception', :with => :render_java_exception
@@ -92,19 +92,13 @@ class ApplicationController < ActionController::Base
     end
   end
 
-  def set_user_session
+  def set_i18n
+    # TODO Is it really needed to do this ?
     if params[:locale]
       I18n.locale = request.compatible_language_from(available_locales, [params[:locale]])
     else
       I18n.locale = request.compatible_language_from(available_locales)
     end
-
-    if current_user && current_user.id
-      user_groups_name = current_user.groups.collect {|g| g.name}.to_a
-      Java::OrgSonarServerUser::RubyUserSession.setSession(current_user.id.to_i, current_user.login, current_user.name, user_groups_name, I18n.locale.to_s)
-    else
-      Java::OrgSonarServerUser::RubyUserSession.setSession(nil, nil, nil, nil, I18n.locale.to_s)
-    end
   end
 
   def check_authentication
index 3032f4038f833bf9e919a44f9e8243c478abd877..02f70471036c2f5ad1f99fc41714237aa6f7483f 100644 (file)
@@ -8,16 +8,14 @@ module AuthenticatedSystem
   # Accesses the current user from the session.
   # Future calls avoid the database because nil is not equal to false.
   def current_user
-    @current_user ||= (login_from_session || login_from_basic_auth) unless @current_user == false
+    @current_user ||= (login_from_java_user_session || login_from_basic_auth) unless @current_user == false
   end
 
-  # Store the given user id in the session.
+  # Store the given user
   def current_user=(new_user)
     if new_user
-      session['user_id'] = new_user.id
       @current_user = new_user
     else
-      session['user_id'] = nil
       @current_user = false
     end
   end
@@ -120,8 +118,10 @@ module AuthenticatedSystem
   #
 
   # Called from #current_user.  First attempt to login by the user id stored in the session.
-  def login_from_session
-    self.current_user = User.find_by_id(session['user_id']) if session['user_id']
+  def login_from_java_user_session
+    userSession = Java::OrgSonarServerPlatform::Platform.component(Java::OrgSonarServerUser::UserSession.java_class)
+    user_id = userSession.getUserId() if userSession && userSession.isLoggedIn()
+    self.current_user = User.find_by_id(user_id) if user_id
   end
 
   # Called from #current_user.  Now, attempt to login by basic authentication information.
index c6331c1c9dc8fbeebd5a50f1c31696778e3d9482..9b83805dbaad7e21ff6ea143f1cdd16cb0b607a0 100644 (file)
  */
 package org.sonar.db.user;
 
+import static java.util.Objects.requireNonNull;
+
 import com.google.common.base.Splitter;
 import com.google.common.collect.Lists;
 import java.util.ArrayList;
 import java.util.List;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
+import org.apache.commons.codec.digest.DigestUtils;
 import org.apache.commons.lang.StringUtils;
 import org.sonar.core.user.DefaultUser;
 
@@ -163,20 +166,22 @@ public class UserDto {
     return this;
   }
 
+  @CheckForNull
   public String getCryptedPassword() {
     return cryptedPassword;
   }
 
-  public UserDto setCryptedPassword(String cryptedPassword) {
+  public UserDto setCryptedPassword(@Nullable String cryptedPassword) {
     this.cryptedPassword = cryptedPassword;
     return this;
   }
 
+  @CheckForNull
   public String getSalt() {
     return salt;
   }
 
-  public UserDto setSalt(String salt) {
+  public UserDto setSalt(@Nullable String salt) {
     this.salt = salt;
     return this;
   }
@@ -199,6 +204,12 @@ public class UserDto {
     return this;
   }
 
+  public static String encryptPassword(String password, String salt) {
+    requireNonNull(password, "Password cannot be empty");
+    requireNonNull(salt, "Salt cannot be empty");
+    return DigestUtils.sha1Hex("--" + salt + "--" + password + "--");
+  }
+
   public DefaultUser toUser() {
     return new DefaultUser()
       .setLogin(login)
index 673c06260a1f2e64052b909f652ecd4c0947226e..08f9d76fe558170063040ef65179fef82e0c69bc 100644 (file)
  */
 package org.sonar.db.user;
 
+import static org.assertj.core.api.Assertions.assertThat;
+
 import java.util.Arrays;
 import java.util.Collections;
+import org.junit.Rule;
 import org.junit.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
+import org.junit.rules.ExpectedException;
 
 public class UserDtoTest {
 
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
   @Test
   public void encode_scm_accounts() {
     assertThat(UserDto.encodeScmAccounts(null)).isNull();
@@ -41,4 +46,21 @@ public class UserDtoTest {
     assertThat(UserDto.decodeScmAccounts("\nfoo\n")).containsOnly("foo");
     assertThat(UserDto.decodeScmAccounts("\nfoo\nbar\n")).containsOnly("foo", "bar");
   }
+
+  @Test
+  public void encrypt_password() throws Exception {
+    assertThat(UserDto.encryptPassword("PASSWORD", "0242b0b4c0a93ddfe09dd886de50bc25ba000b51")).isEqualTo("540e4fc4be4e047db995bc76d18374a5b5db08cc");
+  }
+
+  @Test
+  public void fail_to_encrypt_password_when_password_is_null() throws Exception {
+    expectedException.expect(NullPointerException.class);
+    UserDto.encryptPassword(null, "salt");
+  }
+
+  @Test
+  public void fail_to_encrypt_password_when_salt_is_null() throws Exception {
+    expectedException.expect(NullPointerException.class);
+    UserDto.encryptPassword("password", null);
+  }
 }
index 6de9f5131131bcdabcad0205c79c9aabf639a6c9..65753247e7c30576a5d39f06ddc4b5f0f6b37331 100644 (file)
  */
 package org.sonar.api.server.authentication;
 
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.FluentIterable.from;
+import static org.apache.commons.lang.StringUtils.isNotBlank;
+import static org.sonar.api.user.UserGroupValidation.validateGroupName;
+
 import com.google.common.base.Predicate;
 import java.util.HashSet;
 import java.util.Set;
@@ -28,12 +34,6 @@ import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
 import org.sonar.api.CoreProperties;
 
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.collect.FluentIterable.from;
-import static org.apache.commons.lang.StringUtils.isNotBlank;
-import static org.sonar.api.user.UserGroupValidation.validateGroupName;
-
 /**
  * User information provided by the Identity Provider to be register into the platform.
  *
@@ -171,7 +171,7 @@ public final class UserIdentity {
      * @since 5.5
      */
     public Builder setGroups(Set<String> groups) {
-      checkNotNull(groups, "Groups cannot be null, please don't this method if groups should not be synchronized.");
+      checkNotNull(groups, "Groups cannot be null, please don't use this method if groups should not be synchronized.");
       from(groups).filter(ValidateGroupName.INSTANCE).toList();
       this.groupsProvided = true;
       this.groups = groups;
index 3ef0c9f9bbdf0868f5a6bea4457249f0ea148b86..5c41e477d916c2152720419f36e9dca1a8f8679c 100644 (file)
  */
 package org.sonar.api.server.authentication;
 
+import static com.google.common.collect.Sets.newHashSet;
+import static org.assertj.core.api.Assertions.assertThat;
+
 import com.google.common.base.Strings;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 
-import static com.google.common.collect.Sets.newHashSet;
-import static org.assertj.core.api.Assertions.assertThat;
-
 public class UserIdentityTest {
 
   @Rule
@@ -195,7 +195,7 @@ public class UserIdentityTest {
   @Test
   public void fail_when_groups_are_null() throws Exception {
     thrown.expect(NullPointerException.class);
-    thrown.expectMessage("Groups cannot be null, please don't this method if groups should not be synchronized.");
+    thrown.expectMessage("Groups cannot be null, please don't use this method if groups should not be synchronized.");
 
     UserIdentity.builder()
       .setProviderLogin("john")