Wednesday 7 November 2012

Tapestry5 & Bootstrap: A Modal Dialog

Modal Dialogs are a nice thing that can be very helpful in a web application. I was looking for a straight forward solution to get such thing into our Tapestry5 app and stumbled accross a blog post that explained how to integrate a modal dialog with Tapestry5: Tawus Modal Dialog.

This approach worked well, however it contains some code to write and maintain.

Now, some time later we have the awsome Bootstrap CSS Framework which comes along with a Modal Dialog component which fits pretty well into Tapestry5 with less coding:

What you need

  • Bootstrap CSS + JS files on the classpath
  • jQuery Javascript Library or the tapestry-jquery module

Tapestry5 & jQuery

If you do not like the tapestry-jquery module as a dependency it's no problem to just include the latest and greatest jQuery.js - it's advised to use it in no-conflict mode. In the example below I assume you called: $j = jQuery.noConflict();

Create your Dialog component

Below you see the Dialog-class which lives in the components package:

@Import({ library = "context:js/bootstrap.js", stylesheet = "context:css/bootstrap.css" })
public class Dialog implements ClientElement {

 @Parameter(value = "prop:componentResources.id", defaultPrefix = BindingConstants.LITERAL)
 private String clientId;

 @Parameter(required = true)
 @Property
 private Block content;

 @Property
 @Parameter(required = false, defaultPrefix = "literal")
 private String title;

 @Inject
 private JavaScriptSupport js;

 @Override
 public String getClientId() {
  return clientId;
 }

 @AfterRender
 private void afterRender() {
  js.addScript("$j('#%s').modal()", getClientId());
 }

}

This is basically a very simple client element that contains a title and a content block - both getting set as parameter. Finally a small piece of Javascript code that will trigger the dialog once loaded. Let's look at the Dialog.tml file:





I copied this straight from the Bootstrap example page and placed my title and the content block as appropriate. That's it - the Dialog is done so far. Now let's use it.

Using the Dialog

In our application the Dialog should pop-up when a link is clicked to present some further information/options to the user: so our page contains an ActionLink that will update a zone. The action will simply return a block, in which we are using our dialog:

public class MyPage {
     
     @Inject
     private Block dialogBlock;

     public Object onAction() {
           return dialogBlock;
     )
}

Open Dialog





   
       
           Put some nice content in here, maybe a grid or other components...
       
   

I'm sure there is still some potential to make this even more compact and configurable but for a start not too bad I'd say.

Sunday 25 March 2012

Guice, Jersey & Shiro

Introduction

This is a little write-up I did after some experiences with Jersey, Guice and Shiro. The application used to be secured with Spring-Security, which lead to issues when migrating to Guice3. It didn't really fit in the scene anyways so I decided to replace it with Apache Shiro.

Parameters

The code examples are taken from an enterprise application that publishes a rest webservice using Jersey. So we have the following parameters:

  • Jersey
  • Guice3
  • Apache Shiro
  • Security with Microsoft Active Directory and a Custom Database where separate user accounts are maintained

I will not explain how to setup Guice, Guice-Persistence or Jersey. The following paragraphs will only show how Apache Shiro is integrated esp. with Guice.

Apache Shiro provides some documentation about this topic (see Shiro Guice Documentation) but this is a little thin from my point of view.

Maven Dependencies

As this is a web application we need the "shiro-web" artifact. Shiro's latest version ships Guice Integration, so we want this, too:

<dependency>
   <groupId>org.apache.shiro</groupId>
   <artifactId>shiro-web</artifactId>
   <version>1.2.0</version>
</dependency>

<dependency>
   <groupId>org.apache.shiro</groupId>
   <artifactId>shiro-guice</artifactId>
   <version>1.2.0</version>
</dependency>

Implementing the Realms

To use security with ADS it's best to extend the ActiveDirectoryRealm:

public class CustomActiveDirectoryRealm extends ActiveDirectoryRealm {

    private static final String SEARCH_BASE = "dc=example,dc=org";

    @Inject
    public CustomActiveDirectoryRealm(LdapContextFactory contextFactory, CacheManager cacheManager) {
            setLdapContextFactory(contextFactory);
            setSearchBase(SEARCH_BASE);
            setCacheManager(cacheManager);
            setCachingEnabled(true); 
    }
}

The LdapContextFactory is required to provide a LDAP Connection, the CacheManager is a chaching instance to decrease lookups. As the instances of our CustomerActiveDirectory Realm will be managed by Guice we simply inject both.

Our second Realm will use a database to query user data. Therefore we extend Shiro Authorizing Realm; this requires us to implement two methods to return the account data and role memberships:

public class DatabaseRealm extends AuthorizingRealm {

    private final UserDAO dao;

    @Inject
    public DatabaseRealm(UserDAO dao, CacheManager cacheManager,
                    @Named("SHA1") HashedCredentialsMatcher credentialsMatcher) {
            super(cacheManager, credentialsMatcher);
            setCachingEnabled(true);
            this.dao = dao;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
                    AuthenticationToken token) throws AuthenticationException {

            final UsernamePasswordToken upToken = (UsernamePasswordToken) token;
            final User user = dao.findByUsername(upToken.getUsername());

            return user == null ? null : new SimpleAuthenticationInfo(
                            user.getUsername(), user.getPassword(), getName());
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

            if (principals.fromRealm(getName()).isEmpty()) {
                    return null;
            }

            final String username = (String) principals.fromRealm(getName())
                            .iterator().next();

            final User user = dao.findByUsername(username);
            if (user == null) {
                    return null;
            }

            final SimpleAuthorizationInfo authzInfo = new SimpleAuthorizationInfo();
            for (final UserAuthority role : user.getAuthorities()) {
                    authzInfo.addRole(role.toString());
            }

            return authzInfo;
    }
}

Again, dependencies are injected via the constructor. Our DatabaseRealm is simply linked to a UserDAO that is used to retrieve the user information. Note that depending on how passwords are stored you will need to provide the Credentials Matcher - in this case we inject a SHA1-Credentials Matcher.

Configure the Shiro Module

To configure security we need to setup a Module that we can install later. This is accomplished by extening ShiroWebModule (which extends PrivateModule):

public class ShiroSecurityModule extends ShiroWebModule {

    public ShiroSecurityModule(ServletContext servletContext) {
            super(servletContext);
    }

    @Provides
    @Singleton
    public LdapContextFactory provideContextFactory() {
            final JndiLdapContextFactory contextFactory = new JndiLdapContextFactory();
            contextFactory.setUrl("ldap://ldaphost:3268/");

            return contextFactory;
    }

    @Override
    protected void configureShiroWeb() {
            bind(CacheManager.class).to(MemoryConstrainedCacheManager.class);
            expose(CacheManager.class);

            bind(AuthenticationStrategy.class).to(FirstSuccessfulStrategy.class);
            expose(AuthenticationStrategy.class);

            bind(HashedCredentialsMatcher.class).annotatedWith(Names.named("SHA1"))
                            .toInstance(new HashedCredentialsMatcher("SHA1"));
            expose(HashedCredentialsMatcher.class).annotatedWith(
                            Names.named("SHA1"));

            bindRealm().to(CustomActiveDirectoryRealm.class);
            bindRealm().to(DatabaseRealm.class);

            addFilterChain("/web/**", USER);
            addFilterChain("/api/**", config(NO_SESSION_CREATION, "true"),
                            AUTHC_BASIC);
    }
}

In here things for the Realms are provided - this is pretty straight forward and self-explanatory. The bindRealm() method will create a MultiBinding - the Realms will be processed in the order they are bound.

Furthermore path-specific authentication methods are declared.

Servlet Configuration

Here we pull it all together: modules for Shiro, persistence and the Jersey resources are installed. Note how we pass Jersey's "RolesAllowedResourceFilterFactory" - this allows us to use the Java security @RolesAllowed Annotations to secure resources and methods. Jersey will examine the SecurityContext which will keep the authorization Information:
public class MyServletModule extends ServletModule {

    @Override
    protected void configureServlets() {

            install(new ShiroSecurityModule(getServletContext()));

            install(new PersistenceModule());
            install(new RestApiModule());

            final Map<String, String> params = new HashMap<String, String>();

            params.put(PackagesResourceConfig.PROPERTY_RESOURCE_FILTER_FACTORIES,
                            "com.sun.jersey.api.container.filter.RolesAllowedResourceFilterFactory");

            filter("/*").through(PersistFilter.class);

            filter("/*").through(GuiceShiroFilter.class);

            serve("/api/*").with(GuiceContainer.class, params);

    }
}

Security with Jersey

This is a simple example how to secure a resource:
@Path("helloworld")
@RolesAllowed("USER")
public class InfoResource {

        @GET
        public String getHelloWorld() {
              return "Hello World";
        }

}