Friday, August 2, 2013

Integrate .NET to CAS (improving)

Last week, I have written a tutorial how to integrate Pentaho to CAS. Now I want to share about .NET application to CAS. For me it is important because it is useless to integrate Pentaho to CAS if we don't have other application to be integrated with. I'm doing Pentaho integration to CAS to integrate MaxLogistix: a low cost cloud warehouse management system (WMS) that my team developed. The goal is we want to integrate Pentaho to our application (in my case that application is MaxLogistix) without passing username and password parameter. I have explained this in previous tutorials:

  1. Embed Pentaho Report Viewer to Web Application
  2. Integrating Pentaho to CAS

This tutorial is based on tutorial and sample from https://www.everit.biz/web/guest/everit-blog/-/blogs/single-sign-out-with-cas-in-net and improved some codes to make it working perfectly.

Jasig has already released its .NET CAS client provider. You can check it from Jasig official .NET CAS Client. But in my case I can't use it because I'm not using pure ASP .NET form. Furthermore I'm not using ASP .NET tag at all because I'm using Castle Framework especially Monorail and ActiveRecord. I must use different approach to integrate MaxLogistix to CAS. Then I found and tried this wonderful .NET CAS tutorial. The result is.... it's working!

When I open http://localhost:50780/default.aspx from my web browser, I'm redirected to CAS login screen https://localhost:8443/cas/login?service=http://localhost:50780/CASLogin.aspx. Login service is working well.

CAS Login

Login from CAS bring me to http://localhost:50780/default.aspx and it shows welcoming screen and Pentaho Report Viewer that I embed using iframe.

Pentaho Report Viewer in .NET using CAS

I also open Pentaho and it shows Home screen, not login screen. Seems CAS working well.


Is it done? Not yet because I haven't tried single sign out. Let's try single sign out. I log out directly from CAS server.

CAS Logout

And I refresh Pentaho Home screen, I'm redirected to CAS login screen.

Pentaho Logged Out from CAS

BUT
... when I refreshed http://localhost:50780/default.aspx, I got INTERESTING view. I wasn't redirected to CAS login screen and still in welcome screen, but the embedded Pentaho Report Viewer showed CAS login screen.
Logged in or logged out?

The question is... was I logged out or still logged in CAS? The answer is I was already logged out from CAS. Open https://localhost:8443/cas and it will bring you to login screen.

Furthermore, I always got this error when I was debugging this sample
Ticket error

After spent several hours debugging and learning this source code, I found two things that made these two problems.

  1. When CAS logging out, .NET sample application still hold its ticket in its session so that it won't bring us to login screen. The solution is adding several codes to remove ticket from session when get logout request from CAS.
  2. There is an overlap event between checking ticket function and other function. When checking ticket function triggered (via timer), another function hasn't finished its process. The error is created because a function is trying to read a variable that locked in another function. The solution for this problem is adding exception handler that do nothing when this overlap event happened.
I added several codes to original sample codes and redid the same steps (login and logout). Tada.. now .NET sample application also logged out when I log out from CAS.
.NET also logged out

Now we can embed Pentaho dashboard and report viewer using CAS perfectly ;)

You can download and compare these sample codes:

  1. Original sample
  2. Original sample+iframe
  3. Updated sample
NOTE:
My sample code doesn't use URL Rewrite so that it won't be displayed in other browser but Chrome. You can use URL Rewrite I wrote about in Embed Pentaho Report Viewer.

Thursday, August 1, 2013

MaxLogistix Mobile

Nowadays, mobile application is booming. It's everywhere and everyone use it. Why? Because it's easy to use and it's in our grasp. My developer team in Fordiso also made mobile application for MaxLogistix: a low cost cloud warehouse management system. We have been using it for almost a year and we enjoy its benefits. Read MaxLogistix blog for complete story and information about MaxLogistix mobile.

Wednesday, July 31, 2013

IIS 8 Odd Compression, Is It Intended?

I just encountered odd compression behaviour in IIS 8 when I did testing for MaxLogistix. I don't know whether it's intended by the maker but I think it's not right. In IIS 7 when I enable static content compression, .css files are compressed but .js (javascript) files are not. I've read this article and still don't understand why Microsoft think that javascript is NOT static content (but css is static content, I've tried to change javacript MIME type to text/javascript but the result is same) so that I must enable dynamic content compression to make javascript files compressed in IIS. But right now in IIS 8, I'm shocked! Both of them (.css and .js) are TREATED as DYNAMIC content!

This is what I've done and I've found:
IIS version: 8

IIS 8

I added a new website
Add new site

Compression was disabled
No compression

And yes, transfer size = file size, nothing wrong
Working perfectly

Then I enabled static content compression
Enable static content compression

But still had same result, no matter how I tried to restart IIS server, restart application pool, restart site, and clear browser cache.
Static compression not working

Temporary folder used to store compressed files still empty
Empty temp folder

I tried enabling dynamic content compression
Enabling dynamic content compression

After restart IIS, restart application pool, restart site, and clear browser cache, tada... it's working!
Dynamic content compression is working

And temporary folder was as clean as before
Still empty temp folder

Monday, July 29, 2013

Fordiso in Google Maps

Today I received a post card from Google addressed to Fordiso (I'm working there at CTO). Actually I received two letters because I made a mistake and deleted previous location. From this experience, it seems that Google automates the sending letter process and doesn't bother about small details like this.

Letter from Google

I received this card because about two weeks ago I changed Fordiso office location in Google Maps. We have moved our office from Kebon Jeruk, Jakarta to Alam Sutera, Tangerang a year ago but we forgot to also change its location in Google Maps. When I changed its location, Google sent a verification card to our new location. The instruction to verified the changes is written on that card.

Google Maps instruction

Now I'm waiting Google for reviewing the changes and making the changes happened (several more weeks according to Google).

Pending map

Friday, July 26, 2013

MaxLogistix Blog (the story)

Finally my company (Fordiso) launched MaxLogistix Blog. This blog will be used for sharing anything about MaxLogistix. It's been 4 years since the first version of MaxLogistix (now it's the third version) created by my team with support from our founder: Mr. Benjamin Bunawidjaja (IMS group) and his co-founder: Mr. Untung Kurniawan (Surfgold). 4 years ago I also had a plan to share what we've done in MaxLogistix and what MaxLogistix can do for increasing the efficiency of warehousing business, but in that time we were to busy with technical things and the time goes on and we still had no blog at all. Now we have more people in our team and finally we made the plan from 4 years ago comes true (yeah!).

Thanks to Wordpress and it's tons of plugins, we can make a nice designed blog. We use a responsive theme, a social media plugin, a recaptcha plugin, and a SEO plugin. For me this is a new thing I love to explore. One thing I found about Wordpress and Blogger: Blogger has more mobile friendly view than the theme we used in Wordpress, but Wordpress has advantage in term of feature (thanks to tons of plugins). You can compare my blog and MaxLogistix blog. Please feel free to add some comments in those two blogs.

Thursday, July 25, 2013

Pentaho Single Sign On (SSO) using CAS (tutorial)

In previous story I share about my journey in +MaxLogistix project to implement SSO in Pentaho using CAS. In this article I share completely, step by step how to implement Single Sign On/Out (SSO) using CAS. In this tutorial I use Windows, D:\pentaho\biserver-ce\tomcat\ as my tomcat folder, and D:\pentaho\biserver-ce\pentaho-solutions\ as my pentaho solutions folder.
  1. Download CAS Server. Choose the latest version (3.5.2) and extract cas-server-webapp-3.5.2.war from modules folder. Rename it to cas.war and copy that file to D:\pentaho\biserver-ce\tomcat\webapps\. When Tomcat is running, it will extract that file to new folder named cas.

    Also extract cas-server-support-jdbc-3.5.2.jar from modules folder and copy that file to D:\pentaho\biserver-ce\tomcat\webapps\cas\WEB-INF\lib\.

    And copy commons-dbcp-1.4.jar and commons-pool-1.5.7.jar from D:\pentaho\biserver-ce\tomcat\webapps\pentaho\WEB-INF\lib\ to D:\pentaho\biserver-ce\tomcat\webapps\cas\WEB-INF\lib\





  2. Download CAS Client. Choose latest version (cas-client-core-3.2.1.jar) and copy that file to D:\pentaho\biserver-ce\tomcat\webapps\pentaho\WEB-INF\lib\

  3. Download Spring Security CAS Client version 2.0.5. Place that file in D:\pentaho\biserver-ce\tomcat\webapps\pentaho\WEB-INF\lib\

  4. Create security certificate.
  5. Run as administrator this command in command prompt (cmd).

     "%JAVA_HOME%"\bin\keytool -delete -alias tomcat -keypass changeit  
     "%JAVA_HOME%"\bin\keytool -genkey -alias tomcat -keypass changeit -keyalg RSA  
     "%JAVA_HOME%"\bin\keytool -export -alias tomcat -keypass changeit -file server.crt  
     "%JAVA_HOME%"\bin\keytool -import –alias tomcat -file server.crt -keypass changeit -keystore "%JAVA_HOME%"\jre\lib\security\cacerts  
    

  6. Edit server.xml in D:\pentaho\biserver-ce\tomcat\conf\

  7. Add this part

     <Connector URIEncoding="UTF-8" port="8443"  
       protocol="org.apache.coyote.http11.Http11Protocol" SSLEnabled="true"  
       maxThreads="150" scheme="https" secure="true"keystoreFile="D:/Fabian/.keystore"  
       keyAlias="tomcat" keystorePass="changeit"  
       truststoreFile="C:/Program Files/Java/jdk1.7.0_10/jre/lib/security/cacerts" clientAuth="false"  
       sslProtocol="TLS" />  
    

    Before this part

     <!-- Define an AJP 1.3 Connector on port 8009 -->  
     <Connector URIEncoding="UTF-8" port="8009" protocol="AJP/1.3" redirectPort="8443" />  
    

    Change yellow highlighted part according to user profile and JDK path.

  8. Edit cas.properties in D:\pentaho\biserver-ce\tomcat\webapps\cas\WEB-INF

  9. Change this part

     server.name=http://localhost:8080  
    

    With this part

     server.name=https://localhost:8443  
    

  10. Edit cas.servlet.xml in D:\pentaho\biserver-ce\tomcat\webapps\cas\WEB-INF

  11. Comment this part

     <webflow:flow-execution-listeners>   
       <webflow:listener ref="terminateWebSessionListener" />   
     </webflow:flow-execution-listeners>  
    

    Become this

     <!--  
     <webflow:flow-execution-listeners>   
       <webflow:listener ref="terminateWebSessionListener" />   
     </webflow:flow-execution-listeners>  
     -->  
    

  12. Edit deployerConfigContext.xml in D:\pentaho\biserver-ce\tomcat\webapps\cas\WEB-INF

  13. Replace or comment this part

     <bean class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler" />  
    

    With this part

     <bean class="org.jasig.cas.adaptors.jdbc.SearchModeSearchDatabaseAuthenticationHandler">  
       <property name="tableUsers"><value>Users</value></property>  
       <property name="fieldUser"><value>user_name</value></property>  
       <property name="fieldPassword"><value>pwdhash</value></property>  
       <property name="dataSource" ref="dataSource"/>  
       <property name="passwordEncoder">  
         <bean class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder" p:characterEncoding="UTF-16LE" >  
           <constructor-arg index="0" value="SHA1" />  
         </bean>  
       </property>  
     </bean>  
    

    Highlighted part is database table setting used for authentication. For this tutorial I use Users table with users_name as user id field and pwdhash as password field. Password encoded by SHA1 method. Encoding UTF-16LE used by Microsoft SQL Server (yes, it's not UTF-16 like in other database engine).

    Still in the same file, add this part

     <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">  
       <property name="driverClassName">  
         <value>net.sourceforge.jtds.jdbc.Driver</value>  
       </property>  
       <property name="url">  
         <value>jdbc:jtds:sqlserver://localhost:1433/MainDB</value>  
       </property>  
       <property name="username"><value>authentication</value></property>  
       <property name="password"><value>test123</value></property>  
     </bean>  
    

    Before this part (last line)

     </beans>  
    

    It is database configuration. I use SQL Server with jtds driver and authentication and test123 as database username and password and MainDB as database name. Copy jtds-1.3.1.jar from D:\pentaho\biserver-ce\tomcat\lib\ to D:\pentaho\biserver-ce\tomcat\webapps\cas\WEB-INF\lib\

  14. Create new file named applicationContext-spring-security-cas.xml in D:\pentaho\biserver-ce\pentaho-solutions\system\ and copy paste this code to that file.

  15.  <?xml version="1.0" encoding="UTF-8"?>  
     <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springsource.org/dtd/spring-beans.dtd">  
     <beans default-autowire="no" default-dependency-check="none" default-lazy-init="false">  
          <bean id="filterChainProxy" class="org.springframework.security.util.FilterChainProxy" autowire="default" dependency-check="default" lazy-init="default">  
               <property name="filterInvocationDefinitionSource">  
                    <value>  
                         <![CDATA[CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON  
                         PATTERN_TYPE_APACHE_ANT  
                         /**=securityContextHolderAwareRequestFilter,httpSessionContextIntegrationFilter,logoutFilter,casProcessingFilter,basicProcessingFilter,requestParameterProcessingFilter,anonymousProcessingFilter,pentahoSecurityStartupFilter,exceptionTranslationFilter,filterInvocationInterceptor,casSingleSignOutFilter]]>  
                    </value>  
               </property>  
          </bean>  
          <bean id="serviceProperties" class="org.springframework.security.ui.cas.ServiceProperties" autowire="default" dependency-check="default" lazy-init="default">  
               <property name="service" value="http://localhost:8080/pentaho/j_spring_cas_security_check"/>  
               <property name="sendRenew" value="false"/>  
          </bean>  
          <bean id="casProcessingFilter" class="org.springframework.security.ui.cas.CasProcessingFilter" autowire="default" dependency-check="default" lazy-init="default">  
               <property name="authenticationManager">  
                    <ref bean="authenticationManager"/>  
               </property>  
               <property name="authenticationFailureUrl" value="/public/casFailed"/>  
               <property name="defaultTargetUrl" value="/"/>  
               <property name="filterProcessesUrl" value="/j_spring_cas_security_check"/>  
          </bean>  
          <bean id="casSingleSignOutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter" />  
          <bean id="casSingleSignOutHttpSessionListener" class="org.jasig.cas.client.session.SingleSignOutHttpSessionListener" />  
          <bean id="exceptionTranslationFilter" class="org.springframework.security.ui.ExceptionTranslationFilter" autowire="default" dependency-check="default" lazy-init="default">  
               <property name="authenticationEntryPoint">  
                    <ref local="casProcessingFilterEntryPoint"/>  
               </property>  
               <property name="accessDeniedHandler">  
                    <bean class="org.springframework.security.ui.AccessDeniedHandlerImpl" />  
               </property>  
          </bean>  
          <bean id="casProcessingFilterEntryPoint" class="org.springframework.security.ui.cas.CasProcessingFilterEntryPoint" autowire="default" dependency-check="default" lazy-init="default">  
               <property name="loginUrl" value="https://localhost:8443/cas/login"/>  
               <property name="serviceProperties">  
                    <ref local="serviceProperties"/>  
               </property>  
          </bean>  
          <bean id="authenticationManager" class="org.springframework.security.providers.ProviderManager" autowire="default" dependency-check="default" lazy-init="default">  
               <property name="providers">  
                    <list>  
                         <ref bean="anonymousAuthenticationProvider"/>  
                         <ref bean="casAuthenticationProvider"/>  
                    </list>  
               </property>  
          </bean>  
          <bean id="casAuthenticationProvider" class="org.springframework.security.providers.cas.CasAuthenticationProvider">  
               <property name="userDetailsService">  
                    <ref bean="userDetailsService"/>  
               </property>  
               <property name="serviceProperties">  
                    <ref local="serviceProperties"/>  
               </property>  
               <property name="ticketValidator">  
                    <ref local="ticketValidator"/>  
               </property>  
               <property name="key" value="my_password_for_this_auth_provider_only"/>  
          </bean>  
          <bean id="ticketValidator" class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator" autowire="default" dependency-check="default" lazy-init="default">  
               <constructor-arg index="0" value="https://localhost:8443/cas"/>  
          </bean>  
          <bean id="logoutFilter" class="org.springframework.security.ui.logout.LogoutFilter" autowire="default" dependency-check="default" lazy-init="default">  
               <constructor-arg value="https://localhost:8443/cas/logout"/>  
               <constructor-arg>  
                    <list>  
                         <bean class="org.pentaho.platform.web.http.security.PentahoLogoutHandler"/>  
                         <bean class="org.springframework.security.ui.logout.SecurityContextLogoutHandler"/>  
                    </list>  
               </constructor-arg>  
               <property name="filterProcessesUrl" value="/Logout"/>  
          </bean>  
          <bean id="loggerListener" class="org.springframework.security.event.authentication.LoggerListener" />  
          <bean id="basicProcessingFilter" class="org.springframework.security.ui.basicauth.BasicProcessingFilter">  
               <property name="authenticationManager">  
                    <ref local="authenticationManager" />  
               </property>  
               <property name="authenticationEntryPoint">  
                    <ref local="basicProcessingFilterEntryPoint" />  
               </property>  
          </bean>  
          <bean id="basicProcessingFilterEntryPoint" class="org.springframework.security.ui.basicauth.BasicProcessingFilterEntryPoint">  
               <property name="realmName" value="Pentaho Realm" />  
          </bean>  
          <bean id="requestParameterProcessingFilter" class="org.pentaho.platform.web.http.security.RequestParameterAuthenticationFilter">  
               <property name="authenticationManager">  
                    <ref local="authenticationManager" />  
               </property>  
               <property name="authenticationEntryPoint">  
                    <ref local="requestParameterProcessingFilterEntryPoint" />  
               </property>  
          </bean>  
          <bean id="requestParameterProcessingFilterEntryPoint" class="org.pentaho.platform.web.http.security.RequestParameterFilterEntryPoint" />  
          <bean id="pentahoSecurityStartupFilter" class="org.pentaho.platform.web.http.security.SecurityStartupFilter">  
               <property name="injectAnonymous" value="true" />  
          </bean>  
          <bean id="anonymousProcessingFilter" class="org.springframework.security.providers.anonymous.AnonymousProcessingFilter">  
               <property name="key" value="foobar" />  
               <property name="userAttribute" value="anonymousUser,Anonymous" />  
          </bean>  
          <bean id="anonymousAuthenticationProvider" class="org.springframework.security.providers.anonymous.AnonymousAuthenticationProvider">  
               <property name="key" value="foobar" />  
          </bean>  
          <bean id="httpSessionContextIntegrationFilter" class="org.springframework.security.context.HttpSessionContextIntegrationFilter" />  
          <bean id="securityContextHolderAwareRequestFilter" class="org.springframework.security.wrapper.SecurityContextHolderAwareRequestFilter" />  
          <bean id="httpSessionReuseDetectionFilter" class="org.pentaho.platform.web.http.security.HttpSessionReuseDetectionFilter">  
               <property name="filterProcessesUrl" value="/j_spring_security_check" />  
               <property name="sessionReuseDetectedUrl" value="/Login?login_error=2" />  
          </bean>  
          <bean id="httpRequestAccessDecisionManager" class="org.springframework.security.vote.AffirmativeBased">  
               <property name="allowIfAllAbstainDecisions" value="false" />  
               <property name="decisionVoters">  
                    <list>  
                         <ref bean="roleVoter" />  
                    </list>  
               </property>  
          </bean>  
          <bean id="filterInvocationInterceptor" class="org.springframework.security.intercept.web.FilterSecurityInterceptor">  
               <property name="authenticationManager">  
                    <ref local="authenticationManager" />  
               </property>  
               <property name="accessDecisionManager">  
                    <ref local="httpRequestAccessDecisionManager" />  
               </property>  
               <property name="objectDefinitionSource">  
                    <value>  
                         <![CDATA[  
                         CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON  
                         \A/docs/.*\Z=Anonymous,Authenticated,ea_admin  
                         \A/mantlelogin/.*\Z=Anonymous,Authenticated,ea_admin  
                         \A/mantle/mantleloginservice/*\Z=Anonymous,Authenticated,ea_admin  
                         \A/mantle/.*\Z=Authenticated,ea_admin  
                         \A/welcome/.*\Z=Anonymous,Authenticated,ea_admin  
                         \A/public/.*\Z=Anonymous,Authenticated,ea_admin  
                         \A/login.*\Z=Anonymous,Authenticated,ea_admin  
                         \A/ping/alive.gif.*\Z=Anonymous,Authenticated,ea_admin  
                         \A/j_spring_security_check.*\Z=Anonymous,Authenticated,ea_admin  
                         \A/getimage.*\Z=Anonymous,Authenticated,ea_admin  
                         \A/getresource.*\Z=Anonymous,Authenticated,ea_admin  
                         \A/admin.*\Z=Admin,uidai_admin  
                         \A/auditreport.*\Z=Admin,uidai_admin  
                         \A/auditreportlist.*\Z=Admin,uidai_admin  
                         \A/versioncontrol.*\Z=Admin,uidai_admin  
                         \A/propertieseditor.*\Z=Admin,uidai_admin  
                         \A/propertiespanel.*\Z=Admin,uidai_admin  
                         \A/subscriptionadmin.*\Z=Admin,uidai_admin  
                         \A/resetrepository.*\Z=Admin,uidai_admin  
                         \A/viewaction.*solution.admin.*\Z=Admin,uidai_admin  
                         \A/scheduleradmin.*\Z=Admin,uidai_admin  
                         \A/publish.*\Z=Admin,uidai_admin  
                         \A/logout.*\Z=Anonymous  
                         \A/solutionrepositoryservice.*component=delete.*solution=system.*\Z=Nobody  
                         \A/solutionrepositoryservice.*solution=system.*component=delete.*\Z=Nobody  
                         .*system.*pentaho.xml.*=Nobody  
                         .*system.*applicationcontext.*.xml.*=Nobody  
                         .*system.*pentahoobjects.spring.xml.*=Nobody  
                         .*system.*pentahosystemconfig.xml.*=Nobody  
                         .*system.*adminplugins.xml.*=Nobody  
                         .*system.*plugin.properties.*=Nobody  
                         .*system.*publisher_config.xml.*=Nobody  
                         .*system.*sessionstartupactions.xml.*=Nobody  
                         .*system.*systemlisteners.xml.*=Nobody  
                         .*system.*hibernate.*=Nobody  
                         .*system.*birt/.*=Nobody  
                         .*system.*dialects/.*=Nobody  
                         .*system.*google/.*=Nobody  
                         .*system.*jasperreports/.*=Nobody  
                         .*system.*jfree/.*=Nobody  
                         .*system.*kettle/.*=Nobody  
                         .*system.*logs/.*=Nobody  
                         .*system.*metadata/.*=Nobody  
                         .*system.*mondrian/.*=Nobody  
                         .*system.*olap/.*=Nobody  
                         .*system.*quartz/.*=Nobody  
                         .*system.*simple-jndi/.*=Nobody  
                         .*system.*smtp-email/.*=Nobody  
                         .*system.*ui/.*=Nobody  
                         .*system.*analysistemplate.tpl.*=Nobody  
                         .*system.*\.\./.*=Nobody  
                         \A/.*\Z=Authenticated,ea_admin  
                         ]]>  
                    </value>  
               </property>  
          </bean>  
     </beans>  
    

  16. Edit pentaho-spring-beans.xml in D:\pentaho\biserver-ce\pentaho-solutions\system\

  17. Change this part

     <import resource="applicationContext-spring-security.xml" />  
    

    Become this part

     <import resource="applicationContext-spring-security-cas.xml" />  
    

  18. Edit web.xml in D:\pentaho\biserver-ce\tomcat\webapps\pentaho\WEB-INF

  19. Add this part

     <filter>  
       <filter-name>CAS Single Sign Out Filter</filter-name>  
       <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>  
     </filter>  
    

    Before this part

     <filter> <!-- This must be the first filter listed in the web.xml -->  
       <filter-name>Set Character Encoding Filter</filter-name>  
       <filter-class>org.pentaho.platform.web.http.filters.PentahoAwareCharacterEncodingFilter</filter-class>  
       <init-param>  
         <param-name>ignore</param-name>  
         <param-value>yes</param-value>  
       </init-param>  
     </filter>  
    

    Add this part

     <filter-mapping>  
       <filter-name>CAS Single Sign Out Filter</filter-name>  
       <url-pattern>/*</url-pattern>  
     </filter-mapping>  
    

    Before this part

     <filter-mapping>  
       <filter-name>Set Character Encoding Filter</filter-name>  
       <url-pattern>/*</url-pattern>  
     </filter-mapping>  
    

    Add this part

     <listener>  
       <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>  
     </listener>  
    

    After this part

     <listener>  
       <listener-class>org.pentaho.platform.web.http.session.PentahoCacheSessionListener</listener-class>  
     </listener>  
    

  20. Restart tomcat and open http://localhost:8080/pentaho, we will be redirected to https://localhost:8443/cas/login?service=http%3A%2F%2Flocalhost%3A8080%2Fpentaho%2Fj_spring_cas_security_check. Now when you log in/out from CAS, you will be logged in/out from all application you integrated with CAS.

  21. Let's have a drink and party! ;)

Wednesday, July 24, 2013

Pentaho Single Sign On (SSO) using CAS (story)

I wrote in previous post that passing username and password via parameter in Pentaho to show report viewer is a bad thing. Why? It's obvious that technique is not secure because anybody can read clearly username and password and can use it to get any information from your Pentaho BI Server. So, how if I want to integrate Pentaho to my application but I don't want to do double login: one login for my application and another login for Pentaho? We can use a technique called Single Sign On so that ANY application can be accessed only from one time login. I'm using this technique for developing +MaxLogistix: a low cost cloud warehouse management system.

I have read these 2 articles as my reference:
  1. http://tecnologia.2020mobile.es/single-sign-on-sso-on-pentaho-community-edition-using-cas/
  2. http://blog.datamensional.com/2011/07/pentaho-sso-setup-using-cas-and-ldap/
Those articles are good and very informative but... I found that these articles are not complete enough because there is some lost informations in one of them that given in another. So that I followed instructions in those articles and tried by myself which part is working and which part is not. The result is finally I can login to Pentaho using CAS.

So... is the problem solved? Sadly... not yet! There is one problem has not been solved by those articles. The problem is I can login to Pentaho using CAS and when I logged out from Pentaho, CAS status is also logged out, but.... Pentaho status still logged in eventhough I logged out from CAS. So the new problem I encountered is also SSO, not Single Sign ON but Single Sign OUT!

Well then I spent about a month trying to figure out what's wrong with my configuration. I read Pentaho forum, wiki, and any articles that explains about SSO. Even I was trying to replace spring-security library with the new one, but it didn't work because new spring-security library needs new version of spring library. And when I was trying to replace spring library in Pentaho with the new one, I just knew that I must refactor and recompile ALL Pentaho module with that new library. For me it's very frustating and I almost gave up.

And suddenly I found the solution in these 2 articles provided by wiki.jasig.org itself (jasig.org is an organization that manage CAS development)
  1. https://wiki.jasig.org/display/CASC/Using+the+CAS+Client+3.1+with+Spring+Security
  2. https://wiki.jasig.org/display/CASC/Configuring+Single+Sign+Out
The conclusion I got from those articles is... CAS Single Sign Out Filter must be in the first order to make Single Sign Out work perfectly. I will share a tutorial how to use CAS to implement SSO in Pentaho.

Tuesday, July 23, 2013

Embed Pentaho Report Viewer to Web Application using URL Rewrite

One thing that is very important for any web application is reporting module. Pentaho Community Edition has powerful capabilities to make any reports and we can just use it as reporting module in our web application. Of course we don't want to open a new window to show Pentaho Report Viewer, it makes users feel that reporting is a different web application, outside our application. We want to embed Pentaho Report Viewer in our web application so that users can easily access report from just one window. I do this in my project as a CTO in +Fordiso (a supply chain IT company based in Indonesia: +MaxLogistix, a low cost cloud warehouse management system.

The most common trick is using iframe and passing username and password parameter.
For example: I created test.html

 <html>  
 Hello, this page is showing Pentaho Report Viewer  
 <br/>  
 <iframe width="1300" height="600"  
      src="http://localhost:8080/pentaho/content/reporting/reportviewer/report.html?solution=steel-wheels&path=%2Freports&name=Inventory.prpt&userid=joe&password=password" />  
 </html>   

And I tried to deploy test.html to localhost and open in Chrome, it works!

Pentaho Report Viewer in Chrome
But the problem is... when I opened that same page in Firefox, it shown blank iframe and I have encountered error in Firebug: Permission denied to access property 'mantle_initialized'.

Permission denied in Firefox
After spending several days to find out about this problem, I learned that Firefox doesn't allow cross domain scripting. Because test.html in localhost and Pentaho server in localhost:8080, technically they are placed in different domain so that scripting between these two domain is forbidden.

Now what? Can we just use Chrome and only Chrome? Of course not! I can't just force my users using Chrome. Web application is designed for running in all browser, not only one specific browser! So the journey began. I spent several days more to find the solution for this problem in Pentaho forum, wiki, stackoverflow, blogs, and in many sites I have found in Google. But I found no answer. I found only the very same question! LOL!

In my journey, I found a blog that suggests to directly show output of the report in a specific format (html, pdf, xls, etc):
http://pentahoreportingandintegration.blogspot.com/2013/04/pentaho-report-integration-in-iframe.html
I appreciate this suggestion but I didn't want to use that because it will make me create a module to show and export reports. Moreover, it doesn't solve paging issue that we will encounter using its suggestion.

I also found that there is a technique to inject iframe to page using div and javascript so that cross domain script can be run. One of that technique is EasyXDM. I tried that technique but... it didn't work for Pentaho Report Viewer. Yes, iframe has been injected to consumer div but the same problem still occured. I learned that Pentaho Report Viewer also uses iframe, maybe it's why easyXDM technique didn't work.

Finally, I read that cross domain scripting can be avoided by using proxy server or url rewrite. This technique is mapping another domain to same domain folder so that cross domain scripting can be converted to same domain scripting.

In Apache we can use mod_proxy, the detail can be read in
http://nukoagency.co.uk/2012/11/accessing-an-iframe-using-a-proxy-server/
(I haven't tried this so that I can't give any comment)

In IIS we can use 4 IIS Extensions mentioned in
http://blogs.iis.net/wonyoo/archive/2011/04/20/how-to-install-application-request-routing-arr-2-5-without-web-platform-installer-webpi.aspx

Then we enable Proxy Server in IIS explained in below pictures




2 final step to embed Pentaho Report Viewer:
1. Add rewrite rule in web.config.

 <?xml version="1.0" encoding="UTF-8"?>  
 <configuration>  
   <system.webServer>  
     <rewrite>  
       <rules>  
         <rule name="pentaho" stopProcessing="true">  
           <match url="^pentaho/(.*)" />  
           <action type="Rewrite" url="http://localhost:8080/pentaho/{R:1}" />  
         </rule>  
       </rules>  
     </rewrite>  
   </system.webServer>  
 </configuration>  

2. Edit test.html so that iframe will load same domain URL.

 <html>  
 Hello, this page is showing Pentaho Report Viewer  
 <br/>  
 <iframe width="1300" height="600"  
      src="http://localhost/pentaho/content/reporting/reportviewer/report.html?solution=steel-wheels&path=%2Freports&name=Inventory.prpt&userid=joe&password=password" />  
 </html>   

The result is.... voila! Now Pentaho Report Viewer can be embedded in our web application AND can be opened in any browser. Life is good... :)

Pentaho Report Viewer in Firefox
Finished? Not yet, because passing username and password without encryption is a bad thing. I want to implement Single Sign On (SSO) using CAS Server so that my web application and Pentaho will use same authentication. I will share the trick after I fix some problem I encountered with CAS ticket.

Thank you.