Hello,
I am thinking about adding some help for ownership constraints.
An example of ownership constraints is a public web app, where each
user has 'their own' information, and tables include records attached
to many different users.
In internal web apps, the security is usually based on role. In public
web apps, the security is usually based on the user, not the role.
The Servlet API has help for role-based security, but not for user-
based security. WEB4J should likely add help for user-based security.
Here's a sketch of the idea I am working on. *If you have any
corrections/comments/suggestions, please let me know.*
To implement an ownership constraint, the programmer would needs to do
two things.
1. In web.xml, you would define which requests have an 'ownership
constraint', similar to how role-based constraints are currently
defined, using the Servlet API.
2. Your action would need to add a new method
Id fetchLoginNameOfOwner()
This method talks to the database. Typically, it will use a request
parameter value identifying the 'thing' which is owned. The *owner* of
that thing is looked up in the database, and their *login name* is
returned. When an ownership constraint is encountered, the framework
will call this method, and compare the return value with the login
name of the currently logged in user. IF there is a diff, then an
exception is thrown, since the constraint is violated. Otherwise, the
request proceeds as usual.
Changes required to WEB4J in order to make this happen:
1. Change the Action interface to include two methods, not one
void execute() - the existing method
Id fetchLoginNameOfOwner() - the new method
2. ActionImpl gets an overridable implementation of this method,
*which would throw an exception*.
3. The Controller class will check to see if request X has an
ownership constraint. Roughly:
IF an ownership constraint is present,
call action.fetchLoginNameOfOwner()
compare it with the current user's loginname
if the same, continue, otherwise exception
Remarks:
1.Changing the Action interface means that anyone who implemented the
Action interface *directly* will be broken. At first, this seems like
a really bad idea. In practice, however, it's likely that almost all
apps have *subclassed* ActionImpl (or ActionTemplateXXX) in order to
implement Actions. Such code would *not* be broken, since ActionImpl
will have a default implementation of the new method.
2. When you declare an ownership constraint in web.xml, and you have
forgotten to override
fetchLoginNameOfOwner() for a request protected by an ownership
constraint, then the default implementation defined by
ActionImpl.fetchLoginNameOfOwner()
will simply throw a runtime exception. That's good, because the
security constraint has not been enforced correctly by your action.
This protects you from mistakes.
3. By definition of the Servlet API, the user login name will always
be present in session after a user has successfully logged in.
4.The above mechanism is immune to session expiry issues.
5. Links generated in HTML are allowed to contain 'sensitive'
identifiers owned by person X, even though such params are easily
altered. That's OK, because the above mechanism never trusts such
identifiers, and forces the programmer to explicitly validate the
owner for each request.
Possible Objections :
1. SQL can enforce ownership constraints for SELECT and DELETE
operations, since a WHERE clause can be used. So you don't need this
mechanism at all.
Rebuttal : What about INSERT and UPDATE operations? No WHERE clauses
there.
2. The action will often need to repeat the call to
fetchLoginNameOfOwner()
Rebuttal: that's right. This repetition is a bit annoying. In
addition, two SQL calls are executed, not one. However, in web4j's
philosophy, security trumps response-time considerations. Security is
critical. If your app is unsecure, it's broken, and it doesn't matter
how fast it is. If the repeated checking per request was replaced
with a mechanism that relied on a checked value placed in the session,
then you will run into issues with session expiry. (Such a session-
based style was attempted by me, and I found it did not work well.)
3. The mechanism doesn't use transactions: the
fetchLoginNameOfOwner()
is not performed in the same transaction as the subsequent operation.
Thus, you are not *guaranteed* the kind of consistency available
inside a single transaction.
Rebuttal : That's true. However, that case corresponds to a user
'hacking' their own data. That doesn't make sense. In practice, I
think the lack of a single transaction is acceptable.
Any comments out there?
Regards,
John O'Hanley
web4j.com,
javapractices.com