From 1b5ff563197fdbd5879dd6fd7435122f9b1d125b Mon Sep 17 00:00:00 2001 From: Jan Martiska Date: Mon, 15 Jun 2020 11:03:35 +0200 Subject: [PATCH] Enable programmatic security in SmallRye GraphQL endpoints --- .../smallrye-graphql/deployment/pom.xml | 10 +++ .../deployment/SmallRyeGraphQLProcessor.java | 4 +- .../graphql/deployment/SecurityTest.java | 79 +++++++++++++++++++ .../resources/application-secured.properties | 5 ++ .../src/test/resources/roles.properties | 1 + .../src/test/resources/users.properties | 1 + .../SmallRyeGraphQLExecutionHandler.java | 10 ++- .../runtime/SmallRyeGraphQLRecorder.java | 14 +++- 8 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/SecurityTest.java create mode 100644 extensions/smallrye-graphql/deployment/src/test/resources/application-secured.properties create mode 100644 extensions/smallrye-graphql/deployment/src/test/resources/roles.properties create mode 100644 extensions/smallrye-graphql/deployment/src/test/resources/users.properties diff --git a/extensions/smallrye-graphql/deployment/pom.xml b/extensions/smallrye-graphql/deployment/pom.xml index d7ab02dae4e57..71233a947fce9 100644 --- a/extensions/smallrye-graphql/deployment/pom.xml +++ b/extensions/smallrye-graphql/deployment/pom.xml @@ -70,6 +70,16 @@ quarkus-smallrye-opentracing test + + io.quarkus + quarkus-elytron-security + test + + + io.quarkus + quarkus-elytron-security-properties-file + test + diff --git a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java index 6f0295b4c2686..f4ae7e67f89d7 100644 --- a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java +++ b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java @@ -191,7 +191,9 @@ void buildEndpoints( BuildProducer notFoundPageDisplayableEndpointProducer, LaunchModeBuildItem launchMode, SmallRyeGraphQLRecorder recorder, - ShutdownContextBuildItem shutdownContext) throws IOException { + ShutdownContextBuildItem shutdownContext, + BeanContainerBuildItem beanContainerBuildItem // don't remove this - makes sure beanContainer is initialized + ) { /* * Ugly Hack diff --git a/extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/SecurityTest.java b/extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/SecurityTest.java new file mode 100644 index 0000000000000..d156ab94a277d --- /dev/null +++ b/extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/SecurityTest.java @@ -0,0 +1,79 @@ +package io.quarkus.smallrye.graphql.deployment; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; + +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.graphql.GraphQLApi; +import org.eclipse.microprofile.graphql.Query; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; +import io.restassured.http.Header; + +public class SecurityTest extends AbstractGraphQLTest { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClass(SecuredApi.class) + .addAsResource("application-secured.properties", "application.properties") + .addAsResource("users.properties") + .addAsResource("roles.properties") + .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml")); + + @Test + public void testAuthenticatedUser() { + String query = getPayload("{ foo }"); + RestAssured.given() + .header(new Header("Authorization", "Basic ZGF2aWQ6cXdlcnR5MTIz")) + .body(query) + .contentType(MEDIATYPE_JSON) + .post("/graphql/") + .then() + .assertThat() + .body("errors", nullValue()) + .body("data.foo", equalTo("foo")); + } + + @Test + public void testUnauthorizedRole() { + String query = getPayload("{ bar }"); + RestAssured.given() + .header(new Header("Authorization", "Basic ZGF2aWQ6cXdlcnR5MTIz")) + .body(query) + .contentType(MEDIATYPE_JSON) + .post("/graphql") + .then() + .assertThat() + .body("errors", notNullValue()) + .body("data.bar", nullValue()); + } + + @GraphQLApi + @ApplicationScoped + public static class SecuredApi { + + @Query + @RolesAllowed("fooRole") + public String foo() { + return "foo"; + } + + @Query + @RolesAllowed("barRole") + public String bar() { + return "bar"; + } + + } + +} diff --git a/extensions/smallrye-graphql/deployment/src/test/resources/application-secured.properties b/extensions/smallrye-graphql/deployment/src/test/resources/application-secured.properties new file mode 100644 index 0000000000000..dbe0d0ba62978 --- /dev/null +++ b/extensions/smallrye-graphql/deployment/src/test/resources/application-secured.properties @@ -0,0 +1,5 @@ +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 \ No newline at end of file diff --git a/extensions/smallrye-graphql/deployment/src/test/resources/roles.properties b/extensions/smallrye-graphql/deployment/src/test/resources/roles.properties new file mode 100644 index 0000000000000..ef2a67ac7e9e6 --- /dev/null +++ b/extensions/smallrye-graphql/deployment/src/test/resources/roles.properties @@ -0,0 +1 @@ +david=fooRole \ No newline at end of file diff --git a/extensions/smallrye-graphql/deployment/src/test/resources/users.properties b/extensions/smallrye-graphql/deployment/src/test/resources/users.properties new file mode 100644 index 0000000000000..0f1cc7592d055 --- /dev/null +++ b/extensions/smallrye-graphql/deployment/src/test/resources/users.properties @@ -0,0 +1 @@ +david=qwerty123 \ No newline at end of file diff --git a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLExecutionHandler.java b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLExecutionHandler.java index 558944a56205d..9343d1fc4beb0 100644 --- a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLExecutionHandler.java +++ b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLExecutionHandler.java @@ -13,6 +13,8 @@ import io.quarkus.arc.Arc; import io.quarkus.arc.ManagedContext; +import io.quarkus.security.identity.CurrentIdentityAssociation; +import io.quarkus.vertx.http.runtime.security.QuarkusHttpUser; import io.smallrye.graphql.execution.ExecutionService; import io.vertx.core.Handler; import io.vertx.core.buffer.Buffer; @@ -29,9 +31,11 @@ public class SmallRyeGraphQLExecutionHandler implements Handler private static final String QUERY = "query"; private static final String OK = "OK"; private volatile ExecutionService executionService; + private final CurrentIdentityAssociation currentIdentityAssociation; - public SmallRyeGraphQLExecutionHandler(boolean allowGet) { + public SmallRyeGraphQLExecutionHandler(boolean allowGet, CurrentIdentityAssociation currentIdentityAssociation) { this.allowGet = allowGet; + this.currentIdentityAssociation = currentIdentityAssociation; } @Override @@ -50,6 +54,10 @@ public void handle(final RoutingContext ctx) { } private void doHandle(final RoutingContext ctx) { + if (currentIdentityAssociation != null) { + currentIdentityAssociation.setIdentity(QuarkusHttpUser.getSecurityIdentity(ctx, null)); + } + HttpServerRequest request = ctx.request(); HttpServerResponse response = ctx.response(); diff --git a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLRecorder.java b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLRecorder.java index 4924ad20bb02f..111bb4fae199c 100644 --- a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLRecorder.java +++ b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLRecorder.java @@ -2,9 +2,13 @@ import java.util.function.Supplier; +import javax.enterprise.inject.Instance; +import javax.enterprise.inject.spi.CDI; + import io.quarkus.arc.runtime.BeanContainer; import io.quarkus.runtime.ShutdownContext; import io.quarkus.runtime.annotations.Recorder; +import io.quarkus.security.identity.CurrentIdentityAssociation; import io.quarkus.smallrye.graphql.runtime.spi.QuarkusClassloadingService; import io.quarkus.vertx.http.runtime.ThreadLocalHandler; import io.smallrye.graphql.cdi.producer.GraphQLProducer; @@ -24,7 +28,15 @@ public void createExecutionService(BeanContainer beanContainer, Schema schema) { } public Handler executionHandler(boolean allowGet) { - return new SmallRyeGraphQLExecutionHandler(allowGet); + Instance identityAssociations = CDI.current() + .select(CurrentIdentityAssociation.class); + CurrentIdentityAssociation association; + if (identityAssociations.isResolvable()) { + association = identityAssociations.get(); + } else { + association = null; + } + return new SmallRyeGraphQLExecutionHandler(allowGet, association); } public Handler schemaHandler() {