Sunday, March 27, 2011

View Configuration with Seam 3 Faces - the Introduction

In Seam Faces we've provided JSF developers with a mechanism to centrally configure various aspects of their JSF views. Concerns like transaction control and view security are currently supported, and support for URL rewriting, XSRF attack prevention, and JSF navigation is in the planning stage.

The end-goal is to support view configuration via multiple sources (including some support for <f:metadata> child tags). For now we provide type-safe configuration using annotations on enums. The benefit of type-safety for page configuration is a tremendous benefit, and will become evident in the following code examples.

In this post I will cover the @ViewConfig security annotations supported in the Seam 3.0 release. This view configuration feature is still considered "beta" and the @ViewConfig API is subject to change in upcoming releases - based on the feedback we get from you, our community of Seam developers!

(Note: Seam Faces delegates authorization checks to Seam Security, which must be included in your project for the security annotations to have any effect)

Consider this @ViewConfig interface:
@ViewConfig
public interface Pages {
 
    static enum Pages1 {
 
        @ViewPattern("/*")
        @LoginView("/login.xhtml")
        @AccessDeniedView("/item/list.xhtml")
        ALL,
 
        @ViewPattern("/admin/*")
        @Admin(restrictAtPhase=RESTORE_VIEW)
        ADMIN,
         
        @ViewPattern("/item.xhtml")
        @Owner
        ITEM;
    }
}

The following annotations are available:
@ViewConfig
This marker annotation identifies this interface as containing view configuration annotated enums.
@ViewPattern
This contains the view pattern to which these annotations apply. Notice the use of wildcards in the view
@LoginView, @AccessDeniedView
The viewIds to redirect to for for authentication, and authorization failure
@Admin, @Owner
These "security" annotations are qualified with @SecureBindingType from Seam Security. The authorization methods themselves are each annotated with both the @Secures annotation and the corresponding "security" annotation
@RestrictAtPhase
Specify the phases at which the security restrictions will be applied. Default phases are the Invoke Application and Render Response phases

The secret sauce is in the annotations qualified with the @SecureBindingType qualifier.
@SecurityBindingType
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
public @interface Owner {
}

These annotations are mapped to methods that check for appropriate authorization, functionality provided by the Seam Security module. The authorization methods themselves are annotated with @Secure and the corresponding @SecureBindingType qualified annotation, as in the class below:
public class SecurityRules {
 
    private transient final Logger logger = Logger.getLogger(SecurityRules.class.getName());
 
    public @Secures @Owner boolean ownerCheck(Identity identity) {
        if (identity == null || identity.getUser() == null) {
            logger.info("Identity/User is null");
            return false;
        } else {
            String id = identity.getUser().getId();
            logger.infof("Username is: %s", id);
            return "demo".equals(id);
        }
    }
 
    public @Secures @Admin boolean adminCheck() {
        return false; // No one is an admin!
    }
}

So how do these pieces fit together? Consider a request for the view /list.xhtml. This would match the view pattern, "/*" and access would be granted, as no SecureBindingType qualified annotation is present. On the other hand, a request to the view /item.xhtml is matched by the more specific pattern "/item.xhtml", and the request would be intercepted. Authorization would be checked according to the method annotated by @Secures, and @Owner, and the user redirected to the login view /login.xhtml. If the user was already logged in, and authorization still failed, the user would be instead redirected to the access denied view. If these views are not defined by their respective annotations, a 401 response is returned.

The enforcement of security restrictions is highly tuned to the JSF life-cycle. By default, authorization is checked before the Invoke Application and Render Response phases (because the view requested may change between these phases). However, by including a "restrictAtPhase" method on SecureBindingType qualified annotation, the phases at which that restriction is applied can be fine tuned. Additionally, the @RestrictAtPhase annotation can be applied to an enum value to change the default phase for all SecureBindingType qualified annotations on enums values that match that pattern. In the example above, the @Admin restriction would be checked at the RestoreView phase, and the @Admin annotation is defined as follows:
@SecurityBindingType
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
public @interface Admin {
    public PhaseIdType[] restrictAtPhase();
}

While there are a lot of controls to fine tune your security requirements, the defaults are sensible (we hope!) and your security configuration can be quite straight forward. Of course one still has to configure Seam Security to perform the authentication - see the Seam 3 Security module page for further details.

In an upcoming blog series, I'll examine use cases, and propose how we can build more annotations into the view configuration to further centralize application configuration. The @ViewConfig API will also be further refined, based on community feedback. Feel free to leave comments here, or in one of the many communication avenues of the Seam framework with any ideas you have on how this feature can be refined. And of course, we're always looking for more developers to jump in and contribute!