mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-06-30 18:15:39 +00:00
41454: ALF-15881: Disabling clustering in community 41500: ALF-15883: Move configuration files 41503: ALF-15884: Move cluster package to enterprise repository project 41504: ALF-15884: Move cluster package to enterprise repository project 41519: ALF-15886: References/definition for "hazelcastInstanceFactory" must be in enterprise repo only 41523: ALF-15886: References/definition for "hazelcastInstanceFactory" must be in enterprise repo only 41525: ALF-15886: References/definition for "hazelcastInstanceFactory" must be in enterprise repo only 41527: ALF-15886: References/definition for "hazelcastInstanceFactory" must be in enterprise repo only 41530: ALF-15886: remove import for class no longer in project. 41532: ALF-15887: LockStoreFactoryImpl must be separated into community and enterprise versions 41535: ALF-15883: Move configuration files 41561: ALF-15886: factory class to create key fileserver config beans. 41578: ALF-15888: separate transactional and shared cache bean definitions. 41623: ALF-15888: first pass at DefaultSimpleCache implementation. 41646: ALF-15888: move ehcache-default.xml 41651: ALF-15888: update javadoc to reflect changes 41762: ALF-15888: improve cache test to prove that null values are stored correctly. 41812: ALF-15888: added new cache provider for use by hibernate: DefaultCacheProvider. 41830: ALF-15888: make DefaultSimpleCache BeanNameAware to help with debugging etc. 41831: ALF-15888: missing file from commit - adds enterprise override capability for hibernate-cfg.properties 41850: ALF-15888: move tickets cache to cache-context.xml 41857: ALF-15888: make RemoteAlfrescoTicketServiceImpl cache implementation agnostic. 41866: ALF-15888: extract caches from fileservers and web-client and provide enterprise overrides 41881: ALF-15888: replace use of EhCacheAdapter with DefaultSimpleCache and SimpleCache interface. 41884: ALF-15888: added DefaultSimpleCache configuration to CachingContentStore sample XML. 41885: ALF-15888: move EhCacheAdapter to new package. 41886: ALF-15888: correct absolute class names in config for EhCacheAdapter. 41892: ALF-15888: fix CachingContentStore tests. 41897: ALF-15888: move CacheTest and config to new package. 41898: ALF-15888: remove redundant directory 41899: ALF-15889: move EhCacheManagerFactoryBean to new package. 41902: ALF-15889: moved EhCacheTracerJob to new package. 41913: ALF-15889: move InternalEhCacheManagerFactoryBean to new package. 41916: ALF-15889: move AlfrescoCacheManagerPeerProviderFactory to new package. 41937: ALF-15889: decouple TransactionalCache from EhCache 41966: ALF-15889: decouple RetryingTransactionHelper from ehcache. 41989: ALF-15889: added ContextListener test. 41996: ALF-15889: moved cache test to its own class. 41998: ALF-15889: move ehcache jars. 41999: ALF-15889: modify .classpath to reflect jar moves. 42037: ALF-15889: update poms to reflect lib moves. 42038: ALF-15889: add eclipse library reference to enterprise projects. 42093: ALF-15916: moved core properties to enterprise 42114: ALF-15888: externalized cache sizes to repository.properties. 42127: ALF-16136: move re-indexing configuration 42140: ALF-16136: move cluster check property. 42186: ALF-15889: removing seemingly redundant test config file. 42187: ALF-15888: tidy up config changes. 42189: ALF-15888: cleanup config 42190: ALF-15888: config cleanup 42191: ALF-15888: config cleanup 42198: ALF-16136: restored lost property git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@42210 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
432 lines
18 KiB
Java
432 lines
18 KiB
Java
/*
|
|
* Copyright (C) 2005-2012 Alfresco Software Limited.
|
|
*
|
|
* This file is part of Alfresco
|
|
*
|
|
* Alfresco is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Alfresco is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package org.alfresco.repo.remoteticket;
|
|
|
|
import java.io.IOException;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
|
|
import org.alfresco.error.AlfrescoRuntimeException;
|
|
import org.alfresco.repo.cache.SimpleCache;
|
|
import org.alfresco.repo.remotecredentials.PasswordCredentialsInfoImpl;
|
|
import org.alfresco.repo.security.authentication.AuthenticationException;
|
|
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
|
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
|
import org.alfresco.service.cmr.remoteconnector.RemoteConnectorRequest;
|
|
import org.alfresco.service.cmr.remoteconnector.RemoteConnectorService;
|
|
import org.alfresco.service.cmr.remotecredentials.BaseCredentialsInfo;
|
|
import org.alfresco.service.cmr.remotecredentials.PasswordCredentialsInfo;
|
|
import org.alfresco.service.cmr.remotecredentials.RemoteCredentialsService;
|
|
import org.alfresco.service.cmr.remoteticket.NoCredentialsFoundException;
|
|
import org.alfresco.service.cmr.remoteticket.NoSuchSystemException;
|
|
import org.alfresco.service.cmr.remoteticket.RemoteAlfrescoTicketInfo;
|
|
import org.alfresco.service.cmr.remoteticket.RemoteAlfrescoTicketService;
|
|
import org.alfresco.service.cmr.remoteticket.RemoteSystemUnavailableException;
|
|
import org.apache.commons.logging.Log;
|
|
import org.apache.commons.logging.LogFactory;
|
|
import org.json.simple.JSONObject;
|
|
import org.json.simple.parser.ParseException;
|
|
|
|
/**
|
|
* Service for working with a Remote Alfresco instance, which
|
|
* holds user credentials for the remote system via the
|
|
* {@link RemoteCredentialsService}, and handles ticket
|
|
* negotiation for you.
|
|
*
|
|
* Note - this service will be moved to the Repository Core once
|
|
* it has stabilised (likely after OAuth support is added)
|
|
*
|
|
* TODO OAuth support
|
|
*
|
|
* @author Nick Burch
|
|
* @since 4.0.2
|
|
*/
|
|
public class RemoteAlfrescoTicketServiceImpl implements RemoteAlfrescoTicketService
|
|
{
|
|
/**
|
|
* The logger
|
|
*/
|
|
private static Log logger = LogFactory.getLog(RemoteAlfrescoTicketServiceImpl.class);
|
|
|
|
private RetryingTransactionHelper retryingTransactionHelper;
|
|
private RemoteCredentialsService remoteCredentialsService;
|
|
private RemoteConnectorService remoteConnectorService;
|
|
private SimpleCache<String, String> ticketsCache;
|
|
|
|
private Map<String,String> remoteSystemsUrls = new HashMap<String, String>();
|
|
private Map<String,Map<String,String>> remoteSystemsReqHeaders = new HashMap<String, Map<String,String>>();
|
|
|
|
/**
|
|
* Sets the Remote Credentials Service to use to store and retrieve credentials
|
|
*/
|
|
public void setRemoteCredentialsService(RemoteCredentialsService remoteCredentialsService)
|
|
{
|
|
this.remoteCredentialsService = remoteCredentialsService;
|
|
}
|
|
|
|
/**
|
|
* Sets the Remote Connector Service to use to talk to remote systems with
|
|
*/
|
|
public void setRemoteConnectorService(RemoteConnectorService remoteConnectorService)
|
|
{
|
|
this.remoteConnectorService = remoteConnectorService;
|
|
}
|
|
|
|
/**
|
|
* Sets the SimpleCache to be used to cache remote tickets in
|
|
*/
|
|
public void setTicketsCache(SimpleCache<String, String> ticketsCache)
|
|
{
|
|
this.ticketsCache = ticketsCache;
|
|
}
|
|
|
|
/**
|
|
* Sets the Retrying Transaction Helper, used to write changes to
|
|
* Credentials which turn out to be invalid
|
|
*/
|
|
public void setRetryingTransactionHelper(RetryingTransactionHelper retryingTransactionHelper)
|
|
{
|
|
this.retryingTransactionHelper = retryingTransactionHelper;
|
|
}
|
|
|
|
/**
|
|
* Registers the details of a new Remote System with the service.
|
|
* Any previous details for the system will be overridden
|
|
*/
|
|
public synchronized void registerRemoteSystem(String remoteSystemId, String baseUrl, Map<String,String> requestHeaders)
|
|
{
|
|
remoteSystemsUrls.put(remoteSystemId, baseUrl);
|
|
remoteSystemsReqHeaders.put(remoteSystemId, requestHeaders);
|
|
|
|
if (logger.isDebugEnabled())
|
|
logger.debug("Registered System " + remoteSystemId + " as " + baseUrl);
|
|
}
|
|
|
|
protected void ensureRemoteSystemKnown(String remoteSystemId) throws NoSuchSystemException
|
|
{
|
|
String baseUrl = remoteSystemsUrls.get(remoteSystemId);
|
|
if (baseUrl == null)
|
|
{
|
|
throw new NoSuchSystemException(remoteSystemId);
|
|
}
|
|
}
|
|
protected PasswordCredentialsInfo ensureCredentialsFound(String remoteSystemId, BaseCredentialsInfo credentails)
|
|
{
|
|
// Check they exist, and are of the right type
|
|
if (credentails == null)
|
|
{
|
|
throw new NoCredentialsFoundException(remoteSystemId);
|
|
}
|
|
if (! (credentails instanceof PasswordCredentialsInfo))
|
|
{
|
|
throw new AlfrescoRuntimeException("Credentials found, but of the wrong type, needed PasswordCredentialsInfo but got " + credentails);
|
|
}
|
|
return (PasswordCredentialsInfo)credentails;
|
|
}
|
|
protected String toCacheKey(String remoteSystemId, BaseCredentialsInfo credentials)
|
|
{
|
|
// Cache key is system + separator + remote username
|
|
return remoteSystemId + "===" + credentials.getRemoteUsername();
|
|
}
|
|
|
|
/**
|
|
* Validates and stores the remote credentials for the current user
|
|
*/
|
|
public BaseCredentialsInfo storeRemoteCredentials(String remoteSystemId, String username, String password)
|
|
throws AuthenticationException, RemoteSystemUnavailableException, NoSuchSystemException
|
|
{
|
|
// Check we know about the system
|
|
ensureRemoteSystemKnown(remoteSystemId);
|
|
|
|
// Build the initial stub credentials
|
|
PasswordCredentialsInfoImpl credentials = new PasswordCredentialsInfoImpl();
|
|
|
|
// See if there are existing credentials to update
|
|
BaseCredentialsInfo existing = getRemoteCredentials(remoteSystemId);
|
|
if (existing != null)
|
|
{
|
|
// Update if we can, otherwise delete for re-add
|
|
if (existing instanceof PasswordCredentialsInfoImpl)
|
|
{
|
|
credentials = (PasswordCredentialsInfoImpl)existing;
|
|
if (logger.isDebugEnabled())
|
|
logger.debug("Updating existing credentials from " + credentials.getNodeRef());
|
|
}
|
|
else
|
|
{
|
|
// Wrong type, delete and use new ones
|
|
if (logger.isDebugEnabled())
|
|
logger.debug("Unable to update existing credentials from " + existing.getNodeRef() + ", replacing");
|
|
remoteCredentialsService.deleteCredentials(existing);
|
|
existing = null;
|
|
}
|
|
}
|
|
|
|
// Set the remote system credentials for them
|
|
credentials.setRemoteUsername(username);
|
|
credentials.setRemotePassword(password);
|
|
|
|
// Validate their credentials are correct, by attempting to get a ticket for them
|
|
refreshTicket(remoteSystemId, credentials);
|
|
|
|
if (logger.isDebugEnabled())
|
|
logger.debug("Credentials correct for " + username + " on " + remoteSystemId);
|
|
|
|
// If we get this far, then there credentials are valid, so store them
|
|
credentials.setLastAuthenticationSucceeded(true);
|
|
|
|
if (credentials.getNodeRef() != null)
|
|
{
|
|
return remoteCredentialsService.updateCredentials(credentials);
|
|
}
|
|
else
|
|
{
|
|
return remoteCredentialsService.createPersonCredentials(remoteSystemId, credentials);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves the remote credentials (if any) for the current user
|
|
*
|
|
* @param remoteSystemId The ID of the remote system, as registered with the service
|
|
* @return The current user's remote credentials, or null if they don't have any
|
|
*/
|
|
public BaseCredentialsInfo getRemoteCredentials(String remoteSystemId) throws NoSuchSystemException
|
|
{
|
|
// Check we know about the system
|
|
ensureRemoteSystemKnown(remoteSystemId);
|
|
|
|
// Retrieve, if available, and return
|
|
return remoteCredentialsService.getPersonCredentials(remoteSystemId);
|
|
}
|
|
|
|
/**
|
|
* Deletes the remote credentials (if any) for the current user
|
|
*/
|
|
public boolean deleteRemoteCredentials(String remoteSystemId) throws NoSuchSystemException
|
|
{
|
|
// Try to retrieve
|
|
BaseCredentialsInfo credentials = getRemoteCredentials(remoteSystemId);
|
|
|
|
// If there are none, nothing to do
|
|
if (credentials == null)
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
logger.debug("No credentials found to delete on " + remoteSystemId);
|
|
return false;
|
|
}
|
|
|
|
// Log that we're going to delete
|
|
if (logger.isDebugEnabled())
|
|
logger.debug("Deleting credentials for " + credentials.getRemoteUsername() + " on " + remoteSystemId);
|
|
|
|
// Delete the credentials
|
|
remoteCredentialsService.deleteCredentials(credentials);
|
|
|
|
// Zap the cached ticket, if there is one
|
|
String cacheKey = toCacheKey(remoteSystemId, credentials);
|
|
ticketsCache.remove(cacheKey);
|
|
|
|
// Indicate the delete worked
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns the current Alfresco Ticket for the current user on
|
|
* the remote system, fetching if it isn't already cached.
|
|
*/
|
|
public RemoteAlfrescoTicketInfo getAlfrescoTicket(String remoteSystemId)
|
|
throws AuthenticationException, NoCredentialsFoundException, NoSuchSystemException, RemoteSystemUnavailableException
|
|
{
|
|
// Check we know about the system
|
|
ensureRemoteSystemKnown(remoteSystemId);
|
|
|
|
// Grab the user's details
|
|
BaseCredentialsInfo creds = getRemoteCredentials(remoteSystemId);
|
|
PasswordCredentialsInfo credentials = ensureCredentialsFound(remoteSystemId, creds);
|
|
|
|
// Is there a cached ticket?
|
|
String cacheKey = toCacheKey(remoteSystemId, credentials);
|
|
String ticket = ticketsCache.get(cacheKey);
|
|
|
|
// Refresh if if isn't cached
|
|
if (ticket == null)
|
|
{
|
|
return refreshTicket(remoteSystemId, credentials);
|
|
}
|
|
else
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
logger.debug("Cached ticket found for " + creds.getRemoteUsername() + " on " + remoteSystemId);
|
|
|
|
// Wrap and return
|
|
return new AlfTicketRemoteAlfrescoTicketImpl(ticket);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Forces a re-fetch of the Alfresco Ticket for the current user,
|
|
* if possible, and marks the credentials as failing if not.
|
|
*/
|
|
public RemoteAlfrescoTicketInfo refetchAlfrescoTicket(String remoteSystemId)
|
|
throws AuthenticationException, NoCredentialsFoundException, NoSuchSystemException, RemoteSystemUnavailableException
|
|
{
|
|
// Check we know about the system
|
|
ensureRemoteSystemKnown(remoteSystemId);
|
|
|
|
// Grab the user's details
|
|
BaseCredentialsInfo creds = getRemoteCredentials(remoteSystemId);
|
|
PasswordCredentialsInfo credentials = ensureCredentialsFound(remoteSystemId, creds);
|
|
|
|
// Trigger the refresh
|
|
return refreshTicket(remoteSystemId, credentials);
|
|
}
|
|
|
|
/**
|
|
* Fetches a new ticket for the given user, and caches it
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
protected RemoteAlfrescoTicketInfo refreshTicket(final String remoteSystemId, final PasswordCredentialsInfo credentials)
|
|
throws AuthenticationException, NoSuchSystemException, RemoteSystemUnavailableException
|
|
{
|
|
// Check we know about the system
|
|
String baseUrl = remoteSystemsUrls.get(remoteSystemId);
|
|
if (baseUrl == null)
|
|
{
|
|
throw new NoSuchSystemException(remoteSystemId);
|
|
}
|
|
|
|
if (logger.isDebugEnabled())
|
|
logger.debug("Fetching new ticket for " + credentials.getRemoteUsername() + " on " + remoteSystemId);
|
|
|
|
// Build up the JSON for the ticket request
|
|
JSONObject json = new JSONObject();
|
|
json.put("username", credentials.getRemoteUsername());
|
|
json.put("password", credentials.getRemotePassword());
|
|
|
|
// Build the URL
|
|
String url = baseUrl + "api/login";
|
|
|
|
// Turn this into a remote request
|
|
RemoteConnectorRequest request = remoteConnectorService.buildRequest(url, "POST");
|
|
request.setRequestBody(json.toJSONString());
|
|
|
|
Map<String,String> reqHeaders = remoteSystemsReqHeaders.get(remoteSystemId);
|
|
if (reqHeaders != null)
|
|
{
|
|
for (Map.Entry<String, String> reqHeader : reqHeaders.entrySet())
|
|
{
|
|
request.addRequestHeader(reqHeader.getKey(), reqHeader.getValue());
|
|
}
|
|
}
|
|
|
|
// Work out what key we'll use to cache on
|
|
String cacheKey = toCacheKey(remoteSystemId, credentials);
|
|
|
|
// Perform the request
|
|
String ticket = null;
|
|
try {
|
|
JSONObject response = remoteConnectorService.executeJSONRequest(request);
|
|
if (logger.isDebugEnabled())
|
|
logger.debug("JSON Ticket Response Received: " + response);
|
|
|
|
// Pull out the ticket, validating the JSON along the way
|
|
Object data = response.get("data");
|
|
if (data == null)
|
|
{
|
|
throw new RemoteSystemUnavailableException("Invalid JSON received: " + response);
|
|
}
|
|
if (! (data instanceof JSONObject))
|
|
{
|
|
throw new RemoteSystemUnavailableException("Invalid JSON part received: " + data.getClass() + " - from: " + response);
|
|
}
|
|
|
|
Object ticketJSON = ((JSONObject)data).get("ticket");
|
|
if (ticketJSON == null)
|
|
{
|
|
throw new RemoteSystemUnavailableException("Invalid JSON received, ticket missing: " + response);
|
|
}
|
|
if (! (ticketJSON instanceof String))
|
|
{
|
|
throw new RemoteSystemUnavailableException("Invalid JSON part received: " + ticketJSON.getClass() + " from: " + response);
|
|
}
|
|
ticket = (String)ticketJSON;
|
|
}
|
|
catch (IOException ioEx)
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
logger.debug("Problem communicating with remote Alfresco instance " + remoteSystemId, ioEx);
|
|
|
|
throw new RemoteSystemUnavailableException("Error talking to remote system", ioEx);
|
|
}
|
|
catch (ParseException jsonEx)
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
logger.debug("Invalid JSON from remote Alfresco instance " + remoteSystemId, jsonEx);
|
|
|
|
throw new RemoteSystemUnavailableException("Invalid JSON response from remote system", jsonEx);
|
|
}
|
|
catch (AuthenticationException authEx)
|
|
{
|
|
// Record the credentials as now failing, if they're persisted ones
|
|
// Do this in a read-write transaction (most ticket stuff is read only)
|
|
if (credentials.getNodeRef() != null && credentials.getLastAuthenticationSucceeded())
|
|
{
|
|
retryingTransactionHelper.doInTransaction(
|
|
new RetryingTransactionCallback<Void>()
|
|
{
|
|
public Void execute()
|
|
{
|
|
remoteCredentialsService.updateCredentialsAuthenticationSucceeded(false, credentials);
|
|
return null;
|
|
}
|
|
}, false, true
|
|
);
|
|
}
|
|
|
|
// Clear old the old, invalid ticket from the cache, if it was there
|
|
ticketsCache.remove(cacheKey);
|
|
|
|
// Propagate up the problem
|
|
throw authEx;
|
|
}
|
|
|
|
// Cache the new ticket
|
|
ticketsCache.put(cacheKey, ticket);
|
|
|
|
// If the credentials indicate the previous attempt failed, record as now working
|
|
if (! credentials.getLastAuthenticationSucceeded())
|
|
{
|
|
retryingTransactionHelper.doInTransaction(
|
|
new RetryingTransactionCallback<Void>()
|
|
{
|
|
public Void execute()
|
|
{
|
|
remoteCredentialsService.updateCredentialsAuthenticationSucceeded(true, credentials);
|
|
return null;
|
|
}
|
|
}, false, true
|
|
);
|
|
}
|
|
|
|
// Wrap and return
|
|
return new AlfTicketRemoteAlfrescoTicketImpl(ticket);
|
|
}
|
|
}
|