]> source.dussan.org Git - sonarqube.git/commitdiff
SONARCLOUD-213 add core extension CustomAuthentication
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Fri, 30 Nov 2018 21:13:12 +0000 (22:13 +0100)
committerSonarTech <sonartech@sonarsource.com>
Wed, 12 Dec 2018 19:21:02 +0000 (20:21 +0100)
server/sonar-server/src/main/java/org/sonar/server/authentication/BasicAuthentication.java
server/sonar-server/src/main/java/org/sonar/server/authentication/CustomAuthentication.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/authentication/RequestAuthenticator.java
server/sonar-server/src/main/java/org/sonar/server/authentication/RequestAuthenticatorImpl.java
server/sonar-server/src/test/java/org/sonar/server/authentication/RequestAuthenticatorImplTest.java

index 28dc21f8573e88dc35c116973bf5d663747e28c9..0ecd1f6b72f1b9c22c8468a5c1ba0ea30d7e377d 100644 (file)
@@ -30,7 +30,7 @@ import org.sonar.server.authentication.event.AuthenticationException;
 import org.sonar.server.usertoken.UserTokenAuthentication;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.util.Locale.ENGLISH;
+import static org.apache.commons.lang.StringUtils.startsWithIgnoreCase;
 import static org.sonar.server.authentication.event.AuthenticationEvent.Method;
 import static org.sonar.server.authentication.event.AuthenticationEvent.Source;
 
@@ -43,11 +43,6 @@ import static org.sonar.server.authentication.event.AuthenticationEvent.Source;
  */
 public class BasicAuthentication {
 
-  private static final Base64.Decoder BASE64_DECODER = Base64.getDecoder();
-
-  private static final String AUTHORIZATION_HEADER = "Authorization";
-  private static final String BASIC_AUTHORIZATION = "BASIC";
-
   private final DbClient dbClient;
   private final CredentialsAuthentication credentialsAuthentication;
   private final UserTokenAuthentication userTokenAuthentication;
@@ -62,17 +57,16 @@ public class BasicAuthentication {
   }
 
   public Optional<UserDto> authenticate(HttpServletRequest request) {
-    String authorizationHeader = request.getHeader(AUTHORIZATION_HEADER);
-    if (authorizationHeader == null || !authorizationHeader.toUpperCase(ENGLISH).startsWith(BASIC_AUTHORIZATION)) {
+    return extractCredentialsFromHeader(request)
+      .flatMap(credentials -> Optional.of(authenticate(credentials, request)));
+  }
+
+  public static Optional<Credentials> extractCredentialsFromHeader(HttpServletRequest request) {
+    String authorizationHeader = request.getHeader("Authorization");
+    if (authorizationHeader == null || !startsWithIgnoreCase(authorizationHeader, "BASIC")) {
       return Optional.empty();
     }
 
-    Credentials credentials = extractCredentials(authorizationHeader);
-    UserDto userDto = authenticate(credentials, request);
-    return Optional.of(userDto);
-  }
-
-  private static Credentials extractCredentials(String authorizationHeader) {
     String basicAuthEncoded = authorizationHeader.substring(6);
     String basicAuthDecoded = getDecodedBasicAuth(basicAuthEncoded);
 
@@ -85,12 +79,12 @@ public class BasicAuthentication {
     }
     String login = basicAuthDecoded.substring(0, semiColonPos);
     String password = basicAuthDecoded.substring(semiColonPos + 1);
-    return new Credentials(login, password);
+    return Optional.of(new Credentials(login, password));
   }
 
   private static String getDecodedBasicAuth(String basicAuthEncoded) {
     try {
-      return new String(BASE64_DECODER.decode(basicAuthEncoded.getBytes(UTF_8)), UTF_8);
+      return new String(Base64.getDecoder().decode(basicAuthEncoded.getBytes(UTF_8)), UTF_8);
     } catch (Exception e) {
       throw AuthenticationException.newBuilder()
         .setSource(Source.local(Method.BASIC))
diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/CustomAuthentication.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/CustomAuthentication.java
new file mode 100644 (file)
index 0000000..db20b60
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info 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.Optional;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.sonar.api.server.ServerSide;
+import org.sonar.server.user.UserSession;
+
+/**
+ * Authentication that can create {@link org.sonar.server.user.UserSession}
+ * that are not associated to a user.
+ * That is convenient for authenticating bots that need special permissions.
+ *
+ * This is not an extension point, plugins can not provide their own
+ * implementations.
+ */
+@ServerSide
+public interface CustomAuthentication {
+
+  Optional<UserSession> authenticate(HttpServletRequest request, HttpServletResponse response);
+
+}
index ab565c615c8e65723579fdd563faf73dd844e3a8..5f74851796a6f845ea890a746f9eccc7a0177c45 100644 (file)
@@ -21,9 +21,11 @@ package org.sonar.server.authentication;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import org.sonar.api.server.ServerSide;
 import org.sonar.server.authentication.event.AuthenticationException;
 import org.sonar.server.user.UserSession;
 
+@ServerSide
 public interface RequestAuthenticator {
 
   UserSession authenticate(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException;
index ee0f3e5381650ae9d755a04134f1d75bf5e49b14..1e23d5ee27a263d9e9423404b3aff91569c9efd6 100644 (file)
  */
 package org.sonar.server.authentication;
 
+import java.util.Arrays;
+import java.util.List;
 import java.util.Optional;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import org.sonar.db.user.UserDto;
-import org.sonar.server.authentication.event.AuthenticationException;
 import org.sonar.server.user.UserSession;
 import org.sonar.server.user.UserSessionFactory;
 
@@ -33,17 +34,31 @@ public class RequestAuthenticatorImpl implements RequestAuthenticator {
   private final BasicAuthentication basicAuthentication;
   private final HttpHeadersAuthentication httpHeadersAuthentication;
   private final UserSessionFactory userSessionFactory;
+  private final List<CustomAuthentication> customAuthentications;
 
   public RequestAuthenticatorImpl(JwtHttpHandler jwtHttpHandler, BasicAuthentication basicAuthentication, HttpHeadersAuthentication httpHeadersAuthentication,
-                                  UserSessionFactory userSessionFactory) throws AuthenticationException {
+    UserSessionFactory userSessionFactory, CustomAuthentication[] customAuthentications) {
     this.jwtHttpHandler = jwtHttpHandler;
     this.basicAuthentication = basicAuthentication;
     this.httpHeadersAuthentication = httpHeadersAuthentication;
     this.userSessionFactory = userSessionFactory;
+    this.customAuthentications = Arrays.asList(customAuthentications);
+  }
+
+  public RequestAuthenticatorImpl(JwtHttpHandler jwtHttpHandler, BasicAuthentication basicAuthentication, HttpHeadersAuthentication httpHeadersAuthentication,
+    UserSessionFactory userSessionFactory) {
+    this(jwtHttpHandler, basicAuthentication, httpHeadersAuthentication, userSessionFactory, new CustomAuthentication[0]);
   }
 
   @Override
   public UserSession authenticate(HttpServletRequest request, HttpServletResponse response) {
+    for (CustomAuthentication customAuthentication : customAuthentications) {
+      Optional<UserSession> session = customAuthentication.authenticate(request, response);
+      if (session.isPresent()) {
+        return session.get();
+      }
+    }
+
     Optional<UserDto> userOpt = loadUser(request, response);
     if (userOpt.isPresent()) {
       return userSessionFactory.create(userOpt.get());
index e6cd47ea8e472e3634472d3b352879925d9923f7..65d56484fe1d21e40e45d13ddb6a4c12f8a80726 100644 (file)
@@ -25,6 +25,8 @@ import javax.servlet.http.HttpServletResponse;
 import org.junit.Before;
 import org.junit.Test;
 import org.sonar.db.user.UserDto;
+import org.sonar.server.authentication.event.AuthenticationEvent;
+import org.sonar.server.authentication.event.AuthenticationException;
 import org.sonar.server.tester.AnonymousMockUserSession;
 import org.sonar.server.tester.MockUserSession;
 import org.sonar.server.user.UserSession;
@@ -48,7 +50,10 @@ public class RequestAuthenticatorImplTest {
   private BasicAuthentication basicAuthentication = mock(BasicAuthentication.class);
   private HttpHeadersAuthentication httpHeadersAuthentication = mock(HttpHeadersAuthentication.class);
   private UserSessionFactory sessionFactory = mock(UserSessionFactory.class);
-  private RequestAuthenticator underTest = new RequestAuthenticatorImpl(jwtHttpHandler, basicAuthentication, httpHeadersAuthentication, sessionFactory);
+  private CustomAuthentication customAuthentication1 = mock(CustomAuthentication.class);
+  private CustomAuthentication customAuthentication2 = mock(CustomAuthentication.class);
+  private RequestAuthenticator underTest = new RequestAuthenticatorImpl(jwtHttpHandler, basicAuthentication, httpHeadersAuthentication, sessionFactory,
+    new CustomAuthentication[]{customAuthentication1, customAuthentication2});
 
   @Before
   public void setUp() throws Exception {
@@ -102,4 +107,28 @@ public class RequestAuthenticatorImplTest {
     verify(response, never()).setStatus(anyInt());
   }
 
+  @Test
+  public void delegate_to_CustomAuthentication() {
+    when(customAuthentication1.authenticate(request, response)).thenReturn(Optional.of(new MockUserSession("foo")));
+
+    UserSession session = underTest.authenticate(request, response);
+
+    assertThat(session.getLogin()).isEqualTo("foo");
+  }
+
+  @Test
+  public void CustomAuthentication_has_priority_over_core_authentications() {
+    // use-case: both custom and core authentications check the HTTP header "Authorization".
+    // The custom authentication should be able to test the header because that the core authentication
+    // throws an exception.
+    when(customAuthentication1.authenticate(request, response)).thenReturn(Optional.of(new MockUserSession("foo")));
+    when(basicAuthentication.authenticate(request)).thenThrow(AuthenticationException.newBuilder()
+      .setSource(AuthenticationEvent.Source.sso())
+      .setMessage("message")
+      .build());
+
+    UserSession session = underTest.authenticate(request, response);
+
+    assertThat(session.getLogin()).isEqualTo("foo");
+  }
 }