]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7581 ability to have user login in access.log
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Sat, 6 Aug 2016 08:17:05 +0000 (10:17 +0200)
committerGitHub <noreply@github.com>
Sat, 6 Aug 2016 08:17:05 +0000 (10:17 +0200)
it/it-tests/src/test/java/it/Category4Suite.java
it/it-tests/src/test/java/it/serverSystem/LogsTest.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/authentication/UserSessionInitializer.java
server/sonar-server/src/test/java/org/sonar/server/user/UserSessionFilterTest.java
sonar-application/src/main/assembly/conf/sonar.properties

index 15474220cbd04074108c66ffd4ff4c0fcd665d60..915a1ed8395ff811254b04032b10ea7c2aa460f2 100644 (file)
@@ -34,6 +34,7 @@ import it.http.HttpHeadersTest;
 import it.projectComparison.ProjectComparisonTest;
 import it.projectEvent.EventTest;
 import it.qualityProfile.QualityProfilesPageTest;
+import it.serverSystem.LogsTest;
 import it.serverSystem.ServerSystemTest;
 import it.ui.UiTest;
 import it.uiExtension.UiExtensionsTest;
@@ -95,7 +96,8 @@ import static util.ItUtils.xooPlugin;
   WsLocalCallTest.class,
   WsTest.class,
   // quality profiles
-  QualityProfilesPageTest.class
+  QualityProfilesPageTest.class,
+  LogsTest.class
 })
 public class Category4Suite {
 
@@ -117,5 +119,8 @@ public class Category4Suite {
 
     // Used by WsLocalCallTest
     .addPlugin(pluginArtifact("ws-plugin"))
+
+    // Used by LogsTest
+    .setServerProperty("sonar.web.accessLogs.pattern", LogsTest.ACCESS_LOGS_PATTERN)
     .build();
 }
diff --git a/it/it-tests/src/test/java/it/serverSystem/LogsTest.java b/it/it-tests/src/test/java/it/serverSystem/LogsTest.java
new file mode 100644 (file)
index 0000000..bd77db6
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * 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 it.serverSystem;
+
+import com.sonar.orchestrator.Orchestrator;
+import it.Category4Suite;
+import java.io.File;
+import java.io.IOException;
+import org.apache.commons.io.input.ReversedLinesFileReader;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.sonarqube.ws.client.GetRequest;
+import org.sonarqube.ws.client.WsClient;
+import util.ItUtils;
+
+import static java.lang.String.format;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class LogsTest {
+
+  public static final String ACCESS_LOGS_PATTERN = "\"%reqAttribute{LOGIN}\" \"%r\" %s";
+  private static final String PATH = "/called/from/LogsTest";
+
+  @ClassRule
+  public static final Orchestrator orchestrator = Category4Suite.ORCHESTRATOR;
+
+  /**
+   * SONAR-7581
+   */
+  @Test
+  public void test_access_logs() throws Exception {
+    // log "-" for anonymous
+    sendHttpRequest(ItUtils.newWsClient(orchestrator), PATH);
+    assertThat(accessLogsFile()).isFile().exists();
+    verifyLastAccessLogLine("-", PATH, 404);
+
+    sendHttpRequest(ItUtils.newAdminWsClient(orchestrator), PATH);
+    verifyLastAccessLogLine("admin", PATH, 404);
+  }
+
+  private void verifyLastAccessLogLine(String login, String path, int status) throws IOException {
+    assertThat(readLastAccessLog()).isEqualTo(format("\"%s\" \"GET %s HTTP/1.1\" %d", login, path, status));
+  }
+
+  private String readLastAccessLog() throws IOException {
+    try (ReversedLinesFileReader tailer = new ReversedLinesFileReader(accessLogsFile())) {
+      return tailer.readLine();
+    }
+  }
+
+  private void sendHttpRequest(WsClient client, String path) {
+    client.wsConnector().call(new GetRequest(path));
+  }
+
+  private File accessLogsFile() {
+    return new File(orchestrator.getServer().getHome(), "logs/access.log");
+  }
+}
index 3b2053eee661fe44de012daaf3ba5293357cff13..f29a0807c74df1717269950f56c9b58301a9dc9f 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;
-import static org.sonar.api.web.ServletFilter.UrlPattern.Builder.staticResourcePatterns;
-import static org.sonar.server.authentication.ws.LoginAction.AUTH_LOGIN_URL;
-import static org.sonar.server.authentication.ws.ValidateAction.AUTH_VALIDATE_URL;
-import static org.sonar.server.user.ServerUserSession.createForAnonymous;
-import static org.sonar.server.user.ServerUserSession.createForUser;
-
 import com.google.common.collect.ImmutableSet;
 import java.util.Optional;
 import java.util.Set;
@@ -39,11 +30,28 @@ import org.sonar.api.server.ServerSide;
 import org.sonar.db.DbClient;
 import org.sonar.db.user.UserDto;
 import org.sonar.server.exceptions.UnauthorizedException;
+import org.sonar.server.user.ServerUserSession;
 import org.sonar.server.user.ThreadLocalUserSession;
 
+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;
+import static org.sonar.api.web.ServletFilter.UrlPattern.Builder.staticResourcePatterns;
+import static org.sonar.server.authentication.ws.LoginAction.AUTH_LOGIN_URL;
+import static org.sonar.server.authentication.ws.ValidateAction.AUTH_VALIDATE_URL;
+import static org.sonar.server.user.ServerUserSession.createForAnonymous;
+import static org.sonar.server.user.ServerUserSession.createForUser;
+
 @ServerSide
 public class UserSessionInitializer {
 
+  /**
+   * Key of attribute to be used for displaying user login
+   * in logs/access.log. The pattern to be configured
+   * in property sonar.web.accessLogs.pattern is "%reqAttribute{LOGIN}"
+   */
+  public static final String ACCESS_LOG_LOGIN = "LOGIN";
+
   // SONAR-6546 these urls should be get from WebService
   private static final Set<String> SKIPPED_URLS = ImmutableSet.of(
     "/batch/index", "/batch/file",
@@ -64,15 +72,15 @@ public class UserSessionInitializer {
   private final Settings settings;
   private final JwtHttpHandler jwtHttpHandler;
   private final BasicAuthenticator basicAuthenticator;
-  private final ThreadLocalUserSession userSession;
+  private final ThreadLocalUserSession threadLocalSession;
 
   public UserSessionInitializer(DbClient dbClient, Settings settings, JwtHttpHandler jwtHttpHandler, BasicAuthenticator basicAuthenticator,
-                                ThreadLocalUserSession userSession) {
+                                ThreadLocalUserSession threadLocalSession) {
     this.dbClient = dbClient;
     this.settings = settings;
     this.jwtHttpHandler = jwtHttpHandler;
     this.basicAuthenticator = basicAuthenticator;
-    this.userSession = userSession;
+    this.threadLocalSession = threadLocalSession;
   }
 
   public boolean initUserSession(HttpServletRequest request, HttpServletResponse response) {
@@ -97,17 +105,20 @@ public class UserSessionInitializer {
   private void setUserSession(HttpServletRequest request, HttpServletResponse response) {
     Optional<UserDto> user = authenticate(request, response);
     if (user.isPresent()) {
-      userSession.set(createForUser(dbClient, user.get()));
+      ServerUserSession session = createForUser(dbClient, user.get());
+      threadLocalSession.set(session);
+      request.setAttribute(ACCESS_LOG_LOGIN, session.getLogin());
     } else {
       if (settings.getBoolean(CORE_FORCE_AUTHENTICATION_PROPERTY)) {
         throw new UnauthorizedException("User must be authenticated");
       }
-      userSession.set(createForAnonymous(dbClient));
+      threadLocalSession.set(createForAnonymous(dbClient));
+      request.setAttribute(ACCESS_LOG_LOGIN, "-");
     }
   }
 
   public void removeUserSession() {
-    userSession.remove();
+    threadLocalSession.remove();
   }
 
   // Try first to authenticate from JWT token, then try from basic http header
index 1e75fe2279a9e707b32dbea4bf625ebd06c2350d..55e0b0cda3d165c2abe8df8f0d0c99d3cd58b044 100644 (file)
  */
 package org.sonar.server.user;
 
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-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.FilterConfig;
@@ -37,17 +31,22 @@ import org.sonar.core.platform.ComponentContainer;
 import org.sonar.server.authentication.UserSessionInitializer;
 import org.sonar.server.platform.Platform;
 
-public class UserSessionFilterTest {
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
 
-  UserSessionInitializer userSessionInitializer = mock(UserSessionInitializer.class);
-  Platform platform = mock(Platform.class);
-  ComponentContainer componentContainer = mock(ComponentContainer.class);
+public class UserSessionFilterTest {
 
-  HttpServletRequest request = mock(HttpServletRequest.class);
-  HttpServletResponse response = mock(HttpServletResponse.class);
-  FilterChain chain = mock(FilterChain.class);
+  private UserSessionInitializer userSessionInitializer = mock(UserSessionInitializer.class);
+  private Platform platform = mock(Platform.class);
+  private ComponentContainer componentContainer = mock(ComponentContainer.class);
+  private HttpServletRequest request = mock(HttpServletRequest.class);
+  private HttpServletResponse response = mock(HttpServletResponse.class);
+  private FilterChain chain = mock(FilterChain.class);
 
-  UserSessionFilter underTest = new UserSessionFilter(platform);
+  private UserSessionFilter underTest = new UserSessionFilter(platform);
 
   @Before
   public void setUp() {
index f2912583808f73e7fcbabc25dd6b22b8d326d762..64641e131485a72452f91da37a06e967ebfb47f2 100644 (file)
 #    - "common" is the Common Log Format, shortcut to: %h %l %u %user %date "%r" %s %b
 #    - "combined" is another format widely recognized, shortcut to: %h %l %u [%t] "%r" %s %b "%i{Referer}" "%i{User-Agent}"
 #    - else a custom pattern. See http://logback.qos.ch/manual/layouts.html#AccessPatternLayout.
+# The login of authenticated user is not implemented with "%u" but with "%reqAttribute{LOGIN}" (since version 6.1).
+# The value displayed for anonymous users is "-".
 # If SonarQube is behind a reverse proxy, then the following value allows to display the correct remote IP address:
 #sonar.web.accessLogs.pattern=%i{X-Forwarded-For} %l %u [%t] "%r" %s %b "%i{Referer}" "%i{User-Agent}"
 # Default value is: