Hi,

I found this solution to the security elegant: simply putting a
@RequiresLogin in the page to be protected and let the 
ComponentRequestFilter checks the annotation in the page. now if I need
something else, say @ReqiresOwner, say I'd allow the owner of inbox to go to
a Inbox page, I can pass the instance of Inbox page to authService:

if ( page.getClass().isAnnotationPresent(RequiresOwner.class)) {
     
        if (!authService.checkPageOwner(page)) {
       return false;
    }

   }

And condition to check if a logged in user is the owner of the said page
depends on situation, most of time it depends on the activation context
which might be the user, or some objects belong to the user, what will be a
generic way to take care of this situations? Thanks,

Angelo 



Howard Lewis Ship wrote:
> 
> This subject still keeps coming up on the mailing list and I thought
> I'd show a little bit about how I tackle this problem generally. People
> have been asking for a single solution to handling security ... but I
> don't see any single solution satisfying even the majority of projects?
> Why? There are too many variables. For example, are you using LDAP,
> OpenAuth or some ad-hoc user registry (in your database)? Are pages
> accessible by default, or in-accessible? Are you using role-based
> security? How do you represent roles then? Creating a single solution
> that's pluggable enough for all these possibilities seems like an
> insurmountable challenge ... but perhaps we can come up with a toolkit
> so that you can assemble your own custom solution (more on that later).
> One approach to security could be to define a base class,
> ProtectedPage, that enforced the basic rules (you must be logged in to
> use this page). You can accomplish such a thing using the activate
> event handler ... but I find such an approach clumsy. Anytime you can
> avoid inheritance, you'll find your code easier to understand, easier
> to manage, easier to test and easier to evolve.
> Instead, let's pursue a more declarative approach, where we use an
> annotation to mark pages that require that the user be logged in. We'll
> start with these ground rules:
> - Pages are freely accessible by anyone, unless they have
> a @RequiresLogin annotation
> - Any static resource (in the web context directory) is accessible to
> anybody
> - There's already some kind of UserAuthentication service that knows if
> the user is currently logged in or not, and (if logged in) who they
> are, as a User object
> So, we need to define a RequiresLogin annotation, and we need to
> enforce it, by preventing any access to the page unless the user is
> logged in.
> That poses a challenge: how do you get "inside" Tapestry to enforce
> this annotation? What you really want to do is "slip in" a little bit
> of your code into existing Tapestry code ... the code that analyzes the
> incoming request, determines what type of request it is (a page render
> request vs. a component event request), and ultimately starts calling
> into the page code to do the work.
> This is a great example of the central design of Tapestry and it's IoC
> container: to natively supporting this kind of extensibility. Through
> the use of service configurations it's possible to do exactly that:
> slip a piece of code into the middle of that default Tapestry code. The
> trick is to identify where. This image gives a rough map to how
> Tapestry handles incoming requests:
> 
> In fact, there's a specific place for this kind of extension: the
> ComponentRequestHandler pipeline service1. As a pipeline service,
> ComponentRequestHandler has a configuration of filters, and adding a
> filter to this pipeline is just what we need. Defining the Annotation
> First, lets define our annotation:
> 
> @Target( {
> ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented
> public @interface RequiresLogin { }
> 
> This annotation is designed to be placed on a page class to indicate
> that the user must be logged in to access the page. The retention
> policy is important here: it needs to be visible at runtime for our
> runtime code to see it and act on its presence.
> An annotation by itself does nothing ... we need the code that checks
> for the annotation. Creating a ComponentRequestFilter
> Filters for the ComponentRequestHandler pipeline are instances of the
> interface ComponentRequestFilter:
> /** * Filter interface for {...@link
> org.apache.tapestry5.services.ComponentRequestHandler}. */ public
> interface ComponentRequestFilter { /** * Handler for a component action
> request which will trigger an event on a component and use the return
> value to * send a response to the client (typically, a redirect to a
> page render URL). * * @param parameters defining the request * @param
> handler next handler in the pipeline */ void
> handleComponentEvent(ComponentEventRequestParameters parameters,
> ComponentRequestHandler handler) throws IOException; /** * Invoked to
> activate and render a page. In certain cases, based on values returned
> when activating the page, a * {...@link
> org.apache.tapestry5.services.ComponentEventResultProcessor} may be
> used to send an alternate response * (typically, a
> redirect). * * @param parameters defines the page name and activation
> context * @param handler next handler in the pipeline */ void
> handlePageRender(PageRenderRequestParameters parameters,
> ComponentRequestHandler handler) throws IOException; }
> 
> Our implementation of this filter will check the page referenced in the
> request to see if it has the annotation. If the annotation is present
> and the user has not yet logged in, we'll redirect to the Login page.
> When a redirect is not necessary, we delegate to the next handler in
> the pipeline2
> public class RequiresLoginFilter implements ComponentRequestFilter {
> private final PageRenderLinkSource renderLinkSource; private final
> ComponentSource componentSource; private final Response response;
> private final AuthenticationService authService; public
> PageAccessFilter(PageRenderLinkSource renderLinkSource, ComponentSource
> componentSource, Response response, AuthenticationService authService)
> { this.renderLinkSource = renderLinkSource; this.componentSource =
> componentSource; this.response = response; this.authService =
> authService; } public void handleComponentEvent(
> ComponentEventRequestParameters parameters, ComponentRequestHandler
> handler) throws IOException { if
> (dispatchedToLoginPage(parameters.getActivePageName())) { return; }
> handler.handleComponentEvent(parameters); } public void
> handlePageRender(PageRenderRequestParameters parameters,
> ComponentRequestHandler handler) throws IOException { if
> (dispatchedToLoginPage(parameters.getLogicalPageName())) { return; }
> handler.handlePageRender(parameters); } private boolean
> dispatchedToLoginPage(String pageName) throws IOException { if
> (authService.isLoggedIn()) { return false; } Component page =
> componentSource.getPage(pageName); if (!
> page.getClass().isAnnotationPresent(RequiresLogin.class)) { return
> false; } Link link = renderLinkSource.createPageRenderLink("Login");
> response.sendRedirect(link); return true; } }
> 
> The above code makes a bunch of assumptions and simplifications. First,
> it assumes the name of the page to redirect to is "Login". It also
> doesn't try to capture any part of the incoming request to allow the
> application to continue after the user logs in. Finally, the
> AuthenticationService is not part of Tapestry ... it is something
> specific to the application.
> You'll notice that the dependencies (PageRenderLinkSource, etc.) are
> injected through constructor parameters and then stored in final
> fields. This is the preferred, if more verbose approach. We could also
> have used no constructor, a non-final fields with an @Inject annotation
> (it's largely a style choice, though constructor injection with final
> fields is more guaranteed to be fully thread safe).
> The class on its own is not enough, however: we have to get Tapestry to
> actually use this class. Contributing the Filter
> The last part of this is hooking the above code into the flow. This is
> done by making a contribution to the ComponentEventHandler service's
> configuration.
> Service contributions are implemented as methods of a Tapestry module
> class, such as AppModule:
> public static void contributeComponentRequestHandler(
> OrderedConfiguration configuration) {
> configuration.addInstance("RequiresLogin", RequiresLoginFilter.class); }
> 
> Contributing modules contribute into an OrderedConfiguration: after all
> modules have had a chance to contribute, the configuration is converted
> into a List that's passed to the service implementation.
> The addInstance() method makes it easy to contribute the filter:
> Tapestry will look at the class, see the constructor, and inject
> dependencies into the filter via the constructor parameters. It's all
> very declarative: the code needs the PageRenderLinkSource, so it simply
> defines a final field and a constructor parameter ... Tapestry takes
> care of the rest.
> You might wonder why we need to specify a name ("RequiresLogin") for
> the contribution? The answer addresses a somewhat rare but still
> important case: multiple contributions to the same configuration that
> have some form of interaction. By giving each contribution a unique id,
> it's possible to set up ordering rules (such as "contribution 'Foo'
> comes after contribution 'Bar'"). Here, there is no need for ordering
> because there aren't any other filters (Tapestry provides this service
> and configuration, but doesn't make any contributions of its own into
> it). Improvements and Conclusions
> This is just a first pass at security. For my clients, I've built more
> elaborate solutions, that include capturing the page name and
> activation context to allow the application to "resume" after the login
> is complete, as well as approaches for automatically logging the user
> in as needed (via a cookie or other mechanism).
> Other improvements would be to restrict access to pages based on some
> set of user roles; again, how this is represented both in code and
> annotations, and in the data model is quite up for grabs.
> My experience with different clients really underscores what a fuzzy
> world security can be: there are so many options for how you represent,
> identify and authenticate the user. Even basic decisions are
> underpinnings are subject to interpretation; for example, one of my
> clients wants all pages to require login unless a specific annotation
> is found. Perhaps over time enough of these use cases can be worked out
> to build the toolkit I mentioned earlier.
> Even so, the amount of code to build a solid, custom security
> implementation is still quite small ... though the trick, as always, is
> writing just the write code and hooking it into Tapestry in just the
> right way.
> I expect to follow up this article with part 2, which will expand on
> the solution a bit more, addressing some more of the real world
> constraints my customers demand. Stay tuned!
> 1 In fact, this service and pipeline were created in Tapestry 5.1
> specifically to address this use case. In Tapestry 5.0, this approach
> required two very similar filter contributions to two similar pipelines.
> 2 If there are multiple filters, you'd think that you'd delegate to the
> next filter. Actually you do, but Tapestry provides a bridge: a wrapper
> around the filter that uses the main interface for the service. In this
> way, each filter delegates to either the next filter, or the terminator
> (the service implementation after all filters) in a uniform manner.
> More details about this are in the pipeline documentation.
> 
> --
> Posted By Howard to Tapestry Central at 12/28/2009 02:49:00 PM
> 

-- 
View this message in context: 
http://old.nabble.com/-Tapestry-Central--Securing-Tapestry-pages-with-Annotations%2C-Part-1-tp26949008p27714327.html
Sent from the Tapestry - User mailing list archive at Nabble.com.


---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscr...@tapestry.apache.org
For additional commands, e-mail: users-h...@tapestry.apache.org

Reply via email to