On 23/04/12 18:03, Marek Posolda wrote:
Hi all,
On 22.4.2012 19:27, Christian Kaltepoth wrote:
I prefer the method signature Shane suggested in his initial mail.
Providing a single method with an object parameter makes most sense to
me:
Identity.hasPermission(Object resource, String operation)
This way the developer is completely free to choose how to identify
the resource. I think in most cases the object itself will be used.
And the JSF example Shane showed is a very good example for this.
Of cause people may need other ways to refer to resources like in the
ResourceIdentifier concept Marek showed. But actually I see something
like this more as an usage pattern and I don't think it belongs
directly into the API. Perhaps we could provide something like
ResourceIdentifier as an utility class that people can use if they
want to refer to resources this way. But the API of Identity should be
as simple as possible. And the object parameter will allow people to
use any object they want. Even something like ResourceIdentifier.
That's the question. I think that API should be clear and using single
method:
Identity.hasPermission(Object resource, String operation)
is not clear enough. As in some cases, your argument is directly secured
object and in other cases it's only ResourceIdentifier for this object.
Also it would mean that you will need to ask for the type of object in your
PermissionResolver implementation:
if (resource instanceof ResourceIdentifier)
{
// compute permissions for ResourceIdentifier
}
else
{
// Argument is "resource" so compute permissions for this resource
}
On the other hand, using single method:
Identity.hasPermission(**ResourceIdentifier resource, Operation
operation)
may not be appropriate enough for some use-cases. Let's imagine that you
have Customer and CustomerResourceIdntifier objects:
public class Customer
{
private String ID;
private String customerName;
// getters and setters here
}
public class CustomerResourceIdentifier implements ResourceIdentifier
{
private String customerId;
// getters and setters
}
And you want to create PermissionResolver, which will compute permission
based on customer name. With the API method:
Identity.hasPermission(**ResourceIdentifier resourceIdentifier,
Operation operation)
you can't manage to do it (unless you change implementation of
CustomerResourceIdentifier to encapsulate customerName)
Operation
I agree that big advantage of this approach is type-safety. On the other
hand, using String directly is more flexible as you have more freedom.
Let's imagine that you have some permission rules stored in database and
you want to add new type of operation like "ADMINISTER". With using String
as argument, you can directly create records in DB and use this new
"ADMINISTER" argument without doing any changes in Java code. With the
Operation approach, you would need to add this new ADMINISTER operation
into your Operation enum:
This is exactly why the operation cannot be an enum - permissions will be
stored in the database as string values, and if we use an enum value there
will be no way of knowing which enum class the operation(s) string needs to
be converted back into. I should have actually expanded on the details of
this a little bit earlier which would have made this more obvious.
Another use case which I didn't mention is the ability to use bit flags to
set permissions. This is extremely important for a database with millions
of records - instead of using a String in the database to store the
operations it is possible to use a numerical value instead, with each bit
representing a different operation. It also allows us to define available
permissions for a particular domain object, which is important when
building management interfaces.
Let's say we have the following entity:
@Entity
@Permissions({
@Permission(operation="create"**),
@Permission(operation="read"),
@Permission(operation="update"**),
@Permission(operation="delete"**)
})
public class Customer
{
// snip
}
The @Permissions annotation tells the PermissionManager that we're allowed
to assign four specific permissions; create, read, update and delete for
the Customer entity. We can take this a step further and define a bit mask
value for each of these:
@Entity
@Permissions({
@Permission(operation="create"**, mask = 1),
@Permission(operation="read", mask = 2),
@Permission(operation="update"**, mask = 4),
@Permission(operation="delete"**, mask = 8)
})
public class Customer
{
// snip
}
By providing mask values, the PermissionManager knows that the cumulative
permissions for this entity will be stored in the database as a numerical
value. So a user with create, read and update permissions for a particular
customer will have a stored operation value of 1 (create) + 2 (read) + 4
(update) = 7.
public enum CrudOperation implements Operation
{
CREATE, READ, UPDATE, DELETE, ADMINISTER
}
I wanted to point this out, but I agree that for most cases is type-safe
approach with Operation interface probably better:-)
So after all, my opinion is to have two methods:
Identity.hasPermission(Object object, Operation operation) throws
AuthorizationException;
Identity.hasPermission(**ResourceIdentifier resourceIdentifier,
Operation operation) throws AuthorizationException;
+1, I think that having two methods is the best solution, with the caveat
that overloading like this doesn't cause issues with EL. The Object
parameter version of this method will most likely delegate to the
ResourceIdentifier method anyway, as a ResourceIdentifier will be required
for all lookups. By the way, we should make this as transparent as
possible - out of the box, DeltaSpike should provide a ResourceIdentifier
implementation for entity beans that will build a unique identifier based
on the class name and the primary key value. Another alternative that we
should support is allowing the user to specify which ResourceIdentifier to
use on the domain class itself:
@Entity
@Identifier(**CustomerResourceIdentifier.**class)
public class Customer
{
}
Yet another alternative that we should support is the packaging of
additional ResourceIdentifier implementations with an application, that are
automatically discovered and iterated through when the permission check
can't determine which ResourceIdentifier to use. With that in mind, the
ResourceIdentifier interface should look like this:
public interface ResourceIdentifier
{
boolean canIdentify(Class targetClass);
String getIdentifier(Object resource);
}
The canIdentify() method will return true if the specified class is one
that this ResourceIdentifier is capable of generating identifier strings
for.
Thanks,
Marek