FANDOM


This topic documents the steps necessary to create a basic, secure multi-tenant Grails 1.2.2 application using the Spring Security and Multi-Tenant plugins. The application will associate the user with an organization to obtain a tenant id that qualifies the domain classes. This enables a single web application and database instance to serve multiple independent organizations. Users belonging to any given tenant organization will be unable to view or modify data belonging to any other tenant organization. This capability might be used, for example, to develop web applications that can cost-effectively serve small non-profit clubs and charitable organizations.

When I first wrote this topic, some months ago, my main intent was to walk the reader past some (disappointingly) tricky and poorly-documented aspects of the integration and use of the various Grails plugins involved. As of late December, 2010, it is now possible to use Grails 1.3.6 with versions of the Spring Security and Multi-Tenant plugins that no longer have these vices. At some point, I'll re-vamp these instructions accordingly. For now, they may be useful as a guide for those who, for whatever reason, are stuck on Grails 1.2.2.

Grails Setup

I assume that you have installed Grails 1.2.2 and have a basic knowledge of its workings. If not, there are many good tutorials on installing Grails. I recommend Getting Started with Grails, Second Edition, a free book in PDF format that you can download.

These installation instructions are specific to the following versions of Grails and the plugins, which are the current release versions at the time of this writing:

  • Grails 1.2.2
  • Spring Security 2.0 Plugin (acegi 0.5.2)
  • Falcone Util Project (falcone-util 0.8)
  • Spring Web Flow Plugin (webflow 1.2.2)
  • Multi-Tenant Plugin (multi-tenant 0.17)
  • Multi-Tenant Acegi Integration (multi-tenant-acegi 0.4)

Project Setup

Create the Grails Application

First, we'll create the example application.

  • Create the racetrack application in the development sub-directory of your choice.
grails create-app racetrack
  • Now run the racetrack application for that immediate gratification kind of good feeling.
cd racetrack
grails run-app racetrack

Look familiar?

Setup Spring Security

Next, we install, configure and test the Spring Security Plugin, noting some dependency gotchas along the way.

  • Install the Spring Security Plugin acegi
grails install-plugin acegi 0.5.2

The Spring Security Plugin's user manager and registration controllers are dependent on the Spring Web Flow Plugin. As of Grails 1.2, webflow is no longer installed as a default plug-in. Perhaps because user manager and registration are considered optional, acegi doesn't install webflow automatically. Because we'll want the user manager at least, we'll need to install web flow or face ClassNotFound exceptions.

  • Install the Spring Web Flow Plugin webflow
grails install-plugin webflow 1.2.2
  • Create the authentication domain classes
grails create-auth-domains org.racetrack.User org.racetrack.Role Requestmap
  • Generate the user manager controller and views
grails generate-manager

We'll use annotations, rather than database or static request maps, to secure the controllers. Consequently, we won't need the Requestmap classes. To cut down on clutter, we'll remove them.

  • Delete the Requestmap views and controller
del grails-app\views\requestmap\*.*
rmdir grails-app\views\requestmap
del grails-app\controllers\RequestmapController.groovy
del grails-app\domain\Requestmap.groovy

Now we need to configure Spring Security to use controller annotations to grant or deny access based on the user's authorities (roles).

  • Edit the grails-app/conf/SecurityConfig.groovy file as follows :
    • Remove the requestMapClass property.
    • Add a new property useRequestMapDomainClass and set it to false.
    • Add a useControllerAnnotations property and set its value to true
loginUserDomainClass = "org.racetrack.User"
authorityDomainClass = "org.racetrack.Role"
useRequestMapDomainClass = false
/* requestMapClass = "Requestmap" */

useControllerAnnotations = true

Test Spring Security

It's a good idea to test the Spring Security plugin installation and configuration before we install more plugins that depend on it. To do this, we'll create a trivial test controller and secure it to ROLE_ADMIN with the @Secured annotation.

  • Create a secure test controller
grails create-controller org.racetrack.Secure
  • Edit the test controller grails-app\controllers\org\racetrack\SecureController.groovy source file index closure as follows:
package org.racetrack

import org.codehaus.groovy.grails.plugins.springsecurity.Secured

@Secured(['ROLE_ADMIN'])
class SecureController {
   def index = {
      render 'Secure access only'
   }
}

We'll need a user with the ROLE_ADMIN authority to test the controller. Out of the box, Grails uses an in-memory HSQL datasource that gets re-initialized on each run in development mode. We'll play along with this by adding the necessary security domain classes to the bootstrap configuration.

  • Edit the Grails bootstrap configuration grails-app\conf\BootStrap.groovy as follows:
    • Add a property to permit injection of AuthenticateService
    • Create an admin Role and an admin User in the init closure
import org.racetrack.*

import grails.util.GrailsUtil

class BootStrap {
    def authenticateService

    def init = { servletContext ->
        switch(GrailsUtil.environment) {
            case "development":
                // Admin Role
                def adminRole = new Role(
                    authority: 'ROLE_ADMIN',
                    description: 'Administrator'
                ).save()
                if (adminRole.hasErrors()) { println adminRole.errors }
                // Admin User
                def adminUser = new User(
                    username: 'admin',
                    userRealName: 'Admin User',
                    passwd: authenticateService.encodePassword('password'),
                    enabled: true,
                    email: 'admin@racetrack.com',
                    emailShow: false,
                    description: 'administrator'
                    // pass: 'password',
                ).save()
                if (adminUser.hasErrors()) { println adminUser.errors }
                adminRole.addToPeople(adminUser)
                adminRole.save()
                break

            case "production":
                break                
        }
    }

Note: The Spring Security plugin authentication routine expects that the User password is persisted in encrypted form. To do this, we need to use the encodePassword() method, which means we have to inject AuthenticateService.

Note: The User pass property is a transient. Ignore it as you would any other shiftless bum.

Now it's time to see if everything is working.

  • Start the Grails application
grails run-app
  • Click the link to the SecureController. The browser should display the Login page.
  • Enter User Name: admin and Password: password. Click Login. The browser should display the following message:
Secure access only

Patch the Multi-Tenant Plugin

NOTE: The following instructions apply to versions of the Multi-Tenant Plugin that were current for Grails 1.2.2. With Grails 1.3.5+, it is possible to use the just-released multi-tenant-spring-security plugin 0.2.1 and its dependency, multi-tenant-core 1.0.1. The tenantId issue is fixed in this release and you can (finally) just install the plugins and have it work.

The Multi-Tenant Plugin claims compatibility with Grails 1.2.2 and higher as of release 0.16. However, a known issue effectively prevents the plugin from working except in single-tenant database mode. This is the case even for the latest release, 0.17. To correct this issue, it is necessary to patch the plugin.

  • Download the Multi-Tenant Plugin 0.17 binary ZIP package from the using this direct link. Beware: The download link on the plugin information page currently links to release 0.16.
  • Extract the ZIP archive to the directory C:\dev\libs\grails-multi-tenant-0.17.
  • Edit the file C:\dev\libs\grails-multi-tenant-0.17\MultiTenantGrailsPlugin.groovy to modify the doWithDynamicMethods closure as follows:
    if (ConfigurationHolder.config.tenant.mode != "singleTenant") {
      //Add a nullable contraint for tenantId.
      application.domainClasses.each {DefaultGrailsDomainClass domainClass ->
        if (TenantUtils.isAnnotated(domainClass.clazz)) { 
          domainClass.constraints?.get("tenantId")?.applyConstraint(ConstrainedProperty.NULLABLE_CONSTRAINT, true);
          domainClass.clazz.metaClass.beforeInsert = {
	    if (tenantId == null) tenantId = 0
	  }
	}
      }
    }
  • Re-archive the C:\dev\libs\grails-multi-tenant-0.17 directory as grails-multi-tenant-0.17.zip. Note: Be sure the archive directory structure is rooted as the original was.
  • Copy the new ZIP archive grails-multi-tenant-0.17.zip to the Grails plugin cache $HOME/.grails/1.2.2/plugins where $HOME refers to your home directory. On Windows, your home directory is generally located in a sub-directory named for your user name under C:\Documents and Settings\. On Linux, it's generally under /home, but if you're using Linux as your development environment, I probably didn't have to tell you that, did I?

Setup Multi-Tenant

  • Install the Multi-Tenant plugins multi-tenant and multi-tenant-acegi
grails install-plugin multi-tenant 0.17
grails install-plugin multi-tenant-acegi 0.4

Important: It is vital to specify the release argument 0.17 to the install-plugin command. Otherwise, Grails will not install the patched version of the plugin.

This plugin properly identifies its dependency on the Falcone Util Project plugin falcone-util and installs it. This is how things should work.

The Multi-Tenant plug-in always multi-tenants the web application. It can select the tenant by host name or by using a tenant id associated with the User.

You can choose to have the Multi-Tenant plug-in manage the database in single-tenant or multi-tenant mode. In single-tenant mode, each tenant has a separate database. In multi-tenant mode, the tenants share a database. The plugin uses annotations to identify multi-tenant domain classes and uses a dynamic tenant id property to separate them in queries.

There are trade-offs to these approaches. Here, we're targeting the web application to support a lot of small organizations that come and go all the time. We want to make it easy and cheap to set up a new organization to use the application. For this reason, we'll configure the Multi-Tenant plugin to use one database for all tenants and to use the Multi-Tenant Acegi plugin to derive the tenant context from the current User.

  • Edit the grails-app/conf/Config.groovy file as follows:
    • Set the plugin mode property to specify a multi-tenant database.
    • Set the plugin resolver.type property to use the tenant id associated with the acegi User principal.
tenant {
    mode = "multiTenant"
    resolver.type = "acegi"
}
  • Edit the User domain class grails-app/domain/org/racetrack/User.groovy to add an Integer userTenantId property with a validation constraint that prevents it from being unspecified or negative:
    /** for multi-tenant-acegi plugin **/
    Integer userTenantId

    static constraints = {
        // Existing constraints
        userTenantId(min: 0)
    }

Create a Domain Class for Tenant Organizations

The Multi-Tenant plugin uses an Integer tenant ID to distinguish between tenants. However, it does not provide an entity to represent attributes of the tenants. We'll create a domain class called Organization for this purpose. Like User and Role, the Organization domain class won't be multi-tenant as it must be available before the user authenticates.

  • Create a domain class to represent the tenant organizations.
grails create-domain-class org.racetrack.Organization
  • Change the generated code for the grails-app/domain/com/racetrack/Organization.groovy class as follows:
package org.racetrack

class Organization {
    String name

    static mapping = {
        sort 'name'
    }

    static constraints = {
        name(blank: false, maxSize: 50, unique: true)
    }
    
    String toString() {
        name
    } 
}
  • Create a controller for the Organization domain class.
grails create-controller org.racetrack.Organization
  • Change the generated code for the grails-app/controllers/com/racetrack/OrganizationController.groovy class as follows:
package org.racetrack

class OrganizationController {
    def scaffold = true
}

Associate the Tenants with Organizations

Now that we have a domain object to represent the Organization, we'll need to allow the Administrator to select an Organization when creating or editing a User.

  • Edit the grails-app/views/user/create.gsp view to add a select control for the Organization (right after the Show Email checkbox):
<tr class="prop">
    <td valign="top" class="name"><label for="userTenantId">Organization:</label></td>
    <td valign="top">
        <g:select id="userTenantId" name="userTenantId" value="${person.userTenantId}"
            from="${org.racetrack.Organization.list()}"
            optionKey="id"
            optionValue="name"
        />
    </td>
</tr>
  • Edit the grails-app/views/user/edit.gsp view to add a similar select control in a similar position:
<tr class="prop">
    <td valign="top" class="name"><label for="userTenantId">Organization:</label></td>
    <td valign="top">
        <g:select id="userTenantId" name="userTenantId" value="${person.userTenantId}"
            from="${com.bingodocs.Organization.list()}"
            optionKey="id"
            optionValue="name"
        />
    </td>
</tr>

On the create and edit forms, we're listing the available Organizations in a select control using the Organization id property as the selection key. The value attribute of the g:select tag takes care of binding the selected option key to the userTenantId property on the User.

When we show the User, we want to display the name of the Organization associated with the user through the userTenantId property. To make this easier for the show view, we'll add the Organization object to the request attributes passed from the controller.

  • Edit the user controller class grails-app/controllers/com/racetrack/UserController.groovy as follows:
    • Import the organization domain class
    • Locate the Organization associated with the user and include it in the attribute map returned by the show closure
import org.racetrack.Organization

def show = {
    // existing code

    def organization = Organization.findById(person.userTenantId)
    [person: person, roleNames: roleNames, organization: organization]    
}

  • Edit the grails-app/views/user/show.gsp view to display the Organization name (again, right after Show Email):
<tr class="prop">
    <td valign="top" class="name">Organization:</td>
    <td valign="top" class="value">${organization.name?.encodeAsHTML()}</td>
</tr>

To prime the pump, we'll need to set up a master organization and associate the Admin user with this organization. We'll also need a role to distinguish users who are authorized to perform management functions for a given tenant. Again, we'll want to set up our example data in the bootstrap configuration.

  • Edit the Grails bootstrap configuration grails-app\config\BootStrap.groovy as follows:
    • Import the Multi-Tenant Plugin utility class TenantUtils
    • Create a master Organization and associate the admin user with the tenant ID of this organization
    • Create a Role to represent managers of tenant Organizations
import com.infusion.tenant.util.TenantUtils

class BootStrap {
    def init = { servletContext ->
        switch(GrailsUtil.environment) {
            case "development":
                // Master Organization
                def racetrackOrg = new Organization(
                    name: 'Racetrack.org'
                ).save()
                if (racetrackOrg.hasErrors()) { println racetrackOrg.errors }
                            
                // Admin Role
                def adminRole = new Role(
                    authority: 'ROLE_ADMIN',
                    description: 'Administrator'
                ).save()
                if (adminRole.hasErrors()) { println adminRole.errors }
        
                // Admin User
                def adminUser = new User(
                    username: 'admin',
                    userRealName: 'Admin User',
                    passwd: authenticateService.encodePassword('password'),
                    enabled: true,
                    email: 'admin@racetrack.org',
                    emailShow: false,
                    description: 'Administrator',
                    userTenantId: racetrackOrg.id
                ).save()
                if (adminUser.hasErrors()) { println adminUser.errors }
                adminRole.addToPeople(adminUser)
                adminRole.save()

                // Manager Role
                def managerRole = new Role(
                    authority: 'ROLE_MANAGER',
                    description: 'Manager'
                ).save()
                if (managerRole.hasErrors()) { println managerRole.errors }
                break

            case "production":
                break                
        }
    }   

Now it's time to see if everything is working.

  • Start the Grails application
grails run-app
  • Click the link to the LoginController. The browser should display the Login page.
  • Enter User Name: admin and Password: password. Click Login.
  • Click the link to the OrganizationController. The list should show the Racetrack.org organization.
  • Click New Organization and create a new Organization.
  • Navigate back to the home page and click the link to the UserController. The list should show the admin user.
  • Click New User and create a new User. This user should be associated with the Organization you just created and should have ROLE_MANAGER authority only.
  • Navigate back to the home page and click the LogoutController link.
  • Click the LoginController link and log in as the User you just created.

Multi-Tenant Programming

Multi-Tenant Domain Classes

At this point, we can build out the application as we normally would. For example, we could pick up in Chaper 3 of Getting Started with Grails at the Domain Classes section. As we create each domain class that should be multi-tenant capable, we mark it with the @MultiTenant annotation.

  • Create the domain and controller classes using a package prefix (not strictly necessary, but tidier)
grails create-domain-class org.racetrack.Race
grails create-controller org.racetrack.Race
  • Edit domain classes that should be multi-tenant according the following example using the Race class:
import com.infusion.tenant.groovy.compiler.MultiTenant

@MultiTenant
class Race {

Try setting up multiple organizations and associated users. You will note that if you log in as a user, you will only be able to view, create, edit or delete domain objects for the tenant associated with the user's organization.

Selecting a Tenant ID

One final trick: If you want to create domain objects for a given tenant in code, you need to perform these statements in a closure supplied to the TenantUtils.doWithTenant() method. For example:

import com.infusion.tenant.util.TenantUtils
    void initDevelopmentAppDomain() {
        def southeastern = Organization.findByName("Southeastern Regional Running Club")

        TenantUtils.doWithTenant(southeastern.id as Integer) {
  	    def turkeyTrot = new Race(
		name:'Turkey Trot'
	    ).save()

References

Ad blocker interference detected!


Wikia is a free-to-use site that makes money from advertising. We have a modified experience for viewers using ad blockers

Wikia is not accessible if you’ve made further modifications. Remove the custom ad blocker rule(s) and the page will load as expected.