+++ /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 org.sonar.server.authentication;
-
-import java.util.Arrays;
-import java.util.Optional;
-import javax.annotation.Nullable;
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletRequest;
-
-public class CookieUtils {
-
- private static final String HTTPS_HEADER = "X-Forwarded-Proto";
- private static final String HTTPS_VALUE = "https";
-
- private CookieUtils() {
- // Only static methods
- }
-
- public static Optional<Cookie> findCookie(String cookieName, HttpServletRequest request) {
- Cookie[] cookies = request.getCookies();
- if (cookies == null) {
- return Optional.empty();
- }
- return Arrays.stream(cookies)
- .filter(cookie -> cookieName.equals(cookie.getName()))
- .findFirst();
- }
-
- public static Cookie createCookie(String name, @Nullable String value, boolean httpOnly, int expiry, HttpServletRequest request) {
- Cookie cookie = new Cookie(name, value);
- // Path is set "/" in order to allow rails to be able to remove cookies
- // TODO When logout when be implemented in Java (SONAR-7774), following line should be replaced by
- // cookie.setPath(request.getContextPath()"/");
- cookie.setPath("/");
- cookie.setSecure(isHttps(request));
- cookie.setHttpOnly(httpOnly);
- cookie.setMaxAge(expiry);
- return cookie;
- }
-
- private static boolean isHttps(HttpServletRequest request) {
- return HTTPS_VALUE.equalsIgnoreCase(request.getHeader(HTTPS_HEADER));
- }
-}
--- /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 org.sonar.server.authentication;
+
+import java.util.Arrays;
+import java.util.Optional;
+import javax.annotation.Nullable;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static java.util.Objects.requireNonNull;
+
+public class Cookies {
+
+ private static final String HTTPS_HEADER = "X-Forwarded-Proto";
+ private static final String HTTPS_VALUE = "https";
+
+ private Cookies() {
+ // Only static methods
+ }
+
+ public static Optional<Cookie> findCookie(String cookieName, HttpServletRequest request) {
+ Cookie[] cookies = request.getCookies();
+ if (cookies == null) {
+ return Optional.empty();
+ }
+ return Arrays.stream(cookies)
+ .filter(cookie -> cookieName.equals(cookie.getName()))
+ .findFirst();
+ }
+
+ public static CookieBuilder newCookieBuilder(HttpServletRequest request) {
+ return new CookieBuilder(request);
+ }
+
+ public static class CookieBuilder {
+
+ private final HttpServletRequest request;
+
+ private String name;
+ private String value;
+ private boolean httpOnly;
+ private int expiry;
+
+ public CookieBuilder(HttpServletRequest request) {
+ this.request = request;
+ }
+
+ public CookieBuilder setName(String name) {
+ this.name = requireNonNull(name);
+ return this;
+ }
+
+ public CookieBuilder setValue(@Nullable String value) {
+ this.value = value;
+ return this;
+ }
+
+ public CookieBuilder setHttpOnly(boolean httpOnly) {
+ this.httpOnly = httpOnly;
+ return this;
+ }
+
+ public CookieBuilder setExpiry(int expiry) {
+ this.expiry = expiry;
+ return this;
+ }
+
+ public Cookie build() {
+ Cookie cookie = new Cookie(requireNonNull(name), value);
+ cookie.setPath(getContextPath(request));
+ cookie.setSecure(isHttps(request));
+ cookie.setHttpOnly(httpOnly);
+ cookie.setMaxAge(expiry);
+ return cookie;
+ }
+
+ private static boolean isHttps(HttpServletRequest request) {
+ return HTTPS_VALUE.equalsIgnoreCase(request.getHeader(HTTPS_HEADER));
+ }
+
+ private static String getContextPath(HttpServletRequest request) {
+ String path = request.getContextPath();
+ return isNullOrEmpty(path) ? "/" : path;
+ }
+ }
+}
import org.sonar.server.authentication.event.AuthenticationException;
import static org.apache.commons.lang.StringUtils.isBlank;
-import static org.sonar.server.authentication.CookieUtils.createCookie;
+import static org.sonar.server.authentication.Cookies.newCookieBuilder;
import static org.sonar.server.authentication.event.AuthenticationEvent.Method;
import static org.sonar.server.authentication.event.AuthenticationEvent.Source;
// Create a state token to prevent request forgery.
// Store it in the cookie for later validation.
String state = new BigInteger(130, new SecureRandom()).toString(32);
- response.addCookie(createCookie(CSRF_STATE_COOKIE, state, false, timeoutInSeconds, request));
+ response.addCookie(newCookieBuilder(request).setName(CSRF_STATE_COOKIE).setValue(state).setHttpOnly(false).setExpiry(timeoutInSeconds).build());
return state;
}
}
public void refreshState(HttpServletRequest request, HttpServletResponse response, String csrfState, int timeoutInSeconds) {
- response.addCookie(createCookie(CSRF_STATE_COOKIE, csrfState, false, timeoutInSeconds, request));
+ response.addCookie(newCookieBuilder(request).setName(CSRF_STATE_COOKIE).setValue(csrfState).setHttpOnly(false).setExpiry(timeoutInSeconds).build());
}
public void removeState(HttpServletRequest request, HttpServletResponse response) {
- response.addCookie(createCookie(CSRF_STATE_COOKIE, null, false, 0, request));
+ response.addCookie(newCookieBuilder(request).setName(CSRF_STATE_COOKIE).setValue(null).setHttpOnly(false).setExpiry(0).build());
}
private static boolean shouldRequestBeChecked(HttpServletRequest request) {
import static java.util.Objects.requireNonNull;
import static org.apache.commons.lang.StringUtils.isEmpty;
import static org.apache.commons.lang.time.DateUtils.addSeconds;
-import static org.sonar.server.authentication.CookieUtils.findCookie;
+import static org.sonar.server.authentication.Cookies.findCookie;
+import static org.sonar.server.authentication.Cookies.newCookieBuilder;
@ServerSide
public class JwtHttpHandler {
}
private static Cookie createCookie(HttpServletRequest request, String name, @Nullable String value, int expirationInSeconds) {
- return CookieUtils.createCookie(name, value, true, expirationInSeconds, request);
+ return newCookieBuilder(request).setName(name).setValue(value).setHttpOnly(true).setExpiry(expirationInSeconds).build();
}
private Optional<UserDto> selectUserFromDb(String userLogin) {
import static java.lang.String.format;
import static org.apache.commons.codec.digest.DigestUtils.sha256Hex;
import static org.apache.commons.lang.StringUtils.isBlank;
-import static org.sonar.server.authentication.CookieUtils.createCookie;
-import static org.sonar.server.authentication.CookieUtils.findCookie;
+import static org.sonar.server.authentication.Cookies.findCookie;
+import static org.sonar.server.authentication.Cookies.newCookieBuilder;
import static org.sonar.server.authentication.event.AuthenticationEvent.Source;
public class OAuthCsrfVerifier {
// Create a state token to prevent request forgery.
// Store it in the session for later validation.
String state = new BigInteger(130, new SecureRandom()).toString(32);
- response.addCookie(createCookie(CSRF_STATE_COOKIE, sha256Hex(state), true, -1, request));
+ response.addCookie(newCookieBuilder(request).setName(CSRF_STATE_COOKIE).setValue(sha256Hex(state)).setHttpOnly(true).setExpiry(-1).build());
return state;
}
String hashInCookie = cookie.getValue();
// remove cookie
- response.addCookie(createCookie(CSRF_STATE_COOKIE, null, true, 0, request));
+ response.addCookie(newCookieBuilder(request).setName(CSRF_STATE_COOKIE).setValue(null).setHttpOnly(true).setExpiry(0).build());
String stateInRequest = request.getParameter("state");
if (isBlank(stateInRequest) || !sha256Hex(stateInRequest).equals(hashInCookie)) {
import org.sonar.server.ws.ServletFilterHandler;
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
-import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
import static org.sonar.server.authentication.ws.AuthenticationWs.AUTHENTICATION_CONTROLLER;
import static org.sonarqube.ws.client.WsRequest.Method.POST;
}
private void logout(HttpServletRequest request, HttpServletResponse response) {
+ generateAuthenticationEvent(request, response);
+ jwtHttpHandler.removeToken(request, response);
+ }
+
+ /**
+ * The generation of the authentication event should not prevent the removal of JWT cookie, that's why it's done in a separate method
+ */
+ private void generateAuthenticationEvent(HttpServletRequest request, HttpServletResponse response) {
try {
- generateAuthenticationEvent(request, response);
- jwtHttpHandler.removeToken(request, response);
+ Optional<JwtHttpHandler.Token> token = jwtHttpHandler.getToken(request, response);
+ String userLogin = token.isPresent() ? token.get().getUserDto().getLogin() : null;
+ authenticationEvent.logoutSuccess(request, userLogin);
} catch (AuthenticationException e) {
- response.setStatus(HTTP_UNAUTHORIZED);
authenticationEvent.logoutFailure(request, e.getMessage());
}
}
- private void generateAuthenticationEvent(HttpServletRequest request, HttpServletResponse response) {
- Optional<JwtHttpHandler.Token> token = jwtHttpHandler.getToken(request, response);
- String userLogin = token.isPresent() ? token.get().getUserDto().getLogin() : null;
- authenticationEvent.logoutSuccess(request, userLogin);
- }
-
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// Nothing to do
+++ /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 org.sonar.server.authentication;
-
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletRequest;
-import org.junit.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class CookieUtilsTest {
-
- private static final String HTTPS_HEADER = "X-Forwarded-Proto";
-
- HttpServletRequest request = mock(HttpServletRequest.class);
-
- @Test
- public void create_cookie() throws Exception {
- Cookie cookie = CookieUtils.createCookie("name", "value", true, 10, request);
- assertThat(cookie.getName()).isEqualTo("name");
- assertThat(cookie.getValue()).isEqualTo("value");
- assertThat(cookie.isHttpOnly()).isTrue();
- assertThat(cookie.getMaxAge()).isEqualTo(10);
- assertThat(cookie.getSecure()).isFalse();
- }
-
- @Test
- public void create_not_secured_cookie_when_header_is_not_http() throws Exception {
- when(request.getHeader(HTTPS_HEADER)).thenReturn("http");
- Cookie cookie = CookieUtils.createCookie("name", "value", true, 10, request);
- assertThat(cookie.getSecure()).isFalse();
- }
-
- @Test
- public void create_secured_cookie_when_X_Forwarded_Proto_header_is_https() throws Exception {
- when(request.getHeader(HTTPS_HEADER)).thenReturn("https");
- Cookie cookie = CookieUtils.createCookie("name", "value", true, 10, request);
- assertThat(cookie.getSecure()).isTrue();
- }
-
- @Test
- public void create_secured_cookie_when_X_Forwarded_Proto_header_is_HTTPS() throws Exception {
- when(request.getHeader(HTTPS_HEADER)).thenReturn("HTTPS");
- Cookie cookie = CookieUtils.createCookie("name", "value", true, 10, request);
- assertThat(cookie.getSecure()).isTrue();
- }
-
- @Test
- public void find_cookie() throws Exception {
- Cookie cookie = new Cookie("name", "value");
- when(request.getCookies()).thenReturn(new Cookie[] {cookie});
-
- assertThat(CookieUtils.findCookie("name", request)).isPresent();
- assertThat(CookieUtils.findCookie("NAME", request)).isEmpty();
- assertThat(CookieUtils.findCookie("unknown", request)).isEmpty();
- }
-
- @Test
- public void does_not_fail_to_find_cookie_when_no_cookie() throws Exception {
- assertThat(CookieUtils.findCookie("unknown", request)).isEmpty();
- }
-}
--- /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 org.sonar.server.authentication;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.server.authentication.Cookies.findCookie;
+import static org.sonar.server.authentication.Cookies.newCookieBuilder;
+
+public class CookiesTest {
+
+ private static final String HTTPS_HEADER = "X-Forwarded-Proto";
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private HttpServletRequest request = mock(HttpServletRequest.class);
+
+ @Test
+ public void create_cookie() throws Exception {
+ Cookie cookie = newCookieBuilder(request).setName("name").setValue("value").setHttpOnly(true).setExpiry(10).build();
+ assertThat(cookie.getName()).isEqualTo("name");
+ assertThat(cookie.getValue()).isEqualTo("value");
+ assertThat(cookie.isHttpOnly()).isTrue();
+ assertThat(cookie.getMaxAge()).isEqualTo(10);
+ assertThat(cookie.getSecure()).isFalse();
+ assertThat(cookie.getPath()).isEqualTo("/");
+ }
+
+ @Test
+ public void create_cookie_without_value() throws Exception {
+ Cookie cookie = newCookieBuilder(request).setName("name").build();
+ assertThat(cookie.getName()).isEqualTo("name");
+ assertThat(cookie.getValue()).isNull();
+ }
+
+ @Test
+ public void create_cookie_when_web_context() throws Exception {
+ when(request.getContextPath()).thenReturn("/sonarqube");
+ Cookie cookie = newCookieBuilder(request).setName("name").setValue("value").setHttpOnly(true).setExpiry(10).build();
+ assertThat(cookie.getName()).isEqualTo("name");
+ assertThat(cookie.getValue()).isEqualTo("value");
+ assertThat(cookie.isHttpOnly()).isTrue();
+ assertThat(cookie.getMaxAge()).isEqualTo(10);
+ assertThat(cookie.getSecure()).isFalse();
+ assertThat(cookie.getPath()).isEqualTo("/sonarqube");
+ }
+
+ @Test
+ public void create_not_secured_cookie_when_header_is_not_http() throws Exception {
+ when(request.getHeader(HTTPS_HEADER)).thenReturn("http");
+ Cookie cookie = newCookieBuilder(request).setName("name").setValue("value").setHttpOnly(true).setExpiry(10).build();
+ assertThat(cookie.getSecure()).isFalse();
+ }
+
+ @Test
+ public void create_secured_cookie_when_X_Forwarded_Proto_header_is_https() throws Exception {
+ when(request.getHeader(HTTPS_HEADER)).thenReturn("https");
+ Cookie cookie = newCookieBuilder(request).setName("name").setValue("value").setHttpOnly(true).setExpiry(10).build();
+ assertThat(cookie.getSecure()).isTrue();
+ }
+
+ @Test
+ public void create_secured_cookie_when_X_Forwarded_Proto_header_is_HTTPS() throws Exception {
+ when(request.getHeader(HTTPS_HEADER)).thenReturn("HTTPS");
+ Cookie cookie = newCookieBuilder(request).setName("name").setValue("value").setHttpOnly(true).setExpiry(10).build();
+ assertThat(cookie.getSecure()).isTrue();
+ }
+
+ @Test
+ public void find_cookie() throws Exception {
+ Cookie cookie = newCookieBuilder(request).setName("name").setValue("value").build();
+ when(request.getCookies()).thenReturn(new Cookie[] {cookie});
+
+ assertThat(findCookie("name", request)).isPresent();
+ assertThat(findCookie("NAME", request)).isEmpty();
+ assertThat(findCookie("unknown", request)).isEmpty();
+ }
+
+ @Test
+ public void does_not_fail_to_find_cookie_when_no_cookie() throws Exception {
+ assertThat(findCookie("unknown", request)).isEmpty();
+ }
+
+ @Test
+ public void fail_with_NPE_when_cookie_name_is_null() throws Exception {
+ expectedException.expect(NullPointerException.class);
+ newCookieBuilder(request).setName(null);
+ }
+
+ @Test
+ public void fail_with_NPE_when_cookie_has_no_name() throws Exception {
+ expectedException.expect(NullPointerException.class);
+ newCookieBuilder(request).setName(null);
+ }
+
+}
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doThrow;
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;
executeRequest();
verify(authenticationEvent).logoutFailure(request, "error!");
- verify(jwtHttpHandler, never()).removeToken(any(HttpServletRequest.class), any(HttpServletResponse.class));
- verify(response).setStatus(401);
+ verify(jwtHttpHandler).removeToken(any(HttpServletRequest.class), any(HttpServletResponse.class));
verifyZeroInteractions(chain);
}