Long answer ahead. Breaking it up into (1) building filter chains nicely,
(2) guarding resources at the (method X role) granularity, and (3)
fine-grained authorization based on resource state.
(1) Someone recently posted code here for what they called "transparent
filtering", which looked like a way of bringing web.xml-style filter
patterns to Restlet. Use that if it works for you, but there's no harm in
simply being explicit in your createInboundRoot method. I use decorator
factory methods in my Applications to allow me to specify filtering
compactly for each individual resource path. Doesn't have to be fancy:
// AbcServerResources go through XFilter(x1, x2), then YFilter(y)
router.attach("/a/b/{c}", xFiltered(x1, x2, yFiltered(y,
of(AbcServerResource.class))));
// DefServerResources go through ZFilter, then YFilter(y2)
router.attach("/d/e/{f}", zFiltered(yFiltered(y2,
of(DefServerResource.class))));
// Everything goes through WFilter(wParams) first
return wFiltered(router, wParams);
Whenever you find yourself repeating a particular chain a lot, you can make
a new decorator to shorten it:
// Filter through XFilter(x1, DEFAULT_X2) then YFilter(DEFAULT_Y)
protected Restlet xyDefaultFiltered(XParam1 x1, Restlet next) {
return xFiltered(x1, DEFAULT_X2, yFiltered(DEFAULT_Y, next));
}
If you find yourself using the same kinds of chains in multiple
Applications, your Applications can extend a common abstract base that
defines the shared helpers.
OTOH, if your filter chains are really *this* complicated, it's probably a
sign that you should be doing authorization in the resource methods.
(2) I, too, don't see any way to compose MethodAuthorizer and
RoleAuthorizer by chaining them. Either roll your own MethodRoleAuthorizer,
as you sketched, or add explicit checks in your resource methods.
I use the latter approach, because most of my resources are authorized
purely by role, and it was easier to handle the occasional exception with
an explicit check, e.g.,:
@Delete public void deleteUser() {
checkAuthorization("Users cannot delete themselves", ROOT_ROLE,
ADMIN_ROLE);
// ... delete user here ...
}
Before you roll your own MethodRoleAuthorizer, however, consider whether it
wouldn't be more natural to have multiple resources with different
authorization levels instead of a single resource with a different
authorization level for each method.
For example, let's say GET just requires authentication but everything else
requires an authorized role. Instead of a single FooResource with GET and
PUT, you could split into resource interfaces like this:
public interface PublicFooResource {
@Get State getState();
}
public interface ProtectedFooResource extends PublicResource {
@Put State replaceState(State newState);
}
The common abstract implementation provides implementations of all the
methods in these interfaces, but doesn't actually implement either
interface:
abstract class AbstractFooServerResource extends ServerResource {
public State getState() { ... }
public State replaceState() { ... }
}
Note the absence of @Override. The concrete resource types just extend the
abstract base and implement the appropriate resource interface
public class PublicFooServerResource
extends AbstractFooServerResource implements PublicFooResource {
}
public class ProtectedFooServerResource
extends AbstractFooServerResource implements ProtectedFooResource {
}
These are the types you would route to in createInboundRoot:
router.attach("/public/foo", PublicFooServerResource.class);
router.attach("/protected/foo", guard(ProtectedFooServerResource.class);
They could even be public static classes defined in your Application class,
with one line each.
Full disclosure: I've verified that this technique works, but I don't
actually use it.
(3) If your authorization logic depends on common attributes or query
parameters (e.g., /account/{account}/foo or /foo?account=xyz123), you
*can*keep the logic in the Filter, but if you find yourself creating a
lot of
complexity just to keep the authorization logic in the Filter, or if your
authorization really does depend on resource state that isn't available to
a shared filter, it's easy to implement methods like checkAuthorization
above.
protected final void checkAuthorization(String message, Role... roles) {
if (disjointLists(getClientInfo().getRoles(), Arrays.asList(roles))
{
throw new ResourceException(Status.CLIENT_ERROR_UNAUTHORIZED,
message);
}
}
Common check methods can be added to a common abstract base extending
ServerResource.
Finally, a general observation for the Restlet team: Expectations about
Restlet formed from experience with Servlets are regularly disappointed on
this mailing list. Servlets have filter chains that can be applied via
patterns to multiple target "resources", and the resource instances
(Servlets) are singletons. Restlet Filter chains, OTOH, only apply to a
single target resource (ServerResource), but a new instance of the resource
class is created for every request. It would be great if this inversion
could be conveyed to new Restlet users *ab initio*.
--tim
On Tue, Mar 13, 2012 at 10:16 AM, Andy Dennie <[email protected]> wrote:
> As a followup to my previous question, just to make things more
> interesting,
> I also would like to mix in authorization based on identity and resource
> instance state. For example, in addition to the rules I mentioned, an
> authenticated user may PUT or DELETE a collection item they created, but
> not
> collection items created by someone else.
>
> How do I create a single chain of filters in createInboundRoot that handles
> these different situations for a given collection's resource URI template?
> I just realized that I can't group resources with different rules by
> assigning them to different routers (as I posited in my previous post)
> because I can only return one Restlet from createInboundRoot. So, it seems
> that I need to create different filter chains and attach them to a single
> router appropriately, on a per-URI basis.
>
> The overall logic of the filter chain should be something like:
>
> if the method is GET, execute the method on the ServerResource.
> if the method is POST, authenticate the user and then execute the method on
> the ServerResource.
> if the method is PUT or DELETE, authenticate the user, enrole them, check
> the role to see if it is "admin", and if so, execute the method on the
> ServerResource.
>
> It would be nice to modularize this logic into different filters for
> authentication, enroling, and authorizing, but I'm getting the feeling that
> this is going to need to be wrapped up into a single filter, since the
> sub-rules are conditional rather than sequential.
>
> Or perhaps I could dynamically modify the filter chain at filter execution
> time? For example, in createInboundRoot, attach a custom MethodAuthorizer
> which is chained to the ServerResource to the router for the collection URI
> template. Then, in the custom MethodAuthorizer, if the method is POST,
> dynamically insert an authenticator filter before the next restlet (the
> ServerResource), and if the method is PUT/DELETE, dynamically insert an
> authenticator, enroler, and authorizer trio before the ServerResource.
>
> Am I getting closer?
>
>
> --
> View this message in context:
> http://restlet-discuss.1400322.n2.nabble.com/combining-role-and-method-authorization-tp7366193p7368612.html
> Sent from the Restlet Discuss mailing list archive at Nabble.com.
>
> ------------------------------------------------------
>
> http://restlet.tigris.org/ds/viewMessage.do?dsForumId=4447&dsMessageId=2934971
>
------------------------------------------------------
http://restlet.tigris.org/ds/viewMessage.do?dsForumId=4447&dsMessageId=2935582