OK, so first, this is great, and much needed.But I've feedback.First, Permission is an annoying type to implement, I have to implement 5 methods just to be able to declare essentially what should be one logic method:static class Checker extends Permission {
public Checker(String name) {
super(name);
}
@Override
public boolean implies(Permission permission) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
return false;
}
@Override
public int hashCode() {
// TODO Auto-generated method stub
return 0;
}
@Override
public String getActions() {
// TODO Auto-generated method stub
return null;
}
}
Second, it's frustrating that RESTEasy Reactive won't let you pass deserialised arguments to it. Is this only for body parameters, or all parameters? We could probably relax that and change the order in which security is invoked by turning this from a CDI interceptor to a RR RestHandler, no?
Third, I find it very confusing what Permission does. Apparently I implement a permission checker, via an instance of Permission. So far, so good. But then why do I get another Permission instance passed as parameter? Why are there two permissions? In most cases, I've had to check whether the current user had permission to do something on something else. But there was always a single permission per check.
Four, this makes it easy-ish to access parameters (except for RR but we should fix that) but unnecessarily hard because "injection" of those params is done in the constructor, which is separate from where the check logic is (in "implies"), and also makes this hard to inject CDI parameters. Can I suggest the following instead?public @interface CustomPermission{}
public @interface Checker{
public Class<? extends Annotation> value();
}
@CustomPermission
public @interface MyPermission {
}
public class App extends ControllerWithUser<User> {
@MyPermission
@Path("/hello/{user}")
public String helloForUser(@RestPath String user) {
}
// only allow a user to say hello to itself
@Checker(MyPermission.class)
public boolean isAllowed(String user) {
return getUser().username.equals(user);
}
}So, we'd have a meta-annotation @CustomPermission, which we put on user-defined permission annotations, such as MyPermission, which say that "this thing is a permission annotation", and then we can place a @Checker annotation on a method and we specify which permission it can check. That method is a lot like the methods for RESTEasy Reactive server filters: it must return a boolean, and it can take any parameters as it likes, including parameters of the checked method, RESTEasy Reactive injectable types, and (if we implemented https://github.com/quarkusio/quarkus/issues/13593 like we should have years ago) also CDI types.This allows me to use injection, context and lots of stuff, for a much simpler implementation, no? Or am I missing something?
And last, these permissions, ideally should play a part in how we tell the clients what they can or not invoke. I'm thinking of APIs like https://quarkus.io/guides/rest-data-panache where the client will have UI to do some things, but these things depend on whether they have permission for it or not. No permission to delete object X? Then you get no button for it.In these scenarios, there's the server-side check, and we have a story for that. The missing bit is how the client can tell whether an action is allowed or not. I don't yet have ideas for how to solve this.
Hey StephThanks for a fast feedback, Michal and Stuart can offer more feedback, but some comments are inline
OK, so first, this is great, and much needed.But I've feedback.First, Permission is an annoying type to implement, I have to implement 5 methods just to be able to declare essentially what should be one logic method:static class Checker extends Permission {
public Checker(String name) {
super(name);
}
@Override
public boolean implies(Permission permission) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
return false;
}
@Override
public int hashCode() {
// TODO Auto-generated method stub
return 0;
}
@Override
public String getActions() {
// TODO Auto-generated method stub
return null;
}
}
Good point, `StringPermission` is available for simple cases, but indeed, in IDEs it can be a bit annoying, may be in addition to `StringPermission` we can offer `AbstractPermission`
Second, it's frustrating that RESTEasy Reactive won't let you pass deserialised arguments to it. Is this only for body parameters, or all parameters? We could probably relax that and change the order in which security is invoked by turning this from a CDI interceptor to a RR RestHandler, no?It can work with RestEasy Reactive, it is a doc issue probably, you need to have Permission on the CDI bean injected to ResteasyEndpoint and have this CDI bean accepting the extra parameters incl the body I believe
Third, I find it very confusing what Permission does. Apparently I implement a permission checker, via an instance of Permission. So far, so good. But then why do I get another Permission instance passed as parameter? Why are there two permissions? In most cases, I've had to check whether the current user had permission to do something on something else. But there was always a single permission per check.I'll let Michal clarify it - but I think what you get passed to the checker is a Permission instance representing something in the request, added currently via SecurityidentityAugmentor - but later via other declarative mechanisms
Four, this makes it easy-ish to access parameters (except for RR but we should fix that) but unnecessarily hard because "injection" of those params is done in the constructor, which is separate from where the check logic is (in "implies"), and also makes this hard to inject CDI parameters. Can I suggest the following instead?public @interface CustomPermission{}
public @interface Checker{
public Class<? extends Annotation> value();
}
@CustomPermission
public @interface MyPermission {
}
public class App extends ControllerWithUser<User> {
@MyPermission
@Path("/hello/{user}")
public String helloForUser(@RestPath String user) {
}
// only allow a user to say hello to itself
@Checker(MyPermission.class)
public boolean isAllowed(String user) {
return getUser().username.equals(user);
}
}So, we'd have a meta-annotation @CustomPermission, which we put on user-defined permission annotations, such as MyPermission, which say that "this thing is a permission annotation", and then we can place a @Checker annotation on a method and we specify which permission it can check. That method is a lot like the methods for RESTEasy Reactive server filters: it must return a boolean, and it can take any parameters as it likes, including parameters of the checked method, RESTEasy Reactive injectable types, and (if we implemented https://github.com/quarkusio/quarkus/issues/13593 like we should have years ago) also CDI types.This allows me to use injection, context and lots of stuff, for a much simpler implementation, no? Or am I missing something?I'll let Michal, Stuart clarify here, but given what I said above is that passing the extra parameters can also work for Resteasy Reactive, will it change your proposal ?
And last, these permissions, ideally should play a part in how we tell the clients what they can or not invoke. I'm thinking of APIs like https://quarkus.io/guides/rest-data-panache where the client will have UI to do some things, but these things depend on whether they have permission for it or not. No permission to delete object X? Then you get no button for it.In these scenarios, there's the server-side check, and we have a story for that. The missing bit is how the client can tell whether an action is allowed or not. I don't yet have ideas for how to solve this.Please open an enhancement request/Perhaps, Qute templates can use `permission:implies("something")` handlers to modify the button visibility, similar to how templates can have for ex CSRF tokens injected ?
Thanks Sergey
Second, it's frustrating that RESTEasy Reactive won't let you pass deserialised arguments to it. Is this only for body parameters, or all parameters? We could probably relax that and change the order in which security is invoked by turning this from a CDI interceptor to a RR RestHandler, no?It can work with RestEasy Reactive, it is a doc issue probably, you need to have Permission on the CDI bean injected to ResteasyEndpoint and have this CDI bean accepting the extra parameters incl the body I believeIt doesn't work on endpoints now because the check is done by RESTEasy Reactive `org.jboss.resteasy.reactive.server.spi.ServerRestHandler` (more specifically `io.quarkus.resteasy.reactive.server.runtime.security.EagerSecurityHandler` https://github.com/quarkusio/quarkus/pull/19598), not by an interceptor. The handler runs as first thing after the pre-match interceptors and I don't think we can consume request body there or postpone it after deserializers as it could raise potential for DoS etc. (sending unreasonable bodies for requests that are denied anyway? I suppose there are limits though).
Third, I find it very confusing what Permission does. Apparently I implement a permission checker, via an instance of Permission. So far, so good. But then why do I get another Permission instance passed as parameter? Why are there two permissions? In most cases, I've had to check whether the current user had permission to do something on something else. But there was always a single permission per check.I'll let Michal clarify it - but I think what you get passed to the checker is a Permission instance representing something in the request, added currently via SecurityidentityAugmentor - but later via other declarative mechanismsI'm working on a way to add permissions via configuration properties https://github.com/quarkusio/quarkus/issues/12219. I agree that permission checker is not easy to understand, but contract here is set by `java.security.Permission#implies`. The advantage is that we are already using in Quarkus (Keycloak Authorizer mostly) and it's something users may know as it belongs to `java.security. Maybe we can make it even clearer in docs instead of re-implementing it? Ideally users will avoid SecurityIdentityAugmetnor and use config properties in most cases.I'll try to improve this, thank you.
Four, this makes it easy-ish to access parameters (except for RR but we should fix that) but unnecessarily hard because "injection" of those params is done in the constructor, which is separate from where the check logic is (in "implies"), and also makes this hard to inject CDI parameters. Can I suggest the following instead?public @interface CustomPermission{}
public @interface Checker{
public Class<? extends Annotation> value();
}
@CustomPermission
public @interface MyPermission {
}
public class App extends ControllerWithUser<User> {
@MyPermission
@Path("/hello/{user}")
public String helloForUser(@RestPath String user) {
}
// only allow a user to say hello to itself
@Checker(MyPermission.class)
public boolean isAllowed(String user) {
return getUser().username.equals(user);
}
}So, we'd have a meta-annotation @CustomPermission, which we put on user-defined permission annotations, such as MyPermission, which say that "this thing is a permission annotation", and then we can place a @Checker annotation on a method and we specify which permission it can check. That method is a lot like the methods for RESTEasy Reactive server filters: it must return a boolean, and it can take any parameters as it likes, including parameters of the checked method, RESTEasy Reactive injectable types, and (if we implemented https://github.com/quarkusio/quarkus/issues/13593 like we should have years ago) also CDI types.This allows me to use injection, context and lots of stuff, for a much simpler implementation, no? Or am I missing something?I'll let Michal, Stuart clarify here, but given what I said above is that passing the extra parameters can also work for Resteasy Reactive, will it change your proposal ?@Checker provides same capabilities in regards of argument injection and limitations as @PermissionsAllowed, but you need to inject SecurityIdentity if you want to check user name. I think @PermissionsAllowed should be easier to use as long as you stick with default StringPermission and avoid defining SecurityIdentityAugmentor (Sergey is working on something for OIDC token claims and I'm working on role -> permission mapping), with args you are right, it is harder.However, I agree, @Checker seems easier to write and understand. Don't know what to do about it...
And last, these permissions, ideally should play a part in how we tell the clients what they can or not invoke. I'm thinking of APIs like https://quarkus.io/guides/rest-data-panache where the client will have UI to do some things, but these things depend on whether they have permission for it or not. No permission to delete object X? Then you get no button for it.In these scenarios, there's the server-side check, and we have a story for that. The missing bit is how the client can tell whether an action is allowed or not. I don't yet have ideas for how to solve this.Please open an enhancement request/Perhaps, Qute templates can use `permission:implies("something")` handlers to modify the button visibility, similar to how templates can have for ex CSRF tokens injected ?Qute already supports CDI bean injection, so we can optionally provide such bean that allows you to `permission:implies("something")`, +1 for open issue. Please ping me there.Thanks SergeyThanks Michal
--
You received this message because you are subscribed to the Google Groups "Quarkus Development mailing list" group.
To unsubscribe from this group and stop receiving emails from it, send an email to quarkus-dev...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/quarkus-dev/5ebd9d4d-a470-469e-8724-d948ab89a9cen%40googlegroups.com.
I think you could run it later, I don't think that there would be any expectation that these would take effect before serialization. If you are worried about DOS you could add an 'isAuthenticated' check earlier, so unauthenticated users are denied eagerly (as they will never have permission).
Even though the extra methods are annoying, that is easily solved with an abstract type, and IMHO it is generally better to use existing parts of the JDK unless there is a really good reason not to.
The idea is that permissions do not have to be exactly equal to grant access. e.g. say I have HttpPermission(path, method).if I have a method that requires HttpPermission(path="/admin", method="GET"), I can have a user with a permission HttpPermission(path="/admin", method="*"), and the implies method can figure out that a user with "*" permissions can also access GET.Basically this implies approach allows for any amount of flexibility in how permissions are defined and enforced.
How is this any different to just writing a standard CDI Interceptor, and implementing a custom permission scheme? You can basically get the same result by making MyPermission an interceptor binding and your Checker an interceptor.
On Wed, 22 Mar 2023 at 06:47, Stuart Douglas <sdou...@redhat.com> wrote:Even though the extra methods are annoying, that is easily solved with an abstract type, and IMHO it is generally better to use existing parts of the JDK unless there is a really good reason not to.Sure, I agree with that. But then we should provide the abstract class and tell people to implement it. I have no idea why equals/hashCode are required, and it's hard enough to understand why I need a constructor and an implies method, so no idea what the getActions() is for.
On Wed, 22 Mar 2023 at 06:47, Stuart Douglas <sdou...@redhat.com> wrote:Even though the extra methods are annoying, that is easily solved with an abstract type, and IMHO it is generally better to use existing parts of the JDK unless there is a really good reason not to.Sure, I agree with that. But then we should provide the abstract class and tell people to implement it. I have no idea why equals/hashCode are required, and it's hard enough to understand why I need a constructor and an implies method, so no idea what the getActions() is for.The idea is that permissions do not have to be exactly equal to grant access. e.g. say I have HttpPermission(path, method).if I have a method that requires HttpPermission(path="/admin", method="GET"), I can have a user with a permission HttpPermission(path="/admin", method="*"), and the implies method can figure out that a user with "*" permissions can also access GET.Basically this implies approach allows for any amount of flexibility in how permissions are defined and enforced.OK. So, perhaps that's also a way to say that the "admin" permission implies every other permission? So does this mean that if I have an "edit-order" permission for a given order, that normally would check if the current user "owns" the specific order, we'd have the admin permission imply it?
--
You received this message because you are subscribed to the Google Groups "Quarkus Development mailing list" group.
To unsubscribe from this group and stop receiving emails from it, send an email to quarkus-dev...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/quarkus-dev/CAKU9E9scPf%2BHbAa7qUJdpB%2B5tSHe%3D_C5WP%3DkSOOBYek-ca%3Dqqg%40mail.gmail.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/quarkus-dev/69096df4-58c5-45bd-a377-466458dbe6fen%40googlegroups.com.