/* $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.authorities.authorities.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 org.apache.manifoldcf.connectorcommon.interfaces.*;
import org.apache.manifoldcf.livelink.*;
import java.io.*;
import java.util.*;
import java.net.*;
import java.util.regex.*;
import com.opentext.api.*;
/** 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 $";
//Forward to the javascript to check the configuration parameters.
private static final String EDIT_CONFIGURATION_JS = "editConfiguration.js";
//Forward to the HTML template to edit the configuration parameters.
private static final String EDIT_CONFIGURATION_SERVER_HTML = "editConfiguration_Server.html";
private static final String EDIT_CONFIGURATION_CACHE_HTML = "editConfiguration_Cache.html";
//Forward to the HTML template to view the configuration parameters.
private static final String VIEW_CONFIGURATION_HTML = "viewConfiguration.html";
// Signal that we have set up connection parameters properly
private boolean hasSessionParameters = false;
// Signal that we have set up a connection properly
private boolean hasConnected = false;
// Session expiration time
private long expirationTime = -1L;
// Idle session expiration interval
private final static long expirationInterval = 300000L;
// Data from the parameters
private String serverProtocol = null;
private String serverName = null;
private int serverPort = -1;
private String serverUsername = null;
private String serverPassword = null;
private String serverHTTPCgi = null;
private String serverHTTPNTLMDomain = null;
private String serverHTTPNTLMUsername = null;
private String serverHTTPNTLMPassword = null;
private IKeystoreManager serverHTTPSKeystore = 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;
// 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.
/** 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);
}
/** Initialize the parameters, including the ones needed for caching.
*/
protected void getSessionParameters()
throws ManifoldCFException
{
if (!hasSessionParameters)
{
// Server parameters
serverProtocol = params.getParameter(LiveLinkParameters.serverProtocol);
serverName = params.getParameter(LiveLinkParameters.serverName);
String serverPortString = params.getParameter(LiveLinkParameters.serverPort);
serverUsername = params.getParameter(LiveLinkParameters.serverUsername);
serverPassword = params.getObfuscatedParameter(LiveLinkParameters.serverPassword);
serverHTTPCgi = params.getParameter(LiveLinkParameters.serverHTTPCgiPath);
serverHTTPNTLMDomain = params.getParameter(LiveLinkParameters.serverHTTPNTLMDomain);
serverHTTPNTLMUsername = params.getParameter(LiveLinkParameters.serverHTTPNTLMUsername);
serverHTTPNTLMPassword = params.getObfuscatedParameter(LiveLinkParameters.serverHTTPNTLMPassword);
// Server parameter processing
if (serverProtocol == null || serverProtocol.length() == 0)
serverProtocol = "internal";
if (serverPortString == null)
serverPort = 2099;
else
serverPort = new Integer(serverPortString).intValue();
if (serverHTTPNTLMDomain != null && serverHTTPNTLMDomain.length() == 0)
serverHTTPNTLMDomain = null;
if (serverHTTPNTLMUsername == null || serverHTTPNTLMUsername.length() == 0)
{
serverHTTPNTLMUsername = null;
serverHTTPNTLMPassword = null;
}
// Set up server ssl if indicated
String serverHTTPSKeystoreData = params.getParameter(LiveLinkParameters.serverHTTPSKeystore);
if (serverHTTPSKeystoreData != null)
serverHTTPSKeystore = KeystoreManagerFactory.make("",serverHTTPSKeystoreData);
cacheLifetime = params.getParameter(LiveLinkParameters.cacheLifetime);
if (cacheLifetime == null)
cacheLifetime = "1";
cacheLRUsize = params.getParameter(LiveLinkParameters.cacheLRUSize);
if (cacheLRUsize == null)
cacheLRUsize = "1000";
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 (Logging.authorityConnectors.isDebugEnabled())
{
String passwordExists = (serverPassword!=null && serverPassword.length() > 0)?"password exists":"";
Logging.authorityConnectors.debug("Livelink: Livelink connection parameters: Server='"+serverName+"'; port='"+serverPort+"'; user name='"+serverUsername+"'; "+passwordExists);
}
hasSessionParameters = true;
}
}
/** Set up a session.
*/
protected void getSession()
throws ManifoldCFException, ServiceInterruption
{
getSessionParameters();
if (!hasConnected)
{
int sanityRetryCount = FAILURE_RETRY_COUNT;
while (true)
{
try
{
// Create the session
llServer = new LLSERVER(!serverProtocol.equals("internal"),serverProtocol.equals("https"),
serverName,serverPort,serverUsername,serverPassword,
serverHTTPCgi,serverHTTPNTLMDomain,serverHTTPNTLMUsername,serverHTTPNTLMPassword,
serverHTTPSKeystore);
LLUsers = new LAPI_USERS(llServer.getLLSession());
if (Logging.authorityConnectors.isDebugEnabled())
{
Logging.authorityConnectors.debug("Livelink: Livelink session created.");
}
hasConnected = true;
break;
}
catch (RuntimeException e)
{
sanityRetryCount = handleLivelinkRuntimeException(e,sanityRetryCount);
}
}
}
expirationTime = System.currentTimeMillis() + expirationInterval;
}
// 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
hasConnected = false;
getSession();
// 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("Admin", userObject);
// User Not Found is ok; the server user name may include the domain.
if (status == 103101 || status == 401203)
return super.check();
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();
}
}
/** This method is periodically called for all connectors that are connected but not
* in active use.
*/
@Override
public void poll()
throws ManifoldCFException
{
if (!hasConnected)
return;
long currentTime = System.currentTimeMillis();
if (currentTime >= expirationTime)
{
hasConnected = false;
expirationTime = -1L;
// Shutdown livelink connection
if (llServer != null)
{
llServer.disconnect();
llServer = null;
}
LLUsers = null;
}
}
/** 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 hasConnected;
}
/** Close the connection. Call this before discarding the repository connector.
*/
@Override
public void disconnect()
throws ManifoldCFException
{
hasSessionParameters = false;
hasConnected = false;
expirationTime = -1L;
if (llServer != null)
{
llServer.disconnect();
llServer = null;
}
LLUsers = null;
serverProtocol = null;
serverName = null;
serverPort = -1;
serverUsername = null;
serverPassword = null;
serverHTTPCgi = null;
serverHTTPNTLMDomain = null;
serverHTTPNTLMUsername = null;
serverHTTPNTLMPassword = null;
serverHTTPSKeystore = 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
{
// We need the session parameters here
getSessionParameters();
// Construct a cache description object
ICacheDescription objectDescription = new AuthorizationResponseDescription(userName,
serverProtocol,serverName,serverPort,
serverUsername,serverPassword,
serverHTTPCgi,serverHTTPNTLMDomain,serverHTTPNTLMUsername,serverHTTPNTLMPassword,
serverHTTPSKeystore,
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
{
try
{
getSession();
// 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+"'");
}
String domainAndUser = userName;
if (Logging.authorityConnectors.isDebugEnabled())
{
Logging.authorityConnectors.debug("Livelink: Livelink user name = '"+domainAndUser+"'");
}
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 RESPONSE_USERNOTFOUND;
}
if (status != 0)
{
Logging.authorityConnectors.warn("Livelink: User '"+domainAndUser+"' GetUserInfo error # "+Integer.toString(status)+" "+llServer.getErrors());
// The server is probably down.
return RESPONSE_UNREACHABLE;
}
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 RESPONSE_USERNOTFOUND;
}
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 RESPONSE_USERNOTFOUND;
}
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 RESPONSE_UNREACHABLE;
}
// 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 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,"LivelinkConnector.Server"));
tabsArray.add(Messages.getString(locale,"LivelinkConnector.Cache"));
Messages.outputResourceWithVelocity(out, locale, EDIT_CONFIGURATION_JS, null, true);
}
/** 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
{
Map<String, Object> velocityContext = new HashMap<>();
velocityContext.put("TabName",tabName);
fillInServerTab(velocityContext, out, parameters);
fillInCacheTab(velocityContext, out, parameters);
Messages.outputResourceWithVelocity(out, locale, EDIT_CONFIGURATION_SERVER_HTML, velocityContext);
Messages.outputResourceWithVelocity(out, locale, EDIT_CONFIGURATION_CACHE_HTML, velocityContext);
}
/** Fill in Server tab */
protected static void fillInServerTab(Map<String,Object> velocityContext, IHTTPOutput out, ConfigParams parameters)
{
// LAPI parameters
String serverProtocol = parameters.getParameter(LiveLinkParameters.serverProtocol);
if (serverProtocol == null)
serverProtocol = "internal";
String serverName = parameters.getParameter(LiveLinkParameters.serverName);
if (serverName == null)
serverName = "localhost";
String serverPort = parameters.getParameter(LiveLinkParameters.serverPort);
if (serverPort == null)
serverPort = "2099";
String serverUserName = parameters.getParameter(LiveLinkParameters.serverUsername);
if (serverUserName == null)
serverUserName = "";
String serverPassword = parameters.getObfuscatedParameter(LiveLinkParameters.serverPassword);
if (serverPassword == null)
serverPassword = "";
else
serverPassword = out.mapPasswordToKey(serverPassword);
String serverHTTPCgiPath = parameters.getParameter(LiveLinkParameters.serverHTTPCgiPath);
if (serverHTTPCgiPath == null)
serverHTTPCgiPath = "/livelink/livelink.exe";
String serverHTTPNTLMDomain = parameters.getParameter(LiveLinkParameters.serverHTTPNTLMDomain);
if (serverHTTPNTLMDomain == null)
serverHTTPNTLMDomain = "";
String serverHTTPNTLMUserName = parameters.getParameter(LiveLinkParameters.serverHTTPNTLMUsername);
if (serverHTTPNTLMUserName == null)
serverHTTPNTLMUserName = "";
String serverHTTPNTLMPassword = parameters.getObfuscatedParameter(LiveLinkParameters.serverHTTPNTLMPassword);
if (serverHTTPNTLMPassword == null)
serverHTTPNTLMPassword = "";
else
serverHTTPNTLMPassword = out.mapPasswordToKey(serverHTTPNTLMPassword);
String serverHTTPSKeystore = parameters.getParameter(LiveLinkParameters.serverHTTPSKeystore);
IKeystoreManager localServerHTTPSKeystore;
Map<String,String> serverCertificatesMap = null;
String message = null;
try {
if (serverHTTPSKeystore == null)
localServerHTTPSKeystore = KeystoreManagerFactory.make("");
else
localServerHTTPSKeystore = KeystoreManagerFactory.make("",serverHTTPSKeystore);
// List the individual certificates in the store, with a delete button for each
String[] contents = localServerHTTPSKeystore.getContents();
if (contents.length > 0)
{
serverCertificatesMap = new HashMap<>();
int i = 0;
while (i < contents.length)
{
String alias = contents[i];
String description = localServerHTTPSKeystore.getDescription(alias);
if (description.length() > 128)
description = description.substring(0,125) + "...";
serverCertificatesMap.put(alias, description);
i++;
}
}
} catch (ManifoldCFException e) {
message = e.getMessage();
org.apache.manifoldcf.crawler.system.Logging.connectors.warn(e);
}
velocityContext.put("SERVERPROTOCOL",serverProtocol);
velocityContext.put("SERVERNAME",serverName);
velocityContext.put("SERVERPORT",serverPort);
velocityContext.put("SERVERUSERNAME",serverUserName);
velocityContext.put("SERVERPASSWORD",serverPassword);
velocityContext.put("SERVERHTTPCGIPATH",serverHTTPCgiPath);
velocityContext.put("SERVERHTTPNTLMDOMAIN",serverHTTPNTLMDomain);
velocityContext.put("SERVERHTTPNTLMUSERNAME",serverHTTPNTLMUserName);
velocityContext.put("SERVERHTTPNTLMPASSWORD",serverHTTPNTLMPassword);
if(serverHTTPSKeystore != null)
velocityContext.put("SERVERHTTPSKEYSTORE",serverHTTPSKeystore);
if(serverCertificatesMap != null)
velocityContext.put("SERVERCERTIFICATESMAP", serverCertificatesMap);
if(message != null)
velocityContext.put("MESSAGE", message);
}
/** Fill in Cache tab */
private void fillInCacheTab(Map<String, Object> velocityContext, IHTTPOutput out, ConfigParams parameters)
{
String cacheLifetime = parameters.getParameter(LiveLinkParameters.cacheLifetime);
if (cacheLifetime == null)
cacheLifetime = "1";
String cacheLRUsize = parameters.getParameter(LiveLinkParameters.cacheLRUSize);
if (cacheLRUsize == null)
cacheLRUsize = "1000";
velocityContext.put("CACHELIFETIME",cacheLifetime);
velocityContext.put("CACHELRUSIZE",cacheLRUsize);
}
/** 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
{
// Server parameters
String serverProtocol = variableContext.getParameter("serverprotocol");
if (serverProtocol != null)
parameters.setParameter(LiveLinkParameters.serverProtocol,serverProtocol);
String serverName = variableContext.getParameter("servername");
if (serverName != null)
parameters.setParameter(LiveLinkParameters.serverName,serverName);
String serverPort = variableContext.getParameter("serverport");
if (serverPort != null)
parameters.setParameter(LiveLinkParameters.serverPort,serverPort);
String serverUserName = variableContext.getParameter("serverusername");
if (serverUserName != null)
parameters.setParameter(LiveLinkParameters.serverUsername,serverUserName);
String serverPassword = variableContext.getParameter("serverpassword");
if (serverPassword != null)
parameters.setObfuscatedParameter(LiveLinkParameters.serverPassword,variableContext.mapKeyToPassword(serverPassword));
String serverHTTPCgiPath = variableContext.getParameter("serverhttpcgipath");
if (serverHTTPCgiPath != null)
parameters.setParameter(LiveLinkParameters.serverHTTPCgiPath,serverHTTPCgiPath);
String serverHTTPNTLMDomain = variableContext.getParameter("serverhttpntlmdomain");
if (serverHTTPNTLMDomain != null)
parameters.setParameter(LiveLinkParameters.serverHTTPNTLMDomain,serverHTTPNTLMDomain);
String serverHTTPNTLMUserName = variableContext.getParameter("serverhttpntlmusername");
if (serverHTTPNTLMUserName != null)
parameters.setParameter(LiveLinkParameters.serverHTTPNTLMUsername,serverHTTPNTLMUserName);
String serverHTTPNTLMPassword = variableContext.getParameter("serverhttpntlmpassword");
if (serverHTTPNTLMPassword != null)
parameters.setObfuscatedParameter(LiveLinkParameters.serverHTTPNTLMPassword,variableContext.mapKeyToPassword(serverHTTPNTLMPassword));
String serverHTTPSKeystoreValue = variableContext.getParameter("serverhttpskeystoredata");
final String serverConfigOp = variableContext.getParameter("serverconfigop");
if (serverConfigOp != null)
{
if (serverConfigOp.equals("Delete"))
{
String alias = variableContext.getParameter("serverkeystorealias");
final IKeystoreManager mgr;
if (serverHTTPSKeystoreValue != null)
mgr = KeystoreManagerFactory.make("",serverHTTPSKeystoreValue);
else
mgr = KeystoreManagerFactory.make("");
mgr.remove(alias);
serverHTTPSKeystoreValue = mgr.getString();
}
else if (serverConfigOp.equals("Add"))
{
String alias = IDFactory.make(threadContext);
byte[] certificateValue = variableContext.getBinaryBytes("servercertificate");
final IKeystoreManager mgr;
if (serverHTTPSKeystoreValue != null)
mgr = KeystoreManagerFactory.make("",serverHTTPSKeystoreValue);
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;
}
serverHTTPSKeystoreValue = mgr.getString();
}
}
parameters.setParameter(LiveLinkParameters.serverHTTPSKeystore, serverHTTPSKeystoreValue);
// Cache parameters
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
{
Map<String, Object> paramMap = new HashMap<>();
Map<String,String> configMap = new HashMap<>();
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"))
{
configMap.put(org.apache.manifoldcf.ui.util.Encoder.bodyEscape(param),"********");
}
else if (param.length() >="keystore".length() && param.substring(param.length()-"keystore".length()).equalsIgnoreCase("keystore") ||
param.length() > "truststore".length() && param.substring(param.length()-"truststore".length()).equalsIgnoreCase("truststore"))
{
IKeystoreManager kmanager = KeystoreManagerFactory.make("",value);
configMap.put(org.apache.manifoldcf.ui.util.Encoder.bodyEscape(param),"=<"+Integer.toString(kmanager.getContents().length)+Messages.getBodyString(locale,"LivelinkConnector.certificates")+">");
}
else
{
configMap.put(org.apache.manifoldcf.ui.util.Encoder.bodyEscape(param), org.apache.manifoldcf.ui.util.Encoder.bodyEscape(value));
}
}
paramMap.put("CONFIGMAP",configMap);
Messages.outputResourceWithVelocity(out, locale, VIEW_CONFIGURATION_HTML, paramMap);
}
/** 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.LLSSLNotAvailableException)
{
String details = llServer.getErrors();
throw new ManifoldCFException("Missing llssl.jar 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 || (e instanceof RuntimeException && e.getClass().getName().startsWith("com.opentext.api.")))
{
// Catching obfuscated and unspecified opentext runtime exceptions now too - these come from llssl.jar. We
// have to presume these are SSL connection errors; nothing else to go by unfortunately. UGH.
// 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 final String userName;
// The server connection parameters
protected final String serverProtocol;
protected final String serverName;
protected final int serverPort;
protected final String serverUsername;
protected final String serverPassword;
protected final String serverHTTPCgi;
protected final String serverHTTPNTLMDomain;
protected final String serverHTTPNTLMUsername;
protected final String serverHTTPNTLMPassword;
protected final String serverHTTPSKeystore;
protected long responseLifetime;
/** The expiration time */
protected long expirationTime = -1;
/** Constructor. */
public AuthorizationResponseDescription(String userName,
String serverProtocol,
String serverName, int serverPort,
String serverUsername, String serverPassword,
String serverHTTPCgi, String serverHTTPNTLMDomain, String serverHTTPNTLMUsername, String serverHTTPNTLMPassword,
IKeystoreManager serverHTTPSKeystore,
long responseLifetime, int LRUsize)
throws ManifoldCFException
{
super("LiveLinkAuthority",LRUsize);
this.userName = userName;
this.serverProtocol = serverProtocol;
this.serverName = serverName;
this.serverPort = serverPort;
this.serverUsername = serverUsername;
this.serverPassword = serverPassword;
this.serverHTTPCgi = (serverHTTPCgi==null)?"":serverHTTPCgi;
this.serverHTTPNTLMDomain = (serverHTTPNTLMDomain==null)?"":serverHTTPNTLMDomain;
this.serverHTTPNTLMUsername = (serverHTTPNTLMUsername==null)?"":serverHTTPNTLMUsername;
this.serverHTTPNTLMPassword = (serverHTTPNTLMPassword==null)?"":serverHTTPNTLMPassword;
if (serverHTTPSKeystore != null)
this.serverHTTPSKeystore = serverHTTPSKeystore.getString();
else
this.serverHTTPSKeystore = null;
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 + "-" + serverProtocol + "-" + serverName +
"-" + Integer.toString(serverPort) + "-" + serverUsername + "-" + serverPassword +
"-" + serverHTTPCgi + "-" + serverHTTPNTLMDomain + "-" + serverHTTPNTLMUsername +
"-" + serverHTTPNTLMPassword + "-" + ((serverHTTPSKeystore==null)?"":serverHTTPSKeystore);
}
/** Return the object expiration interval */
public long getObjectExpirationTime(long currentTime)
{
if (expirationTime == -1)
expirationTime = currentTime + responseLifetime;
return expirationTime;
}
public int hashCode()
{
return userName.hashCode() +
serverProtocol.hashCode() + serverName.hashCode() + new Integer(serverPort).hashCode() +
serverUsername.hashCode() + serverPassword.hashCode() +
serverHTTPCgi.hashCode() + serverHTTPNTLMDomain.hashCode() + serverHTTPNTLMUsername.hashCode() +
serverHTTPNTLMPassword.hashCode() + ((serverHTTPSKeystore==null)?0:serverHTTPSKeystore.hashCode());
}
public boolean equals(Object o)
{
if (!(o instanceof AuthorizationResponseDescription))
return false;
AuthorizationResponseDescription ard = (AuthorizationResponseDescription)o;
return ard.userName.equals(userName) &&
ard.serverProtocol.equals(serverProtocol) && ard.serverName.equals(serverName) && ard.serverPort == serverPort &&
ard.serverUsername.equals(serverUsername) && ard.serverPassword.equals(serverPassword) &&
ard.serverHTTPCgi.equals(serverHTTPCgi) && ard.serverHTTPNTLMDomain.equals(serverHTTPNTLMDomain) &&
ard.serverHTTPNTLMUsername.equals(serverHTTPNTLMUsername) && ard.serverHTTPNTLMPassword.equals(serverHTTPNTLMPassword) &&
((ard.serverHTTPSKeystore != null && serverHTTPSKeystore != null && ard.serverHTTPSKeystore.equals(serverHTTPSKeystore)) ||
((ard.serverHTTPSKeystore == null || serverHTTPSKeystore == null) && ard.serverHTTPSKeystore == serverHTTPSKeystore));
}
}
}