Skip to content

Commit

Permalink
Merge pull request #44668 from michalvavrik/feature/kotlin-authorizat…
Browse files Browse the repository at this point in the history
…ion-policy-fix

Support @AuthorizationPolicy on suspended Kotlin endpoint methods
  • Loading branch information
geoand authored Nov 24, 2024
2 parents 1e7e874 + 61cdd88 commit cb7acc9
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ public class HttpSecurityProcessor {
private static final DotName AUTH_MECHANISM_NAME = DotName.createSimple(HttpAuthenticationMechanism.class);
private static final DotName BASIC_AUTH_MECH_NAME = DotName.createSimple(BasicAuthenticationMechanism.class);
private static final DotName BASIC_AUTH_ANNOTATION_NAME = DotName.createSimple(BasicAuthentication.class);
private static final String KOTLIN_SUSPEND_IMPL_SUFFIX = "$suspendImpl";

@Record(ExecutionTime.STATIC_INIT)
@BuildStep
Expand Down Expand Up @@ -576,9 +577,16 @@ private static Stream<MethodInfo> getPolicyTargetEndpointCandidates(AnnotationTa
if (target.kind() == AnnotationTarget.Kind.METHOD) {
var method = target.asMethod();
if (!hasProperEndpointModifiers(method)) {
if (method.isSynthetic() && method.name().endsWith(KOTLIN_SUSPEND_IMPL_SUFFIX)) {
// ATM there are 2 methods for Kotlin endpoint like this:
// @AuthorizationPolicy(name = "suspended")
// suspend fun sayHi() = "Hi"
// the synthetic method doesn't need to be secured, but it keeps security annotations
return Stream.empty();
}
throw new RuntimeException("""
Found method annotated with the @AuthorizationPolicy annotation that is not an endpoint: %s#%s
""".formatted(method.asClass().name().toString(), method.name()));
""".formatted(method.declaringClass().name().toString(), method.name()));
}
return Stream.of(method);
}
Expand Down
17 changes: 17 additions & 0 deletions integration-tests/resteasy-reactive-kotlin/standard/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-kotlin</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-security</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-integration-test-shared-library</artifactId>
Expand Down Expand Up @@ -186,6 +190,19 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-security-deployment</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>


</dependencies>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.quarkus.it.resteasy.reactive.kotlin

import io.quarkus.vertx.http.security.AuthorizationPolicy
import jakarta.annotation.security.PermitAll
import jakarta.ws.rs.GET
import jakarta.ws.rs.Path

@AuthorizationPolicy(name = "suspended")
@Path("/secured-class")
class SecuredClassResource {

@Path("/authorization-policy-suspend")
@GET
suspend fun authorizationPolicySuspend() = "Hello from Quarkus REST"

@PermitAll @Path("/public") @GET suspend fun publicEndpoint() = "Hello to everyone!"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.quarkus.it.resteasy.reactive.kotlin

import io.quarkus.vertx.http.security.AuthorizationPolicy
import jakarta.ws.rs.GET
import jakarta.ws.rs.Path

@Path("/secured-method")
class SecuredMethodResource {

@Path("/authorization-policy-suspend")
@GET
@AuthorizationPolicy(name = "suspended")
suspend fun authorizationPolicySuspend() = "Hello from Quarkus REST"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.quarkus.it.resteasy.reactive.kotlin

import io.quarkus.security.identity.SecurityIdentity
import io.quarkus.vertx.http.runtime.security.HttpSecurityPolicy
import io.quarkus.vertx.http.runtime.security.HttpSecurityPolicy.CheckResult
import io.smallrye.mutiny.Uni
import io.vertx.core.http.HttpHeaders
import io.vertx.ext.web.RoutingContext
import jakarta.enterprise.context.ApplicationScoped

@ApplicationScoped
class SuspendAuthorizationPolicy : HttpSecurityPolicy {
override fun checkPermission(
request: RoutingContext?,
identity: Uni<SecurityIdentity>?,
requestContext: HttpSecurityPolicy.AuthorizationRequestContext?
): Uni<CheckResult> {
val authZHeader = request?.request()?.getHeader(HttpHeaders.AUTHORIZATION)
if (authZHeader == "you-can-trust-me") {
return CheckResult.permit()
}
return CheckResult.deny()
}

override fun name() = "suspended"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package io.quarkus.it.resteasy.reactive.kotlin

import io.quarkus.test.junit.QuarkusTest
import io.restassured.module.kotlin.extensions.Given
import io.restassured.module.kotlin.extensions.Then
import io.restassured.module.kotlin.extensions.When
import io.vertx.core.http.HttpHeaders
import org.hamcrest.CoreMatchers
import org.junit.jupiter.api.Test

@QuarkusTest
class SecurityTest {

@Test
fun testAuthorizationPolicyOnSuspendedMethod_MethodLevel() {
When { get("/secured-method/authorization-policy-suspend") } Then { statusCode(403) }
Given { header(HttpHeaders.AUTHORIZATION.toString(), "you-can-trust-me") } When
{
get("/secured-method/authorization-policy-suspend")
} Then
{
statusCode(200)
body(CoreMatchers.`is`("Hello from Quarkus REST"))
}
}

@Test
fun testAuthorizationPolicyOnSuspendedMethod_ClassLevel() {
// test class-level annotation is applied on a secured method
When { get("/secured-class/authorization-policy-suspend") } Then { statusCode(403) }
Given { header(HttpHeaders.AUTHORIZATION.toString(), "you-can-trust-me") } When
{
get("/secured-class/authorization-policy-suspend")
} Then
{
statusCode(200)
body(CoreMatchers.`is`("Hello from Quarkus REST"))
}

// test method-level @PermitAll has priority over @AuthorizationPolicy on the class
When { get("/secured-class/public") } Then
{
statusCode(200)
body(CoreMatchers.`is`("Hello to everyone!"))
}
}
}

0 comments on commit cb7acc9

Please sign in to comment.