-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JAX-RS default security is applied to annotated, inherited endpoints #38754
Comments
/cc @geoand (kotlin) |
Standard security annotations are not supposed to be inherited. I remember I read some discussion in some old opened issues in Quarkus where @stuartwdouglas wrote that, I hope I do not misinterpret him. Please comment Stuart if I do. Anyway, I think we should treat Therefore I vote for not a bug. Interesting it worked before though. I'd be interested in a reproducer, but I do not plan to fix it. Please comment if you disagree @sberyozkin |
also @geoand could have opinion as this is related to inheritance of endpoints.. |
@michalvavrik The inheritance works in general but I agree it is not really a bug, at least in the JAX-RS classic, the inheritance is not partial, i.e, all JAX-RS annotations must be inherited, so I'd rather not treat this issue as a breaking fix side-effect. IMHO, if the users want to continue using the deny all feature they should align the annotations. Worth giving it a migration doc notice though |
I agree with @michalvavrik |
agreed @sberyozkin , thanks |
I generally agree, too, but the example below also doesn't work (added @Authenticated
abstract class AbstractFoo {
@GET
@Authenticated
fun foo() = "Hello world!"
}
@ApplicationScoped
@Path("foo1")
@Authenticated
class Foo1 : AbstractFoo()
@ApplicationScoped
@Path("foo2")
@Authenticated
class Foo2 : AbstractFoo() |
@Foobartender Can you please align JAX-RS annotations, the problem is, you have |
The example below works, even though I didn't annotate the overridden @Authenticated
abstract class AbstractFoo {
@GET
@Authenticated
fun foo() = "Hello world!"
}
@ApplicationScoped
@Path("foo1")
@Authenticated
class Foo1 : AbstractFoo() {
override fun foo() = super.foo()
}
@ApplicationScoped
@Path("foo2")
@Authenticated
class Foo2 : AbstractFoo() {
override fun foo() = super.foo()
} |
The example you provided has |
@Foobartender My understanding is that given
Only access to
|
Yes, that makes sense, because the overridden method is now a method of the concrete class and the class annotation applies. @michalvavrik: I don't really need default JAX-RS security (i.e. deny-unannotated-endpoints), I only use it as a fail-safe for missing security annotations on JAX-RS beans. I'm very cautious about not creating information leakage/privacy vulnerabilities by forgetting them. When I was using WildFly, I even put security annotations on repository beans to guard data access at the lowest level. I already had to compromise on that, because there are |
okay @Foobartender , I need to divide answer into 3 topics, I think second point is of interest for you:
|
In reference to my example from #38754 (comment) ...
Both Thus I'd argue it's a bug, even though Regarding 1. and 3.: relying on complex inheritance rules is something that happened when I actually wanted to do composition, e.g. implementing my own repository pattern or something like rest-data-panache. While doing so, I often checked the specs, which usually left plenty of room for implementation, and didn't cover new language features like default interface methods. |
I used RBAC as a shortcut, sure,
Sorry, I think current implementation is safer and easier. I'll keep documentation improvements in mind. Feel free to open new issue (not a bug) that suggests changing it. Maybe others will agree... |
I attached a reproducer. Run with When enabling Also, Below the key parts of the reproducer, Java variant omitted. Endpoints: package org.example.kotlin
import jakarta.annotation.security.RolesAllowed
import jakarta.ws.rs.GET
import jakarta.ws.rs.Path
object Roles {
const val ROLE1 = "role1"
const val ROLE2 = "role2"
}
abstract class Base {
@[GET Path("base/"+Roles.ROLE1)]
@RolesAllowed(Roles.ROLE1)
fun baseRole1() = "base/"+Roles.ROLE1
@[GET Path("base/"+Roles.ROLE2)]
@RolesAllowed(Roles.ROLE2)
fun baseRole2() = "base/"+Roles.ROLE2
}
@Path("/kotlin")
class DerivedK : Base() {
@[GET Path(Roles.ROLE1)]
@RolesAllowed(Roles.ROLE1)
fun derivedRole1() = Roles.ROLE1
@[GET Path(Roles.ROLE2)]
@RolesAllowed(Roles.ROLE2)
fun derivedRole2() = Roles.ROLE2
} Tests: package org.example
import io.quarkus.test.common.http.TestHTTPEndpoint
import io.quarkus.test.junit.QuarkusTest
import io.quarkus.test.security.TestSecurity
import io.restassured.module.kotlin.extensions.Then
import io.restassured.module.kotlin.extensions.When
import org.example.java.DerivedJ
import org.example.kotlin.DerivedK
import org.example.kotlin.Roles
import org.junit.jupiter.api.Test
interface ResourceTest {
val myRole: String?
private fun test(role: String, base: Boolean) {
When {
get((if (base) "base/" else "")+role)
} Then {
statusCode(
when (myRole) {
null -> 401
role -> 200
else -> 403
}
)
}
}
@Test
fun `base role1`() = test(Roles.ROLE1, true)
@Test
fun `base role2`() = test(Roles.ROLE2, true)
@Test
fun `derived role1`() = test(Roles.ROLE1, false)
@Test
fun `derived role2`() = test(Roles.ROLE2, false)
}
@QuarkusTest
@TestHTTPEndpoint(DerivedK::class)
class KotlinResourceTestWithNoRole : ResourceTest {
override val myRole = null
}
@QuarkusTest
@TestHTTPEndpoint(DerivedK::class)
// Note that Kotlin compiles default interfaces methods into the class, so @TestSecurity applies.
@TestSecurity(user = "test1", roles = [Roles.ROLE1])
class KotlinResourceTestWithRole1 : ResourceTest {
override val myRole = Roles.ROLE1
}
@QuarkusTest
@TestHTTPEndpoint(DerivedK::class)
@TestSecurity(user = "test2", roles = [Roles.ROLE2])
class KotlinResourceTestWithRole2 : ResourceTest {
override val myRole = Roles.ROLE2
} |
I'll look at it in the next few days (ideally this weekend). |
I can confirm there is a bug, I don't think it's vulnerability (as it does opposite, harden unsolicitously), so I'll go ahead here. In short: before the CVE fix there was a bug in the Fix is quite easy, I'll create it tomorrow. We will need to backport it to all the streams though. cc @sberyozkin @gsmet Thank you @Foobartender |
I've reopened it although the issue description is not up to date with your latest comment, if you could replace the issue description with this comment #38754 (comment) it would be nice. |
Done, also updated the title, hope that fits. Thank you, and please enjoy your weekend, it's not urgent. 😉 |
Thanks @michalvavrik @Foobartender The extra hardening causing some side-effects causing the denial of access is unfortunate but not critical IMHO, so Michal, please do take your time, do not spend weekends. I have to admit I'm not exactly sure where a side-effect is, sorry. The Base class has all the annotations, then the DerivedK class has the same annotations but overrides the path, so the inheritance is not working already (all the annotations are on the DerivedK methid), and no non-annotated methods exist. |
Hey @sberyozkin, agreed. Your question is good, but it's difficult to catch this in words, it should be clear from the fix here: #38832 |
@sberyozkin: I'll try to explain. First, please note the difference between security annotations ( The documentation for My original example was indeed wrong (which might have caused some confusion), and I was a bit shocked about my own misconception that There's some confusion about JAX-RS annotations. I think all JAX-RS annotions in my examples are correct. I didn't understand what you meant with "aligning them". Their inheritance is described here. You put |
OK
I missed you had a different method name in DerivedK, thought it was the same |
Updated description
I attached a reproducer. Run with
./gradlew test
. Please note that all tests pass out-of-the box, all endpoints are annotated with@RolesAllowed
, and that unauthenticated requests are covered and result in 401 - it works as it should.When enabling
deny-unannotated-endpoints
ordefault-roles-allowed
inapplication.properties
, some tests of the base class fail, but shouldn't.Also,
default-roles-allowed
shows some weird behavior on endpoints of the base class. Without@RolesAllowed
default roles are applied as expected. But when present,@RolesAllowed
is not ignored as when usingdeny-unannotated-endpoints
. It works as expected when the annotated and default role match, but fails when they are different.Below the key parts of the reproducer, Java variant omitted.
Endpoints:
Tests:
q-jds-test.tar.gz
Original description
Describe the bug
Not sure, whether this is now intended behavior or a bug, so I'll keep it short. Below is a rough example. If it's supposed to work (maybe it does, I left out some probably irrelevant details), I'll create a reproducer.
Expected behavior
Requests to /foo1 and /foo2 should work when authenticated, because the derived concrete classes have an explicit
@Authenticated
annotation.Actual behavior
Requests fail with status 403, although they are authenticated.
The text was updated successfully, but these errors were encountered: