All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.predic8.membrane.core.interceptor.authentication.session.LDAPUserDataProvider Maven / Gradle / Ivy

There is a newer version: 5.7.3
Show newest version
/* Copyright 2012 predic8 GmbH, www.predic8.com

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License. */
package com.predic8.membrane.core.interceptor.authentication.session;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.regex.Pattern;

import javax.naming.AuthenticationException;
import javax.naming.CommunicationException;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;

import com.predic8.membrane.annot.MCAttribute;
import com.predic8.membrane.annot.MCChildElement;
import com.predic8.membrane.annot.MCElement;
import com.predic8.membrane.core.Router;

/**
 * @description A user data provider querying an LDAP server to authorize users and retrieve attributes.
 * @explanation 

* The LDAP User Data Provider performs two jobs: *

    *
  1. Authentication of a username and password.
  2. *
  3. Retrieval of user attributes.
  4. *
*

*

* To achieve this, it first binds to base on the LDAP server url. If binddn is not * present, it binds to the LDAP server anonymously, elsewise binddn and bindpw are used for * authentication. *

*

* Next, a search searchPattern with scope searchScope is executed where "%LOGIN%" * is replaced by the escaped version of the username. *

*

* The search returning no node or more than one node is treated as failure. *

*

* If passwordAttribute is set, and the node has an attribute with this name and this attribute's * value starts with "{x-plain}", the password is checked against the rest of the value for * equality. If passwordAttribute is not set, a second binding is attempted on the node using the * password the user provided. *

*

* The user attribute keys specified in the mapping are then renamed according to the mapping and used for * further processing (see the other modules of the login interceptor). *

*

*

*

* For the initial binding, connectTimeout can be used to specify a timeout in milliseconds. For the * search, timeout can be used. *

*

* If readAttributesAsSelf is not set, the user attributes are collected from the search result. If * it is set, an additional request is made after the second successful binding to retrieve the node's * attributes. *

*/ @MCElement(name="ldapUserDataProvider", topLevel=false) public class LDAPUserDataProvider implements UserDataProvider { private static Logger log = LoggerFactory.getLogger(LDAPUserDataProvider.class.getName()); String url; // the LDAP server String base; // the base DN String binddn; // the DN to bind to the server, or null to bind anonymously String bindpw; // binddn's password, if binddn != null String searchPattern; // search expression to find user int searchScope = SearchControls.SUBTREE_SCOPE; // search scope to find user String passwordAttribute; // attribute containing the user's password, bind-to-authenticate to the user's node if null String timeout = "1000"; // timeout in milliseconds String connectTimeout = "1000"; boolean readAttributesAsSelf = true; // whether reading the user's attributes requires authentication HashMap attributeMap = new HashMap(); // maps LDAP attributes to TokenGenerator attributes AttributeMap map; @MCElement(name="map", topLevel=false, id="ldapUserDataProvider-map") public static class AttributeMap { @MCElement(name="attribute", topLevel=false) public static class Attribute { String from; String to; public String getFrom() { return from; } @Required @MCAttribute public void setFrom(String from) { this.from = from; } public String getTo() { return to; } @Required @MCAttribute public void setTo(String to) { this.to = to; } } private List attributes = new ArrayList(); public List getAttributes() { return attributes; } @MCChildElement public void setAttributes(List attributes) { this.attributes = attributes; } } /** * @throws NoSuchElementException if no user could be found with the given login * @throws AuthenticationException if the password does not match * @throws CommunicationException e.g. on server timeout * @throws NamingException on any other LDAP error */ private HashMap auth(String login, String password) throws NamingException { Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); env.put(Context.PROVIDER_URL, url); env.put("com.sun.jndi.ldap.read.timeout", timeout); env.put("com.sun.jndi.ldap.connect.timeout", connectTimeout); if (binddn != null) { env.put(Context.SECURITY_AUTHENTICATION, "simple"); env.put(Context.SECURITY_PRINCIPAL, binddn); env.put(Context.SECURITY_CREDENTIALS, bindpw); } HashMap userAttrs = new HashMap(); String uid; DirContext ctx = new InitialDirContext(env); try { uid = searchUser(login, userAttrs, ctx); } finally { ctx.close(); } if (passwordAttribute != null) { if (!userAttrs.containsKey("_pass")) throw new NoSuchElementException(); String pass = userAttrs.get("_pass"); if (pass == null || !pass.startsWith("{x-plain}")) throw new NoSuchElementException(); log.debug("found password"); pass = pass.substring(9); if (!pass.equals(password)) throw new NoSuchElementException(); userAttrs.remove("_pass"); } else { env.put(Context.SECURITY_AUTHENTICATION, "simple"); env.put(Context.SECURITY_PRINCIPAL, uid + "," + base); env.put(Context.SECURITY_CREDENTIALS, password); DirContext ctx2 = new InitialDirContext(env); try { if (readAttributesAsSelf) searchUser(login, userAttrs, ctx2); } finally { ctx2.close(); } } return userAttrs; } private String searchUser(String login, HashMap userAttrs, DirContext ctx) throws NamingException { String uid; SearchControls ctls = new SearchControls(); ctls.setReturningObjFlag(true); ctls.setSearchScope(searchScope); String search = searchPattern.replaceAll(Pattern.quote("%LOGIN%"), escapeLDAPSearchFilter(login)); log.debug("Searching LDAP for " + search); NamingEnumeration answer = ctx.search(base, search, ctls); try { if (!answer.hasMore()) throw new NoSuchElementException(); log.debug("LDAP returned >=1 record."); SearchResult result = answer.next(); uid = result.getName(); for (Map.Entry e : attributeMap.entrySet()) { log.debug("found LDAP attribute: " + e.getKey()); Attribute a = result.getAttributes().get(e.getKey()); if (a != null) userAttrs.put(e.getValue(), a.get().toString()); } } finally { answer.close(); } return uid; } private static final String escapeLDAPSearchFilter(String filter) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < filter.length(); i++) { char curChar = filter.charAt(i); switch (curChar) { case '\\': sb.append("\\5c"); break; case '*': sb.append("\\2a"); break; case '(': sb.append("\\28"); break; case ')': sb.append("\\29"); break; case '\u0000': sb.append("\\00"); break; default: sb.append(curChar); } } return sb.toString(); } @Override public Map verify(Map postData) { String username = postData.get("username"); String password = postData.get("password"); if (username == null || password == null) throw new NoSuchElementException(); try { return auth(username, password); } catch (NoSuchElementException e) { throw e; } catch (AuthenticationException e) { log.debug("",e); throw new NoSuchElementException(); } catch (Exception e) { throw new RuntimeException(e); } } public String getUrl() { return url; } @Required @MCAttribute public void setUrl(String url) { this.url = url; } public String getBase() { return base; } @Required @MCAttribute public void setBase(String base) { this.base = base; } public String getBinddn() { return binddn; } @MCAttribute public void setBinddn(String binddn) { this.binddn = binddn; } public String getBindpw() { return bindpw; } @MCAttribute public void setBindpw(String bindpw) { this.bindpw = bindpw; } public String getSearchPattern() { return searchPattern; } @Required @MCAttribute public void setSearchPattern(String searchPattern) { this.searchPattern = searchPattern; } public static enum SearchScope { OBJECT, ONELEVEL, SUBTREE, } public SearchScope getSearchScope() { return SearchScope.values()[searchScope]; } /** * @default subtree */ @MCAttribute public void setSearchScope(SearchScope searchScope) { this.searchScope = searchScope.ordinal(); } public String getPasswordAttribute() { return passwordAttribute; } @MCAttribute public void setPasswordAttribute(String passwordAttribute) { this.passwordAttribute = passwordAttribute; if (passwordAttribute != null) { attributeMap.put(passwordAttribute, "_pass"); } } public String getTimeout() { return timeout; } /** * @default 1000 */ @MCAttribute public void setTimeout(String timeout) { this.timeout = timeout; } public String getConnectTimeout() { return connectTimeout; } /** * @default 1000 */ @MCAttribute public void setConnectTimeout(String connectTimeout) { this.connectTimeout = connectTimeout; } public boolean isReadAttributesAsSelf() { return readAttributesAsSelf; } /** * @default true */ @MCAttribute public void setReadAttributesAsSelf(boolean readAttributesAsSelf) { this.readAttributesAsSelf = readAttributesAsSelf; } public HashMap getAttributeMap() { return attributeMap; } public void setAttributeMap(HashMap attributeMap) { this.attributeMap = attributeMap; if (passwordAttribute != null) { attributeMap.put(passwordAttribute, "_pass"); } } @Override public void init(Router router) { if (passwordAttribute != null && readAttributesAsSelf) throw new RuntimeException("@passwordAttribute is not compatible with @readAttributesAsSelf."); if (map != null) { for (AttributeMap.Attribute a : map.getAttributes()) attributeMap.put(a.getFrom(), a.getTo()); } if (passwordAttribute != null) { attributeMap.put(passwordAttribute, "_pass"); } } public AttributeMap getMap() { return map; } @MCChildElement public void setMap(AttributeMap map) { this.map = map; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy