Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ public void configure(WebSocketServletFactory factory) {
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
if (factory.isUpgradeRequest(request, response)) {
try {
if (!validator.validateWebSocketUpgrade(request, response)) {
return;
}
final ServletUpgradeRequest upReq = new ServletUpgradeRequest(request);
for (String subProtocol : upReq.getSubProtocols()) {
if (subProtocol.startsWith("graphql")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,25 @@ public GraphQLServletSecurityValidator() {
parser = new Parser();
}

/**
* Authenticates a WebSocket upgrade. Subscriptions are never public, so only Basic
* JAAS credentials are accepted.
*
* @return true when the caller is authenticated
*/
public boolean validateWebSocketUpgrade(HttpServletRequest req, HttpServletResponse res) throws IOException {
if (req.getHeader("Authorization") == null) {
res.addHeader("WWW-Authenticate", "Basic realm=\"karaf\"");
res.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
if (isAuthenticatedUser(req)) {
return true;
}
res.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}

public boolean validate(String query, String operationName, HttpServletRequest req, HttpServletResponse res) throws IOException {
if (isPublicOperation(query)) {
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import io.reactivex.subscribers.DefaultSubscriber;
import org.eclipse.jetty.websocket.api.RemoteEndpoint;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.UpgradeException;
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient;
Expand All @@ -32,9 +33,12 @@
import org.slf4j.LoggerFactory;

import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

Expand All @@ -52,6 +56,7 @@ public void testWebSocketConnectionSegment() throws Exception {

URI echoUri = new URI("ws://localhost:" + getHttpPort() + "/graphql");
ClientUpgradeRequest request = new ClientUpgradeRequest();
request.setHeader("Authorization", basicAuthHeader(BASIC_AUTH_USER_NAME, BASIC_AUTH_PASSWORD));

Future<Session> onConnected = client.connect(socket, echoUri, request);
RemoteEndpoint remote = onConnected.get().getRemote();
Expand All @@ -74,15 +79,52 @@ public void testWebSocketConnectionSegment() throws Exception {

LOGGER.info("Waiting for socket to close...");

CloseStatus status = socket.waitClose().get(10, TimeUnit.SECONDS);
// Assert.assertEquals(1000, (int) status.getStatus()); TODO skip for now
socket.waitClose().get(10, TimeUnit.SECONDS);

} finally {
client.stop();
LOGGER.info("Web socket client stopped.");
}
}

@Test
public void testWebSocketUpgrade_withoutAuth_returns401() throws Exception {
assertWebSocketUpgradeRejected(new ClientUpgradeRequest());
}


@Test
public void testWebSocketUpgrade_withWrongJaasPassword_returns401() throws Exception {
ClientUpgradeRequest request = new ClientUpgradeRequest();
request.setHeader("Authorization", basicAuthHeader(BASIC_AUTH_USER_NAME, "definitely-not-the-password"));
assertWebSocketUpgradeRejected(request);
}


private void assertWebSocketUpgradeRejected(ClientUpgradeRequest request) throws Exception {
WebSocketClient client = new WebSocketClient();
Socket socket = new Socket();
try {
client.start();
URI echoUri = new URI("ws://localhost:" + getHttpPort() + "/graphql");
Future<Session> onConnected = client.connect(socket, echoUri, request);
try {
onConnected.get(10, TimeUnit.SECONDS);
Assert.fail("Unauthenticated GraphQL WebSocket upgrade should be rejected");
} catch (ExecutionException e) {
Throwable cause = e.getCause();
Assert.assertTrue("Expected UpgradeException, got: " + cause, cause instanceof UpgradeException);
Assert.assertEquals(401, ((UpgradeException) cause).getResponseStatusCode());
}
} finally {
client.stop();
}
}

private static String basicAuthHeader(String user, String password) {
return "Basic " + Base64.getEncoder().encodeToString((user + ":" + password).getBytes(StandardCharsets.UTF_8));
}

private class Socket extends WebSocketAdapter {

private Flowable<String> publisher;
Expand Down