Java and JBoss related stuff
RSS icon Home icon
  • New features in cdi-ext

    Posted on August 2nd, 2010 Adam Warski No comments

    Tomek SzymaƄski just commited two new features to cdi-ext.

    The first is the ability to secure JSF pages when using the Nav component to handle navigation (more on it here). If you want to make a page accessible only if a certain EL expression is true, when defining a page, you can write:

    private final Page adminPage = new ViewIdPageBuilder("/admin.xhtml")
        .setRequiresLogin(true)
        .setSecurityEL("#{currentUser.isAdmin)").b();
    

    If the condition is not met, the user will get 403 Forbidden. Also, Tomek extended handling of setRequiresLogin(true), so that if a user is redirected to the login page, because he was not logged in and the page is secured, the page which the user tried to access is stored and after logging in, he is redirected back to the original page.

    The second is the ability to specify the transaction timeout using a @TransactionTimeout(timeout = 5) annotation on a method.

    Moreover, we have moved our repository infrastructure to Nexus. The new address where cdi-ext artifacts are deployed is http://tools.softwaremill.pl/nexus/content/repositories/snapshots.

    Adam

  • Ruby on Rails + CDI? Why not! Enter TorqueBox + Weld

    Posted on July 27th, 2010 Adam Warski 3 comments

    I guess many people are often “unsatisfied” with how JSF works and how much time it sometimes takes to do a simple thing. That’s why we are trying out a new combination: RoR for the frontend and CDI for the backend. How?

    Deploying RoR applications to JBoss AS is really easy thanks to the TorqueBox project. You just deploy a .yml file using the provided rake tasks and you can develop the application “live” – no redeploys, instant changes, and so on.

    Using RoR as a frontend to a CDI/Weld based application requries two more steps, so that RoR can see the business logic classes and share the same http session with CDI (so it’s possible to access @SessionScoped beans from RoR and CDI code).

    First you need to deploy your application in the DefaultDomain (at least until TORQUE-85 is fixed). To do this, add a jboss-classloading.xml file to the META-INF directory with this content:

    <classloading xmlns="urn:jboss:classloading:1.0"
                  domain="DefaultDomain"
                  top-level-classloader="true"
                  export-all="NON_EMPTY"
                  import-all="true">
    </classloading>
    

    Secondly, you need to add a filter to RoR’s web application, so that Weld and RoR share the same session. Just edit config/web.xml in your RoR application (the magic in the RoR deployer will add it to the virtual .war deployment it creates) and add the following:

    <web-app>
        <listener>
            <listener-class>org.jboss.weld.servlet.WeldListener</listener-class>
        </listener>
    </web-app>
    

    Now RoR and CDI share the same session (so you can use @SessionScoped beans etc, probably also @ConversationScoped, but I haven’t tried that). You can lookup CDI beans from RoR code using e.g. the BeanInject class from cdi-ext, or just by writing a very simple utility method which lookups the BeanManager.

    Adam

  • Extending the security interceptor for Weld/JSF2

    Posted on April 13th, 2010 Adam Warski 4 comments

    In my previous post, I described how to create a simple security interceptor, which checks conditions defined using EL expressions, e.g.:

    @Secure("#{loggedInUser.name == arg0.name}")
    public List<Message> listMessages(User owner) { ... }
    

    Now, it would be nice to be able to stack such annotations, so that they can be placed:
    * on methods
    * on classes – then the constraint applies to all methods
    * on other annotations, to create “security bindings”

    An example usage could be:

    @Secure("#{loggedInUser != null}")
    public class Messages {
       @AdminOnly
       public void deleteAllMessages() { ... }
    
       @Secure("#{loggedInUser.maxMessageListCount == count}")
       public List<Message> listMessages(@ELVar("user") User owner, @ELVar("count") int count) { ... }
    }
    

    where @AdminOnly is defined as:

    @SecureBinding
    @Secure("#{loggedInUser.isAdministrator}")
    public @interface AdminOnly { }
    

    This way common security constraints can be expressed as annotations or on the class. I’m also using an improvement suggested by Dan Allen, to name the method arguments using @ELVar, instead of naming them arg0, arg1, etc. This also allows to refer to method arguments in the “security binding” annotations, whatever the position of the argument is.

    How to implement such annotations? Well, the first step is to create a portable extension (meaning it will work with any CDI implementation, not just Weld), which will gather, for each method, all the @Secure annotations, and their values, into one single annotation, @InterceptSecure. Having such a set of constraints to check for each method, we add the annotation to the method meta-data, using a utility class from Weld Extensions: the NewAnnotatedTypeBuilder. As the @InterceptSecure is an interceptor binding, the security interceptor will be called whenever the method is invoked.

    The extension observes the ProcessAnnotatedType event, which is fired for each bean type. If necessary, the type can be modified, to include the new annotations. The annotation is only added to Weld meta-data, not to the method itself (so there’s no bytecode manipulation or such).

    One last obstacle to overcome is to get the value of the generated @InterceptSecure annotation in the interceptor. Currently this is not possible using e.g. BeanManager, but should be address in CDI Maintenance Release (see here), so as a temporary solution all the generated annotations are stored in a map in the extension. All extensions are application-scoped beans, so the information can be accessed from the interceptor. One shortcoming of the solution is that one method may belong to several, differently annotated CDI beans.

    The code for the annotation and extension:

    @InterceptorBinding
    public @interface InterceptSecure {
        @Nonbinding
        String[]    value();
    }
    
    public class SecurityExtension implements Extension {
        private final Map<Method, InterceptSecure> interceptSecureForMethods = new HashMap<Method, InterceptSecure>();
    
        public InterceptSecure getInterceptSecure(Method m) {
            return interceptSecureForMethods.get(m);
        }
    
        public <T> void processAnnotatedType(@Observes ProcessAnnotatedType<T> event) {
            // A flag indicating if the builder was used to modify the annotations
            boolean used = false;
            NewAnnotatedTypeBuilder<T> builder = new NewAnnotatedTypeBuilder<T>(event.getAnnotatedType());
    
            // We need to read the values of the @Secure annotation that are present on:
            // 1. types (classes)
            // 2. methods
            // 3. arbitrarily nested on @SecureBinding annotations
    
            // Gathering the initial secure values from the type
            List<String> initialSecureValues = new ArrayList<String>();
            for (Annotation annotation : event.getAnnotatedType().getAnnotations()) {
                collectSecureValues(annotation, initialSecureValues);
            }
    
            for (AnnotatedMethod<?> m : event.getAnnotatedType().getMethods()) {
                // Gathering the secure values from the method
                final List<String> values = new ArrayList<String>(initialSecureValues);
                collectSecureValues(m, values);
    
                // If any values have been gathered, adding the annotation to the method and storing it
                // in the map.
                if (values.size() > 0) {
                    InterceptSecure is = new InterceptSecureImpl(values.toArray(new String[values.size()]));
                    builder.addToMethod(m.getJavaMember(), is);
                    used = true;
    
                    interceptSecureForMethods.put(m.getJavaMember(), is);
                }
            }
    
            // Setting the new annotated type, if any changed were made
            if (used) {
                event.setAnnotatedType(builder.create());
            }
        }
    
        private void collectSecureValues(AnnotatedMethod m, List<String> values) {
            for (Annotation annotation : m.getAnnotations()) {
                collectSecureValues(annotation, values);
            }
        }
    
        private void collectSecureValues(Annotation annotation, List<String> values) {
            if (Secure.class.isAssignableFrom(annotation.annotationType())) {
                values.add(((Secure) annotation).value());
            } else {
                if (annotation.annotationType().getAnnotation(SecureBinding.class) != null) {
                    for (Annotation nestedAnnotation : annotation.annotationType().getAnnotations()) {
                        collectSecureValues(nestedAnnotation, values);
                    }
                }
            }
        }
    
        private static class InterceptSecureImpl extends AnnotationLiteral<InterceptSecure> implements InterceptSecure {
            private final String[] values;
    
            private InterceptSecureImpl(String[] values) {
                this.values = values;
            }
    
            @Override
            public String[] value() {
                return values;
            }
        }
    }
    

    And for the interceptor:

    @Interceptor
    @InterceptSecure("")
    public class SecurityInterceptor {
        @Inject
        private SecurityExtension se;
    
        @AroundInvoke
        public Object checkSecurity(InvocationContext ctx) throws Exception {
            // Getting the generated @InterceptSecure annotation for the method.
            // After the CDI Maintenance Release is released, it should be possible to get the annotated
            // type of the currently invoked bean, see:
            // http://old.nabble.com/Retrieving-the-Bean-object-for-an-interceptor-td28147499.html
            // For now, we just use a map in the extension. One limitation is that this doesn't allow
            // different security annotations for methods which are used in several beans.
            InterceptSecure is = se.getInterceptSecure(ctx.getMethod());
            String[] toCheck = is == null ? null : is.value();
    
            // Check the el conditions as in the previous post
            // (...)
    
            return ctx.proceed();
        }
    }
    

    Adam

  • Simple security interceptor in Weld/JSF2

    Posted on March 31st, 2010 Adam Warski 7 comments

    Waiting for the Seam3 security module, I wrote a simple security interceptor (inspired by the Seam2 security annotation). You can use it like this:

    @Secure("#{loggedInUser.name == arg0.name}")
    public List<Message> listMessages(User owner) { ... }
    

    Here, loggedInUser is a @Named Weld (CDI) bean, and arg0 is the first argument of the method. This is a slight modification to the Seam2 security annotation, where it wasn’t possible to reference arguments. If the EL expression doesn’t evaluate to true, an exception is thrown.

    The implementation is pretty straightforward, as adding an interceptor in Weld is really simple. First, we need the annotation:

    /**
     * @author Adam Warski (adam at warski dot org)
     */
    @Retention(RUNTIME)
    @Target({TYPE, METHOD})
    @InterceptorBinding
    public @interface Secure {
        /**
         * @return The EL expression that should be evaluated. If it evaluates to
         * {@code true}, access will be granted. The EL expression may reference
         * any objects that are in any context, as well as the arguments of the method,
         * under the names {@code arg0, arg1, arg2, ...}.
         */
        @Nonbinding
        String  value();
    }
    

    The key components here is the @InterceptorBinding meta-annotation, and specifying the value of to be @Nonbinding. If the value element was binding, then we would need to define an interceptor for each possible String value.

    Next, the interceptor itself:

    @Interceptor
    @Secure("")
    public class SecurityInterceptor {
        private String getArgName(int index) { return "arg" + index; }
    
        @AroundInvoke
        public Object invoke(InvocationContext ctx) throws Exception {
            FacesContext facesCtx = FacesContext.getCurrentInstance();
            ELContext elCtx = facesCtx.getELContext();
    
            Secure secure = getSecureAnnotation(ctx.getMethod());
            String expression = secure.value();
    
            // Populating the request map so that parameters are available (arg0, ...)
            Map<String, Object> requestMap = facesCtx.getExternalContext()
                    .getRequestMap();
            for (int i = 0; i < ctx.getParameters().length; i++) {
                Object parameter = ctx.getParameters()[i];
                requestMap.put(getArgName(i), parameter);
            }
    
            Boolean expressionValue = (Boolean) facesCtx.getApplication()
                    .getExpressionFactory()
                    .createValueExpression(elCtx, expression, Boolean.class)
                    .getValue(elCtx);
    
            // Removing the parameters (arg0, arg1, ...)
            for (int i = 0; i < ctx.getParameters().length; i++) {
                requestMap.remove(getArgName(i));
            }
    
            if (expressionValue == null || !expressionValue) {
                throw new SecurityException();
            }
    
            return ctx.proceed();
        }
    
        private Secure getSecureAnnotation(Method m) {
            for (Annotation a: m.getAnnotations()) {
                if (a instanceof Secure) { return (Secure) a; }
            }
            for (Annotation a: m.getDeclaringClass().getAnnotations()) {
                if (a instanceof Secure) { return (Secure) a; }
            }
    
            throw new RuntimeException("@Secure not found on method " + m.getName() +
                    " or its class " + m.getClass().getName());
        }
    }
    

    And finally, we need an entry in beans.xml, enabling the interceptor:

    <interceptors>
       <class>util.security.SecurityInterceptor</class>
    </interceptors>
    

    One shortcoming is that it’s not currently possible to place one @Secure annotation on the class, and another on the methods (see this thread on the forum). The idea is that then the class-level annotation expresses general security constraints, which can be later refined on the method level.

    Another missing feature, which could be easily added, is a message parameter to the annotation, which would be included in the exception in case the check fails.

    Adam

  • JSF2 navigation: post->redirect->get

    Posted on March 11th, 2010 Adam Warski 2 comments

    JSF2 improves a lot both how navigation can be done (you can now return a view id from an action method, no need to describe every navigation case in faces-config.xml) and how URLs are handled (finally, GET support). JSF2 introduces view parameters (for those who know Seam: standardized page parameters). Each page can define a metadata section, where the view parameters are described, bound to bean values, converted and validated.

    As an example, a blog-entry-viewing page would define the id of the entry to be displayed as follows:

    <f:metadata>
       <f:viewParam name="entry_id" value="#{blog.entry}" required="true">
          <f:converter converterId="blog-entry-converter" />
       </f:viewParam>
    </f:metadata>
    

    Unfortunately I had some trouble with one thing: how to redirect to a page, including the view parameters, after a POST? This post->redirect->get pattern is very common. E.g. when you post a new comment for a blog entry and press submit, the data is persisted and you want to be redirected back to view.jsf?entry_id=819 (using the default JSF command-button behavior, you would land on a plain, non-bookmarkable view.jsf).

    Dan Allen wrote a series of very good introductory articles to JSF2 on DZone. There, he writes that what I described above should be possible to achieve by adding a <redirect include-view-params="true"/> tag to the appropriate <navigation-case> in faces-config.xml. Unfortunately, the xsd doesn’t allow such an attribute and it doesn’t work – I suppose that this construct didn’t make it into the final version of the spec (although somebody may correct me if I’m wrong).

    Another solution, this time working, can be found on Ed Burns’s blog. The trick is to return a string containing the view id and some additional parameters from the action method or use them as the command button/link action, e.g.:

    public String action() {
         // business logic ...
         return "view.xhtml?faces-redirect=true&includeViewParams=true"
    }
    

    However this way you’ll have to repeat the combination of the “magical parameters” a lot in your code. And it’s pretty easy to do a spelling mistake in one of the strings you return. Furthermore, it’s not possible to easily include one view parameter, without repeating the value mapping.

    The way I solved this is by introducing a Nav component (I’m using Weld), which holds information about pages. It contains a nested Page class, which has a “fluent” interface for building a link. Navigation then looks as follows:

    @Inject
    private Nav nav;
    
    public String action() {
         // business logic ...
         return nav.getViewEntry().redirect().includeViewParams().s();
    }
    

    Or, if you want to include only one parameter:

    public String action() {
         // business logic ...
         return nav.getViewEntry().redirect().includeViewParam("name").s();
    }
    

    In xhtml pages, you can also use the nav component to generate links:

    <h:link outcome="#{nav.manageIndex.s}">Manage</h:link>
    

    Notice that you completely abstract away from the actual names of the xhtml views (pages) – they are stored centrally only in the nav component! This makes any refactorings really easy.

    Speaking of the nav component, here’s the code:

    /**
     * @author Adam Warski (adam at warski dot org)
     */
    @Named
    @ApplicationScoped
    public class Nav {
        public static class Page {
            private final String viewId;
            private final Map<String, String> params;
    
            private Page(String viewId) {
                this.viewId = viewId;
                this.params = new LinkedHashMap<String, String>();
            }
    
            private Page(String viewId, Map<String, String> params) {
                this.viewId = viewId;
                this.params = params;
            }
    
            public Page redirect() {
                return includeParam("faces-redirect", "true");
            }
    
            public Page includeViewParams() {
                return includeParam("includeViewParams", "true");
            }
    
            public Page includeViewParam(String name) {
                // Getting the metadata facet of the view
                FacesContext ctx = FacesContext.getCurrentInstance();
                ViewDeclarationLanguage vdl = ctx.getApplication().getViewHandler()
                      .getViewDeclarationLanguage(ctx, viewId);
                ViewMetadata viewMetadata = vdl.getViewMetadata(ctx, viewId);
                UIViewRoot viewRoot = viewMetadata.createMetadataView(ctx);
                UIComponent metadataFacet = viewRoot.getFacet(
                      UIViewRoot.METADATA_FACET_NAME);
    
                // Looking for a view parameter with the specified name
                UIViewParameter viewParam = null;
                for (UIComponent child : metadataFacet.getChildren()) {
                    if (child instanceof UIViewParameter) {
                        UIViewParameter tempViewParam = (UIViewParameter) child;
                        if (name.equals(tempViewParam.getName())) {
                            viewParam = tempViewParam;
                            break;
                        }
                    }
                }
    
                if (viewParam == null) {
                    throw new FacesException("Unknown parameter: '" + name +
                         "' for view: " + viewId);
                }
    
                // Getting the value
                String value = viewParam.getStringValue(ctx);
                return includeParam(name, value);
            }
    
            public Page includeParam(String name, String value) {
                Map<String, String> newParams = new LinkedHashMap<String, String>(params);
                newParams.put(name, value);
                return new Page(viewId, newParams);
            }
    
            public String s() {
                StringBuilder sb = new StringBuilder();
                sb.append(viewId);
    
                String paramSeparator = "?";
                for (Map.Entry<String, String> nameValue : params.entrySet()) {
                    sb.append(paramSeparator).append(nameValue.getKey())
                          .append("=").append(nameValue.getValue());
                    paramSeparator = "&amp;amp;";
                }
    
                return sb.toString();
            }
    
            public String getS() { return s(); }
        }
    
        private final Page manageIndex = new Page("/manage/index.xhtml");
        private final Page manageUsers = new Page("/manage/users.xhtml");
        private final Page home = new Page("/home.xhtml");
        private final Page thisPage = new Page("");
        // other pages ...
    
        public Page getManageIndex() {
            return manageIndex;
        }
    
        // other getters ...
    }
    

    Looking forward, the Page class may also include e.g. security management, however that would require some more JSF bindings.

    Adam