| /* $Id$ */ |
| /** |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with this |
| * work for additional information regarding copyright ownership. The ASF |
| * licenses this file to You 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 org.apache.manifoldcf.authorities.authorities.ldap; |
| |
| import java.io.*; |
| import java.util.*; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import javax.naming.*; |
| import javax.naming.directory.*; |
| import javax.naming.ldap.*; |
| import org.apache.manifoldcf.authorities.interfaces.*; |
| import org.apache.manifoldcf.authorities.system.ManifoldCF; |
| import org.apache.manifoldcf.authorities.system.Logging; |
| import org.apache.manifoldcf.core.interfaces.*; |
| import org.apache.manifoldcf.connectorcommon.interfaces.*; |
| import org.apache.manifoldcf.ui.util.Encoder; |
| import org.apache.manifoldcf.core.common.LDAPSSLSocketFactory; |
| |
| /** |
| * This is the Active Directory implementation of the IAuthorityConnector |
| * interface. Access tokens for this connector are simple SIDs, except for the |
| * "global deny" token, which is designed to allow the authority to shut off |
| * access to all authorized documents when the user is unrecognized or the |
| * domain controller does not respond. |
| */ |
| public class LDAPAuthority extends org.apache.manifoldcf.authorities.authorities.BaseAuthorityConnector { |
| |
| public static final String _rcsid = "@(#)$Id$"; |
| |
| /** |
| * Session information for all DC's we talk with. |
| */ |
| private LdapContext session = null; |
| private StartTlsResponse tls = null; |
| |
| private long sessionExpirationTime = -1L; |
| |
| //private ConfigParams parameters; |
| |
| private String bindUser; |
| private String bindPass; |
| private String serverProtocol; |
| private String serverName; |
| private String serverPort; |
| private String serverBase; |
| private String userBase; |
| private String userSearch; |
| private String groupBase; |
| private String groupSearch; |
| private String groupNameAttr; |
| private boolean groupMemberDN; |
| private boolean addUserRecord; |
| private List<String> forcedTokens; |
| private String userNameAttr; |
| private String sslKeystoreData; |
| |
| private IKeystoreManager sslKeystore; |
| |
| private long responseLifetime = 60000L; //60sec |
| |
| private int LRUsize = 1000; |
| |
| /** |
| * Cache manager. |
| */ |
| private ICacheManager cacheManager = null; |
| |
| /** |
| * Constructor. |
| */ |
| public LDAPAuthority() { |
| } |
| |
| /** |
| * Set thread context. |
| */ |
| @Override |
| public void setThreadContext(IThreadContext tc) |
| throws ManifoldCFException { |
| super.setThreadContext(tc); |
| cacheManager = CacheManagerFactory.make(tc); |
| } |
| |
| /** |
| * Connect. The configuration parameters are included. |
| * |
| * @param configParams are the configuration parameters for this connection. |
| */ |
| @Override |
| public void connect(ConfigParams configParams) { |
| super.connect(configParams); |
| //parameters = configParams; |
| |
| // Credentials |
| bindUser = configParams.getParameter("ldapBindUser"); |
| bindPass = configParams.getObfuscatedParameter("ldapBindPass"); |
| |
| // We get the parameters here, so we can check them in case they are missing |
| serverProtocol = configParams.getParameter("ldapProtocol"); |
| serverName = configParams.getParameter("ldapServerName"); |
| serverPort = configParams.getParameter("ldapServerPort"); |
| serverBase = configParams.getParameter("ldapServerBase"); |
| |
| sslKeystoreData = configParams.getParameter("sslKeystore"); |
| |
| userBase = configParams.getParameter("ldapUserBase"); |
| userSearch = configParams.getParameter("ldapUserSearch"); |
| groupBase = configParams.getParameter("ldapGroupBase"); |
| groupSearch = configParams.getParameter("ldapGroupSearch"); |
| groupNameAttr = configParams.getParameter("ldapGroupNameAttr"); |
| userNameAttr = configParams.getParameter("ldapUserNameAttr"); |
| groupMemberDN = "1".equals(getParam(configParams, "ldapGroupMemberDn", "")); |
| addUserRecord = "1".equals(getParam(configParams, "ldapAddUserRecord", "")); |
| |
| forcedTokens = new ArrayList<String>(); |
| int i = 0; |
| while (i < configParams.getChildCount()) { |
| ConfigNode sn = configParams.getChild(i++); |
| if (sn.getType().equals("access")) { |
| String token = "" + sn.getAttributeValue("token"); |
| forcedTokens.add(token); |
| } |
| } |
| } |
| |
| // All methods below this line will ONLY be called if a connect() call succeeded |
| // on this instance! |
| /** |
| * Session setup. Anything that might need to throw an exception should go |
| * here. |
| */ |
| protected LdapContext getSession() |
| throws ManifoldCFException { |
| |
| try { |
| if (session == null) { |
| if (serverName == null || serverName.length() == 0) { |
| throw new ManifoldCFException("Server name parameter missing but required"); |
| } |
| if (serverPort == null || serverPort.length() == 0) { |
| throw new ManifoldCFException("Server port parameter missing but required"); |
| } |
| if (serverBase == null) { |
| throw new ManifoldCFException("Server base parameter missing but required"); |
| } |
| if (userBase == null) { |
| throw new ManifoldCFException("User base parameter missing but required"); |
| } |
| if (userSearch == null || userSearch.length() == 0) { |
| throw new ManifoldCFException("User search expression missing but required"); |
| } |
| if (groupBase == null) { |
| throw new ManifoldCFException("Group base parameter missing but required"); |
| } |
| if (groupSearch == null || groupSearch.length() == 0) { |
| throw new ManifoldCFException("Group search expression missing but required"); |
| } |
| if (groupNameAttr == null || groupNameAttr.length() == 0) { |
| throw new ManifoldCFException("Group name attribute missing but required"); |
| } |
| if (userNameAttr == null || userNameAttr.length() == 0) { |
| throw new ManifoldCFException("User name attribute missing but required"); |
| } |
| |
| if (sslKeystoreData != null) { |
| sslKeystore = KeystoreManagerFactory.make("", sslKeystoreData); |
| } else { |
| sslKeystore = KeystoreManagerFactory.make(""); |
| } |
| |
| // Set thread local for keystore stuff |
| LDAPSSLSocketFactory.setSocketFactoryProducer(sslKeystore); |
| |
| final String protocolToUse; |
| final boolean useTls; |
| if (serverProtocol == null || serverProtocol.length() == 0) { |
| protocolToUse = "ldap"; |
| useTls = false; |
| } else { |
| int plusIndex = serverProtocol.indexOf("+"); |
| if (plusIndex == -1) { |
| plusIndex = serverProtocol.length(); |
| useTls = false; |
| } else { |
| useTls = true; |
| } |
| protocolToUse = serverProtocol.substring(0,plusIndex); |
| } |
| |
| final Hashtable env = new Hashtable(); |
| env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); |
| env.put(Context.PROVIDER_URL, "ldap://" + serverName + ":" + serverPort + "/" + serverBase); |
| if (protocolToUse.equals("ldaps")) |
| env.put(Context.SECURITY_PROTOCOL, "ssl"); |
| |
| env.put("java.naming.ldap.factory.socket", "org.apache.manifoldcf.core.common.LDAPSSLSocketFactory"); |
| |
| if (bindUser != null && !bindUser.isEmpty()) { |
| env.put(Context.SECURITY_AUTHENTICATION, "simple"); |
| env.put(Context.SECURITY_PRINCIPAL, bindUser); |
| env.put(Context.SECURITY_CREDENTIALS, bindPass); |
| } |
| |
| session = new InitialLdapContext(env, null); |
| |
| if (useTls) { |
| // Start TLS |
| StartTlsResponse tls = (StartTlsResponse) session.extendedOperation(new StartTlsRequest()); |
| tls.negotiate(sslKeystore.getSecureSocketFactory()); |
| } |
| |
| } else { |
| // Set thread local for keystore stuff |
| LDAPSSLSocketFactory.setSocketFactoryProducer(sslKeystore); |
| session.reconnect(null); |
| } |
| sessionExpirationTime = System.currentTimeMillis() + 300000L; |
| return session; |
| } catch (AuthenticationException e) { |
| session = null; |
| sessionExpirationTime = -1L; |
| throw new ManifoldCFException("Authentication error: " + e.getMessage() + ", explanation: " + e.getExplanation(), e); |
| } catch (CommunicationException e) { |
| session = null; |
| sessionExpirationTime = -1L; |
| throw new ManifoldCFException("Communication error: " + e.getMessage(), e); |
| } catch (NamingException e) { |
| session = null; |
| sessionExpirationTime = -1L; |
| throw new ManifoldCFException("Naming error: " + e.getMessage(), e); |
| } catch (InterruptedIOException e) { |
| session = null; |
| sessionExpirationTime = -1L; |
| throw new ManifoldCFException(e.getMessage(), ManifoldCFException.INTERRUPTED); |
| } catch (IOException e) { |
| session = null; |
| sessionExpirationTime = -1L; |
| throw new ManifoldCFException("IO error: " + e.getMessage(), e); |
| } |
| } |
| |
| /** |
| * Check connection for sanity. |
| */ |
| @Override |
| public String check() |
| throws ManifoldCFException { |
| disconnectSession(); |
| getSession(); |
| // MHL for a real check of all the search etc. |
| return super.check(); |
| } |
| |
| /** This method is called to assess whether to count this connector instance should |
| * actually be counted as being connected. |
| *@return true if the connector instance is actually connected. |
| */ |
| @Override |
| public boolean isConnected() |
| { |
| return session != null; |
| } |
| |
| /** |
| * Poll. The connection should be closed if it has been idle for too long. |
| */ |
| @Override |
| public void poll() |
| throws ManifoldCFException { |
| if (session != null && System.currentTimeMillis() > sessionExpirationTime) { |
| disconnectSession(); |
| } |
| super.poll(); |
| } |
| |
| /** |
| * Disconnect a session. |
| */ |
| protected void disconnectSession() { |
| if (session != null) { |
| try { |
| if (tls != null) |
| tls.close(); |
| session.close(); |
| } catch (NamingException e) { |
| // Eat this error |
| } catch (IOException e) { |
| // Eat this error |
| } |
| tls = null; |
| session = null; |
| sessionExpirationTime = -1L; |
| } |
| } |
| |
| /** |
| * Close the connection. Call this before discarding the repository connector. |
| */ |
| @Override |
| public void disconnect() |
| throws ManifoldCFException { |
| disconnectSession(); |
| super.disconnect(); |
| // Zero out all the stuff that we want to be sure we don't use again |
| serverName = null; |
| serverPort = null; |
| serverBase = null; |
| userBase = null; |
| userSearch = null; |
| groupBase = null; |
| groupSearch = null; |
| groupNameAttr = null; |
| userNameAttr = null; |
| forcedTokens = null; |
| sslKeystoreData = null; |
| sslKeystore = null; |
| } |
| |
| protected String createCacheConnectionString() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append(serverName).append(":").append(serverPort).append("/").append(serverBase); |
| return sb.toString(); |
| } |
| |
| protected String createUserSearchString() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append(userBase).append("|").append(userSearch).append("|").append(userNameAttr).append("|").append(addUserRecord ? 'Y' : 'N'); |
| return sb.toString(); |
| } |
| |
| protected String createGroupSearchString() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append(groupBase).append("|").append(groupSearch).append("|").append(groupNameAttr).append("|").append(groupMemberDN ? 'Y' : 'N'); |
| return sb.toString(); |
| } |
| |
| /** |
| * Obtain the access tokens for a given user name. |
| * |
| * @param userName is the user name or identifier. |
| * @return the response tokens (according to the current authority). (Should |
| * throws an exception only when a condition cannot be properly described |
| * within the authorization response object.) |
| */ |
| @Override |
| public AuthorizationResponse getAuthorizationResponse(String userName) |
| throws ManifoldCFException { |
| |
| getSession(); |
| // Construct a cache description object |
| ICacheDescription objectDescription = new LdapAuthorizationResponseDescription(userName, |
| createCacheConnectionString(), createUserSearchString(), createGroupSearchString(), this.responseLifetime, this.LRUsize); |
| |
| // Enter the cache |
| ICacheHandle ch = cacheManager.enterCache(new ICacheDescription[]{objectDescription}, null, null); |
| try { |
| ICacheCreateHandle createHandle = cacheManager.enterCreateSection(ch); |
| try { |
| // Lookup the object |
| AuthorizationResponse response = (AuthorizationResponse) cacheManager.lookupObject(createHandle, objectDescription); |
| if (response != null) { |
| return response; |
| } |
| // Create the object. |
| response = getAuthorizationResponseUncached(userName); |
| // Save it in the cache |
| cacheManager.saveObject(createHandle, objectDescription, response); |
| // And return it... |
| return response; |
| } finally { |
| cacheManager.leaveCreateSection(createHandle); |
| } |
| } finally { |
| cacheManager.leaveCache(ch); |
| } |
| } |
| |
| protected AuthorizationResponse getAuthorizationResponseUncached(String userName) |
| throws ManifoldCFException { |
| getSession(); |
| try { |
| //find user in LDAP tree |
| SearchResult usrRecord = getUserEntry(session, userName); |
| if (usrRecord == null) { |
| return RESPONSE_USERNOTFOUND; |
| } |
| |
| ArrayList theGroups = new ArrayList(); |
| theGroups.addAll(forcedTokens); |
| |
| String usrName = userName.split("@")[0]; |
| if (userNameAttr != null && !"".equals(userNameAttr)) { |
| if (usrRecord.getAttributes() != null) { |
| Attribute attr = usrRecord.getAttributes().get(userNameAttr); |
| if (attr != null) { |
| usrName = attr.get().toString(); |
| if (addUserRecord) { |
| NamingEnumeration values = attr.getAll(); |
| while (values.hasMore()) { |
| theGroups.add(values.next().toString()); |
| } |
| } |
| } |
| } |
| } |
| |
| if (groupSearch != null && !groupSearch.isEmpty()) { |
| //specify the LDAP search filter |
| String searchFilter = groupSearch.replaceAll("\\{0\\}", escapeLDAPSearchFilter(groupMemberDN ? usrRecord.getNameInNamespace() : usrName)); |
| SearchControls searchCtls = new SearchControls(); |
| searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE); |
| String returnedAtts[] = {groupNameAttr}; |
| searchCtls.setReturningAttributes(returnedAtts); |
| |
| NamingEnumeration answer = session.search(groupBase, searchFilter, searchCtls); |
| |
| while (answer.hasMoreElements()) { |
| SearchResult sr = (SearchResult) answer.next(); |
| Attributes attrs = sr.getAttributes(); |
| if (attrs != null) { |
| NamingEnumeration values = attrs.get(groupNameAttr).getAll(); |
| while (values.hasMore()) { |
| theGroups.add(values.next().toString()); |
| } |
| } |
| } |
| } |
| |
| String[] tokens = new String[theGroups.size()]; |
| int k = 0; |
| while (k < tokens.length) { |
| tokens[k] = (String) theGroups.get(k); |
| k++; |
| } |
| |
| return new AuthorizationResponse(tokens, AuthorizationResponse.RESPONSE_OK); |
| |
| } catch (NameNotFoundException e) { |
| // This means that the user doesn't exist |
| return RESPONSE_USERNOTFOUND; |
| } catch (NamingException e) { |
| // Unreachable |
| return RESPONSE_UNREACHABLE; |
| } |
| } |
| |
| /** |
| * Obtain the default access tokens for a given user name. |
| * |
| * @param userName is the user name or identifier. |
| * @return the default response tokens, presuming that the connect method |
| * fails. |
| */ |
| @Override |
| public AuthorizationResponse getDefaultAuthorizationResponse(String userName) { |
| // The default response if the getConnection method fails |
| return RESPONSE_UNREACHABLE; |
| } |
| |
| // UI support methods. |
| // |
| // These support methods are involved in setting up authority connection configuration information. The configuration methods cannot assume that the |
| // current authority object is connected. That is why they receive a thread context argument. |
| /** |
| * Output the configuration header section. This method is called in the head |
| * section of the connector's configuration page. Its purpose is to add the |
| * required tabs to the list, and to output any javascript methods that might |
| * be needed by the configuration editing HTML. |
| * |
| * @param threadContext is the local thread context. |
| * @param out is the output to which any HTML should be sent. |
| * @param parameters are the configuration parameters, as they currently |
| * exist, for this connection being configured. |
| * @param tabsArray is an array of tab names. Add to this array any tab names |
| * that are specific to the connector. |
| */ |
| @Override |
| public void outputConfigurationHeader(IThreadContext threadContext, IHTTPOutput out, Locale locale, ConfigParams parameters, List<String> tabsArray) |
| throws ManifoldCFException, IOException { |
| tabsArray.add(Messages.getString(locale, "LDAP.LDAP")); |
| tabsArray.add(Messages.getString(locale, "LDAP.ForcedTokens")); |
| final Map<String,Object> paramMap = new HashMap<String,Object>(); |
| fillInLDAPTab(paramMap, out, parameters); |
| fillInForcedTokensTab(paramMap, out, parameters); |
| Messages.outputResourceWithVelocity(out, locale, "editConfiguration.js", paramMap); |
| } |
| |
| /** |
| * Output the configuration body section. This method is called in the body |
| * section of the authority connector's configuration page. Its purpose is to |
| * present the required form elements for editing. The coder can presume that |
| * the HTML that is output from this configuration will be within appropriate |
| * <html>, <body>, and <form> tags. The name of the form is "editconnection". |
| * |
| * @param threadContext is the local thread context. |
| * @param out is the output to which any HTML should be sent. |
| * @param parameters are the configuration parameters, as they currently |
| * exist, for this connection being configured. |
| * @param tabName is the current tab name. |
| */ |
| @Override |
| public void outputConfigurationBody(IThreadContext threadContext, IHTTPOutput out, Locale locale, ConfigParams parameters, String tabName) |
| throws ManifoldCFException, IOException { |
| final Map<String,Object> paramMap = new HashMap<String,Object>(); |
| paramMap.put("TabName",tabName); |
| fillInLDAPTab(paramMap, out, parameters); |
| fillInForcedTokensTab(paramMap, out, parameters); |
| Messages.outputResourceWithVelocity(out, locale, "editConfiguration_LDAP.html", paramMap); |
| Messages.outputResourceWithVelocity(out, locale, "editConfiguration_ForcedTokens.html", paramMap); |
| } |
| |
| private boolean copyParam(IPostParameters variableContext, ConfigParams parameters, String name) { |
| String val = variableContext.getParameter(name); |
| if (val == null) { |
| return false; |
| } |
| parameters.setParameter(name, val); |
| return true; |
| } |
| |
| private void copyParam(IPostParameters variableContext, ConfigParams parameters, String name, String def) { |
| String val = variableContext.getParameter(name); |
| if (val == null) { |
| val = def; |
| } |
| parameters.setParameter(name, val); |
| } |
| |
| /** |
| * Process a configuration post. This method is called at the start of the |
| * authority connector's configuration page, whenever there is a possibility |
| * that form data for a connection has been posted. Its purpose is to gather |
| * form information and modify the configuration parameters accordingly. The |
| * name of the posted form is "editconnection". |
| * |
| * @param threadContext is the local thread context. |
| * @param variableContext is the set of variables available from the post, |
| * including binary file post information. |
| * @param parameters are the configuration parameters, as they currently |
| * exist, for this connection being configured. |
| * @return null if all is well, or a string error message if there is an error |
| * that should prevent saving of the connection (and cause a redirection to an |
| * error page). |
| */ |
| @Override |
| public String processConfigurationPost(IThreadContext threadContext, IPostParameters variableContext, Locale locale, ConfigParams parameters) |
| throws ManifoldCFException { |
| copyParam(variableContext, parameters, "ldapProtocol"); |
| copyParam(variableContext, parameters, "ldapServerName"); |
| copyParam(variableContext, parameters, "ldapServerPort"); |
| copyParam(variableContext, parameters, "ldapServerBase"); |
| copyParam(variableContext, parameters, "ldapUserBase"); |
| copyParam(variableContext, parameters, "ldapUserSearch"); |
| copyParam(variableContext, parameters, "ldapUserNameAttr"); |
| copyParam(variableContext, parameters, "ldapGroupBase"); |
| copyParam(variableContext, parameters, "ldapGroupSearch"); |
| copyParam(variableContext, parameters, "ldapGroupNameAttr"); |
| |
| copyParam(variableContext, parameters, "ldapGroupMemberDn", "0"); //checkbox boolean value |
| copyParam(variableContext, parameters, "ldapAddUserRecord", "0"); //checkbox boolean value |
| |
| copyParam(variableContext, parameters, "ldapBindUser"); |
| final String bindPass = variableContext.getParameter("ldapBindPass"); |
| if (bindPass != null) { |
| parameters.setObfuscatedParameter("ldapBindPass", variableContext.mapKeyToPassword(bindPass)); |
| } |
| |
| final String xc = variableContext.getParameter("tokencount"); |
| if (xc != null) { |
| // Delete all tokens first |
| int i = 0; |
| while (i < parameters.getChildCount()) { |
| ConfigNode sn = parameters.getChild(i); |
| if (sn.getType().equals("access")) { |
| parameters.removeChild(i); |
| } else { |
| i++; |
| } |
| } |
| |
| final int accessCount = Integer.parseInt(xc); |
| i = 0; |
| while (i < accessCount) { |
| final String accessDescription = "_" + Integer.toString(i); |
| final String accessOpName = "accessop" + accessDescription; |
| final String command = variableContext.getParameter(accessOpName); |
| if (command != null && command.equals("Delete")) { |
| // Next row |
| i++; |
| continue; |
| } |
| // Get the stuff we need |
| String accessSpec = variableContext.getParameter("spectoken" + accessDescription); |
| ConfigNode node = new ConfigNode("access"); |
| node.setAttribute("token", accessSpec); |
| parameters.addChild(parameters.getChildCount(), node); |
| i++; |
| } |
| |
| String op = variableContext.getParameter("accessop"); |
| if (op != null && op.equals("Add")) { |
| String accessspec = variableContext.getParameter("spectoken"); |
| ConfigNode node = new ConfigNode("access"); |
| node.setAttribute("token", accessspec); |
| parameters.addChild(parameters.getChildCount(), node); |
| } |
| } |
| |
| String sslKeystoreValue = variableContext.getParameter("sslkeystoredata"); |
| final String sslConfigOp = variableContext.getParameter("sslconfigop"); |
| if (sslConfigOp != null) |
| { |
| if (sslConfigOp.equals("Delete")) |
| { |
| final String alias = variableContext.getParameter("sslkeystorealias"); |
| final IKeystoreManager mgr; |
| if (sslKeystoreValue != null) |
| mgr = KeystoreManagerFactory.make("",sslKeystoreValue); |
| else |
| mgr = KeystoreManagerFactory.make(""); |
| mgr.remove(alias); |
| sslKeystoreValue = mgr.getString(); |
| } |
| else if (sslConfigOp.equals("Add")) |
| { |
| String alias = IDFactory.make(threadContext); |
| byte[] certificateValue = variableContext.getBinaryBytes("sslcertificate"); |
| final IKeystoreManager mgr; |
| if (sslKeystoreValue != null) |
| mgr = KeystoreManagerFactory.make("",sslKeystoreValue); |
| else |
| mgr = KeystoreManagerFactory.make(""); |
| java.io.InputStream is = new java.io.ByteArrayInputStream(certificateValue); |
| String certError = null; |
| try |
| { |
| mgr.importCertificate(alias,is); |
| } |
| catch (Throwable e) |
| { |
| certError = e.getMessage(); |
| } |
| finally |
| { |
| try |
| { |
| is.close(); |
| } |
| catch (IOException e) |
| { |
| // Eat this exception |
| } |
| } |
| |
| if (certError != null) |
| { |
| return "Illegal certificate: "+certError; |
| } |
| sslKeystoreValue = mgr.getString(); |
| } |
| } |
| if (sslKeystoreValue != null) |
| parameters.setParameter("sslkeystore",sslKeystoreValue); |
| |
| return null; |
| } |
| |
| /** |
| * View configuration. This method is called in the body section of the |
| * authority connector's view configuration page. Its purpose is to present |
| * the connection information to the user. The coder can presume that the HTML |
| * that is output from this configuration will be within appropriate <html> |
| * and <body> tags. |
| * |
| * @param threadContext is the local thread context. |
| * @param out is the output to which any HTML should be sent. |
| * @param parameters are the configuration parameters, as they currently |
| * exist, for this connection being configured. |
| */ |
| @Override |
| public void viewConfiguration(IThreadContext threadContext, IHTTPOutput out, Locale locale, ConfigParams parameters) |
| throws ManifoldCFException, IOException { |
| final Map<String,Object> paramMap = new HashMap<String,Object>(); |
| fillInLDAPTab(paramMap, out, parameters); |
| fillInForcedTokensTab(paramMap, out, parameters); |
| Messages.outputResourceWithVelocity(out, locale, "viewConfiguration.html", paramMap); |
| } |
| |
| // Protected methods |
| |
| private static String getParam(final ConfigParams parameters, final String name, final String def) { |
| String rval = parameters.getParameter(name); |
| return rval != null ? rval : def; |
| } |
| |
| /** Fill in ForcedTokens tab */ |
| protected static void fillInForcedTokensTab(Map<String,Object> velocityContext, IHTTPOutput out, ConfigParams parameters) |
| { |
| final List<String> forcedTokenList = new ArrayList<String>(); |
| for (int i = 0; i < parameters.getChildCount(); i++) { |
| final ConfigNode sn = parameters.getChild(i); |
| if (sn.getType().equals("access")) { |
| forcedTokenList.add(sn.getAttributeValue("token")); |
| } |
| } |
| velocityContext.put("FORCEDTOKENS", forcedTokenList); |
| } |
| |
| /** Fill in LDAP tab */ |
| protected static void fillInLDAPTab(Map<String,Object> velocityContext, IHTTPOutput out, ConfigParams parameters) |
| { |
| velocityContext.put("FSERVERPROTOCOL", getParam(parameters, "ldapProtocol", "ldap")); |
| velocityContext.put("FSERVERNAME", getParam(parameters, "ldapServerName", "")); |
| velocityContext.put("FSERVERPORT", getParam(parameters, "ldapServerPort", "389")); |
| velocityContext.put("FSERVERBASE", getParam(parameters, "ldapServerBase", "")); |
| String sslKeystoreData = parameters.getParameter("sslkeystore"); |
| if (sslKeystoreData != null) |
| velocityContext.put("SSLKEYSTOREDATA", sslKeystoreData); |
| velocityContext.put("FUSERBASE", getParam(parameters, "ldapUserBase", "ou=People")); |
| velocityContext.put("FUSERSEARCH", getParam(parameters, "ldapUserSearch", "(&(objectClass=inetOrgPerson)(uid={0}))")); |
| velocityContext.put("FUSERNAMEATTR", getParam(parameters, "ldapUserNameAttr", "uid")); |
| velocityContext.put("FADDUSERRECORD", getParam(parameters, "ldapAddUserRecord", "")); |
| velocityContext.put("FGROUPBASE", getParam(parameters, "ldapGroupBase", "ou=Groups")); |
| velocityContext.put("FGROUPSEARCH", getParam(parameters, "ldapGroupSearch", "(&(objectClass=groupOfNames)(member={0}))")); |
| velocityContext.put("FGROUPNAMEATTR", getParam(parameters, "ldapGroupNameAttr", "cn")); |
| velocityContext.put("FGROUPMEMBERDN", getParam(parameters, "ldapGroupMemberDn", "")); |
| velocityContext.put("FBINDUSER", getParam(parameters, "ldapBindUser", "")); |
| String fBindPass = parameters.getObfuscatedParameter("ldapBindPass"); |
| if (fBindPass == null) |
| fBindPass = ""; |
| else |
| fBindPass = out.mapPasswordToKey(fBindPass); |
| velocityContext.put("FBINDPASS", fBindPass); |
| |
| Map<String,String> sslCertificatesMap = null; |
| String message = null; |
| |
| try { |
| final IKeystoreManager localSslKeystore; |
| if (sslKeystoreData == null) |
| localSslKeystore = KeystoreManagerFactory.make(""); |
| else |
| localSslKeystore = KeystoreManagerFactory.make("",sslKeystoreData); |
| |
| // List the individual certificates in the store, with a delete button for each |
| final String[] contents = localSslKeystore.getContents(); |
| if (contents.length > 0) |
| { |
| sslCertificatesMap = new HashMap<>(); |
| for (final String alias : contents) |
| { |
| String description = localSslKeystore.getDescription(alias); |
| if (description.length() > 128) |
| description = description.substring(0,125) + "..."; |
| sslCertificatesMap.put(alias, description); |
| } |
| } |
| } catch (ManifoldCFException e) { |
| message = e.getMessage(); |
| org.apache.manifoldcf.authorities.system.Logging.authorityConnectors.warn(e); |
| } |
| |
| if(sslCertificatesMap != null) |
| velocityContext.put("SSLCERTIFICATESMAP", sslCertificatesMap); |
| if(message != null) |
| velocityContext.put("MESSAGE", message); |
| } |
| |
| /** |
| * Obtain the user LDAP record for a given user logon name. |
| * |
| * @param ctx is the ldap context to use. |
| * @param userName (Domain Logon Name) is the user name or identifier. |
| * @param searchBase (Full Domain Name for the search ie: |
| * DC=qa-ad-76,DC=metacarta,DC=com) |
| * @return SearchResult for given domain user logon name. (Should throws an |
| * exception if user is not found.) |
| */ |
| protected SearchResult getUserEntry(LdapContext ctx, String userName) |
| throws ManifoldCFException { |
| String searchFilter = userSearch.replaceAll("\\{0\\}", escapeDN(userName.split("@")[0])); |
| SearchControls searchCtls = new SearchControls(); |
| searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE); |
| |
| try { |
| NamingEnumeration answer = ctx.search(userBase, searchFilter, searchCtls); |
| if (answer.hasMoreElements()) { |
| return (SearchResult) answer.next(); |
| } |
| return null; |
| } catch (Exception e) { |
| throw new ManifoldCFException(e.getMessage(), e); |
| } |
| } |
| |
| /** |
| * LDAP escape a string. |
| */ |
| protected static String ldapEscape(String input) { |
| //Add escape sequence to all commas |
| StringBuilder sb = new StringBuilder(); |
| int index = 0; |
| while (true) { |
| int oldIndex = index; |
| index = input.indexOf(",", oldIndex); |
| if (index == -1) { |
| sb.append(input.substring(oldIndex)); |
| break; |
| } |
| sb.append(input.substring(oldIndex, index)).append("\\,"); |
| index++; |
| } |
| return sb.toString(); |
| } |
| |
| public static String escapeDN(String name) { |
| StringBuilder sb = new StringBuilder(); // If using JDK >= 1.5 consider using StringBuilder |
| if ((name.length() > 0) && ((name.charAt(0) == ' ') || (name.charAt(0) == '#'))) { |
| sb.append('\\'); // add the leading backslash if needed |
| } |
| for (int i = 0; i < name.length(); i++) { |
| char curChar = name.charAt(i); |
| switch (curChar) { |
| case '\\': |
| sb.append("\\\\"); |
| break; |
| case ',': |
| sb.append("\\,"); |
| break; |
| case '+': |
| sb.append("\\+"); |
| break; |
| case '"': |
| sb.append("\\\""); |
| break; |
| case '<': |
| sb.append("\\<"); |
| break; |
| case '>': |
| sb.append("\\>"); |
| break; |
| case ';': |
| sb.append("\\;"); |
| break; |
| default: |
| sb.append(curChar); |
| } |
| } |
| if ((name.length() > 1) && (name.charAt(name.length() - 1) == ' ')) { |
| sb.insert(sb.length() - 1, '\\'); // add the trailing backslash if needed |
| } |
| return sb.toString(); |
| } |
| |
| public static String escapeLDAPSearchFilter(String filter) { |
| StringBuilder sb = new StringBuilder(); // If using JDK >= 1.5 consider using 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(); |
| } |
| |
| protected static StringSet emptyStringSet = new StringSet(); |
| |
| /** |
| * This is the cache object descriptor for cached access tokens from this |
| * connector. |
| */ |
| protected class LdapAuthorizationResponseDescription extends org.apache.manifoldcf.core.cachemanager.BaseDescription { |
| |
| /** |
| * The user name |
| */ |
| protected String userName; |
| |
| /** |
| * LDAP connection string with server name and base DN |
| */ |
| protected String connectionString; |
| |
| /** |
| * User search definition |
| */ |
| protected String userSearch; |
| |
| /** |
| * Group search definition |
| */ |
| protected String groupSearch; |
| |
| /** |
| * The response lifetime |
| */ |
| protected long responseLifetime; |
| |
| /** |
| * The expiration time |
| */ |
| protected long expirationTime = -1; |
| |
| /** |
| * Constructor. |
| */ |
| public LdapAuthorizationResponseDescription(String userName, String connectionString, String userSearch, String groupSearch, long responseLifetime, int LRUsize) { |
| super("LDAPAuthority", LRUsize); |
| this.userName = userName; |
| this.connectionString = connectionString; |
| this.userSearch = userSearch; |
| this.groupSearch = groupSearch; |
| this.responseLifetime = responseLifetime; |
| } |
| |
| /** |
| * Return the invalidation keys for this object. |
| */ |
| public StringSet getObjectKeys() { |
| return emptyStringSet; |
| } |
| |
| /** |
| * Get the critical section name, used for synchronizing the creation of the |
| * object |
| */ |
| public String getCriticalSectionName() { |
| StringBuilder sb = new StringBuilder(getClass().getName()); |
| sb.append("-").append(userName).append("-").append(connectionString); |
| return sb.toString(); |
| } |
| |
| /** |
| * Return the object expiration interval |
| */ |
| @Override |
| public long getObjectExpirationTime(long currentTime) { |
| if (expirationTime == -1) { |
| expirationTime = currentTime + responseLifetime; |
| } |
| return expirationTime; |
| } |
| |
| @Override |
| public int hashCode() { |
| return userName.hashCode() + connectionString.hashCode() + userSearch.hashCode() + groupSearch.hashCode(); |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (!(o instanceof LdapAuthorizationResponseDescription)) { |
| return false; |
| } |
| LdapAuthorizationResponseDescription ard = (LdapAuthorizationResponseDescription) o; |
| if (!ard.userName.equals(userName)) { |
| return false; |
| } |
| if (!ard.connectionString.equals(connectionString)) { |
| return false; |
| } |
| if (!ard.userSearch.equals(userSearch)) { |
| return false; |
| } |
| if (!ard.groupSearch.equals(groupSearch)) { |
| return false; |
| } |
| return true; |
| } |
| } |
| } |