blob: 19bd6d96621fecfeffcca37b0730e4282883ff1f [file] [log] [blame]
/* $Id: LivelinkAuthority.java 988245 2010-08-23 18:39:35Z kwright $ */
/**
* 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.crawler.connectors.livelink;
import org.apache.manifoldcf.core.interfaces.*;
import org.apache.manifoldcf.agents.interfaces.*;
import org.apache.manifoldcf.authorities.interfaces.*;
import org.apache.manifoldcf.authorities.system.Logging;
import org.apache.manifoldcf.authorities.system.ManifoldCF;
import java.io.*;
import java.util.*;
import java.net.*;
import java.util.regex.*;
import com.opentext.api.*;
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.*;
import org.apache.commons.httpclient.params.*;
/** This is the Livelink implementation of the IAuthorityConnector interface.
* This is not based on Volant code, but has been developed by me at the behest of
* James Maupin for use at Shell.
*
* Access tokens for livelink are simply user and usergroup node identifiers. Therefore,
* this class retrieves those using the standard livelink call, being sure to map anything
* that looks like an active directory user name to something that looks like a Livelink
* domain/username form.
*
*/
public class LivelinkAuthority extends org.apache.manifoldcf.authorities.authorities.BaseAuthorityConnector
{
public static final String _rcsid = "@(#)$Id: LivelinkAuthority.java 988245 2010-08-23 18:39:35Z kwright $";
// Data from the parameters
private String serverName = null;
private String serverPortString = null;
private int serverPort = -1;
private String serverUsername = null;
private String serverPassword = null;
// Data required for maintaining livelink connection
private LAPI_USERS LLUsers = null;
private LLSERVER llServer = null;
// Cache variables
private String cacheLifetime = null;
private String cacheLRUsize = null;
private long responseLifetime = 60000L;
private int LRUsize = 1000;
// Match map for username
private MatchMap matchMap = null;
// Retry count. This is so we can try to install some measure of sanity into situations where LAPI gets confused communicating to the server.
// So, for some kinds of errors, we just retry for a while hoping it will go away.
private static final int FAILURE_RETRY_COUNT = 5;
/** Cache manager. */
protected ICacheManager cacheManager = null;
// Livelink does not have "deny" permissions, and there is no such thing as a document with no tokens, so it is safe to not have a local "deny" token.
// However, people feel that a suspenders-and-belt approach is called for, so this restriction has been added.
// Livelink tokens are numbers, "SYSTEM", or "GUEST", so they can't collide with the standard form.
private static final String denyToken = "DEAD_AUTHORITY";
private static final AuthorizationResponse unreachableResponse = new AuthorizationResponse(new String[]{denyToken},
AuthorizationResponse.RESPONSE_UNREACHABLE);
private static final AuthorizationResponse userNotFoundResponse = new AuthorizationResponse(new String[]{denyToken},
AuthorizationResponse.RESPONSE_USERNOTFOUND);
/** Constructor.
*/
public LivelinkAuthority()
{
}
/** Set thread context.
*/
@Override
public void setThreadContext(IThreadContext tc)
throws ManifoldCFException
{
super.setThreadContext(tc);
cacheManager = CacheManagerFactory.make(tc);
}
/** Clear thread context.
*/
@Override
public void clearThreadContext()
{
super.clearThreadContext();
cacheManager = null;
}
/** Connect. The configuration parameters are included.
*@param configParams are the configuration parameters for this connection.
*/
@Override
public void connect(ConfigParams configParams)
{
super.connect(configParams);
// First, create server object (llServer)
serverName = configParams.getParameter(LiveLinkParameters.serverName);
serverPortString = configParams.getParameter(LiveLinkParameters.serverPort);
serverUsername = configParams.getParameter(LiveLinkParameters.serverUsername);
serverPassword = configParams.getObfuscatedParameter(LiveLinkParameters.serverPassword);
// These have been deprecated
String userNamePattern = configParams.getParameter(LiveLinkParameters.userNameRegexp);
String userEvalExpression = configParams.getParameter(LiveLinkParameters.livelinkNameSpec);
String userNameMapping = configParams.getParameter(LiveLinkParameters.userNameMapping);
if ((userNameMapping == null || userNameMapping.length() == 0) && userNamePattern != null && userEvalExpression != null)
{
// Create a matchmap using the old system
matchMap = new MatchMap();
matchMap.appendOldstyleMatchPair(userNamePattern,userEvalExpression);
}
else
{
if (userNameMapping == null)
userNameMapping = "(.*)\\\\@([A-Z|a-z|0-9|_|-]*)\\\\.(.*)=$(2)\\$(1l)";
matchMap = new MatchMap(userNameMapping);
}
if (serverPortString == null)
serverPort = 80;
else
serverPort = new Integer(serverPortString).intValue();
cacheLifetime = configParams.getParameter(LiveLinkParameters.cacheLifetime);
if (cacheLifetime == null)
cacheLifetime = "1";
cacheLRUsize = configParams.getParameter(LiveLinkParameters.cacheLRUSize);
if (cacheLRUsize == null)
cacheLRUsize = "1000";
}
protected void attemptToConnect()
throws ManifoldCFException, ServiceInterruption
{
try
{
responseLifetime = Long.parseLong(this.cacheLifetime) * 60L * 1000L;
LRUsize = Integer.parseInt(this.cacheLRUsize);
}
catch (NumberFormatException e)
{
throw new ManifoldCFException("Cache lifetime or Cache LRU size must be an integer: "+e.getMessage(),e);
}
if (LLUsers == null)
{
if (Logging.authorityConnectors.isDebugEnabled())
{
String passwordExists = (serverPassword!=null && serverPassword.length() > 0)?"password exists":"";
Logging.authorityConnectors.debug("Livelink: Livelink connection parameters: Server='"+serverName+"'; port='"+serverPortString+"'; user name='"+serverUsername+"'; "+passwordExists);
}
int sanityRetryCount = FAILURE_RETRY_COUNT;
while (true)
{
try
{
llServer = new LLSERVER(serverName,serverPort,serverUsername,serverPassword);
LLUsers = new LAPI_USERS(llServer.getLLSession());
if (Logging.authorityConnectors.isDebugEnabled())
{
Logging.authorityConnectors.debug("Livelink: Livelink session created.");
}
return;
}
catch (RuntimeException e)
{
sanityRetryCount = handleLivelinkRuntimeException(e,sanityRetryCount);
}
}
}
}
// All methods below this line will ONLY be called if a connect() call succeeded
// on this instance!
/** Check connection for sanity.
*/
@Override
public String check()
throws ManifoldCFException
{
try
{
// Reestablish the session
LLUsers = null;
attemptToConnect();
// Get user info for the crawl user, to make sure it works
int sanityRetryCount = FAILURE_RETRY_COUNT;
while (true)
{
try
{
LLValue userObject = new LLValue();
int status = LLUsers.GetUserInfo(serverUsername, userObject);
if (status != 0)
return "Connection failed: User authentication failed";
return super.check();
}
catch (RuntimeException e)
{
sanityRetryCount = handleLivelinkRuntimeException(e,sanityRetryCount);
}
}
}
catch (ServiceInterruption e)
{
return "Temporary service interruption: "+e.getMessage();
}
catch (ManifoldCFException e)
{
return "Connection failed: "+e.getMessage();
}
}
/** Close the connection. Call this before discarding the repository connector.
*/
@Override
public void disconnect()
throws ManifoldCFException
{
if (llServer != null)
{
llServer.disconnect();
llServer = null;
}
LLUsers = null;
matchMap = null;
serverName = null;
serverPortString = null;
serverPort = -1;
serverUsername = null;
serverPassword = null;
cacheLifetime = null;
cacheLRUsize = null;
super.disconnect();
}
/** 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
{
// Construct a cache description object
ICacheDescription objectDescription = new AuthorizationResponseDescription(userName,serverName,serverPort,
serverUsername,serverPassword,responseLifetime,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);
}
}
/** Uncached method to get access tokens for a user name. */
protected AuthorizationResponse getAuthorizationResponseUncached(String userName)
throws ManifoldCFException
{
// First, do what's necessary to map the user name that comes in to a reasonable
// Livelink domain\\user combination.
if (Logging.authorityConnectors.isDebugEnabled())
{
Logging.authorityConnectors.debug("Authentication user name = '"+userName+"'");
}
// Use the matchMap object to do the translation
String domainAndUser = matchMap.translate(userName);
if (Logging.authorityConnectors.isDebugEnabled())
{
Logging.authorityConnectors.debug("Livelink: Livelink user name = '"+domainAndUser+"'");
}
try
{
attemptToConnect();
int sanityRetryCount = FAILURE_RETRY_COUNT;
while (true)
{
try
{
ArrayList list = new ArrayList();
// Find out if the specified user is a member of the Guest group, or is a member
// of the System group.
// Get information about the current user. This is how we will determine if the
// user exists, and also what permissions s/he has.
LLValue userObject = new LLValue();
int status = LLUsers.GetUserInfo(domainAndUser, userObject);
if (status == 103101 || status == 401203)
{
if (Logging.authorityConnectors.isDebugEnabled())
Logging.authorityConnectors.debug("Livelink: Livelink user '"+domainAndUser+"' does not exist");
return userNotFoundResponse;
}
if (status != 0)
{
Logging.authorityConnectors.warn("Livelink: User '"+domainAndUser+"' GetUserInfo error # "+Integer.toString(status)+" "+llServer.getErrors());
// The server is probably down.
return unreachableResponse;
}
int deleted = userObject.toInteger("Deleted");
if (deleted == 1)
{
if (Logging.authorityConnectors.isDebugEnabled())
Logging.authorityConnectors.debug("Livelink: Livelink user '"+domainAndUser+"' has been deleted");
// Since the user cannot become undeleted, then this should be treated as 'user does not exist'.
return userNotFoundResponse;
}
int privs = userObject.toInteger("UserPrivileges");
if ((privs & LAPI_USERS.PRIV_PERM_WORLD) == LAPI_USERS.PRIV_PERM_WORLD)
list.add("GUEST");
if ((privs & LAPI_USERS.PRIV_PERM_BYPASS) == LAPI_USERS.PRIV_PERM_BYPASS)
list.add("SYSTEM");
LLValue childrenObjects = new LLValue();
status = LLUsers.ListRights(LAPI_USERS.USER, domainAndUser, childrenObjects);
if (status == 103101 || status == 401203)
{
if (Logging.authorityConnectors.isDebugEnabled())
Logging.authorityConnectors.debug("Livelink: Livelink error looking up user rights for '"+domainAndUser+"' - user does not exist");
return userNotFoundResponse;
}
if (status != 0)
{
// If the user doesn't exist, return null. Right now, not sure how to figure out the
// right error code, so just stuff it in the log.
Logging.authorityConnectors.warn("Livelink: For user '"+domainAndUser+"', ListRights error # "+Integer.toString(status)+" "+llServer.getErrors());
// An error code at this level has to indicate a suddenly unreachable authority
return unreachableResponse;
}
// Go through the individual objects, and get their IDs. These id's will be the access tokens
int size;
if (childrenObjects.isRecord())
size = 1;
else if (childrenObjects.isTable())
size = childrenObjects.size();
else
size = 0;
// We need also to add in support for the special rights objects. These are:
// -1: RIGHT_WORLD
// -2: RIGHT_SYSTEM
// -3: RIGHT_OWNER
// -4: RIGHT_GROUP
//
// RIGHT_WORLD means guest access.
// RIGHT_SYSTEM is "Public Access".
// RIGHT_OWNER is access by the owner of the object.
// RIGHT_GROUP is access by a member of the base group containing the owner
//
// These objects are returned by the corresponding GetObjectRights() call made during
// the ingestion process. We have to figure out how to map these to things that are
// the equivalent of acls.
// Idea:
// 1) RIGHT_WORLD is based on some property of the user.
// 2) RIGHT_SYSTEM is based on some property of the user.
// 3) RIGHT_OWNER and RIGHT_GROUP are managed solely in the ingestion side of the world.
// NOTE: It turns out that -1 and -2 are in fact returned as part of the list of
// rights requested above. They get mapped to special keywords already in the above
// code, so it *may* be reasonable to filter them from here. It's not a real problem because
// it's effectively just a duplicate of what we are doing.
int j = 0;
while (j < size)
{
int token = childrenObjects.toInteger(j, "ID");
list.add(Integer.toString(token));
j++;
}
String[] rval = new String[list.size()];
j = 0;
while (j < rval.length)
{
rval[j] = (String)list.get(j);
j++;
}
return new AuthorizationResponse(rval,AuthorizationResponse.RESPONSE_OK);
}
catch (RuntimeException e)
{
sanityRetryCount = handleLivelinkRuntimeException(e,sanityRetryCount);
}
}
}
catch (ServiceInterruption e)
{
Logging.authorityConnectors.warn("Livelink: Server seems to be down: "+e.getMessage(),e);
return unreachableResponse;
}
}
/** 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 unreachableResponse;
}
// 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,"LivelinkConnector.Server"));
tabsArray.add(Messages.getString(locale,"LivelinkConnector.UserMapping"));
tabsArray.add(Messages.getString(locale,"LivelinkConnector.Cache"));
out.print(
"<script type=\"text/javascript\">\n"+
"<!--\n"+
"function checkConfig()\n"+
"{\n"+
" if (editconnection.serverport.value != \"\" && !isInteger(editconnection.serverport.value))\n"+
" {\n"+
" alert(" + Messages.getBodyJavascriptString(locale,"LivelinkConnector.AValidNumberIsRequired") + ");\n"+
" editconnection.serverport.focus();\n"+
" return false;\n"+
" }\n"+
" if (editconnection.usernameregexp.value != \"\" && !isRegularExpression(editconnection.usernameregexp.value))\n"+
" {\n"+
" alert(" + Messages.getBodyJavascriptString(locale,"LivelinkConnector.UserNameRegularExpressionMustBeValidRegularExpression") + ");\n"+
" editconnection.usernameregexp.focus();\n"+
" return false;\n"+
" }\n"+
" return true;\n"+
"}\n"+
"\n"+
"function checkConfigForSave()\n"+
"{\n"+
" if (editconnection.servername.value == \"\")\n"+
" {\n"+
" alert(" + Messages.getBodyJavascriptString(locale,"LivelinkConnector.EnterALivelinkServerName") + ");\n"+
" SelectTab(" + Messages.getBodyJavascriptString(locale,"LivelinkConnector.Server") + ");\n"+
" editconnection.servername.focus();\n"+
" return false;\n"+
" }\n"+
" if (editconnection.serverport.value == \"\")\n"+
" {\n"+
" alert(" + Messages.getBodyJavascriptString(locale,"LivelinkConnector.AServerPortNumberIsRequired") + ");\n"+
" SelectTab(" + Messages.getBodyJavascriptString(locale,"LivelinkConnector.Server") + ");\n"+
" editconnection.serverport.focus();\n"+
" return false;\n"+
" }\n"+
" if (editconnection.usernameregexp.value == \"\")\n"+
" {\n"+
" alert(" + Messages.getBodyJavascriptString(locale,"LivelinkConnector.UserNameRegularExpressionCannotBeNull") + ");\n"+
" SelectTab(" + Messages.getBodyJavascriptString(locale,"LivelinkConnector.UserMapping") + ");\n"+
" editconnection.usernameregexp.focus();\n"+
" return false;\n"+
" }\n"+
" if (editconnection.cachelifetime.value == \"\")\n"+
" {\n"+
" alert(\"" + Messages.getBodyJavascriptString(locale,"LivelinkConnector.CacheLifetimeCannotBeNull") + "\");\n"+
" SelectTab(\"" + Messages.getBodyJavascriptString(locale,"LivelinkConnector.Cache") + "\");\n"+
" editconnection.cachelifetime.focus();\n"+
" return false;\n"+
" }\n"+
" if (editconnection.cachelifetime.value != \"\" && !isInteger(editconnection.cachelifetime.value))\n"+
" {\n"+
" alert(\"" + Messages.getBodyJavascriptString(locale,"LivelinkConnector.CacheLifetimeMustBeAnInteger") + "\");\n"+
" SelectTab(\"" + Messages.getBodyJavascriptString(locale,"LivelinkConnector.Cache") + "\");\n"+
" editconnection.cachelifetime.focus();\n"+
" return false;\n"+
" }\n"+
" if (editconnection.cachelrusize.value == \"\")\n"+
" {\n"+
" alert(\"" + Messages.getBodyJavascriptString(locale,"LivelinkConnector.CacheLRUSizeCannotBeNull") + "\");\n"+
" SelectTab(\"" + Messages.getBodyJavascriptString(locale,"LivelinkConnector.Cache") + "\");\n"+
" editconnection.cachelrusize.focus();\n"+
" return false;\n"+
" }\n"+
" if (editconnection.cachelrusize.value != \"\" && !isInteger(editconnection.cachelrusize.value))\n"+
" {\n"+
" alert(\"" + Messages.getBodyJavascriptString(locale,"LivelinkConnector.CacheLRUSizeMustBeAnInteger") + "\");\n"+
" SelectTab(\"" + Messages.getBodyJavascriptString(locale,"LivelinkConnector.Cache") + "\");\n"+
" editconnection.cachelrusize.focus();\n"+
" return false;\n"+
" }\n"+
" return true;\n"+
"}\n"+
"\n"+
"//-->\n"+
"</script>\n"
);
}
/** 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
{
String serverName = parameters.getParameter(org.apache.manifoldcf.crawler.connectors.livelink.LiveLinkParameters.serverName);
if (serverName == null)
serverName = "localhost";
String serverPort = parameters.getParameter(org.apache.manifoldcf.crawler.connectors.livelink.LiveLinkParameters.serverPort);
if (serverPort == null)
serverPort = "2099";
String serverUserName = parameters.getParameter(org.apache.manifoldcf.crawler.connectors.livelink.LiveLinkParameters.serverUsername);
if (serverUserName == null)
serverUserName = "";
String serverPassword = parameters.getObfuscatedParameter(org.apache.manifoldcf.crawler.connectors.livelink.LiveLinkParameters.serverPassword);
if (serverPassword == null)
serverPassword = "";
String cacheLifetime = parameters.getParameter(LiveLinkParameters.cacheLifetime);
if (cacheLifetime == null)
cacheLifetime = "1";
String cacheLRUsize = parameters.getParameter(LiveLinkParameters.cacheLRUSize);
if (cacheLRUsize == null)
cacheLRUsize = "1000";
org.apache.manifoldcf.crawler.connectors.livelink.MatchMap matchMap = null;
String usernameRegexp = parameters.getParameter(org.apache.manifoldcf.crawler.connectors.livelink.LiveLinkParameters.userNameRegexp);
String livelinkUserExpr = parameters.getParameter(org.apache.manifoldcf.crawler.connectors.livelink.LiveLinkParameters.livelinkNameSpec);
if (usernameRegexp != null && usernameRegexp.length() > 0 && livelinkUserExpr != null)
{
// Old-style configuration. Convert to the new.
matchMap = new org.apache.manifoldcf.crawler.connectors.livelink.MatchMap();
matchMap.appendOldstyleMatchPair(usernameRegexp,livelinkUserExpr);
}
else
{
// New style configuration.
String userNameMapping = parameters.getParameter(org.apache.manifoldcf.crawler.connectors.livelink.LiveLinkParameters.userNameMapping);
if (userNameMapping == null)
userNameMapping = "^(.*)\\\\@([A-Z|a-z|0-9|_|-]*)\\\\.(.*)$=$(2)\\\\$(1l)";
matchMap = new org.apache.manifoldcf.crawler.connectors.livelink.MatchMap(userNameMapping);
}
usernameRegexp = matchMap.getMatchString(0);
livelinkUserExpr = matchMap.getReplaceString(0);
// The "Server" tab
if (tabName.equals(Messages.getString(locale,"LivelinkConnector.Server")))
{
out.print(
"<table class=\"displaytable\">\n"+
" <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale,"LivelinkConnector.ServerName") + "</nobr></td>\n"+
" <td class=\"value\"><input type=\"text\" size=\"64\" name=\"servername\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(serverName)+"\"/></td>\n"+
" </tr>\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale,"LivelinkConnector.ServerPort") + "</nobr></td>\n"+
" <td class=\"value\"><input type=\"text\" size=\"5\" name=\"serverport\" value=\""+serverPort+"\"/></td>\n"+
" </tr>\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale,"LivelinkConnector.ServerUserName") + "</nobr></td>\n"+
" <td class=\"value\"><input type=\"text\" size=\"32\" name=\"serverusername\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(serverUserName)+"\"/></td>\n"+
" </tr>\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale,"LivelinkConnector.ServerPassword") + "</nobr></td>\n"+
" <td class=\"value\"><input type=\"password\" size=\"32\" name=\"serverpassword\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(serverPassword)+"\"/></td>\n"+
" </tr>\n"+
"</table>\n"
);
}
else
{
// Hiddens for Server tab
out.print(
"<input type=\"hidden\" name=\"servername\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(serverName)+"\"/>\n"+
"<input type=\"hidden\" name=\"serverport\" value=\""+serverPort+"\"/>\n"+
"<input type=\"hidden\" name=\"serverusername\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(serverUserName)+"\"/>\n"+
"<input type=\"hidden\" name=\"serverpassword\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(serverPassword)+"\"/>\n"
);
}
// The "User Mapping" tab
if (tabName.equals(Messages.getString(locale,"LivelinkConnector.UserMapping")))
{
out.print(
"<table class=\"displaytable\">\n"+
" <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale,"LivelinkConnector.UserNameRegularExpression") + "</nobr></td>\n"+
" <td class=\"value\"><input type=\"text\" size=\"40\" name=\"usernameregexp\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(usernameRegexp)+"\"/></td>\n"+
" </tr>\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale,"LivelinkConnector.LivelinkUserExpression") + "</nobr></td>\n"+
" <td class=\"value\"><input type=\"text\" size=\"40\" name=\"livelinkuserexpr\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(livelinkUserExpr)+"\"/></td>\n"+
" </tr>\n"+
"</table>\n"
);
}
else
{
// Hiddens for "User Mapping" tab
out.print(
"<input type=\"hidden\" name=\"usernameregexp\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(usernameRegexp)+"\"/>\n"+
"<input type=\"hidden\" name=\"livelinkuserexpr\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(livelinkUserExpr)+"\"/>\n"
);
}
// "Cache" tab
if(tabName.equals(Messages.getString(locale,"LivelinkConnector.Cache")))
{
out.print(
"<table class=\"displaytable\">\n"+
" <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale,"LivelinkConnector.CacheLifetime") + "</nobr></td>\n"+
" <td class=\"value\"><input type=\"text\" size=\"5\" name=\"cachelifetime\" value=\"" + org.apache.manifoldcf.ui.util.Encoder.attributeEscape(cacheLifetime) + "\"/> " + Messages.getBodyString(locale,"LivelinkConnector.minutes") + "</td>\n"+
" </tr>\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale,"LivelinkConnector.CacheLRUSize") + "</nobr></td>\n"+
" <td class=\"value\"><input type=\"text\" size=\"5\" name=\"cachelrusize\" value=\"" + org.apache.manifoldcf.ui.util.Encoder.attributeEscape(cacheLRUsize) + "\"/></td>\n"+
" </tr>\n"+
"</table>\n"
);
}
else
{
// Hiddens for "Cache" tab
out.print(
"<input type=\"hidden\" name=\"cachelifetime\" value=\"" + org.apache.manifoldcf.ui.util.Encoder.attributeEscape(cacheLifetime) + "\"/>\n"+
"<input type=\"hidden\" name=\"cachelrusize\" value=\"" + org.apache.manifoldcf.ui.util.Encoder.attributeEscape(cacheLRUsize) + "\"/>\n"
);
}
}
/** 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
{
String serverName = variableContext.getParameter("servername");
if (serverName != null)
parameters.setParameter(org.apache.manifoldcf.crawler.connectors.livelink.LiveLinkParameters.serverName,serverName);
String serverPort = variableContext.getParameter("serverport");
if (serverPort != null)
parameters.setParameter(org.apache.manifoldcf.crawler.connectors.livelink.LiveLinkParameters.serverPort,serverPort);
String serverUserName = variableContext.getParameter("serverusername");
if (serverUserName != null)
parameters.setParameter(org.apache.manifoldcf.crawler.connectors.livelink.LiveLinkParameters.serverUsername,serverUserName);
String serverPassword = variableContext.getParameter("serverpassword");
if (serverPassword != null)
parameters.setObfuscatedParameter(org.apache.manifoldcf.crawler.connectors.livelink.LiveLinkParameters.serverPassword,serverPassword);
String usernameRegexp = variableContext.getParameter("usernameregexp");
String livelinkUserExpr = variableContext.getParameter("livelinkuserexpr");
if (usernameRegexp != null && livelinkUserExpr != null)
{
parameters.setParameter(org.apache.manifoldcf.crawler.connectors.livelink.LiveLinkParameters.userNameRegexp,null);
parameters.setParameter(org.apache.manifoldcf.crawler.connectors.livelink.LiveLinkParameters.livelinkNameSpec,null);
org.apache.manifoldcf.crawler.connectors.livelink.MatchMap matchMap = new org.apache.manifoldcf.crawler.connectors.livelink.MatchMap();
matchMap.appendMatchPair(usernameRegexp,livelinkUserExpr);
parameters.setParameter(org.apache.manifoldcf.crawler.connectors.livelink.LiveLinkParameters.userNameMapping,matchMap.toString());
}
String cacheLifetime = variableContext.getParameter("cachelifetime");
if (cacheLifetime != null)
parameters.setParameter(LiveLinkParameters.cacheLifetime,cacheLifetime);
String cacheLRUsize = variableContext.getParameter("cachelrusize");
if (cacheLRUsize != null)
parameters.setParameter(LiveLinkParameters.cacheLRUSize,cacheLRUsize);
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
{
out.print(
"<table class=\"displaytable\">\n"+
" <tr>\n"+
" <td class=\"description\" colspan=\"1\"><nobr>" + Messages.getBodyString(locale,"LivelinkConnector.Parameters") + "</nobr></td>\n"+
" <td class=\"value\" colspan=\"3\">\n"
);
Iterator iter = parameters.listParameters();
while (iter.hasNext())
{
String param = (String)iter.next();
String value = parameters.getParameter(param);
if (param.length() >= "password".length() && param.substring(param.length()-"password".length()).equalsIgnoreCase("password"))
{
out.print(
" <nobr>"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(param)+"=********</nobr><br/>\n"
);
}
else if (param.length() >="keystore".length() && param.substring(param.length()-"keystore".length()).equalsIgnoreCase("keystore"))
{
IKeystoreManager kmanager = KeystoreManagerFactory.make("",value);
out.print(
" <nobr>"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(param)+"=&lt;"+Integer.toString(kmanager.getContents().length)+" certificate(s)&gt;</nobr><br/>\n"
);
}
else
{
out.print(
" <nobr>"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(param)+"="+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(value)+"</nobr><br/>\n"
);
}
}
out.print(
" </td>\n"+
" </tr>\n"+
"</table>\n"
);
}
/** Interpret runtimeexception to search for livelink API errors. Throws an appropriately reinterpreted exception, or
* just returns if the exception indicates that a short-cycle retry attempt should be made. (In that case, the appropriate
* wait has been already performed).
*@param e is the RuntimeException caught
*/
protected int handleLivelinkRuntimeException(RuntimeException e, int sanityRetryCount)
throws ManifoldCFException, ServiceInterruption
{
if (
e instanceof com.opentext.api.LLHTTPAccessDeniedException ||
e instanceof com.opentext.api.LLHTTPClientException ||
e instanceof com.opentext.api.LLHTTPServerException ||
e instanceof com.opentext.api.LLIndexOutOfBoundsException ||
e instanceof com.opentext.api.LLNoFieldSpecifiedException ||
e instanceof com.opentext.api.LLNoValueSpecifiedException ||
e instanceof com.opentext.api.LLSecurityProviderException ||
e instanceof com.opentext.api.LLUnknownFieldException
)
{
String details = llServer.getErrors();
throw new ManifoldCFException("Livelink API error: "+e.getMessage()+((details==null)?"":"; "+details),e,ManifoldCFException.REPOSITORY_CONNECTION_ERROR);
}
else if (
e instanceof com.opentext.api.LLBadServerCertificateException ||
e instanceof com.opentext.api.LLHTTPCGINotFoundException ||
e instanceof com.opentext.api.LLCouldNotConnectHTTPException ||
e instanceof com.opentext.api.LLHTTPForbiddenException ||
e instanceof com.opentext.api.LLHTTPProxyAuthRequiredException ||
e instanceof com.opentext.api.LLHTTPRedirectionException ||
e instanceof com.opentext.api.LLUnsupportedAuthMethodException ||
e instanceof com.opentext.api.LLWebAuthInitException
)
{
String details = llServer.getErrors();
throw new ManifoldCFException("Livelink API error: "+e.getMessage()+((details==null)?"":"; "+details),e);
}
else if (e instanceof com.opentext.api.LLIllegalOperationException)
{
// This usually means that LAPI has had a minor communication difficulty but hasn't reported it accurately.
// We *could* throw a ServiceInterruption, but OpenText recommends to just retry almost immediately.
String details = llServer.getErrors();
return assessRetry(sanityRetryCount,new ManifoldCFException("Livelink API illegal operation error: "+e.getMessage()+((details==null)?"":"; "+details),e));
}
else if (e instanceof com.opentext.api.LLIOException)
{
// LAPI is returning errors that are not terribly explicit, and I don't have control over their wording, so check that server can be resolved by DNS,
// so that a better error message can be returned.
try
{
InetAddress.getByName(serverName);
}
catch (UnknownHostException e2)
{
throw new ManifoldCFException("Server name '"+serverName+"' cannot be resolved",e2);
}
throw new ServiceInterruption("Transient error: "+e.getMessage(),e,System.currentTimeMillis()+5*60000L,System.currentTimeMillis()+12*60*60000L,-1,true);
}
else
throw e;
}
/** Do a retry, or throw an exception if the retry count has been exhausted
*/
protected static int assessRetry(int sanityRetryCount, ManifoldCFException e)
throws ManifoldCFException
{
if (sanityRetryCount == 0)
{
throw e;
}
sanityRetryCount--;
try
{
ManifoldCF.sleep(1000L);
}
catch (InterruptedException e2)
{
throw new ManifoldCFException(e2.getMessage(),e2,ManifoldCFException.INTERRUPTED);
}
// Exit the method
return sanityRetryCount;
}
protected static StringSet emptyStringSet = new StringSet();
/** This is the cache object descriptor for cached access tokens from
* this connector.
*/
protected static class AuthorizationResponseDescription extends org.apache.manifoldcf.core.cachemanager.BaseDescription
{
/** The user name associated with the access tokens */
protected String userName;
// The server connection parameters
protected String serverName;
protected int serverPort;
protected String serverUsername;
protected String serverPassword;
protected long responseLifetime;
/** The expiration time */
protected long expirationTime = -1;
/** Constructor. */
public AuthorizationResponseDescription(String userName, String serverName, int serverPort,
String serverUsername, String serverPassword, long responseLifetime, int LRUsize)
{
super("LiveLinkAuthority",LRUsize);
this.userName = userName;
this.serverName = serverName;
this.serverPort = serverPort;
this.serverUsername = serverUsername;
this.serverPassword = serverPassword;
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()
{
return getClass().getName() + "-" + userName + "-" + serverName +
"-" + Integer.toString(serverPort) + "-" + serverUsername + "-" + serverPassword;
}
/** Return the object expiration interval */
public long getObjectExpirationTime(long currentTime)
{
if (expirationTime == -1)
expirationTime = currentTime + responseLifetime;
return expirationTime;
}
public int hashCode()
{
return userName.hashCode() + serverName.hashCode() + new Integer(serverPort).hashCode() +
serverUsername.hashCode() + serverPassword.hashCode();
}
public boolean equals(Object o)
{
if (!(o instanceof AuthorizationResponseDescription))
return false;
AuthorizationResponseDescription ard = (AuthorizationResponseDescription)o;
return ard.userName.equals(userName) && ard.serverName.equals(serverName) && ard.serverPort == serverPort &&
ard.serverUsername.equals(serverUsername) && ard.serverPassword.equals(serverPassword);
}
}
}