Thursday 27 October 2011

How to: interface JasperReports Server and LDAP (2/2)

Multi-tenancy option


As predefined option, JasperReports Server maps all external users inside organization_1. If you want to simply change the destined organization:

  • Open the file /jasperserver-pro/WEB-INF/applicationContext-multiTenancy-security.xml
  • Locate the bean defaultExternalUserProcessor
  • Modify the property defaultOrganizationIfNotDetected (as shown below)
<bean id="defaultExternalUserProcessor" class="com.jaspersoft.jasperserver.multipleTenancy.DefaultExternalUserProcessor">
<property name="multiTenancyService"><ref bean="internalMultiTenancyService"/></property>
<property name="defaultOrganizationIfNotDetected" value="organization_1"/>
        <property name="multiTenancyConfiguration"><ref bean="multiTenancyConfiguration"/></property>
        <property name="rootOrganizationRolesMap">
            <map>
                <!-- Mapping customers roles to JS roles Example -->
                <!--<entry>-->
                    <!--<key>-->
                       <!-- Сustomer role(with adding ROLE_ prefix) which need to be mapped to root JS roles -->
                       <!--<value>ROLE_ADMIN</value>-->
                    <!--</key>-->
                       <!-- root JS role customer role to be mapped to -->
                       <!--<value>ROLE_ADMINISTRATOR</value>-->
                <!--</entry>-->
            </map>
        </property>
</bean>

WARNING: if you don't specify anything in defaultOrganizationIfNotDetected, the external users will be mapped inside the root folder and will have the same visibility as superuser!
If you want instead to map on JasperReports Server the LDAP directory structure we have to proceed as follows:

  • Open the file /jasperserver-pro/WEB-INF/applicationContext-multiTenancy-security.xml
  • Locate the bean mtUserAuthorityServiceTarget
  • Locate the property userProcessors
  • Uncomment the ldapExternalUserProcessor reference and comment defaultExternalUserProcessor
<bean id="mtUserAuthorityServiceTarget"
        class="com.jaspersoft.jasperserver.multipleTenancy.MTUserAuthorityServiceImpl">
        <property name="sessionFactory" ref="sessionFactory"/>
        <property name="objectMappingFactory" ref="mappingResourceFactory"/>
        <property name="persistentClassFactory" ref="persistentMappings"/>
        <property name="profileAttributeService" ref="profileAttributeService"/>
        <property name="defaultInternalRoles">
          <list>
            <value>ROLE_USER</value>
          </list>
        </property>
        <property name="multiTenancyConfiguration"><ref bean="multiTenancyConfiguration"/></property>
        <property name="securityProvider"><ref local="tenantSecurityProvider"/></property>
        <property name="securityContextProvider"><ref bean="${bean.securityContextProvider}"/></property>
        <property name="tenantPersistenceResolver"><ref bean="${bean.hibernateTenantService}"/></property>
        <property name="userProcessors">
            <list>
                <!-- For LDAP authentication -->
                <ref bean="ldapExternalUserProcessor"/>
                <!--ref bean="defaultExternalUserProcessor"/-->
            </list>
        </property>
        <property name="auditContext" ref="${bean.auditContext}"/>
        <property name="databaseCharactersEscapeResolver" ref="databaseCharactersEscapeResolver"/>
        <property name="defaultExternalUserProcessor" ref="defaultExternalUserProcessor"/>
</bean>


  • Find the bean ldapExternalUserProcessor and uncomment it
  • Add the following informations:
    • excludeRootDN: specify if the baseDN (inside the connection parameters) should be mapped with the RDN (in our example, if the folders com and maxcrc should be created or not)
    • organizationsRDN: a list of attributes which will be mapped as organization names. If this list is empty or there is no match between this list and the LDAP directory, the organization name will be determined solely by the excludeRootDN and rootOrganizationId properties
    • rootOrganizationId: the organizationId under which the organizations mapped from the LDAP directory will be created. If empty, it's root
    • rootOrganizationRolesMap: a useful property if you want to map JasperReports Server system roles with the ones defined in LDAP (see below)
<!-- For LDAP authentication -->
<bean id="ldapExternalUserProcessor" class="com.jaspersoft.jasperserver.multipleTenancy.ldap.LdapExternalUserProcessor">
<property name="ldapContextSource" ref="ldapContextSource"/>
<property name="multiTenancyService"><ref bean="internalMultiTenancyService"/></property>
<property name="excludeRootDn" value="true"/>
<!-- only following RDNs will matter in creating of organization hierarchy -->
<property name="organizationRDNs">
<list>
<!--<value>dc</value>-->
<!--<value>c</value>-->
<value>ou</value>
<value>o</value>
<!--<value>st</value>-->
</list>
</property>
<property name="rootOrganizationId" value=""/>
        <property name="multiTenancyConfiguration"><ref bean="multiTenancyConfiguration"/></property>
        <property name="rootOrganizationRolesMap">
            <map>
            </map>
        </property>
</bean>

In our example:

  • excludeRootDN: true (we don't want to create com and maxcrc folders)
  • organizationsRDN: o, ou (we want to map a first-level organization named People and two second-level organizations: jobs and users)
  • rootOrganizationId: null (we want People to be a first-level organization)
WARNING(1): if we have rootOrganizationId = null, excludeRootDN = true and no match between the attributes in organizationsRDN and the LDAP directory structure, the external users will be mapped inside the root folder and will have the same visibility as superuser!
WARNING(2): it's necessary to create beforehand the People folder: jobs and users will be created at the first login of either paul or tom

Role Mapping


In order to find user roles, LDAP makes a second search inside the directory structure: in order to specify search parameters, we have to proceed as follows:

  • Open the file /jasperserver-pro/WEB-INF/applicationContext-security.xml
  • Locate the bean ldapAuthenticationProvider
  • In the second constructor add the following informations:
    • constructor-arg index="1": a branch of the LDAP directory where the roles are defined. If empty, LDAP will cover the entire directory tree
    • groupRoleAttribute: the attribute whose value is the role name to map
    • groupSearchFilter: a filter search. In this case, the expression {0} is the complete DN of the username (ex: cn=tom,o=users,ou=People,dc=maxcrc,dc=com), while the expression {1} is the username specified in the login page (ex: tom)
    • searchSubTree: a flag that indicates whether or not the search should extend to eventual children nodes
    • convertToUpperCase: a flag that indicates whether the group name should be converted to upper case when used as a role name in JasperReports Server (the default is true)
    • rolePrefix: a prefix to the group name to add when creating a Jasper role (the default is ROLE_)
To all users will be assigned the predefined role ROLE_USER at the first access.



<!-- For LDAP authentication -->
   
   <bean id="ldapAuthenticationProvider" class="org.springframework.security.providers.ldap.LdapAuthenticationProvider">
     <constructor-arg>
       <bean class="org.springframework.security.providers.ldap.authenticator.BindAuthenticator">
          <constructor-arg><ref local="ldapContextSource"/></constructor-arg>
          <!-- property name="userDnPatterns"><list><value>uid={0}</value></list></property -->
          <property name="userSearch" ref="userSearch"/>
       </bean>
     </constructor-arg>
     <constructor-arg>
       <bean class="org.springframework.security.ldap.populator.DefaultLdapAuthoritiesPopulator">
          <constructor-arg index="0"><ref local="ldapContextSource"/></constructor-arg>
          <constructor-arg index="1"><value>o=group,ou=departmentGroups</value></constructor-arg>
          <property name="groupRoleAttribute"><value>cn</value></property>
          <!--<property name="groupSearchFilter"><value>
(&amp;(uniqueMember={0})(objectclass=groupOfUniqueNames))</value></property>-->
          <property name="searchSubtree"><value>false</value></property>            
       </bean>
     </constructor-arg>
   </bean>


In our example:

  • constructor-arg index="1": o=group, ou=departmentGroups
  • groupRoleAttribute: cn
  • groupSearchFilter: nothing (JasperReports will user the member attribute as a filter if nothing is specified)
  • searchSubTree: false
System Role Mapping


Once you have tested that JasperReports Server creates the roles defined externally in the LDAP directory, it's possible to map these roles to the predefined system ones in JasperReports Server: for example, in our case we want to associate ROLE_DEVELOPER to ROLE_ADMINISTRATOR.
WARNING: with this procedure user paul won't have the ROLE_DEVELOPER anymore, just ROLE_ADMINISTRATOR; therefore, it's best to delete ROLE_DEVELOPER or user paul entirely if already present, in order to avoid Jasper launching an IllegalArgumentException.

  • Open the file /jasperserver-pro/WEB-INF/applicationContext-multiTenancy-security.xml
  • Locate the bean defaultExternalUserProcessor (if you are mapping the users inside a single organization) or ldapExternalUserProcessor (if you are using multi-Tenancy)
  • Locate the property rootOrganizationRolesMap and map the LDAP role to the corresponding Jasper role, as shown below



<!-- For LDAP authentication -->
<bean id="ldapExternalUserProcessor" class="com.jaspersoft.jasperserver.multipleTenancy.ldap.LdapExternalUserProcessor">
<property name="ldapContextSource" ref="ldapContextSource"/>
<property name="multiTenancyService"><ref bean="internalMultiTenancyService"/></property>
<property name="excludeRootDn" value="true"/>
<!-- only following RDNs will matter in creating of organization hierarchy -->
<property name="organizationRDNs">
<list>
<!--<value>dc</value>-->
<!--<value>c</value>-->
<value>ou</value>
<value>o</value>
<!--<value>st</value>-->
</list>
</property>
<property name="rootOrganizationId" value=""/>
        <property name="multiTenancyConfiguration"><ref bean="multiTenancyConfiguration"/></property>
        <property name="rootOrganizationRolesMap">
            <map>
<entry>
<key>
<value>ROLE_DEVELOPER</value>
</key>
<value>ROLE_ADMINISTRATOR</value>
</entry>
            </map>
        </property>
</bean>





Saturday 8 October 2011

How to: interface JasperReports Server and LDAP (1/2)

Recently I've managed at work to interface JasperReports Server 4.1 with OpenLDAP: since it's a very common problem, with very little documentation around the net (particularly regarding Jaspersoft BI Suite 4.x), I thought I'd describe here a sample configuration.
The software I used in my demo was:
  • OpenLDAP for Windows
  • LDAP Browser/Editor v. 2.8.1
  • JasperReports Server 4.1 (commercial edition)
First of all, I strongly recommend to enable the LDAP log in JasperReports Server: in order to do so, we simply add the following line in the /jasperserver-pro/WEB-INF/log4.properties:
log4j.logger.org.springframework.security.ldap=DEBUG, stdout, fileout
This way, we can monitor the LDAP activities by reading the output lines in /jasperserver-pro/WEB-INF/logs/jasperserver.log
This is the LDAP structure described in this sample configuration:

We want to add in JasperReports Server two users:
  • Tom, a normal user, under the users organization;
  • Paul, a developer, under the jobs organization
Both organizations are under the OrganizationalUnit People, which we want to add to JasperReports Server too.
In the LDAP directory we have a GroupOfNames called developer, which paul is a member of: we want to map this information as a role in JasperReports Server, and assign this new ROLE_DEVELOPER to Paul during his first login.
The first thing we want to do is enabling the ldapAuthenticationProvider: in order to do so, we have to open the file /jasperserver-pro/WEB-INF/applicationContext-security.xml, locate the AuthenticationManager bean and uncomment the ldapAuthenticationProvider reference:

<bean id="authenticationManager" class="org.springframework.security.providers.ProviderManager">
        <property name="providers">
            <list>
                <!-- not on by default <ref local="ldapAuthenticationProvider"/>  -->
         <ref local="ldapAuthenticationProvider"/>
                <ref bean="${bean.daoAuthenticationProvider}"/>
                <ref bean="anonymousAuthenticationProvider"/>
                <!--ref local="jaasAuthenticationProvider"/-->
            </list>
        </property>
</bean>
It's important not to comment the daoAuthenticationProvider (the one that enables the default authentication) if you want to keep using the predefined administrator users (jasperadmin and superuser). AnonymousAuthenticationProvider is necessary in order to view the login page.
Next step is setting the LDAP connection parameters: in the same file /jasperserver-pro/WEB-INF/applicationContext-security.xml, we have to locate and uncomment the ldapContextSource bean and add the following informations:
  • constructor-arg: the URL of the LDAP server (including the baseDN)
  • userDN: the DN (distinguished name) of the LDAP administrator
  • password: the password of the LDAP administrator
<!-- For LDAP authentication -->
   
   <bean id="ldapContextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
     <constructor-arg value="ldap://192.168.71.131:389/dc=maxcrc,dc=com"/>
     
     <property name="userDn"><value>cn=Manager,dc=maxcrc,dc=com</value></property>
     <property name="password"><value>secret</value></property>
          
   </bean>
Finally, we need to specify the users search parameters in the LDAP directory. In the same file /jasperserver-pro/WEB-INF/applicationContext-security.xml, we have to locate and uncomment the userSearch bean and add the following informations:
  • A branch in the LDAP directory where the users are located: if empty, the application will search the entire LDAP directory, starting from the baseDN
  • A filter expression that matches an attribute (or a combination of attributes) with the login name. The login name indicated by the user must is substituted with the {0} placeholder
  • Whether or not the search must be extended to eventual subTrees
<!-- For LDAP authentication
   This bean is not used by default -->
   
   <bean id="userSearch"
            class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
     <constructor-arg index="0">
       <value>ou=People</value>
     </constructor-arg>
     <constructor-arg index="1">
       <value>(cn={0})</value>
     </constructor-arg>
     <constructor-arg index="2">
       <ref local="ldapContextSource" />
     </constructor-arg>            
     <property name="searchSubtree">
       <value>true</value>
     </property>            
   </bean>       
For this example, we limit the search inside the People organizational unit, and we search all nodes that have the cn attribute equal to the login name.
Now we just have to uncomment the ldapAuthenticationProvider, and specify we want to use the userSearch bean to search the users:
<!-- For LDAP authentication -->
<bean id="ldapAuthenticationProvider" class="org.springframework.security.providers.ldap.LdapAuthenticationProvider">
     <constructor-arg>
       <bean class="org.springframework.security.providers.ldap.authenticator.BindAuthenticator">
          <constructor-arg><ref local="ldapContextSource"/></constructor-arg>
          <!-- property name="userDnPatterns"><list><value>uid={0}</value></list></property -->
          <property name="userSearch" ref="userSearch"/>
       </bean>
     </constructor-arg>
    ...
    </bean>
These are all the steps necessary in order to authenticate an external user defined in a LDAP directory in JasperReports Server. In the next post, we'll see the implementation of a simple multi-tenancy system, and the mapping of external roles in JasperReports Server.

Thursday 6 October 2011

My first post

This is my first post on my new blog. I'm really excited! I hope to start posting soon :)