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;
WsLocalCallTest.class,
WsTest.class,
// quality profiles
- QualityProfilesPageTest.class
+ QualityProfilesPageTest.class,
+ LogsTest.class
})
public class Category4Suite {
// Used by WsLocalCallTest
.addPlugin(pluginArtifact("ws-plugin"))
+
+ // Used by LogsTest
+ .setServerProperty("sonar.web.accessLogs.pattern", LogsTest.ACCESS_LOGS_PATTERN)
.build();
}
--- /dev/null
+/*
+ * 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");
+ }
+}
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;
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",
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) {
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
*/
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;
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() {
# - "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: