Skip to content

Commit 63fd7f7

Browse files
committed
Add a Filter to handle basic authentication
We could also implement Digest authentication, but this will get us off the ground, and allow us to start to secure a public-facing Router.
1 parent 7f8544b commit 63fd7f7

File tree

9 files changed

+113
-29
lines changed

9 files changed

+113
-29
lines changed

java/client/test/org/openqa/selenium/devtools/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ java_selenium_test_suite(
2222
"//java/client/test/org/openqa/selenium/environment",
2323
"//java/client/test/org/openqa/selenium/testing:annotations",
2424
"//java/client/test/org/openqa/selenium/testing:test-base",
25+
"//java/server/src/org/openqa/selenium/grid/security",
2526
artifact("com.google.guava:guava"),
2627
artifact("junit:junit"),
2728
artifact("org.assertj:assertj-core"),

java/client/test/org/openqa/selenium/devtools/CdpFacadeTest.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,23 @@
1717

1818
package org.openqa.selenium.devtools;
1919

20+
import com.google.common.net.MediaType;
2021
import org.junit.AfterClass;
2122
import org.junit.BeforeClass;
2223
import org.junit.Test;
2324
import org.openqa.selenium.By;
2425
import org.openqa.selenium.HasAuthentication;
2526
import org.openqa.selenium.UsernameAndPassword;
26-
import org.openqa.selenium.environment.webserver.BasicAuthHandler;
2727
import org.openqa.selenium.environment.webserver.NettyAppServer;
28+
import org.openqa.selenium.grid.security.BasicAuthenticationFilter;
29+
import org.openqa.selenium.remote.http.Contents;
2830
import org.openqa.selenium.remote.http.HttpResponse;
2931
import org.openqa.selenium.remote.http.Route;
3032
import org.openqa.selenium.support.devtools.NetworkInterceptor;
31-
import org.openqa.selenium.testing.Ignore;
3233
import org.openqa.selenium.testing.NotYetImplemented;
3334
import org.openqa.selenium.testing.drivers.Browser;
3435

36+
import static java.nio.charset.StandardCharsets.UTF_8;
3537
import static org.assertj.core.api.Assertions.assertThat;
3638
import static org.assertj.core.api.Assumptions.assumeThat;
3739
import static org.openqa.selenium.remote.http.Contents.utf8String;
@@ -43,7 +45,12 @@ public class CdpFacadeTest extends DevToolsTestBase {
4345

4446
@BeforeClass
4547
public static void startServer() {
46-
server = new NettyAppServer(new BasicAuthHandler());
48+
server = new NettyAppServer(
49+
new BasicAuthenticationFilter("test", "test")
50+
.andFinally(req ->
51+
new HttpResponse()
52+
.addHeader("Content-Type", MediaType.HTML_UTF_8.toString())
53+
.setContent(Contents.string("<h1>authorized</h1>", UTF_8))));
4754
server.start();
4855
}
4956

java/client/test/org/openqa/selenium/environment/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ java_library(
5252
"//java/client/src/org/openqa/selenium/remote/http",
5353
"//java/client/test/org/openqa/selenium/build",
5454
"//java/server/src/org/openqa/selenium/grid/config",
55+
"//java/server/src/org/openqa/selenium/grid/security",
5556
"//java/server/src/org/openqa/selenium/grid/server",
5657
"//java/server/src/org/openqa/selenium/grid/web",
5758
"//java/server/src/org/openqa/selenium/netty/server",

java/client/test/org/openqa/selenium/environment/webserver/HandlersForTests.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@
1717

1818
package org.openqa.selenium.environment.webserver;
1919

20+
import com.google.common.net.MediaType;
2021
import org.openqa.selenium.build.InProject;
22+
import org.openqa.selenium.grid.security.BasicAuthenticationFilter;
2123
import org.openqa.selenium.grid.web.PathResource;
2224
import org.openqa.selenium.grid.web.ResourceHandler;
25+
import org.openqa.selenium.remote.http.Contents;
2326
import org.openqa.selenium.remote.http.HttpRequest;
2427
import org.openqa.selenium.remote.http.HttpResponse;
2528
import org.openqa.selenium.remote.http.Routable;
@@ -28,6 +31,7 @@
2831
import java.io.UncheckedIOException;
2932
import java.nio.file.Path;
3033

34+
import static java.nio.charset.StandardCharsets.UTF_8;
3135
import static org.openqa.selenium.remote.http.HttpMethod.GET;
3236

3337
public class HandlersForTests implements Routable {
@@ -46,7 +50,11 @@ public HandlersForTests(String hostname, int port, Path tempPageDir) {
4650
Path webSrc = InProject.locate("common/src/web");
4751

4852
Route route = Route.combine(
49-
Route.get("/basicAuth").to(BasicAuthHandler::new),
53+
Route.get("/basicAuth").to(() -> req ->
54+
new HttpResponse()
55+
.addHeader("Content-Type", MediaType.HTML_UTF_8.toString())
56+
.setContent(Contents.string("<h1>authorized</h1>", UTF_8)))
57+
.with(new BasicAuthenticationFilter("test", "test")),
5058
Route.get("/echo").to(EchoHandler::new),
5159
Route.get("/cookie").to(CookieHandler::new),
5260
Route.get("/encoding").to(EncodingHandler::new),

java/server/src/org/openqa/selenium/grid/security/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ java_library(
55
name = "security",
66
srcs = glob(["*.java"]),
77
visibility = [
8+
"//java/client/test/org/openqa/selenium:__subpackages__",
89
"//java/server/src/org/openqa/selenium/events:__subpackages__",
910
"//java/server/src/org/openqa/selenium/grid:__subpackages__",
1011
"//java/server/test/org/openqa/selenium/events:__subpackages__",

java/client/test/org/openqa/selenium/environment/webserver/BasicAuthHandler.java renamed to java/server/src/org/openqa/selenium/grid/security/BasicAuthenticationFilter.java

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,44 +15,48 @@
1515
// specific language governing permissions and limitations
1616
// under the License.
1717

18-
package org.openqa.selenium.environment.webserver;
18+
package org.openqa.selenium.grid.security;
1919

20-
import com.google.common.net.MediaType;
21-
import org.openqa.selenium.remote.http.Contents;
20+
import org.openqa.selenium.internal.Require;
21+
import org.openqa.selenium.remote.http.Filter;
2222
import org.openqa.selenium.remote.http.HttpHandler;
23-
import org.openqa.selenium.remote.http.HttpRequest;
2423
import org.openqa.selenium.remote.http.HttpResponse;
2524

26-
import java.io.UncheckedIOException;
2725
import java.net.HttpURLConnection;
2826
import java.util.Base64;
2927

3028
import static java.nio.charset.StandardCharsets.UTF_8;
3129

32-
public class BasicAuthHandler implements HttpHandler {
33-
private static final String CREDENTIALS = "test:test";
34-
private final Base64.Decoder decoder = Base64.getDecoder();
30+
public class BasicAuthenticationFilter implements Filter {
31+
32+
private static final Base64.Decoder DECODER = Base64.getDecoder();
33+
private final String passphrase;
34+
35+
public BasicAuthenticationFilter(String user, String password) {
36+
passphrase = Base64.getEncoder().encodeToString((user + ":" + password).getBytes(UTF_8));
37+
}
3538

3639
@Override
37-
public HttpResponse execute(HttpRequest req) throws UncheckedIOException {
38-
if (isAuthorized(req.getHeader("Authorization"))) {
39-
return new HttpResponse()
40-
.addHeader("Content-Type", MediaType.HTML_UTF_8.toString())
41-
.setContent(Contents.string("<h1>authorized</h1>", UTF_8));
42-
}
40+
public HttpHandler apply(HttpHandler next) {
41+
return req -> {
42+
Require.nonNull("Request", req);
43+
44+
if (!isAuthorized(req.getHeader("Authorization"))) {
45+
return new HttpResponse()
46+
.setStatus(HttpURLConnection.HTTP_UNAUTHORIZED)
47+
.addHeader("WWW-Authenticate", "Basic realm=\"selenium-server\"");
48+
}
4349

44-
return new HttpResponse()
45-
.setStatus(HttpURLConnection.HTTP_UNAUTHORIZED)
46-
.addHeader("WWW-Authenticate", "Basic realm=\"basic-auth-test\"");
50+
return next.execute(req);
51+
};
4752
}
4853

4954
private boolean isAuthorized(String auth) {
5055
if (auth != null) {
5156
final int index = auth.indexOf(' ') + 1;
5257

5358
if (index > 0) {
54-
final String credentials = new String(decoder.decode(auth.substring(index)), UTF_8);
55-
return CREDENTIALS.equals(credentials);
59+
return passphrase.equals(auth.substring(index));
5660
}
5761
}
5862

java/server/src/org/openqa/selenium/grid/web/AuthenticationFilter.java

Lines changed: 0 additions & 6 deletions
This file was deleted.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
load("//java:defs.bzl", "artifact", "java_test_suite")
2+
3+
java_test_suite(
4+
name = "small-tests",
5+
size = "small",
6+
srcs = glob(["*.java"]),
7+
deps = [
8+
"//java/client/src/org/openqa/selenium/remote/http",
9+
"//java/server/src/org/openqa/selenium/grid/security",
10+
artifact("junit:junit"),
11+
artifact("org.assertj:assertj-core"),
12+
]
13+
)
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.openqa.selenium.grid.security;
19+
20+
import org.junit.Test;
21+
import org.openqa.selenium.remote.http.HttpHandler;
22+
import org.openqa.selenium.remote.http.HttpRequest;
23+
import org.openqa.selenium.remote.http.HttpResponse;
24+
25+
import java.net.HttpURLConnection;
26+
import java.util.Base64;
27+
28+
import static java.nio.charset.StandardCharsets.UTF_8;
29+
import static org.assertj.core.api.Assertions.assertThat;
30+
import static org.openqa.selenium.remote.http.HttpMethod.GET;
31+
32+
public class BasicAuthenticationFilterTest {
33+
34+
@Test
35+
public void shouldAskAnUnauthenticatedRequestToAuthenticate() {
36+
HttpHandler handler = new BasicAuthenticationFilter("cheese", "cheddar").apply(req -> new HttpResponse());
37+
38+
HttpResponse res = handler.execute(new HttpRequest(GET, "/"));
39+
40+
assertThat(res.getStatus()).isEqualTo(HttpURLConnection.HTTP_UNAUTHORIZED);
41+
assertThat(res.getHeader("Www-Authenticate")).startsWith("Basic ");
42+
assertThat(res.getHeader("Www-Authenticate")).contains("Basic ");
43+
}
44+
45+
@Test
46+
public void shouldAllowAuthenticatedTrafficThrough() {
47+
HttpHandler handler = new BasicAuthenticationFilter("cheese", "cheddar").apply(req -> new HttpResponse());
48+
49+
HttpResponse res = handler.execute(
50+
new HttpRequest(GET, "/")
51+
.setHeader("Authorization", "Basic " + Base64.getEncoder().encodeToString("cheese:cheddar".getBytes(UTF_8))));
52+
53+
assertThat(res.isSuccessful()).isTrue();
54+
}
55+
}

0 commit comments

Comments
 (0)