About Author: Christian Strijbos

Posts by Christian Strijbos

0

Howto setup LDAP-Authentication for Wavemaker (Part 2)

Remember Part 1:

http://www.vcoportal.de/2012/05/howto-setup-ldap-authentication-for-wavemaker-part-1/

The Workaround

In the solution of Part 1, you can only find groups in one OU. In the wavemaker community, there are some links to additional documentation, were this post is based on.

Let’s have a look to the AD. In the Wavemaker examples a “flat” AD Structure is used

Whow, I have never seen an AD like this….. In the Wavemaker community examples always a “flat” AD environment is used. In such an environment it is easy to setup a Group Authentication…but in the environments, where I work I never had such an easy AD. So let’s have a look to a “real” AD:

When you use Wavemaker to configure your AD you are limited in the GUI. You cannot point your configuration to different OUs. Your only option is to get user out of one OU. But how do you configure your application if your users were sprawl in different OUs?

So when your people are from different OUs you have a problem….
For this problem, there is a solution, but therefore you have to make your LDAP configuration manually. First of all, you have to configure your AD Server connection. I assume that you have done this already. After that, we need some configuration changes…

First we have do modify the file project-security.xml. This is located under:

%PROJECTPATH%\webapproot\WEB-INF\project-security.xml

Insert the following code

<bean id="userSearch">

<constructor-arg index="0">

<value>cn=users</value> --> Here you must put the parent directory were to start the user search

</constructor-arg>

<constructor-arg index="1">

<value>(sAMAccountName={0})</value>

</constructor-arg>

<constructor-arg index="2">

<ref local="initialDirContextFactory"/>

</constructor-arg>

<property name="searchSubtree">

<value>true</value>

</property>

</bean>

One word to the code above, I was not able to start my search onto the root of the domain. I didn’t find a solution to start the search from there! I had to choose a OU or subfolder.

After that, you have to change some code in the file… See the screenshot for the “summary” of all changes, and find the complete before/after .xml-files below.

Be aware: After that, you cannot use the  LDAP configuration page in Wavemaker anymore! If you do so, everything will be overwritten and you have to start from scratch again!

The limitation above is not a Wavemaker limitation but comes from the used ACEGI Security which is used in Wavemaker. Additional information can be found here:

http://www.opennms.org/wiki/Acegi_Security_and_LDAP

So have fun with Orchestrator, Wavemaker and your Active Directory!

Complete before/after-xml-files

Change this…

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<beans xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd" xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <bean id="filterChainProxy">
 <property name="filterInvocationDefinitionSource">
 <value>
 CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
 PATTERN_TYPE_APACHE_ANT
 /**=httpSessionContextIntegrationFilter,logoutFilter,formAuthenticationProcessingFilter,anonymousProcessingFilter,jsonExceptionTranslationFilter,filterSecurityInterceptor
 </value>
 </property>
 </bean>
 <bean id="httpSessionContextIntegrationFilter"/>
 <bean id="logoutFilter">
 <constructor-arg value="/index.html"/>
 <constructor-arg>
 <list>
 <bean/>
 </list>
 </constructor-arg>
 <property value="/j_acegi_logout" name="filterProcessesUrl"/>
 </bean>
 <bean id="formAuthenticationProcessingFilter">
 <property name="filterProcessesUrl">
 <value>/j_acegi_security_check</value>
 </property>
 <property name="authenticationFailureUrl">
 <value>/login.html?login_error=1</value>
 </property>
 <property name="defaultTargetUrl">
 <value>/</value>
 </property>
 <property name="authenticationManager">
 <ref bean="authenticationManager"/>
 </property>
 </bean>
 <bean id="jsonExceptionTranslationFilter">
 <property name="authenticationEntryPoint">
 <ref bean="formLoginAuthenticationEntryPoint"/>
 </property>
 </bean>
 <bean id="formLoginAuthenticationEntryPoint">
 <property name="loginFormUrl">
 <value>/login.html</value>
 </property>
 <property name="forceHttps">
 <value>false</value>
 </property>
 </bean>
 <bean id="anonymousProcessingFilter">
 <property name="key">
 <value>sharedsecret</value>
 </property>
 <property name="userAttribute">
 <value>anonymousUser,ROLE_ANONYMOUS</value>
 </property>
 </bean>
 <bean id="anonymousAuthenticationProvider">
 <property name="key">
 <value>sharedsecret</value>
 </property>
 </bean>
 <bean id="filterSecurityInterceptor">
 <property name="authenticationManager">
 <ref bean="authenticationManager"/>
 </property>
 <property name="accessDecisionManager">
 <ref bean="accessDecisionManager"/>
 </property>
 <property name="objectDefinitionSource">
 <value>
 CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
 PATTERN_TYPE_APACHE_ANT
 /*.upload=IS_AUTHENTICATED_FULLY
 /*.download=IS_AUTHENTICATED_FULLY
 /index.html=IS_AUTHENTICATED_FULLY
 /=IS_AUTHENTICATED_FULLY
 /securityservice.json=IS_AUTHENTICATED_ANONYMOUSLY
 /*.json=IS_AUTHENTICATED_FULLY
 </value>
 </property>
 </bean>
 <bean id="authenticationManager">
 <property name="providers">
 <list>
 <ref bean="ldapAuthProvider"/>
 <ref bean="anonymousAuthenticationProvider"/>
 </list>
 </property>
 </bean>
 <bean id="daoAuthenticationProvider">
 <property name="userDetailsService">
 <ref bean="inMemoryDaoImpl"/>
 </property>
 </bean>
 <bean id="inMemoryDaoImpl">
 <property name="userMap">
 <value>
 demo=demo,ROLE_DEFAULT_NO_ROLES
 </value>
 </property>
 </bean>
 <bean id="jdbcDaoImpl">
 <property name="dataSource">
 <ref bean="jdbcDataSource"/>
 </property>
 <property name="usersByUsernameQuery">
 <value>SELECT first_name, last_name, 1 FROM employee WHERE first_name = ?</value>
 </property>
 <property name="authoritiesByUsernameQuery">
 <value>SELECT first_name, "DEFAULT_NO_ROLES" FROM employee WHERE first_name = ?</value>
 </property>
 <property name="rolePrefix">
 <value>ROLE_</value>
 </property>
 <property name="usernameBasedPrimaryKey">
 <value>true</value>
 </property>
 </bean>
 <bean id="jdbcDataSource"/>
 <bean id="ldapAuthProvider">
 <constructor-arg>
 <bean>
 <constructor-arg>
 <ref local="initialDirContextFactory"/>
 </constructor-arg>
 <property name="userDnPatterns">
 <list>
 <value>cn={0},ou=people</value>
 </list>
 </property>
 </bean>
 </constructor-arg>
 <constructor-arg>
 <bean>
 <constructor-arg>
 <ref local="initialDirContextFactory"/>
 </constructor-arg>
 <constructor-arg>
 <value>ou=groups</value>
 </constructor-arg>
 <property name="groupSearchDisabled">
 <value>false</value>
 </property>
 <property name="roleProvider">
 <value>LDAP</value>
 </property>
 <property name="groupRoleAttribute">
 <value>cn</value>
 </property>
 <property name="groupSearchFilter">
 <value>(member={0})</value>
 </property>
 </bean>
 </constructor-arg>
 </bean>
 <bean id="initialDirContextFactory">
 <constructor-arg value="ldap://localhost:389/dc=wavemaker,dc=com"/>
 <property name="managerDn">
 <value>cn=manager,dc=wavemaker,dc=com</value>
 </property>
 <property name="managerPassword">
 <value>7b6a43524a282c146a6b626e425c32205754</value>
 </property>
 </bean>
 <bean id="accessDecisionManager">
 <property name="allowIfAllAbstainDecisions">
 <value>false</value>
 </property>
 <property name="decisionVoters">
 <list>
 <bean/>
 <bean/>
 </list>
 </property>
 </bean>
 <bean id="roleVoter">
 <property name="rolePrefix">
 <value>ROLE_</value>
 </property>
 </bean>
 <bean id="autoProxyCreator">
 <property name="proxyTargetClass">
 <value>true</value>
 </property>
 <property name="interceptorNames">
 <list>
 <value>securityInterceptor</value>
 </list>
 </property>
 </bean>
 <bean id="securityInterceptor">
 <property ref="authenticationManager" name="authenticationManager"/>
 <property ref="accessDecisionManager" name="accessDecisionManager"/>
 <property name="objectDefinitionSource">
 <value>
 </value>
 </property>
 </bean>
 <bean scope="singleton" id="securityService">
 <property name="authenticationManager">
 <ref bean="authenticationManager"/>
 </property>
 <property name="rolePrefix">
 <value>ROLE_</value>
 </property>
 <property name="noRolesMarkerRole">
 <value>DEFAULT_NO_ROLES</value>
 </property>
 <property name="roles">
 <list/>
 </property>
 </bean>
</beans>

…to this:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<beans xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd" xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <bean id="filterChainProxy">
 <property name="filterInvocationDefinitionSource">
 <value>
 CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
 PATTERN_TYPE_APACHE_ANT
 /**=httpSessionContextIntegrationFilter,logoutFilter,formAuthenticationProcessingFilter,anonymousProcessingFilter,jsonExceptionTranslationFilter,filterSecurityInterceptor
 </value>
 </property>
 </bean>
 <bean id="httpSessionContextIntegrationFilter"/>
 <bean id="logoutFilter">
 <constructor-arg value="/index.html"/>
 <constructor-arg>
 <list>
 <bean/>
 </list>
 </constructor-arg>
 <property value="/j_acegi_logout" name="filterProcessesUrl"/>
 </bean>
 <bean id="formAuthenticationProcessingFilter">
 <property name="filterProcessesUrl">
 <value>/j_acegi_security_check</value>
 </property>
 <property name="authenticationFailureUrl">
 <value>/login.html?login_error=1</value>
 </property>
 <property name="defaultTargetUrl">
 <value>/</value>
 </property>
 <property name="authenticationManager">
 <ref bean="authenticationManager"/>
 </property>
 </bean>
 <bean id="jsonExceptionTranslationFilter">
 <property name="authenticationEntryPoint">
 <ref bean="formLoginAuthenticationEntryPoint"/>
 </property>
 </bean>
 <bean id="formLoginAuthenticationEntryPoint">
 <property name="loginFormUrl">
 <value>/login.html</value>
 </property>
 <property name="forceHttps">
 <value>false</value>
 </property>
 </bean>
 <bean id="anonymousProcessingFilter">
 <property name="key">
 <value>sharedsecret</value>
 </property>
 <property name="userAttribute">
 <value>anonymousUser,ROLE_ANONYMOUS</value>
 </property>
 </bean>
 <bean id="anonymousAuthenticationProvider">
 <property name="key">
 <value>sharedsecret</value>
 </property>
 </bean>
 <bean id="filterSecurityInterceptor">
 <property name="authenticationManager">
 <ref bean="authenticationManager"/>
 </property>
 <property name="accessDecisionManager">
 <ref bean="accessDecisionManager"/>
 </property>
 <property name="objectDefinitionSource">
 <value>
 CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
 PATTERN_TYPE_APACHE_ANT
 /*.upload=IS_AUTHENTICATED_FULLY
 /*.download=IS_AUTHENTICATED_FULLY
 /index.html=IS_AUTHENTICATED_FULLY
 /=IS_AUTHENTICATED_FULLY
 /securityservice.json=IS_AUTHENTICATED_ANONYMOUSLY
 /*.json=IS_AUTHENTICATED_FULLY
 </value>
 </property>
 </bean>
 <bean id="authenticationManager">
 <property name="providers">
 <list>
 <ref bean="ldapAuthProvider"/>
 <ref bean="anonymousAuthenticationProvider"/>
 </list>
 </property>
 </bean>
 <bean id="daoAuthenticationProvider">
 <property name="userDetailsService">
 <ref bean="inMemoryDaoImpl"/>
 </property>
 </bean>
 <bean id="inMemoryDaoImpl">
 <property name="userMap">
 <value>
 demo=demo,ROLE_DEFAULT_NO_ROLES
 </value>
 </property>
 </bean>
 <bean id="jdbcDaoImpl">
 <property name="dataSource">
 <ref bean="jdbcDataSource"/>
 </property>
 <property name="usersByUsernameQuery">
 <value>SELECT first_name, last_name, 1 FROM employee WHERE first_name = ?</value>
 </property>
 <property name="authoritiesByUsernameQuery">
 <value>SELECT first_name, "DEFAULT_NO_ROLES" FROM employee WHERE first_name = ?</value>
 </property>
 <property name="rolePrefix">
 <value>ROLE_</value>
 </property>
 <property name="usernameBasedPrimaryKey">
 <value>true</value>
 </property>
 </bean>
 <bean id="userSearch">
 <constructor-arg index="0">
 <value>cn=users</value>
 </constructor-arg>
 <constructor-arg index="1">
 <value>(sAMAccountName={0})</value>
 </constructor-arg>
 <constructor-arg index="2">
 <ref local="initialDirContextFactory"/>
 </constructor-arg>
 <property name="searchSubtree">
 <value>true</value>
 </property>
 </bean>

<bean id="jdbcDataSource"/>
 <bean id="ldapAuthProvider">
<constructor-arg>
 <bean>
 <constructor-arg>
 <ref local="initialDirContextFactory"/>
 </constructor-arg>
 <property name="userSearch">
 <ref bean="userSearch"/>
 </property>
 </bean>
 </constructor-arg>
 </constructor-arg>
 <constructor-arg>
 <bean>
 <constructor-arg>
 <ref local="initialDirContextFactory"/>
 </constructor-arg>
 <constructor-arg>
 <value>ou=groups</value>
 </constructor-arg>
 <property name="groupSearchDisabled">
 <value>false</value>
 </property>
 <property name="roleProvider">
 <value>LDAP</value>
 </property>
 <property name="groupRoleAttribute">
 <value>cn</value>
 </property>
 <property name="groupSearchFilter">
 <value>(member={0})</value>
 </property>
 </bean>
 </constructor-arg>
 </bean>
 <bean id="initialDirContextFactory">
 <constructor-arg value="ldap://localhost:389/dc=wavemaker,dc=com"/>
 <property name="managerDn">
 <value>cn=manager,dc=wavemaker,dc=com</value>
 </property>
 <property name="managerPassword">
 <value>7b6a43524a282c146a6b626e425c32205754</value>
 </property>
 </bean>
 <bean id="accessDecisionManager">
 <property name="allowIfAllAbstainDecisions">
 <value>false</value>
 </property>
 <property name="decisionVoters">
 <list>
 <bean/>
 <bean/>
 </list>
 </property>
 </bean>
 <bean id="roleVoter">
 <property name="rolePrefix">
 <value>ROLE_</value>
 </property>
 </bean>
 <bean id="autoProxyCreator">
 <property name="proxyTargetClass">
 <value>true</value>
 </property>
 <property name="interceptorNames">
 <list>
 <value>securityInterceptor</value>
 </list>
 </property>
 </bean>
 <bean id="securityInterceptor">
 <property ref="authenticationManager" name="authenticationManager"/>
 <property ref="accessDecisionManager" name="accessDecisionManager"/>
 <property name="objectDefinitionSource">
 <value>
 </value>
 </property>
 </bean>
 <bean scope="singleton" id="securityService">
 <property name="authenticationManager">
 <ref bean="authenticationManager"/>
 </property>
 <property name="rolePrefix">
 <value>ROLE_</value>
 </property>
 <property name="noRolesMarkerRole">
 <value>DEFAULT_NO_ROLES</value>
 </property>
 <property name="roles">
 <list/>
 </property>
 </bean>
</beans>
0

Howto setup LDAP-Authentication for Wavemaker (Part 1)

You can leverage VMware Wavemaker to create a nice web-frontend for your users to start workflows.
(how? See the links at the end of this article!)

Typically you don’t let just everybody do that, but only identified users. So you have to implement some kind of User Authentication mechanism on the web-frontend.

The Good

Wavemaker provides included mechanisms to implement a login page, and authenticate the users against a database or a LDAP-directory, using a so called Security Service. This LDAP authentication also works against Active Directory.

The Security Service also allows a group-based distinction into different roles: you can show or hide different parts of the website to users, depending if they are in an AD-group or not.

This works very well, and is (as usual for Wavemaker  :-)) very well documented:
http://dev.wavemaker.com/wiki/bin/Authentication
http://dev.wavemaker.com/wiki/bin/SecurityTutorial

The Bad

The normal login page (automatically created when you activate this checkbox in the security service) does not allow you to re-use the user credentials “later” on webpage. But, you might need these credentials to authenticate against vCO for workflow execution. Then you have to rebuild the authentication mechanism manually, and create a JavaService to check if the user is in a specific LDAP-group.

Let’s see, how to do:

First of all, you have to configure the connection to your LDAP/AD Server. The easiest ways to do so is over the Wavemaker GUI.

 You can insert the LDAP/AD integration in the menu Services / Security.

 On the site you have to choose your Security Provider and to enable Security.

Important here:

  • Do NOT check the “Show Login Page”-checkbox! You will create your own login screen later…
  • Check the “Search User Role”-checkbox to activate the mechanism which does the association to LDAP-groups
***Very Useful Site Note***
You can test your settings using the “Test Connection”-Button. (Even this is not vCO-related (yet 😉 ): If you get an error, remember what you learned from vCO configuration 😀 e.g. the “Administrator” as Manager DN in AD is identified as “cn=Administrator,cn=Users,dc=lab,dc=local”
For troubleshooting: I’m sure you remember this post on how to troubleshoot LDAP issues?
http://www.vcoportal.de/2011/07/troubleshooting-ldap-erros-in-vco/ 
***Another Useful Site Note***
As frequent reader of this blog you remember adfind: A small, very powerful, very fast, very handy tool to figure out where and how to find objects in Active Directory…

To implement the group-checking manually you have to insert a JavaService into your project.

In the menu choose Services / Java Service:

 

In the popup you must enter a name and a package name for the java service.

 

In my case I choose the Service Name GroupAuth and LDAPGroupAuth.

Here is the java source code for that:

/**
 * This is a client-facing service class. All
 * public methods will be exposed to the client. Their return
 * values and parameters will be passed to the client or taken
 * from the client, respectively. This will be a singleton
 * instance, shared between all requests.
 *
 * To log, call the superclass method log(LOG_LEVEL, String) or log(LOG_LEVEL, String, Exception).
 * LOG_LEVEL is one of FATAL, ERROR, WARN, INFO and DEBUG to modify your log level.
 * For info on these levels, look for tomcat/log4j documentation
 */
 import com.wavemaker.runtime.RuntimeAccess;
 import com.wavemaker.runtime.security.SecurityService;

public class LDAPGroupAuth extends com.wavemaker.runtime.javaservice.JavaServiceSuperClass {
 /* Pass in one of FATAL, ERROR, WARN, INFO and DEBUG to modify your log level;
 * recommend changing this to FATAL or ERROR before deploying. For info on these levels, look for tomcat/log4j documentation
 */
 public String LDAPGroupAuth() {
 log(INFO,"ldapauth...");
 String LoginTrue= "Yes";
 String LoginFalse= "No";
 if (isUserInRole("VCOADMINS")){

 return LoginTrue;
 }else{

 return LoginFalse;
 }
 }

 private static boolean isUserInRole(String role){
 SecurityService srv = (SecurityService) RuntimeAccess.getInstance().getService("securityService");
 String[]uRoles = srv.getUserRoles();
 for(String uRole : uRoles){
 // log(INFO,"role: " + uRole);
 if(uRole.equals(role)){
 return true;
 }
 }
 return false;
 }

}

Important here: The name of the AD-group has to be typed in UPPERCASE!

Next, I had to create the “flow” of the login process, using different Layers in Wavemaker.

I copy the “Login Page” Box and the scripts into my Main site. There I work with different layer and I am able to use the “variables” on all layers.

For the integration of your GroupAuth into your project, you have to modify our “Login” Button.
During my tests for the project, I searched for a good solution to check the credentials (Username and password) against the AD Server and validate the group membership in one step. I didn’t find a, for me, robust solution.
If someone finds a better solution than the one below, please comment!  😎

Due that circumstance, I decided to use a temporary Auth_Layer. By clicking on the “Login” button, I check the credentials, on the Login site and after checking these where valid I check the group membership.

So, here is the code for checking the AD credentials:

loginButtonClick: function(inSender) {
dojo.cookie("user", this.usernameInput.getDataValue(), {expires: 365});
 this.loginErrorMsg.setCaption("");
 wm.login(
 [this.usernameInput.getDataValue(), this.passwordInput.getDataValue()],
 dojo.hitch(this, "loginSuccess"), dojo.hitch(this, "loginFailed"));
 },
 loginSuccess: function(inResponse) {
 try{
 // This will set the currently showing layer to the AuthLayer
 main.layers1.setLayer('Auth_layer');

 } catch(e) {
 console.error('ERROR IN loginSuccess: ' + e);
 }
 },
 loginFailed: function(inResponse) {
 this.loginErrorMsg.setCaption("Invalid username or password.");
 this.usernameInput.focus();
 },

When the user credentials where valid, the layer is changed to the “Auth_Layer”.

On this layer, the group membership is checked. Here is the code for the check (executed in the “onShow()”-Event of this Layer):

loginButtonClick: function(inSender) {
 dojo.cookie("user", this.usernameInput.getDataValue(), {expires: 365});
 this.loginErrorMsg.setCaption("");
 wm.login(
 [this.usernameInput.getDataValue(), this.passwordInput.getDataValue()],
 dojo.hitch(this, "loginSuccess"), dojo.hitch(this, "loginFailed"));
 },
 loginSuccess: function(inResponse) {
 try{
 // This will set the currently showing layer to the AuthLayer
 main.layers1.setLayer('Auth_layer');

 } catch(e) {
 console.error('ERROR IN loginSuccess: ' + e);
 }
 },
 loginFailed: function(inResponse) {
 this.loginErrorMsg.setCaption("Invalid username or password.");
 this.usernameInput.focus();
 },

So if the group membership of the user is okay, the user is redirected to the “Request_Layer”. If the group membership is not okay, the user is redirect to the “Denied_Layer”.

In sum, the system provides for three different conditions:

  • Username or Password is wrong: This shows an small “login unsuccessful”-message directly in the login-box
  • Username / Password is correct (so he is logged-in from the Wavemaker Security Service perspective), but the user is NOT in the AD-group. Then he’s redirected to the “Denied_Layer”.
  • Username / Password is correct (so he is logged-in from the Wavemaker Security Service perspective), AND he is in the AD-group. Then he’s redirected to the “Request_Layer”, which for instance contains the buttons that trigger Orchestrator Workflows.

The Ugly

Given some restrictions of the underlying mechanisms used for the Security Service implementation in Wavemaker, it only supports groups within one Organizational Unit (OU) in Active Directory.
However, there is a workaround for this, expect Part 2 of this article soon :mrgreen:

References

To give you an idea about it looks like at the end of the day, download the project below!
(It’s a Wavemaker 6.4 exported project, should be directly importable. Just make sure to adjust the LDAP-Settings of the Security Service!)

Aaaaand, some links:
http://www.vcoportal.de/2011/11/using-wavemaker-as-web-frontend-for-vco/
http://www.virtuallyghetto.com/2011/12/leveraging-vcd-vco-wavemaker-part-1.html
http://www.virtuallyghetto.com/2011/12/leveraging-vcd-vco-wavemaker-part-2.html
http://dev.wavemaker.com/wiki/bin/Learning/Mastering+LDAP+Security

Wavemaker-LDAP-Authentication
Wavemaker-LDAP-Authentication
vCO_WM_LDAP.1.Alpha.zip
160.2 KiB
Details...