Wiki Markup |
---|
{scrollbar} h1. Getting Security Data There are two key interfaces that fetch security data: {{UserDetailsService}} and {{IUserRoleListService}}. These are known as security data access objects (DAO). h2. UserDetailsService The Spring Security class {{[UserDetailsService|http://static.springsource.org/spring-security/site/docs/2.0.x/apidocs/org/springframework/security/userdetails/UserDetailsService.html]}} defines a single method: {{UserDetails loadUserByUsername(String username)}}. Given a username, it returns a {{[UserDetails|http://static.springsource.org/spring-security/site/docs/2.0.x/apidocs/org/springframework/security/userdetails/UserDetails.html]}} instance. h2. IUserRoleListService The Pentaho class {{[IUserRoleListService|http://javadoc.pentaho.com/bi_-platform/3.6/org/pentaho/platform/api/engine/IUserRoleListService.html]}} defines 4 methods: * {{GrantedAuthority[] getAllAuthorities()}} * {{String[] getAllUsernames()}} * {{String[] getUsernamesInRole(GrantedAuthority authority)}} * {{GrantedAuthority[] getAuthoritiesForUser(String username)}} For the final method above, all {{IUserRoleListService}} implementations should delegate to the associated {{UserDetailsService}}, if possible. h2. 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 Spring Security, the platform offers these choices of security back-ends: || Backend || {{UserDetailsService}} implementation || {{IUserRoleListService}} 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. h2. Configuration Files The platform stores its security data access object (DAO) configuration files in the {{pentaho-solutions/system}} directory. The table below enumerates these files. || Filename || Description || | {{applicationContext-spring-security-*.xml}} | Defines a {{UserDetailsService}} based on \*. Examples of \* include {{memory}}, {{jdbc}}, and {{ldap}}. The {{UserDetailsService}} defined in this file is referenced in [{{applicationContext-spring-security.xml}}|Web Resource Authorization] 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|#UserDetailsService]. | | {{applicationContext-pentaho-security-*.xml}} | Defines an {{IUserRoleListService}} based on \*. Examples of \* include {{memory}}, {{jdbc}}, and {{ldap}}. Read more about the {{IUserRoleListService}} implementations and how to configure them in [Getting All Usernames and Roles|#IUserRoleListService]. | One might ask: Why are there so many configuration files? The Spring 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 example configurations for each of the three supported security backends: memory, dbms, and ldap. The configuration files that are enabled are specified in {{pentaho-solutions/system/pentaho-spring-beans.xml}}. {code:xml|title=pentaho-spring-beans.xml} <beans> <!-- omitted --> <import resource="applicationContext-spring-security.xml" /> <import resource="applicationContext-common-authorization.xml" /> <import resource="applicationContext-spring-security-*.xml" /> <import resource="applicationContext-pentaho-security-*.xml" /> <!-- omitted --> </beans> {code} 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}}. {anchor:UserDetailsService} h2. 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-spring-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-spring-security.xml}} has a dependency on a bean named {{userDetailsService}}. For this reason, do not change the names of these beans. {quote}*Note:* The vast majority of the configuration contained in the {{applicationContext-spring-security-*.xml}} files is a standard Spring Security setup and is well-documented in the Spring Security documentation. Where the configuration strays from the Spring Security documentation, it is documented below. {quote} h3. 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()}}. {code:xml|title=applicationContext-spring-security-memory.xml} <bean id="userDetailsService" class="org.springframework.security.userdetails.memory.InMemoryDaoImpl"> <property name="userMap"> <ref local="userMapFactoryBean" /> </property> </bean> <bean id="userMap" class="java.lang.String"> <constructor-arg type="java.lang.String"> <!-- case matters --> <value> <![CDATA[joe=password,ceo,Admin,User,Authenticated suzy=password,cto,is,User,Authenticated pat=password,dev,User,Authenticated tiffany=password,dev,devmgr,User,Authenticated]]> </value> </constructor-arg> </bean> <bean id="userMapFactoryBean" class="org.pentaho.platform.plugin.services.security.userrole.memory.UserMapFactoryBean"> <property name="userMap"> <ref local="userMap" /> </property> </bean> {code} h3. DBMS The configuration for this section does not stray from the default Spring Security distribution. {anchor:LdapUserDetailsService} h3. LDAP The configuration for this section does not stray from the default Spring Security distribution. However, here are some tips. {include:LDAP Search Filter Tip Include} {quote}*Note:* According to [Spring Security Issue SEC-251|http://opensource.atlassian.com/projects/spring/browse/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.) {quote} {quote}*Warning:* Be careful about ampersand symbols within your LDAP search filters. They must be escaped! {quote} {anchor:IUserRoleListService} h2. 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 {{IUserRoleListService}} and is defined in {{applicationContext-pentaho-security-*.xml}}. {quote}*Warning:* The {{UserDetailsRoleListService}} class introduced in this section wraps a {{IUserRoleListService}} instance and must be defined in the Spring XML Beans document for proper functioning of the Pentaho BI Platform. {quote} h3. Memory The in-memory implementation of {{IUserRoleListService}} is {{InMemoryUserRoleListService}}. Notice the bean below named {{userRoleListEnhancedUserMapFactoryBean}}? It refers to the bean defined in {{applicationContext-spring-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. {code:xml|title=applicationContext-pentaho-security-memory.xml} <bean id="userRoleListEnhancedUserMapFactoryBean" class="org.pentaho.platform.plugin.services.security.userrole.memory.UserRoleListEnhancedUserMapFactoryBean"> <property name="userMap" ref="userMap" /> </bean> <bean id="inMemoryUserRoleListService" class="org.pentaho.platform.plugin.services.security.userrole.memory.InMemoryUserRoleListService"> <property name="userRoleListEnhancedUserMap"> <ref local="userRoleListEnhancedUserMapFactoryBean" /> </property> <property name="userDetailsService" ref="userDetailsService" /> <property name="allAuthorities"> <list> <bean class="org.springframework.security.GrantedAuthorityImpl"> <constructor-arg value="Authenticated" /> </bean> <bean class="org.springframework.security.GrantedAuthorityImpl"> <constructor-arg value="Admin" /> </bean> <!-- some authorities omitted --> </list> </property> <property name="usernameComparator"> <bean class="org.pentaho.platform.engine.security.DefaultUsernameComparator" /> </property> <property name="grantedAuthorityComparator"> <bean class="org.pentaho.platform.engine.security.DefaultGrantedAuthorityComparator" /> </property> </bean> <bean id="pentahoUserRoleListService" class="org.pentaho.platform.engine.security.userrole.UserDetailsRoleListService"> <property name="userRoleListService"> <ref local="inMemoryUserRoleListService" /> </property> </bean> {code} h3. DBMS The DBMS implementation of {{IUserRoleListService}} 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 {{IUserRoleListService}} interface. {quote}*Note:* Be sure to add {{as username}} and {{as authorities}} where appropriate in your queries.{quote} {code:xml|title=applicationContext-pentaho-security-jdbc.xml} <bean id="jdbcUserRoleListService" class="org.pentaho.platform.plugin.services.security.userrole.jdbc.JdbcUserRoleListService"> <constructor-arg index="0" ref="userDetailsService" /> <property name="allAuthoritiesQuery"> <value> <![CDATA[SELECT distinct(authority) as authority FROM AUTHORITIES ORDER BY authority]]> </value> </property> <property name="allUsernamesInRoleQuery"> <value> <![CDATA[SELECT distinct(username) as username FROM GRANTED_AUTHORITIES where authority = ? ORDER BY username]]> </value> </property> <property name="allUsernamesQuery"> <value> <![CDATA[SELECT distinct(username) as username FROM USERS ORDER BY username]]> </value> </property> <property name="dataSource" ref="dataSource" /> </bean> <bean id="pentahoUserRoleListService" class="org.pentaho.platform.engine.security.userrole.UserDetailsRoleListService"> <property name="userRoleListService"> <ref local="jdbcUserRoleListService" /> </property> </bean> {code} h3. LDAP {quote}*Warning:* Be careful about ampersand symbols within your LDAP search filters. They must be escaped!{quote} The LDAP implementation of {{IUserRoleListService}} is {{DefaultLdapUserRoleListService}}. Before continuing, it is important to be familiar with a key JNDI class: [{{DirContext}}|http://java.sun.com/javase/6/docs/api/javax/naming/directory/DirContext.html]. This class is the interface into directory services. In particular, there is a method with the following signature: {code} public NamingEnumeration search(String name, String filterExpr, Object[] filterArgs, SearchControls cons) throws NamingException; {code} Notice the parameters to this method? In the platform, these parameters are encapsulated into {{LdapSearchParams}}. h4. 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|http://java.sun.com/javase/6/docs/api/javax/naming/directory/DirContext.html] but a brief description of each is below. || Parameter Name || Description || | {{name}} | The [DN|http://en.wikipedia.org/wiki/Distinguished_name] 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|http://java.sun.com/javase/6/docs/api/javax/naming/directory/SearchControls.html]. | {{LdapSearchParams}} can only be created by {{LdapSearchParamsFactory}} instances. h4. LdapSearchParamsFactory {{LdapSearchParamsFactory}} defines a single method: {code} LdapSearchParams createParams(Object[] filterArgs); {code} 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. h4. LdapSearch The {{LdapSearch}} interface is a generalization of {{org.springframework.security.ldap.LdapUserSearch}}. In Spring 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}}. h5. 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 || | {{contextSource}} | An instance of [{{ContextSource}}|http://static.springsource.org/spring-ldap/docs/1.2.1/api/spring-ldap/org/springframework/ldap/core/ContextSource.html]. | | {{paramsFactory}} | An instance of {{LdapSearchParamsFactory}}. | | {{resultsTransformer}} | Transforms LDAP search results into custom objects. The type of this parameter is [{{Transformer}}|http://jakarta.apache.org/commons/collections/api/org/apache/commons/collections/Transformer.html]. | | {{filterArgsTransformer}} | Transforms filter arguments before passing to the {{paramsFactory}}. The type of this parameter is [{{Transformer}}|http://jakarta.apache.org/commons/collections/api/org/apache/commons/collections/Transformer.html]. | 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. h5. SearchResultToAttrValueList {{SearchResultToAttrValueList}} extracts the value of the token {{tokenName}} from the attribute {{attributeName}} which is taken from the [{{SearchResult}}|http://java.sun.com/javase/6/docs/api/javax/naming/directory/SearchResult.html] input. h5. 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. h5. 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. {panel:title=Example LDAP Object Graph|bgColor=#FFFFFF} !ldap-graph.png|align=center! The picture above depicts an example wiring of LDAP-related beans. {panel} {panel:title=GenericLdapSearch Internals|bgColor=#FFFFFF} !genericldapsearch.png|align=center! {panel} h4. UnionizingLdapSearch The purpose of this class is to merge the results returned by two or more {{LdapSearch}} instances. h4. DefaultLdapUserRoleListService Remember that {{DefaultLdapUserRoleListService}} is the LDAP implementation of {{IUserRoleListService}}. As a consequence, it must implement the methods defined in {{IUserRoleListService}}. 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|#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. {code:java|title=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 getContextSource()--just know that // it returns an ContextSource) LdapSearch allUsernamesSearch = new GenericLdapSearch( getContextSource(), paramsFactory, transformer); {code} Now the equivalent Spring config is presented. {code:xml|title=Creating a GenericLdapSearch Using Spring} <bean id="allUsernamesSearch" class="org.pentaho.platform.plugin.services.security.userrole.ldap.search.GenericLdapSearch"> <constructor-arg index="0" ref="contextSource" /> <constructor-arg index="1"> <bean class="org.pentaho.platform.plugin.services.security.userrole.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="org.pentaho.platform.plugin.services.security.userrole.ldap.transform.SearchResultToAttrValueList"> <constructor-arg index="0" value="uid" /> </bean> </constructor-arg> </bean> {code} {quote}*Note:* 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. {quote} {include:LDAP Search Filter Tip Include} 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|#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 beans defined in {{applicationContext-spring-security-ldap.xml}}. {code:xml|title=applicationContext-pentaho-security-ldap.xml} <!-- be sure to escape ampersands --> <bean id="allUsernamesSearch" class="org.pentaho.platform.plugin.services.security.userrole.ldap.search.GenericLdapSearch"> <constructor-arg index="0" ref="contextSource" /> <constructor-arg index="1"> <bean class="org.pentaho.platform.plugin.services.security.userrole.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="org.pentaho.platform.plugin.services.security.userrole.ldap.transform.SearchResultToAttrValueList"> <constructor-arg index="0" value="uid" /> </bean> </constructor-arg> </bean> <!-- be sure to escape ampersands --> <bean id="allAuthoritiesSearch" class="org.pentaho.platform.plugin.services.security.userrole.ldap.search.GenericLdapSearch"> <constructor-arg index="0" ref="contextSource" /> <constructor-arg index="1"> <bean class="org.pentaho.platform.plugin.services.security.userrole.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="org.pentaho.platform.plugin.services.security.userrole.ldap.transform.SearchResultToAttrValueList"> <constructor-arg index="0" value="cn" /> </bean> <bean class="org.pentaho.platform.plugin.services.security.userrole.ldap.transform.StringToGrantedAuthority"> <property name="rolePrefix" value="" /> <property name="convertToUpperCase" value="false" /> </bean> </list> </constructor-arg> </bean> </constructor-arg> </bean> <!-- not currently used --> <bean id="usernamesInRoleSearch" class="org.pentaho.platform.plugin.services.security.userrole.ldap.search.NoOpLdapSearch"> </bean> <bean id="ldapUserRoleListService" class="org.pentaho.platform.plugin.services.security.userrole.ldap.DefaultLdapUserRoleListService"> <constructor-arg index="0" ref="contextSource" /> <property name="allAuthoritiesSearch"> <ref local="allAuthoritiesSearch" /> </property> <property name="allUsernamesSearch"> <ref local="allUsernamesSearch" /> </property> <property name="userDetailsService"> <ref bean="userDetailsService" /> </property> <property name="usernamesInRoleSearch"> <ref local="usernamesInRoleSearch" /> </property> <property name="usernameComparator"> <bean class="org.pentaho.platform.engine.security.DefaultUsernameComparator" /> </property> <property name="grantedAuthorityComparator"> <bean class="org.pentaho.platform.engine.security.DefaultGrantedAuthorityComparator" /> </property> </bean> <bean id="pentahoUserRoleListService" class="org.pentaho.platform.engine.security.userrole.UserDetailsRoleListService"> <property name="userRoleListService"> <ref local="ldapUserRoleListService" /> </property> </bean> {code} {include:Search Scope Include} |
Page Comparison
General
Content
Integrations