Notice: This Wiki is now read only and edits are no longer possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.
Scout/HowTo/3.9/Authentication with a LDAPSecurityFilter
The Scout documentation has been moved to https://eclipsescout.github.io/.
The Security Concept page is rather brief on the use of the LDAPSecurityFilter. This how-to describes how to use it and offers an alternate security filter.
LDAPSecurityFilter
In order to use the LDAPSecurityFilter, the corresponding filter class must be added to the server's plugin.xml in the org.eclipse.scout.http.servletfilter.filters extension point
<filter aliases="/process" class="org.eclipse.scout.http.servletfilter.security.LDAPSecurityFilter" ranking="50"> </filter>
Next, the necessary configuration lines must be added to the server's config.ini file:
org.eclipse.scout.http.servletfilter.security.LDAPSecurityFilter#active=true org.eclipse.scout.http.servletfilter.security.LDAPSecurityFilter#failover=false org.eclipse.scout.http.servletfilter.security.LDAPSecurityFilter#realm=developmnet org.eclipse.scout.http.servletfilter.security.LDAPSecurityFilter#ldapServer=ldap://ldap.company.com:389 org.eclipse.scout.http.servletfilter.security.LDAPSecurityFilter#ldapBaseDN=dc=company,dc=com org.eclipse.scout.http.servletfilter.security.LDAPSecurityFilter#lDAPgroupDN=??? org.eclipse.scout.http.servletfilter.security.LDAPSecurityFilter#lDAPgroupAttributeId=cn
Note: It is not yet quite clear what value is needed for the lDAPgroupDN parameter.
The LDAPSecurityFilter connects to the LDAP server anonymously. This may well work for many servers, but the some LDAP servers only return empty responses when queried anonymously.
LDAPExSecurityFilter
To work around this problem, an extended LDAPSecurityFilter which connects to the LDAP server using a pre-configured system user can be used.
package org.eclipse.minicrm.server.services.custom.security; import java.io.IOException; import java.util.Enumeration; import java.util.Hashtable; import javax.naming.Context; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.scout.commons.Base64Utility; import org.eclipse.scout.commons.logger.IScoutLogger; import org.eclipse.scout.commons.logger.ScoutLogManager; import org.eclipse.scout.commons.security.SimplePrincipal; import org.eclipse.scout.http.servletfilter.FilterConfigInjection; import org.eclipse.scout.http.servletfilter.security.AbstractChainableSecurityFilter; import org.eclipse.scout.http.servletfilter.security.PrincipalHolder; /** * LDAPSecurityFilter The following properties can be set in the config.ini file: * * <fully qualified name of class>#active=true/false might be set in the extension point * <fully qualified name of class>#realm=abcde required * <fully qualified name of class>#failover=true/false default false * <fully qualified name of class>#ldapDirectory=[e.g. ldap://100.100.29.4:389] required * <fully qualified name of class>#ldapBaseDN=[e.g. dc=bridgesolutions,dc=net] required * <fully qualified name of class>#ldapSearchFilter=[e.g. (uid=%s)] required * <fully qualified name of class>#ldapSearchBindPassword=[e.g. changeme] * <fully qualified name of class>#m_searchBindDN=[e.g. cn=readonly,dc=bridgesolutions,dc=net] * * based on org.eclipse.scout.http.servletfilter.security.LDAPSecurityFilter */ public class LDAPExSecurityFilter extends AbstractChainableSecurityFilter { private static final IScoutLogger LOG = ScoutLogManager.getLogger(LDAPExSecurityFilter.class); public static final String PROP_BASIC_ATTEMPT = "LDAPExSecurityFilter.basicAttempt"; private String m_directory; private String m_baseDn; private String m_searchFilter; private String m_searchBindPassword; private String m_searchBindDN; @Override public void init(FilterConfig config0) throws ServletException { super.init(config0); FilterConfigInjection.FilterConfig config = new FilterConfigInjection(config0, getClass()).getAnyConfig(); m_directory = getParam(config, "ldapDirectory", false); m_baseDn = getParam(config, "ldapBaseDN", false); m_searchFilter = getParam(config, "ldapSearchFilter", false); m_searchBindPassword = getParam(config, "ldapSearchBindPassword", true); m_searchBindDN = getParam(config, "ldapSearchBindDN", true); } protected String getParam(FilterConfig filterConfig, String paramName, boolean nullAllowed) throws ServletException { String paramValue = filterConfig.getInitParameter(paramName); boolean exists = false; if (paramValue == null && nullAllowed) { // check if parameter exists Enumeration initParameterNames = filterConfig.getInitParameterNames(); while (initParameterNames.hasMoreElements() && exists == false) { String object = (String) initParameterNames.nextElement(); exists = object.equals(paramName); } } if (paramValue == null && !exists) { throw new ServletException("Missing init-param with name '" + paramName + "'."); } return paramValue; } @Override protected int negotiate(HttpServletRequest req, HttpServletResponse resp, PrincipalHolder holder) throws IOException, ServletException { String h = req.getHeader("Authorization"); if (h != null && h.matches("Basic .*")) { String[] a = new String(Base64Utility.decode(h.substring(6)), "ISO-8859-1").split(":", 2); String user = a[0].toLowerCase(); String pass = a[1]; if (user != null && pass != null) { if (login(user, pass, m_directory, m_baseDn, m_searchBindDN, m_searchBindPassword, m_searchFilter)) { // success holder.setPrincipal(new SimplePrincipal(user)); return STATUS_CONTINUE_WITH_PRINCIPAL; } } } int attempts = getBasicAttempt(req); if (attempts > 2) { return STATUS_CONTINUE_CHAIN; } else { setBasicAttept(req, attempts + 1); resp.setHeader("WWW-Authenticate", "Basic realm=\"" + getRealm() + "\""); return STATUS_CONTINUE_CHAIN; } } private int getBasicAttempt(HttpServletRequest req) { int basicAtttempt = 0; Object attribute = req.getSession().getAttribute(PROP_BASIC_ATTEMPT); if (attribute instanceof Integer) { basicAtttempt = ((Integer) attribute).intValue(); } return basicAtttempt; } private void setBasicAttept(HttpServletRequest req, int attempts) { req.getSession().setAttribute(PROP_BASIC_ATTEMPT, attempts); } @SuppressWarnings("unchecked") protected boolean login(String username, String userPassword, String directory, String baseDN, String searchBindDN, String searchBindPassword, String searchFilter) throws ServletException { boolean isAuthenticated = false; Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); env.put(Context.PROVIDER_URL, directory); env.put(Context.SECURITY_PRINCIPAL, searchBindDN); env.put(Context.SECURITY_CREDENTIALS, searchBindPassword); try { DirContext ctx = new InitialDirContext(env); SearchControls constraints = new SearchControls(); constraints.setSearchScope(SearchControls.SUBTREE_SCOPE); String search = String.format(searchFilter, username); NamingEnumeration<SearchResult> results = ctx.search(baseDN, search, constraints); SearchResult sr = null; if (results != null && results.hasMore()) { // Step 2 - simple authentication sr = results.next(); env.put(Context.SECURITY_AUTHENTICATION, "simple"); if (sr.isRelative()) { env.put(Context.SECURITY_PRINCIPAL, sr.getName() + "," + baseDN); } else { env.put(Context.SECURITY_PRINCIPAL, sr.getName()); } env.put(Context.SECURITY_CREDENTIALS, userPassword); try { new InitialDirContext(env); isAuthenticated = true; } catch (NamingException ne) { LOG.warn("User '" + username + "' could not be authenticated"); isAuthenticated = false; } } else { LOG.warn("User '" + username + "' does not exist on LDAP"); isAuthenticated = false; } } catch (NamingException ne) { LOG.error("Exception in getting user DN from LDAP: " + ne); throw new SecurityException(ne.getMessage(), ne); } return isAuthenticated; } }
It must be registered in the server's plugin.xml file in the org.eclipse.scout.http.servletfilter.filters extension point
<filter aliases="/process" class="org.eclipse.minicrm.server.services.custom.security.LDAPExSecurityFilter" ranking="30"> </filter>
It must be configured in the server product's config.ini file:
org.eclipse.minicrm.server.services.custom.security.LDAPExSecurityFilter#active=true org.eclipse.minicrm.server.services.custom.security.LDAPExSecurityFilter#realm=LDAP org.eclipse.minicrm.server.services.custom.security.LDAPExSecurityFilter#ldapDirectory=ldap://ldap.company.com:389 org.eclipse.minicrm.server.services.custom.security.LDAPExSecurityFilter#ldapBaseDN=dc=company,dc=com org.eclipse.minicrm.server.services.custom.security.LDAPExSecurityFilter#ldapSearchFilter=(cn=%s) org.eclipse.minicrm.server.services.custom.security.LDAPExSecurityFilter#ldapSearchBindDN=cn=<system user name>,ou=Administrators,dc=company,dc=com org.eclipse.minicrm.server.services.custom.security.LDAPExSecurityFilter#ldapSearchBindPassword=<system user password> org.eclipse.minicrm.server.services.custom.security.LDAPExSecurityFilter#failover=false org.eclipse.minicrm.server.services.custom.security.LDAPExSecurityFilter#silent=true