Hey all,
I'm listing just a few security issues and possible solutions below. For the benefit of all, by all means challenge my assumptions, and add your own problems. But please at least consider my Problem 3 listed below, as I'm very curious as to whether someone's found a better elegant solution. Problem 1: Protecting pages from being accessed anonymously In other words, unless the user is logged in, they should not have access to any page other than, most likely, your login-page. A common solution is to create an abstract superclass for your pages that, in its pageBeginRender listener, redirects to the login page if there isn't, for example, a User ASO present. Problem 2: Protecting different pages from different logged-in users This is really just a more-specific version of Problem 1, and you could use a more-specific version of the solution: create an abstract superclass that subclasses the superclass from Problem 1 that checks to ensure that the user is a specific user. This can be hard to maintain, though, especially if your various pages can be accessed by various combinations of user-types. E.g. you don't want to have to create superclass protection-page for every combination of user-types in your system. Better to, instead, assign a set of accesses/permissions to your users, and protecting your pages based on the presence/absence of a specific access, which could be assigned to multiple user-types. You also might consider working something like Acegi security into your system, which can protect a method based on whether a given role/permission exists. Google revealed at least one project with this approach: http://www.carmanconsulting.com/tapestry-acegi Problem 3: Protecting the application from logged-in users who are spoofing form parameters So, you've ensured that the only users accessing your system have valid accounts, and are only accessing the pages and interfaces you've given them. Well, what if they're malicious, after all, and know how a Tapestry URL works? They see that when selecting an Entity to edit from a list, that the URLs differ like so: "...sp=1...", "...sp=2...", "...sp=5...", etc. Correctly guessing that's the ID of the corresponding Entity, the user spoofs the URL to be "...sp=10...". Obviously, they should have permission to edit Entities, since they need to be able to edit their own Entities, so this page renders correctly. i.e. even with Problems 1 and 2 solved, there is still the issue of the user being able to modify data for other users. We could have our listener method first call an appropriate service method that determines, in the example above, whether the logged-in user has permission to edit the given entity. However, we don't want to violate the DRY principle: if there are multiple places where a given Entity can be selected for editing, we would have to add this check in each place. Is AOP a solution? Through AOP/Acegi/Spring, you could wrap advice around your service methods that has access to the currently-logged-in-user. This would be perfect if you were only verifying the type of the user, but in this case we're determining validity based on the values of the given method-parameters. Most service methods will require a different query/method for determining if the user and the given parameters are a "match", so this would probably end up requiring a different Aspect for each method, which doesn't really gain us much. So ... the best I can come up with at the moment is to pass the User object into the service methods that need to be protected, and doing the check inside the method. Persistence layers should perform individual units of work, relatively agnostic to what environment it exists in. The service layer, however, is for business logic, and it's certainly business logic to declare that a user can only edit the Entities that it owns. And since the User object should be a POJO that is not Tapestry specific, this doesn't tie the service layer to a particular UI-implementation. Further, you're defining this logic in only one place, and your compiler will remind you in each corresponding Tapestry listener that a given service method must be made aware of what user is calling it. A way to improve on this is probably to inject the user-object into your service object at the object-level instead of the method-level. If you're using HiveMind for your services, this is simple enough. It's well-documented how to make Spring services available to Tapestry/HiveMind - has anyone done the reverse? i.e. made it possible to inject Tapestry ASOs into a Spring service? Has anyone tackled this more elegantly than I've described? Perhaps with a totally different design approach to security and user-separation? This problem has other forms ... such as the user tampering with serialized objects. This would be an even rarer case than above, however, and partially mitigated if we've ensured the user can only screw with his/her own information. However, one potential problem that comes to mind with this has to do with transitive persistence (i.e. if you're using an ORM like Hibernate, entities attached to the entity you're persisting might also be persisted). If for some reason you've mapped your entities so that AddressType is persisted when the Address it's attached to is persisted, and this is further being serialized in the HTML, it could be possible for the user to change that AddressType (which could, of course, be associated with thousands of other Addresses). Obviously, that example is an anti-pattern that shouldn't happen in the first place, but it nonetheless illustrates a couple areas that deserve some critical thinking. Thanks, Jim