forked from quarkusio/quarkus
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for GraphQL WebSocket authorization
- Loading branch information
1 parent
c8c2e05
commit 8099553
Showing
16 changed files
with
807 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
207 changes: 207 additions & 0 deletions
207
...s/smallrye/graphql/client/deployment/DynamicGraphQLClientWebSocketAuthenticationTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
package io.quarkus.smallrye.graphql.client.deployment; | ||
|
||
import static org.awaitility.Awaitility.await; | ||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
import static org.junit.jupiter.api.Assertions.assertFalse; | ||
import static org.junit.jupiter.api.Assertions.assertNotNull; | ||
import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
||
import java.util.concurrent.atomic.AtomicBoolean; | ||
|
||
import jakarta.annotation.security.RolesAllowed; | ||
import jakarta.json.JsonValue; | ||
|
||
import org.eclipse.microprofile.graphql.GraphQLApi; | ||
import org.eclipse.microprofile.graphql.Query; | ||
import org.jboss.shrinkwrap.api.asset.EmptyAsset; | ||
import org.junit.jupiter.api.Assertions; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.RegisterExtension; | ||
|
||
import io.quarkus.test.QuarkusUnitTest; | ||
import io.smallrye.common.annotation.NonBlocking; | ||
import io.smallrye.graphql.api.Subscription; | ||
import io.smallrye.graphql.client.Response; | ||
import io.smallrye.graphql.client.dynamic.api.DynamicGraphQLClient; | ||
import io.smallrye.graphql.client.dynamic.api.DynamicGraphQLClientBuilder; | ||
import io.smallrye.mutiny.Multi; | ||
|
||
/** | ||
* Due to the complexity of establishing a WebSocket, WebSocket/Subscription testing of the GraphQL server is done here, | ||
* as the client framework comes in very useful for establishing the connection to the server. | ||
* <br> | ||
* This test establishes connections to the server, and ensures that the connected user has the necessary permissions to | ||
* execute the operation. | ||
*/ | ||
public class DynamicGraphQLClientWebSocketAuthenticationTest { | ||
|
||
static String url = "http://" + System.getProperty("quarkus.http.host", "localhost") + ":" + | ||
System.getProperty("quarkus.http.test-port", "8081") + "/graphql"; | ||
|
||
@RegisterExtension | ||
static QuarkusUnitTest test = new QuarkusUnitTest() | ||
.withApplicationRoot((jar) -> jar | ||
.addClasses(SecuredApi.class, Foo.class) | ||
.addAsResource("application-secured.properties", "application.properties") | ||
.addAsResource("users.properties") | ||
.addAsResource("roles.properties") | ||
.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml")); | ||
|
||
@Test | ||
public void testAuthenticatedUserForSubscription() throws Exception { | ||
DynamicGraphQLClientBuilder clientBuilder = DynamicGraphQLClientBuilder.newBuilder() | ||
.url(url) | ||
.header("Authorization", "Basic ZGF2aWQ6cXdlcnR5MTIz"); | ||
try (DynamicGraphQLClient client = clientBuilder.build()) { | ||
Multi<Response> subscription = client | ||
.subscription("subscription fooSub { fooSub { message } }"); | ||
|
||
assertNotNull(subscription); | ||
|
||
AtomicBoolean hasData = new AtomicBoolean(false); | ||
AtomicBoolean hasCompleted = new AtomicBoolean(false); | ||
|
||
subscription.subscribe().with(item -> { | ||
assertFalse(hasData.get()); | ||
assertTrue(item.hasData()); | ||
assertEquals(JsonValue.ValueType.OBJECT, item.getData().get("fooSub").getValueType()); | ||
assertEquals("foo", item.getData().getJsonObject("fooSub").getString("message")); | ||
hasData.set(true); | ||
}, Assertions::fail, () -> { | ||
hasCompleted.set(true); | ||
}); | ||
|
||
await().untilTrue(hasCompleted); | ||
assertTrue(hasData.get()); | ||
} | ||
} | ||
|
||
@Test | ||
public void testAuthenticatedUserForQueryWebSocket() throws Exception { | ||
DynamicGraphQLClientBuilder clientBuilder = DynamicGraphQLClientBuilder.newBuilder() | ||
.url(url) | ||
.header("Authorization", "Basic ZGF2aWQ6cXdlcnR5MTIz") | ||
.executeSingleOperationsOverWebsocket(true); | ||
try (DynamicGraphQLClient client = clientBuilder.build()) { | ||
Response response = client.executeSync("{ foo { message} }"); | ||
assertTrue(response.hasData()); | ||
assertEquals("foo", response.getData().getJsonObject("foo").getString("message")); | ||
} | ||
} | ||
|
||
@Test | ||
public void testAuthorizedAndUnauthorizedForQueryWebSocket() throws Exception { | ||
DynamicGraphQLClientBuilder clientBuilder = DynamicGraphQLClientBuilder.newBuilder() | ||
.url(url) | ||
.header("Authorization", "Basic ZGF2aWQ6cXdlcnR5MTIz") | ||
.executeSingleOperationsOverWebsocket(true); | ||
try (DynamicGraphQLClient client = clientBuilder.build()) { | ||
Response response = client.executeSync("{ foo { message} }"); | ||
assertTrue(response.hasData()); | ||
assertEquals("foo", response.getData().getJsonObject("foo").getString("message")); | ||
|
||
// Run a second query with a different result to validate that the result of the first query isn't being cached at all. | ||
response = client.executeSync("{ bar { message} }"); | ||
assertEquals(JsonValue.ValueType.NULL, response.getData().get("bar").getValueType()); | ||
} | ||
} | ||
|
||
@Test | ||
public void testUnauthorizedUserForSubscription() throws Exception { | ||
DynamicGraphQLClientBuilder clientBuilder = DynamicGraphQLClientBuilder.newBuilder() | ||
.url(url) | ||
.header("Authorization", "Basic ZGF2aWQ6cXdlcnR5MTIz"); | ||
try (DynamicGraphQLClient client = clientBuilder.build()) { | ||
Multi<Response> subscription = client | ||
.subscription("subscription barSub { barSub { message } }"); | ||
|
||
assertNotNull(subscription); | ||
|
||
AtomicBoolean returned = new AtomicBoolean(false); | ||
|
||
subscription.subscribe().with(item -> { | ||
assertEquals(JsonValue.ValueType.NULL, item.getData().get("barSub").getValueType()); | ||
returned.set(true); | ||
}, throwable -> Assertions.fail(throwable)); | ||
|
||
await().untilTrue(returned); | ||
} | ||
} | ||
|
||
@Test | ||
public void testUnauthorizedUserForQueryWebSocket() throws Exception { | ||
DynamicGraphQLClientBuilder clientBuilder = DynamicGraphQLClientBuilder.newBuilder() | ||
.url(url) | ||
.header("Authorization", "Basic ZGF2aWQ6cXdlcnR5MTIz") | ||
.executeSingleOperationsOverWebsocket(true); | ||
try (DynamicGraphQLClient client = clientBuilder.build()) { | ||
Response response = client.executeSync("{ bar { message } }"); | ||
assertEquals(JsonValue.ValueType.NULL, response.getData().get("bar").getValueType()); | ||
} | ||
} | ||
|
||
@Test | ||
public void testUnauthenticatedForQueryWebSocket() throws Exception { | ||
DynamicGraphQLClientBuilder clientBuilder = DynamicGraphQLClientBuilder.newBuilder() | ||
.url(url) | ||
.executeSingleOperationsOverWebsocket(true); | ||
try (DynamicGraphQLClient client = clientBuilder.build()) { | ||
Response response = client.executeSync("{ foo { message} }"); | ||
assertEquals(JsonValue.ValueType.NULL, response.getData().get("foo").getValueType()); | ||
} | ||
} | ||
|
||
public static class Foo { | ||
|
||
private String message; | ||
|
||
public Foo(String foo) { | ||
this.message = foo; | ||
} | ||
|
||
public String getMessage() { | ||
return message; | ||
} | ||
|
||
public void setMessage(String message) { | ||
this.message = message; | ||
} | ||
|
||
} | ||
|
||
@GraphQLApi | ||
public static class SecuredApi { | ||
|
||
@Query | ||
@RolesAllowed("fooRole") | ||
@NonBlocking | ||
public Foo foo() { | ||
return new Foo("foo"); | ||
} | ||
|
||
@Query | ||
@RolesAllowed("barRole") | ||
public Foo bar() { | ||
return new Foo("bar"); | ||
} | ||
|
||
@Subscription | ||
@RolesAllowed("fooRole") | ||
public Multi<Foo> fooSub() { | ||
return Multi.createFrom().emitter(emitter -> { | ||
emitter.emit(new Foo("foo")); | ||
emitter.complete(); | ||
}); | ||
} | ||
|
||
@Subscription | ||
@RolesAllowed("barRole") | ||
public Multi<Foo> barSub() { | ||
return Multi.createFrom().emitter(emitter -> { | ||
emitter.emit(new Foo("bar")); | ||
emitter.complete(); | ||
}); | ||
} | ||
|
||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
...ions/smallrye-graphql-client/deployment/src/test/resources/application-secured.properties
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
quarkus.security.users.file.enabled=true | ||
quarkus.security.users.file.plain-text=true | ||
quarkus.security.users.file.users=users.properties | ||
quarkus.security.users.file.roles=roles.properties | ||
quarkus.http.auth.basic=true | ||
|
||
quarkus.smallrye-graphql.log-payload=queryAndVariables | ||
quarkus.smallrye-graphql.print-data-fetcher-exception=true | ||
quarkus.smallrye-graphql.error-extension-fields=exception,classification,code,description,validationErrorType,queryPath |
1 change: 1 addition & 0 deletions
1
extensions/smallrye-graphql-client/deployment/src/test/resources/roles.properties
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
david=fooRole |
1 change: 1 addition & 0 deletions
1
extensions/smallrye-graphql-client/deployment/src/test/resources/users.properties
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
david=qwerty123 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.