Hi

The main goal of this proposal is to provide a easy to use service in
Sling to restrict (or grant) access to resources for special use cases
(like giving access to some resources only between 8am and 5pm). The
service should be very lightweight and should NOT be a replacement of ACLs
- and of course should not harm performance. The service should be
decoupled from the ResourceProvider to make it easy to define an access
gate which applies to resources from different ResourceProvider services.
Actually this last point is important to make it very easy to use.

I propose a new service interface ResourceAccessGateService:

interface ResourceAccessGateService
{
        enum GateResult { GRANTED, DENIED, NOTRESPONSIBLE };

        GateResult canRead( Resource, authenticationInfo );
        GateResult canCreate( absPathName, authenticationInfo );
        GateResult canUpdate( Resource, authenticationInfo );
        GateResult canDelete( Resource, authenticationInfo );
        GateResult canExecute( Resource, authenticationInfo );

        GateResult canReadValue( Resource, valueName, authenticationInfo );
        GateResult canCreateValue( Resource, valueName, authenticationInfo );
        GateResult canUpdateValue( Resource, valueName, authenticationInfo );
        GateResult canDeleteValue( Resource, valueName, authenticationInfo );

        String sanitizeQuery( query, language, authenticationInfo ) throws
AccessGateException;

        /* for convenience (and performance) */
        boolean hasReadRestrictions( authenticationInfo );
        boolean hasCreateRestrictions( authenticationInfo );
        boolean hasUpdateRestrictions( authenticationInfo );
        boolean hasDeleteRestrictions( authenticationInfo );
        boolean hasExecuteRestrictions( authenticationInfo );

        boolean canReadAllValues( Resource, authenticationInfo );
        boolean canCreateAllValues( Resource, authenticationInfo );
        boolean canUpdateAllValues( Resource, authenticationInfo );
        boolean canDeleteAllValues( Resource, authenticationInfo );
}

Implementations of this service interface must be registered like
ResourceProvider with a path (like provider.roots). If different
ResourceAccessGateService services match a path, not only the
ResourceAccessGateService with the longest path should be called, but all
of them, that's in contrast to the ResourceProvider, but in this case more
logical (and secure!).

service properties:
path: regexp to define on which paths the service should be called
(default .*)
operations: set of operations on which the service should be called
("read,create,update,delete", default all of them)
finaloperations: set of operations on which the service answer is final an
no other service should be called (default none of them)

Okay, how should it work: First of all, if there's no registered
ResourceAccessGateService, nothing changes.
If one ore more ResourceAccessGateServices are registered Sling takes them
in account as follows:

Sling registers a ResourceDecorator and wraps with that service all
resources into a AccessGateResourceWrapper.

In general:
All servcies are called in the order of the service ranking. If one
service defines its answer as "final" (service property finaloperations),
then Sling takes the answer of this service and no other service is called
despite the service returns NOTRESPONSIBLE.

For READ:
After resolving and getting a resource from the ResourceProvider all
matching ResourceAccessGateService are called (canRead) in the order of
the service ranking and the resource only will be returned if one services
return GRANTED, otherwise a NonExistingResource is returned. This also
applies to findResources and queryResources. And yes in this case the
number of returned results can differ dependent on the implementations of
the ResourceAccessGateService. I don't think that's a big issue and users
of the ResourceAccessGateService have to be aware of this.
If there's a query to find resources, first of all the resource resolver
calls method sanitizeQuery on all ResourceAccessGateServices (because
there's no matching path). The ResourceAccessGateService either returns a
sanitized query (or the original query if everything is fine) or an
AccessGateException if the query is not allowed or illegal.

For UPDATE:
AccessGateResourceWrapper overwrites adaptTo an returns instead a
ModifiableValueMap (if requested) a ModifiableValueMapWrapper. If the
resource can't be modified (all ResourceAccessGateService#canUpdate
returns 
DENIED) an AccessGateException will be throwed. The
ModifiableValueMapWrapper overwrites the methods put, putAll, remove and
clear and calls the appropriate methods (canUpdateValue and
canDeleteValue) on the registered ResourceAccessGateServices. The first
check will always be a call to canUpdateAllValues/canDeleteAllValues, if
this method returns true (from one service at least), no further call to
canUpdateValue/canDeleteValue has to be made. An implementation which does
not care about access to values is not slowed down. That's why this
additional (convenience) methods are important as well.

For CREATE:
This case is easy to implement: On ResourceResolver#create we call
ResourceAccessGateService#canCreateAllResources, if one returns GRANTED,
we 
do nothing else. Otherwise we return an AccessGateResourceWrapper which is
aware, that the resource is just created. As long as the resource is not
committed (through ResourceResolver#commit) the returned
ModifiableValueMap (which is also aware of the "just created" through a
"pointer" to the AccessGateResourceWrapper) does call the appropriate
canCreateAllValues/canCreateValue. After calling commit the state "just
created" on the AccessGateResourceWrapper will be erased and therefore the
appropriate canUpdate methods will be called on the
ResourceAccessGateService.

For DELETE:
ResourceResolver#delete will take care to call the canDelete methods on
all matching ResourceAccessGateService. If one service returns GRANTED the
resource can be deleted.

Alternatively, to make the interface more flexible, we could combine the
canXXX() methods in a method named hasPermission with a parameter
"operation" as String.

So WDYT?

best regards
Mike

Reply via email to