Hi Tim, wow, thanks for the detailed response.
Let me take this bit by bit to keep things comprehensible.
> 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));
> }
That's a useful technique, and I think I followed most of it, but let me
confirm a few things. I infer that your YourApplication.*Filtered
methods instantiate *Filter classes (passing their arguments, excluding
the last, to the *Filter constructor), and chaining that *Filter to the
final argument, which is the next Restlet in the chain. Yes? What does
YourApplication.of() do?
>
> (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 ...
> }
>
So you do your authenticating and enroling via filters, then do your
role-based authorization in your resource methods? I suspect I'll need
something like that down the road. Right now my only role is "admin",
but I'll have more later. But do all of your requests need to be
authenticated? I need to allow unauthenticated GETs, so I still need to
figure out how to authenticate in some cases and not others, which l
leads us to:
> 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.
That's actually what I'm doing now, with the same logical resource being
exposed via two URIs, e.g. /unauthenticateduser/someresource and
/admin/someresource. But the proliferation of classes is kind of a
hassle. And I got to thinking -- if there's one logical resource, does
it really make sense for different users to use different URIs to
address it? I realize that there can be legitimate reasons to have the
same resource addressable by multiple URIs (e.g. /users/andy/hometown
and /users/johndoe/hometown), but in this case, I felt like I was
altering my API to work around Restlet's idiosyncrasies, which seemed
like a Bad Thing.
>
> 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.
>
That's a good technique to keep in mind, and the public static classes
would at least tamp down on the class file proliferation.
>
> (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.
>
Thanks again the the detailed response. I'll mull things over and
report back later if I come up with something worth sharing.
-Andy
--
View this message in context:
http://restlet-discuss.1400322.n2.nabble.com/combining-role-and-method-authorization-tp7366193p7372274.html
Sent from the Restlet Discuss mailing list archive at Nabble.com.
------------------------------------------------------
http://restlet.tigris.org/ds/viewMessage.do?dsForumId=4447&dsMessageId=2935651