Hitachi Vantara Pentaho Community Wiki
Skip to end of metadata
Go to start of metadata

Useful Information

Starting in version 1.6, security is a feature of the Pentaho BI Platform. Prior to this version, security was only available in the Pentaho Professional BI Platform (now called the Subscription Edition).

Furthermore, this document is relevant only to the Pentaho Professional BI Platform version 1.2.1 or later or the Pentaho BI Platform version 1.6 or later. See the Pentaho Professional BI Platform version 1.2.0 security documentation if you're using Pentaho Professional BI Platform version 1.2.0. (You can find the version you are running in several ways: (1) look at the log when the Pentaho BI Platform starts or (2) look at the bottom right of any page within the Pentaho BI Platform.)

Getting Security Data

There are two key interfaces that fetch security data: UserDetailsService and UserRoleListService.

UserDetailsService

The Acegi Security class UserDetailsService defines a single method: UserDetails loadUserByUsername(String username). Given a username, it returns a UserDetails instance.

UserRoleListService

The Pentaho class UserRoleListService defines 4 methods:

  • GrantedAuthority[] getAllAuthorities()
  • String[] getAllUsernames()
  • String[] getUsernamesInRole(GrantedAuthority authority)
  • GrantedAuthority[] getAuthoritiesForUser(String username)

For the final method above, all UserRoleListService implementations should delegate to the associated UserDetailsService, if possible.

Choice of Security Back-end

An organization can keep their security data in one or more back-ends. Typical security back-ends include relational databases and directory servers accessed via LDAP. Using Acegi Security, the platform offers these choices of security back-ends:

Backend

UserDetailsService implementation

UserRoleListService implementation

In-Memory*

InMemoryDaoImpl

InMemoryUserRoleListService

DBMS

JdbcDaoImpl

JdbcUserRoleListService

Directory Server

LdapUserDetailsService

DefaultLdapUserRoleListService

* An in-memory security back-end is primarily provided for testing or small user populations.

Configuration Files

The platform stores its security data access object (DAO) configuration files in the WEB-INF directory. The table below enumerates these files.

Filename

Description

applicationContext-acegi-security-*.xml

Defines a UserDetailsService based on *. Examples of * include memory, jdbc, and ldap. The UserDetailsService defined in this file is referenced in applicationContext-acegi-security.xml and applicationContext-pentaho-security-*.xml. Read more about the UserDetailsService implementations and how to configure them in Getting Password and Granted Authorities of a User.

applicationContext-pentaho-security-*.xml

Defines a UserRoleListService based on *. Examples of * include memory, jdbc, and ldap. Read more about the UserRoleListService implementations and how to configure them in Getting All Usernames and Roles.

One might ask: Why are there so many configuration files? The Acegi Security Contacts Sample Application doesn't have this many! The reason for all the files is (1) to partition the files to allow them to be swapped out in order to connect to different security backends and (2) to provide examples configurations for each of the three supported security backends: memory, dbms, and ldap.

Because of the partitioning, the contextConfigLocation context-param in web.xml has a consistent template which looks like the following:

Warning

Note that the end-of-line backslashes that occur in the excerpt below are present for formatting purposes only and should not be present in the actual file.

web.xml
<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/applicationContext-acegi-security.xml \
    /WEB-INF/applicationContext-common-authorization.xml \
    /WEB-INF/applicationContext-acegi-security-*.xml \
    /WEB-INF/applicationContext-pentaho-security-*.xml</param-value>
</context-param>

Given this template, to switch to an LDAP-based security backend, you simply replace the * above with ldap. To switch to memory-based, use memory. And finally, to switch to db-based, use jdbc.

Getting Password and Granted Authorities of a User

The DAO that has the responsibility of fetching a password and granted authorities, given a username, is an instance of UserDetailsService and is defined in applicationContext-acegi-security-*.xml.

You'll notice a naming convention in the files listed in this section. Each UserDetailsService implementation has a bean name of userDetailsService. Why is that? It's because applicationContext-acegi-security.xml has a dependency on a bean named userDetailsService. For this reason, do not change the names of these beans.

Useful Information

The vast majority of the configuration contained in the applicationContext-acegi-security-*.xml files is a standard Acegi Security setup and is well-documented in the Acegi Security documentation. Where the configuration strays from the Acegi Security documentation, it is documented below.

Memory

This DAO reads usernames, passwords, and roles specified in a Spring XML file.

The InMemoryDaoImpl class uses an instance of UserMap. But a UserMap restricts how the information passed into its constructor can be accessed. For example, one cannot ask the question of a UserMap: What are all the users? Since the platform user management system needs to answer this exact question, the platform comes with a way to intercept the information passed into a UserMap constructor. The information passed into the UserMap constructor is intercepted as a String and later fed into a UserMapFactoryBean for use in the InMemoryDaoImpl.

But InMemoryDaoImpl doesn't take a UserMapFactoryBean! It takes a UserMap! The secret to this working lies in the Spring type called FactoryBean. When Spring detects a bean of this type, instead of returning the instance, it returns instance.getObject().

applicationContext-acegi-security-memory.xml
<bean id="userMap" class="java.lang.String">
  <constructor-arg type="java.lang.String">
    <value>
    <![CDATA[
    joe=password,ROLE_ADMIN,ROLE_CEO,ROLE_AUTHENTICATED
    suzy=password,ROLE_CTO,ROLE_IS,ROLE_AUTHENTICATED
    pat=password,ROLE_DEV,ROLE_AUTHENTICATED
    tiffany=password,ROLE_DEV,ROLE_DEVMGR,ROLE_AUTHENTICATED
    admin=secret,ROLE_ADMIN,ROLE_AUTHENTICATED
    ]]>
    </value>
  </constructor-arg>
</bean>

<bean id="userMapFactoryBean"
  class="com.pentaho.security.memory.UserMapFactoryBean">
  <property name="userMap">
    <ref local="userMap" />
  </property>
</bean>

<bean id="userDetailsService"
  class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
  <property name="userMap">
    <ref local="userMapFactoryBean" />
  </property>
</bean>

DBMS

The configuration for this section does not stray from the default Acegi Security distribution.

LDAP

This DAO reads usernames, passwords, and roles specified in directory server.

Acegi Security doesn't provide an LDAP-based UserDetailsService. So the platform filled that gap with LdapUserDetailsService. LdapUserDetailsService uses an LdapUserSearch, an LdapAuthoritiesPopulator, and an optional LdapUserDetailsMapper. Note that LdapUserSearch, LdapAuthoritiesPopulator, and LdapUserDetailsMapper are highly specialized classes whose purpose is to find a user record, find the roles of that user, and map the attributes of the user record into a UserDetails instance respectively.

Handy Hint

For an overview of search filter syntax, see LDAP Search Filter Syntax.

Useful Information

According to Spring Security Issue SEC-251, you can use {1} (as opposed to {0}) in your groupSearchFilter which will be replaced with a username. ({0} will be replaced with a user DN.)

Warning

Be careful about ampersand symbols within your LDAP search filters. They must be escaped!

applicationContext-acegi-security-ldap.xml
<bean id="initialDirContextFactory"
  class="org.acegisecurity.ldap.DefaultInitialDirContextFactory">
  <constructor-arg index="0"
    value="ldap://localhost:10389/ou=system" />
  <property name="managerDn" value="uid=admin,ou=system" />
  <property name="managerPassword" value="secret" />
</bean>

<bean id="ldapAuthProvider"
  class="org.acegisecurity.providers.ldap.LdapAuthenticationProvider">
  <constructor-arg>
    <bean
      class="org.acegisecurity.providers.ldap.authenticator.BindAuthenticator">
      <constructor-arg>
        <ref local="initialDirContextFactory" />
      </constructor-arg>
      <property name="userSearch">
        <ref local="userSearch" />
      </property>
    </bean>
  </constructor-arg>
  <constructor-arg>
    <ref local="populator" />
  </constructor-arg>
</bean>

<bean id="populator"
  class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">
  <constructor-arg index="0">
    <ref local="initialDirContextFactory" />
  </constructor-arg>
  <constructor-arg index="1" value="ou=roles" />
  <property name="groupRoleAttribute" value="cn" />
  <!-- {0} will be replaced with user DN; {1} will be replaced with username -->
  <property name="groupSearchFilter" value="roleOccupant={0}" />
</bean>

<bean id="userSearch"
  class="org.acegisecurity.ldap.search.FilterBasedLdapUserSearch">
  <constructor-arg index="0" value="ou=users" />
  <constructor-arg index="1" value="cn={0}" />
  <constructor-arg index="2">
    <ref local="initialDirContextFactory" />
  </constructor-arg>
</bean>

<bean id="userDetailsService"
  class="com.pentaho.security.ldap.LdapUserDetailsService">
  <property name="userSearch">
    <ref local="userSearch" />
  </property>
  <property name="populator">
    <ref local="populator" />
  </property>
</bean>

Getting All Usernames and Roles

The DAO that has the responsibility of fetching all usernames and authorities (plus a few other responsibilities) is an instance of UserRoleListService and is defined in applicationContext-pentaho-security-*.xml.

Warning

The UserDetailsRoleListService class introduced in this section wraps a UserRoleListService instance and must be defined in the Spring XML Beans document for proper functioning of the Pentaho BI Platform.

Memory

The in-memory implementation of UserRoleListService is InMemoryUserRoleListService. Notice the bean below named userRoleListEnhancedUserMapFactoryBean? It refers to the bean defined in applicationContext-acegi-security-memory.xml. It uses the same FactoryBean trick that is used by UserMapFactoryBean. In an indirect fashion, the information contained in a UserMap is passed into the InMemoryUserRoleListService.

There's one more property to mention: allAuthorities. This defines all authorities that are allowed to be granted to users. Why can't the platform get this information from the UserRoleListEnhancedUserMap? That's because the UserRoleListEnhancedUserMap might only contain a subset of all the available authorities.

applicationContext-pentaho-security-memory.xml
<bean id="userRoleListEnhancedUserMapFactoryBean"
  class="com.pentaho.security.memory.UserRoleListEnhancedUserMapFactoryBean">
  <property name="userMap" ref="userMap" />
</bean>

<bean id="inMemoryUserRoleListService"
  class="com.pentaho.security.memory.InMemoryUserRoleListService">
  <property name="userRoleListEnhancedUserMap">
    <ref local="userRoleListEnhancedUserMapFactoryBean" />
  </property>
  <property name="userDetailsService" ref="userDetailsService" />
  <property name="allAuthorities">
    <list>
      <bean class="org.acegisecurity.GrantedAuthorityImpl">
        <constructor-arg value="ROLE_AUTHENTICATED" />
      </bean>
      <!-- some authorities omitted -->
      <bean class="org.acegisecurity.GrantedAuthorityImpl">
        <constructor-arg value="ROLE_ANONYMOUS" />
      </bean>
    </list>
  </property>
</bean>

<bean id="pentahoUserRoleListService"
  class="com.pentaho.security.UserDetailsRoleListService">
  <property name="userRoleListService">
    <ref local="inMemoryUserRoleListService" />
  </property>
</bean>

DBMS

The DBMS implementation of UserRoleListService is JdbcUserRoleListService. It is analogous to JdbcDaoImpl. It simply has three more properties defining the SQL queries that can return the information defined by the UserRoleListService interface.

Be Careful

Be sure to add as username and as authorities where appropriate in your queries.

applicationContext-pentaho-security-jdbc.xml
<bean id="jdbcUserRoleListService"
  class="com.pentaho.security.jdbc.JdbcUserRoleListService">
  <property name="allAuthoritiesQuery">
    <value>
      <![CDATA[SELECT distinct(authority) as authority FROM authorities]]>
    </value>
  </property>
  <property name="allUsernamesInRoleQuery">
    <value>
      <![CDATA[SELECT distinct(username) as username FROM granted_authorities where authority = ?]]>
    </value>
  </property>
  <property name="allUsernamesQuery">
    <value>
      <![CDATA[SELECT distinct(username) as username FROM users]]>
    </value>
  </property>
  <property name="dataSource" ref="dataSource" />
</bean>

<bean id="pentahoUserRoleListService"
  class="com.pentaho.security.UserDetailsRoleListService">
  <property name="userRoleListService">
    <ref local="jdbcUserRoleListService" />
  </property>
</bean>

LDAP

Warning

Be careful about ampersand symbols within your LDAP search filters. They must be escaped!

The LDAP implementation of UserRoleListService is DefaultLdapUserRoleListService.

Useful Information

Note that this version is a significant refactoring of the previous LDAP implementation. See LDAP Configuration Migration for a side-by-side comparison of old and new.

In this release of the platform, some new types have been introduced. These new types are outlined below. Before the new types are introduced, it is important to be familiar with a key JNDI class: DirContext. This class is the interface into directory services. In particular, there is a method with the following signature:

public NamingEnumeration search(String name,
                                String filterExpr,
                                Object[] filterArgs,
                                SearchControls cons)
                         throws NamingException;

Notice the parameters to this method? In the platform, these parameters are encapsulated into LdapSearchParams.

LdapSearchParams

LdapSearchParams bundle together the parameters that are eventually passed into a search call on a DirContext instance. The best description of these parameters is in the javadoc for the DirContext class but a brief description of each is below.

Parameter Name

Description

name

The DN of the node at which to begin the search. This parameter is also referred to as a search base.

filterExpr

A query for objects in the directory. This query can contain placeholders. This is analogous to a parameterized SQL query. See LDAP Search Filter Syntax for a quick overview of the syntax. See LDAP Search Filter Syntax for a quick overview of the syntax.

filterArgs

The values to be substituted for the placeholders in filterExpr.

cons

Search controls.

LdapSearchParams can only be created by LdapSearchParamsFactory instances.

LdapSearchParamsFactory

LdapSearchParamsFactory defines a single method:

LdapSearchParams createParams(Object[] filterArgs);

The platform provides a single implementation of this interface called LdapSearchParamsFactoryImpl. The constructor of this class requires a name, filterExpr, and searchControls--all of which go into creating LdapSearchParams instances.

LdapSearch

The LdapSearch interface is a generalization of org.acegisecurity.ldap.LdapUserSearch. In Acegi Security's LdapUserSearch, the goal was to find a user object in the directory; in Pentaho's LdapSearch, the goal is to find any object in the directory. In the platform, there are two implementations of this interface: GenericLdapSearch and UnionizingLdapSearch.

GenericLdapSearch

GenericLdapSearch instances are created using the four parameters shown below. The diagram below shows how GenericLdapSearch uses these parameters to execute a search.

Parameter Name

Description

initialDirContextFactory

An instance of InitialDirContextFactory.

paramsFactory

An instance of LdapSearchParamsFactory.

resultsTransformer

Transforms LDAP search results into custom objects. The type of this parameter is Transformer.

filterArgsTransformer

Transforms filter arguments before passing to the paramsFactory. The type of this parameter is Transformer.

The Transformer interface is a simple but powerful type. Transformers can be chained together, each doing a small part of a large task. There are three transformers provided with the platform.

SearchResultToAttrValueList

SearchResultToAttrValueList extracts the value of the token tokenName from the attribute attributeName which is taken from the SearchResult input.

StringToGrantedAuthority

Authorities in a directory server are simple strings. In order to each of those strings into a GrantedAuthority, this transformer is used. It provides the option of adding a role prefix and converting the string's case.

GrantedAuthorityToString

This class does the reverse of StringToGrantedAuthority except that it cannot control string case. This could be used in the query for what users are in particular role. The role is passed in with a role prefix, this transformer removes it, and the new role string is passed in as a filter argument.

Example LDAP Object Graph


The picture above depicts an example wiring of LDAP-related beans.

GenericLdapSearch Internals

UnionizingLdapSearch

The purpose of this class is to merge the results returned by two or more LdapSearch instances.

DefaultLdapUserRoleListService

Remember that DefaultLdapUserRoleListService is the LDAP implementation of UserRoleListService. As a consequence, it must implement the methods defined in UserRoleListService. Recall that those methods are: getAllAuthorities(), getAllUsernames(), getUsernamesInRole(), and getAuthoritiesForUser(). DefaultLdapUserRoleListService implements the first three methods by delegating to three separate LdapSearch instances--stored in its allAuthoritiesSearch, allUsernamesSearch, and usernamesInRoleSearch properties. (Remember GenericLdapSearch is an implementation of LdapSearch.) DefaultLdapUserRoleListService implements the last method by delegating to an LdapUserDetailsService instance--stored in its userDetailsService property. The userDetailsService property is not detailed below since it is covered in the section on LdapUserDetailsService.

And since DefaultLdapUserRoleListService is configured via Spring, the task is reduced to defining three GenericLdapSearch instances (plus an LdapUserDetailsService) in Spring! But before the Spring config is presented, the equivalent configuration via Java code is presented. This route is chosen since Spring configuration can be very verbose and most are more familiar with the more ubiquitous Java syntax.

Creating a GenericLdapSearch Using Java Code
// create params factory with the following settings:
// search base="ou=users",
// filterExpr="(objectClass=person)",
LdapSearchParamsFactory paramsFactory = new LdapSearchParamsFactoryImpl(
  "ou=users", "(objectClass=Person)");

// create a resultsTransformer that extracts the uid attribute
Transformer transformer = new SearchResultToAttrValueList("uid");

// create a GenericLdapSearch with objects created above;
// (don't worry about getInitialCtxFactory()--just know that
// it returns an InitialDirContextFactory)
LdapSearch allUsernamesSearch = new GenericLdapSearch(
  getInitialCtxFactory(), paramsFactory, transformer);

Now the equivalent Spring config is presented.

Creating a GenericLdapSearch Using Spring
<bean id="allUsernamesSearch"
  class="com.pentaho.security.ldap.search.GenericLdapSearch">
  <constructor-arg index="0" ref="initialDirContextFactory" />
  <constructor-arg index="1">
    <bean
      class="com.pentaho.security.ldap.search.LdapSearchParamsFactoryImpl">
      <constructor-arg index="0" value="ou=users" />
      <constructor-arg index="1" value="objectClass=Person" />
    </bean>
  </constructor-arg>
  <constructor-arg index="2">
    <bean
      class="com.pentaho.security.ldap.transform.SearchResultToAttrValueList">
      <constructor-arg index="0" value="uid" />
    </bean>
  </constructor-arg>
</bean>

Useful Information

Why are constructor-arg elements used? Why not call property setters instead? The reason for this is that the only way to set some of the properties in the example below is to pass those properties in during object creation. This enforces the policy that these properties should be set once and never changed.

Handy Hint

For an overview of search filter syntax, see LDAP Search Filter Syntax.

The steps to create a GenericLdapSearch have been introduced. Follow these steps to create the three searches required by DefaultLdapUserRoleListService. The only remaining property to set on DefaultLdapUserRoleListService is userDetailsService. Since that was introduced in the section on LdapUserDetailsService, it will not be covered again here.

The contents of application-pentaho-security-ldap.xml is below. Notice that some of the bean references refer to beens defined in applicationContext-acegi-security-ldap.xml.

applicationContext-pentaho-security-ldap.xml
<bean id="allUsernamesSearch"
  class="com.pentaho.security.ldap.search.GenericLdapSearch">
  <constructor-arg index="0" ref="initialDirContextFactory" />
  <constructor-arg index="1">
    <bean
      class="com.pentaho.security.ldap.search.LdapSearchParamsFactoryImpl">
      <constructor-arg index="0" value="ou=users" />
      <constructor-arg index="1" value="objectClass=Person" />
    </bean>
  </constructor-arg>
  <constructor-arg index="2">
    <bean
      class="com.pentaho.security.ldap.transform.SearchResultToAttrValueList">
      <constructor-arg index="0" value="uid" />
    </bean>
  </constructor-arg>
</bean>

<bean id="allAuthoritiesSearch"
  class="com.pentaho.security.ldap.search.GenericLdapSearch">
  <constructor-arg index="0" ref="initialDirContextFactory" />
  <constructor-arg index="1">
    <bean
      class="com.pentaho.security.ldap.search.LdapSearchParamsFactoryImpl">
      <constructor-arg index="0" value="ou=roles" />
      <constructor-arg index="1"
        value="objectClass=organizationalRole" />
    </bean>
  </constructor-arg>
  <constructor-arg index="2">
    <bean
      class="org.apache.commons.collections.functors.ChainedTransformer">
      <constructor-arg index="0">
        <list>
          <bean
            class="com.pentaho.security.ldap.transform.SearchResultToAttrValueList">
            <constructor-arg index="0" value="cn" />
          </bean>
          <bean
            class="com.pentaho.security.ldap.transform.StringToGrantedAuthority" />
        </list>
      </constructor-arg>
    </bean>
  </constructor-arg>
</bean>

<bean id="usernamesInRoleSearch"
  class="com.pentaho.security.ldap.search.GenericLdapSearch">
  <constructor-arg index="0" ref="initialDirContextFactory" />
  <constructor-arg index="1">
    <bean
      class="com.pentaho.security.ldap.search.LdapSearchParamsFactoryImpl">
      <constructor-arg index="0" value="ou=roles" />
      <constructor-arg index="1">
        <value>
          <![CDATA[(&(objectClass=organizationalRole)(cn={0}))]]>
        </value>
      </constructor-arg>
    </bean>
  </constructor-arg>
  <constructor-arg index="2">
    <bean
      class="com.pentaho.security.ldap.transform.SearchResultToAttrValueList">
      <constructor-arg index="0" value="roleOccupant" />
      <constructor-arg index="1" value="uid" />
    </bean>
  </constructor-arg>
</bean>

<bean id="ldapUserRoleListService"
  class="com.pentaho.security.ldap.DefaultLdapUserRoleListService">
  <constructor-arg index="0" ref="initialDirContextFactory" />
  <property name="allAuthoritiesSearch">
    <ref local="allAuthoritiesSearch" />
  </property>
  <property name="allUsernamesSearch">
    <ref local="allUsernamesSearch" />
  </property>
  <property name="usernamesInRoleSearch">
    <ref local="usernamesInRoleSearch" />
  </property>
  <property name="userDetailsService">
    <ref bean="userDetailsService" />
  </property>
</bean>

<bean id="pentahoUserRoleListService"
  class="com.pentaho.security.UserDetailsRoleListService">
  <property name="userRoleListService">
    <ref local="ldapUserRoleListService" />
  </property>
</bean>

Useful Information

Just as DefaultLdapAuthoritiesPopulator has a configurable search scope via its searchSubtree property, LdapSearchParamsFactoryImpl provides a way to set a search scope by passing a SearchControls instance into the constructor. The XML below demonstrates this. If no SearchControls is supplied via the constructor, the default search scope is ONELEVEL_SCOPE.
<bean class="com.pentaho.security.ldap.search.LdapSearchParamsFactoryImpl">
  <constructor-arg index="0" value="ou=users" />
  <constructor-arg index="1" value="objectClass=Person" />
  <constructor-arg index="2">
    <bean class="javax.naming.directory.SearchControls">
      <!-- 2 comes from http://java.sun.com/j2se/1.3/docs/api/javax/naming/directory/SearchControls.html#SUBTREE_SCOPE -->
      <property name="searchScope" value="2" />
    </bean>
  </constructor-arg>
</bean>
  • No labels