import org.sonar.core.platform.Module;
import org.sonar.server.authentication.ws.AuthenticationWs;
import org.sonar.server.authentication.ws.LoginAction;
+import org.sonar.server.authentication.ws.ValidateAction;
public class AuthenticationModule extends Module {
@Override
LoginAction.class,
CredentialsAuthenticator.class,
RealmAuthenticator.class,
- BasicAuthenticator.class);
+ BasicAuthenticator.class,
+ ValidateAction.class);
}
}
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;
"/sessions/*",
"/api/system/db_migration_status", "/api/system/status", "/api/system/migrate_db",
"/api/server/*",
- AUTH_LOGIN_URL);
+ AUTH_LOGIN_URL, AUTH_VALIDATE_URL);
private static final UrlPattern URL_PATTERN = UrlPattern.builder()
.includes("/*")
NewAction action = controller.createAction("validate")
.setDescription("Check credentials.")
.setSince("3.3")
- .setHandler(RailsHandler.INSTANCE)
+ .setHandler(ServletFilterHandler.INSTANCE)
.setResponseExample(Resources.getResource(this.getClass(), "example-validate.json"));
RailsHandler.addFormatParam(action);
--- /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.ws;
+
+import static org.sonar.api.CoreProperties.CORE_FORCE_AUTHENTICATION_PROPERTY;
+
+import java.io.IOException;
+import java.util.Optional;
+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.config.Settings;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.api.web.ServletFilter;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.authentication.BasicAuthenticator;
+import org.sonar.server.authentication.JwtHttpHandler;
+import org.sonar.server.exceptions.UnauthorizedException;
+import org.sonarqube.ws.MediaTypes;
+
+public class ValidateAction extends ServletFilter {
+
+ public static final String AUTH_VALIDATE_URL = "/api/authentication/validate";
+
+ private final Settings settings;
+ private final JwtHttpHandler jwtHttpHandler;
+ private final BasicAuthenticator basicAuthenticator;
+
+ public ValidateAction(Settings settings, BasicAuthenticator basicAuthenticator, JwtHttpHandler jwtHttpHandler) {
+ this.settings = settings;
+ this.basicAuthenticator = basicAuthenticator;
+ this.jwtHttpHandler = jwtHttpHandler;
+ }
+
+ @Override
+ public UrlPattern doGetPattern() {
+ return UrlPattern.create(AUTH_VALIDATE_URL);
+ }
+
+ @Override
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
+ HttpServletRequest request = (HttpServletRequest) servletRequest;
+ HttpServletResponse response = (HttpServletResponse) servletResponse;
+
+ boolean isAuthenticated = authenticate(request, response);
+ response.setContentType(MediaTypes.JSON);
+
+ JsonWriter jsonWriter = JsonWriter.of(response.getWriter());
+ jsonWriter.beginObject();
+ jsonWriter.prop("valid", isAuthenticated);
+ jsonWriter.endObject();
+ }
+
+ private boolean authenticate(HttpServletRequest request, HttpServletResponse response) {
+ try {
+ Optional<UserDto> user = jwtHttpHandler.validateToken(request, response);
+ if (user.isPresent()) {
+ return true;
+ }
+ user = basicAuthenticator.authenticate(request);
+ if (user.isPresent()) {
+ return true;
+ }
+ return !settings.getBoolean(CORE_FORCE_AUTHENTICATION_PROPERTY);
+ } catch (UnauthorizedException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ // Nothing to do
+ }
+
+ @Override
+ public void destroy() {
+ // Nothing to do
+ }
+}
assertPathIsNotIgnored("/foo");
assertPathIsIgnored("/api/authentication/login");
+ assertPathIsIgnored("/api/authentication/validate");
assertPathIsIgnored("/batch/index");
assertPathIsIgnored("/batch/file");
assertPathIsIgnored("/maintenance/index");
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;
WebService.Action validate = controller.action("validate");
assertThat(validate).isNotNull();
- assertThat(validate.handler()).isInstanceOf(RailsHandler.class);
+ assertThat(validate.handler()).isInstanceOf(ServletFilterHandler.class);
assertThat(validate.responseExampleAsString()).isNotEmpty();
assertThat(validate.params()).hasSize(1);
--- /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.ws;
+
+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 java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Optional;
+import javax.servlet.FilterChain;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.config.Settings;
+import org.sonar.server.authentication.BasicAuthenticator;
+import org.sonar.server.authentication.JwtHttpHandler;
+import org.sonar.server.exceptions.UnauthorizedException;
+import org.sonar.test.JsonAssert;
+import org.sonarqube.ws.MediaTypes;
+
+public class ValidateActionTest {
+
+ StringWriter stringWriter = new StringWriter();
+
+ HttpServletRequest request = mock(HttpServletRequest.class);
+ HttpServletResponse response = mock(HttpServletResponse.class);
+ FilterChain chain = mock(FilterChain.class);
+
+ BasicAuthenticator basicAuthenticator = mock(BasicAuthenticator.class);
+ JwtHttpHandler jwtHttpHandler = mock(JwtHttpHandler.class);
+
+ Settings settings = new Settings();
+
+ ValidateAction underTest = new ValidateAction(settings, basicAuthenticator, jwtHttpHandler);
+
+ @Before
+ public void setUp() throws Exception {
+ PrintWriter writer = new PrintWriter(stringWriter);
+ when(response.getWriter()).thenReturn(writer);
+ }
+
+ @Test
+ public void return_true_when_jwt_token_is_set() throws Exception {
+ when(jwtHttpHandler.validateToken(request, response)).thenReturn(Optional.of(newUserDto()));
+ when(basicAuthenticator.authenticate(request)).thenReturn(Optional.empty());
+
+ underTest.doFilter(request, response, chain);
+
+ verify(response).setContentType(MediaTypes.JSON);
+ JsonAssert.assertJson(stringWriter.toString()).isSimilarTo("{\"valid\":true}");
+ }
+
+ @Test
+ public void return_true_when_basic_auth() throws Exception {
+ when(jwtHttpHandler.validateToken(request, response)).thenReturn(Optional.empty());
+ when(basicAuthenticator.authenticate(request)).thenReturn(Optional.of(newUserDto()));
+
+ underTest.doFilter(request, response, chain);
+
+ verify(response).setContentType(MediaTypes.JSON);
+ JsonAssert.assertJson(stringWriter.toString()).isSimilarTo("{\"valid\":true}");
+ }
+
+ @Test
+ public void return_true_when_no_jwt_nor_basic_auth_and_no_force_authentication() throws Exception {
+ settings.setProperty("sonar.forceAuthentication", "false");
+ when(jwtHttpHandler.validateToken(request, response)).thenReturn(Optional.empty());
+ when(basicAuthenticator.authenticate(request)).thenReturn(Optional.empty());
+
+ underTest.doFilter(request, response, chain);
+
+ verify(response).setContentType(MediaTypes.JSON);
+ JsonAssert.assertJson(stringWriter.toString()).isSimilarTo("{\"valid\":true}");
+ }
+
+ @Test
+ public void return_false_when_no_jwt_nor_basic_auth_and_force_authentication_is_true() throws Exception {
+ settings.setProperty("sonar.forceAuthentication", "true");
+ when(jwtHttpHandler.validateToken(request, response)).thenReturn(Optional.empty());
+ when(basicAuthenticator.authenticate(request)).thenReturn(Optional.empty());
+
+ underTest.doFilter(request, response, chain);
+
+ verify(response).setContentType(MediaTypes.JSON);
+ JsonAssert.assertJson(stringWriter.toString()).isSimilarTo("{\"valid\":false}");
+ }
+
+ @Test
+ public void return_false_when_jwt_throws_unauthorized_exception() throws Exception {
+ doThrow(UnauthorizedException.class).when(jwtHttpHandler).validateToken(request, response);
+ when(basicAuthenticator.authenticate(request)).thenReturn(Optional.empty());
+
+ underTest.doFilter(request, response, chain);
+
+ verify(response).setContentType(MediaTypes.JSON);
+ JsonAssert.assertJson(stringWriter.toString()).isSimilarTo("{\"valid\":false}");
+ }
+
+ @Test
+ public void return_false_when_basic_authenticator_throws_unauthorized_exception() throws Exception {
+ when(jwtHttpHandler.validateToken(request, response)).thenReturn(Optional.empty());
+ doThrow(UnauthorizedException.class).when(basicAuthenticator).authenticate(request);
+
+ underTest.doFilter(request, response, chain);
+
+ verify(response).setContentType(MediaTypes.JSON);
+ JsonAssert.assertJson(stringWriter.toString()).isSimilarTo("{\"valid\":false}");
+ }
+}
+++ /dev/null
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube 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.
-#
-# SonarQube 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.
-#
-class Api::AuthenticationController < Api::ApiController
- skip_before_filter :check_authentication, :set_user_session
-
- # prevent HTTP proxies from caching authentication status
- before_filter :set_cache_buster
-
- #
- # GET /api/authentication/validate
- # curl http://localhost:9000/api/authentication/validate -v -u admin:admin
- #
- # Since v.3.3
- def validate
- hash={:valid => valid?}
-
- # make sure no authentication information is left by
- # this validation
- reset_session
-
- respond_to do |format|
- format.json { render :json => jsonp(hash) }
- format.xml { render :xml => hash.to_xml(:skip_types => true, :root => 'authentication') }
- format.text { render :text => text_not_supported }
- end
- end
-
- private
-
- def valid?
- begin
- logged_in? || (!force_authentication? && anonymous?)
- rescue Errors::AccessDenied
- false
- end
- end
-
- def force_authentication?
- property = Property.by_key(org.sonar.api.CoreProperties.CORE_FORCE_AUTHENTICATION_PROPERTY)
- property ? property.value == 'true' : false
- end
-
- def anonymous?
- current_user.nil?
- end
-
- def set_cache_buster
- response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
- response.headers["Pragma"] = "no-cache"
- response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
- end
-
-end