Fixed ALF-11201: CMIS ignores user login and runs as 'admin' sometimes

- Change the way the CMIS implementation interacts with authentication, logging and transactions
   by constructing a service layered in three interceptors:
        <property name="cmisTransactions"       ref="CMISService_Transactions" />
        <property name="cmisExceptions"         ref="CMISService_Exceptions" />
        <property name="cmisControl"            ref="CMISService_Control" />
 - CMISService_Transactions:
   Retrying transactions are initiated for all operations and we no longer attempt to hold
   transactions open across method calls.  This optimization is secondary to having full retrying
   behaviour, without which the server could throw avoidable exceptions in concurrent environments.
 - CMISService_Exceptions:
   Performs translation from typical repo exceptions into CMIS exceptions
 - CMISService_Control:
   Provides details logging of inbound authentication states, arguments, exceptions and return values
   To get logging without method arguments (can be verbose):
      log4j.logger.org.alfresco.opencmis.AlfrescoCmisServiceInterceptor=DEBUG
   To include method call arguments:
      log4j.logger.org.alfresco.opencmis.AlfrescoCmisServiceInterceptor=TRACE
   Logging will include details of inbound, method-call and outbound thread authentication credentials.


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@32331 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2011-11-27 20:55:29 +00:00
parent ec242b6090
commit 16d0d0e786
10 changed files with 3113 additions and 3093 deletions

View File

@@ -33,10 +33,32 @@
<property name="cmisServiceFactory" ref="CMISServiceFactory" />
</bean>
<!-- Factory providing OpenCMIS with a CMISService -->
<bean id="CMISServiceFactory" class="org.alfresco.opencmis.AlfrescoCmisServiceFactory">
<property name="cmisConnector" ref="CMISConnector" />
<property name="cmisTransactions" ref="CMISService_Transactions" />
<property name="cmisExceptions" ref="CMISService_Exceptions" />
<property name="cmisControl" ref="CMISService_Control" />
</bean>
<bean id="CMISService_Transactions" class="org.alfresco.repo.transaction.RetryingTransactionInterceptor">
<property name="transactionService" ref="TransactionService" />
<property name="transactionManager" ref="transactionManager" />
<property name="transactionAttributes">
<props>
<prop key="get*">${server.transaction.mode.readOnly}</prop>
<prop key="query">${server.transaction.mode.readOnly}</prop>
<prop key="open">PROPAGATION_SUPPORTS, readOnly</prop>
<prop key="close">PROPAGATION_SUPPORTS, readOnly</prop>
<prop key="*">${server.transaction.mode.default}</prop>
</props>
</property>
</bean>
<bean id="CMISService_Exceptions" class="org.alfresco.opencmis.AlfrescoCmisExceptionInterceptor" />
<bean id="CMISService_Control" class="org.alfresco.opencmis.AlfrescoCmisServiceInterceptor" />
<bean id="CMISConnector" class="org.alfresco.opencmis.CMISConnector">
<property name="store" value="${spaces.store}" />
<property name="rootPath" value="/${spaces.company_home.childname}" />

View File

@@ -0,0 +1,84 @@
/*
* Copyright (C) 2005-2010 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.opencmis;
import org.alfresco.repo.node.integrity.IntegrityException;
import org.alfresco.repo.security.authentication.AuthenticationException;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.service.cmr.coci.CheckOutCheckInServiceException;
import org.alfresco.service.cmr.model.FileExistsException;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.chemistry.opencmis.commons.exceptions.CmisBaseException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisConstraintException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisContentAlreadyExistsException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisPermissionDeniedException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisVersioningException;
/**
* Interceptor to catch various exceptions and translate them into CMIS-related exceptions
* <p/>
* TODO: Externalize messages
* TODO: Use ExceptionStackUtil to dig out exceptions of interest regardless of depth
*
* @author Derek Hulley
* @since 4.0
*/
public class AlfrescoCmisExceptionInterceptor implements MethodInterceptor
{
public Object invoke(MethodInvocation mi) throws Throwable
{
try
{
return mi.proceed();
}
catch (AuthenticationException e)
{
throw new CmisPermissionDeniedException(e.getMessage(), e);
}
catch (CheckOutCheckInServiceException e)
{
throw new CmisVersioningException("Check out failed: " + e.getMessage(), e);
}
catch (FileExistsException fee)
{
throw new CmisContentAlreadyExistsException("An object with this name already exists!", fee);
}
catch (IntegrityException ie)
{
throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie);
}
catch (AccessDeniedException ade)
{
throw new CmisPermissionDeniedException("Permission denied!", ade);
}
catch (Exception e)
{
if (e instanceof CmisBaseException)
{
throw (CmisBaseException) e;
}
else
{
throw new CmisRuntimeException(e.getMessage(), e);
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -20,26 +20,30 @@ package org.alfresco.opencmis;
import java.util.Map;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.RetryingTransactionInterceptor;
import org.apache.chemistry.opencmis.commons.impl.server.AbstractServiceFactory;
import org.apache.chemistry.opencmis.commons.server.CallContext;
import org.apache.chemistry.opencmis.commons.server.CmisService;
import org.apache.chemistry.opencmis.server.support.CmisServiceWrapper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.ProxyFactory;
/**
* Factory for OpenCMIS service objects.
*
* @author florian.mueller
* @author Derek Hulley
*/
public class AlfrescoCmisServiceFactory extends AbstractServiceFactory
{
private ThreadLocal<CmisServiceWrapper<AlfrescoCmisService>> threadLocalService = new ThreadLocal<CmisServiceWrapper<AlfrescoCmisService>>();
private static final Log logger = LogFactory.getLog(AlfrescoCmisServiceFactory.class);
private CMISConnector connector;
@Override
public void init(Map<String, String> parameters)
{
}
private RetryingTransactionInterceptor cmisTransactions;
private AlfrescoCmisExceptionInterceptor cmisExceptions;
private AlfrescoCmisServiceInterceptor cmisControl;
/**
* Sets the CMIS connector.
@@ -49,25 +53,74 @@ public class AlfrescoCmisServiceFactory extends AbstractServiceFactory
this.connector = connector;
}
/**
* @param cmisTransactions the interceptor that applies appropriate transactions
*/
public void setCmisTransactions(RetryingTransactionInterceptor cmisTransactions)
{
this.cmisTransactions = cmisTransactions;
}
/**
* @param cmisExceptions interceptor to translate exceptions
*/
public void setCmisExceptions(AlfrescoCmisExceptionInterceptor cmisExceptions)
{
this.cmisExceptions = cmisExceptions;
}
/**
* @param cmisControl interceptor that provides logging and authentication checks
*/
public void setCmisControl(AlfrescoCmisServiceInterceptor cmisControl)
{
this.cmisControl = cmisControl;
}
@Override
public void init(Map<String, String> parameters)
{
}
@Override
public void destroy()
{
threadLocalService = null;
}
/**
* TODO:
* We are producing new instances each time.
*/
@Override
public CmisService getService(CallContext context)
{
CmisServiceWrapper<AlfrescoCmisService> wrapperService = threadLocalService.get();
if (wrapperService == null)
if (logger.isDebugEnabled())
{
wrapperService = new CmisServiceWrapper<AlfrescoCmisService>(new AlfrescoCmisService(connector),
connector.getTypesDefaultMaxItems(), connector.getTypesDefaultDepth(),
connector.getObjectsDefaultMaxItems(), connector.getObjectsDefaultDepth());
threadLocalService.set(wrapperService);
logger.debug("\n" +
"CMIS getService(): \n" +
" Authenticated as: " + AuthenticationUtil.getFullyAuthenticatedUser() + "\n" +
" Running as: " + AuthenticationUtil.getRunAsUser() + "\n" +
" User: " + context.getUsername() + "\n" +
" Repo: " + context.getRepositoryId());
}
wrapperService.getWrappedService().beginCall(context);
AlfrescoCmisService cmisServiceTarget = new AlfrescoCmisServiceImpl(connector);
// Wrap it
ProxyFactory proxyFactory = new ProxyFactory(cmisServiceTarget);
proxyFactory.addInterface(AlfrescoCmisService.class);
proxyFactory.addAdvice(cmisExceptions);
proxyFactory.addAdvice(cmisControl);
proxyFactory.addAdvice(cmisTransactions);
AlfrescoCmisService cmisService = (AlfrescoCmisService) proxyFactory.getProxy();
CmisServiceWrapper<CmisService> wrapperService = new CmisServiceWrapper<CmisService>(
cmisService,
connector.getTypesDefaultMaxItems(), connector.getTypesDefaultDepth(),
connector.getObjectsDefaultMaxItems(), connector.getObjectsDefaultDepth());
// We use our specific open method here because only we know about it
cmisService.open(context);
return wrapperService;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,111 @@
/*
* Copyright (C) 2005-2011 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.opencmis;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Interceptor to manage threads and perform other menial jobs that are common to all
* calls made to the service. It also provides detailed logging of values passing
* in and out of the service.
* <p/>
* <b>DEBUG</b> shows authentication and inbound arguments. <b>TRACE</b> shows full
* return results as well.
*
* @author Derek Hulley
* @since 4.0
*/
public class AlfrescoCmisServiceInterceptor implements MethodInterceptor
{
private static Log logger = LogFactory.getLog(AlfrescoCmisServiceInterceptor.class);
public AlfrescoCmisServiceInterceptor()
{
}
@Override
public synchronized Object invoke(MethodInvocation invocation) throws Throwable
{
// Keep note of whether debug is required
boolean debug = logger.isDebugEnabled();
boolean trace = logger.isTraceEnabled();
StringBuilder sb = null;
if (debug)
{
sb = new StringBuilder("\n" +
"CMIS invocation: \n" +
" Method: " + invocation.getMethod().getName() + "\n" +
" Arguments: \n");
for (Object arg : invocation.getArguments())
{
sb.append(" ").append(arg).append("\n");
}
}
Object ret = null;
AlfrescoCmisService service = (AlfrescoCmisService) invocation.getThis();
try
{
// Wrap with pre- and post-method calls
try
{
sb.append(
" Pre-call authentication: \n" +
" Full auth: " + AuthenticationUtil.getFullyAuthenticatedUser() + "\n" +
" Effective auth: " + AuthenticationUtil.getRunAsUser() + "\n");
service.beforeCall();
sb.append(
" In-call authentication: \n" +
" Full auth: " + AuthenticationUtil.getFullyAuthenticatedUser() + "\n" +
" Effective auth: " + AuthenticationUtil.getRunAsUser() + "\n");
ret = invocation.proceed();
}
finally
{
service.afterCall();
sb.append(
" Post-call authentication: \n" +
" Full auth: " + AuthenticationUtil.getFullyAuthenticatedUser() + "\n" +
" Effective auth: " + AuthenticationUtil.getRunAsUser() + "\n");
}
if (trace)
{
sb.append(
" Returning: ").append(ret).append("\n");
logger.debug(sb);
}
// Done
return ret;
}
catch (Throwable e)
{
if (debug)
{
sb.append(" Throwing: " + e.getMessage());
logger.debug(sb, e);
}
// Rethrow
throw e;
}
}
}

View File

@@ -61,14 +61,11 @@ public class AlfrescoLocalCmisServiceFactory extends AbstractServiceFactory
CmisServiceWrapper<AlfrescoCmisService> wrapperService = THREAD_LOCAL_SERVICE.get();
if (wrapperService == null)
{
wrapperService = new CmisServiceWrapper<AlfrescoCmisService>(new AlfrescoCmisService(CMIS_CONNECTOR),
wrapperService = new CmisServiceWrapper<AlfrescoCmisService>(new AlfrescoCmisServiceImpl(CMIS_CONNECTOR),
CMIS_CONNECTOR.getTypesDefaultMaxItems(), CMIS_CONNECTOR.getTypesDefaultDepth(),
CMIS_CONNECTOR.getObjectsDefaultMaxItems(), CMIS_CONNECTOR.getObjectsDefaultDepth());
THREAD_LOCAL_SERVICE.set(wrapperService);
}
wrapperService.getWrappedService().beginCall(context);
return wrapperService;
}
}

View File

@@ -25,9 +25,6 @@ import javax.transaction.UserTransaction;
import junit.framework.TestCase;
import org.alfresco.cmis.CMISAccessControlService;
import org.alfresco.cmis.CMISRenditionService;
import org.alfresco.cmis.CMISServices;
import org.alfresco.opencmis.dictionary.CMISDictionaryService;
import org.alfresco.opencmis.mapping.CMISMapping;
import org.alfresco.opencmis.search.CMISQueryService;

View File

@@ -174,6 +174,7 @@ import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyStringImpl
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyUriImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.RepositoryCapabilitiesImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.RepositoryInfoImpl;
import org.apache.chemistry.opencmis.commons.server.CmisService;
import org.apache.chemistry.opencmis.commons.spi.Holder;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
@@ -185,11 +186,14 @@ import org.springframework.extensions.surf.util.AbstractLifecycleBean;
/**
* Bridge connecting Alfresco and OpenCMIS.
* <p/>
* This class provides many of the typical services that the {@link CmisService} implementation
* will need to use Alfresco.
*
* @author florian.mueller
* @author Derek Hulley
*/
public class CMISConnector implements ApplicationContextAware, ApplicationListener<ApplicationContextEvent>,
TenantDeployer
public class CMISConnector implements ApplicationContextAware, ApplicationListener<ApplicationContextEvent>, TenantDeployer
{
public static final char ID_SEPERATOR = ';';
public static final String ASSOC_ID_PREFIX = "assoc:";
@@ -565,9 +569,14 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen
return dictionaryService;
}
/**
* Not implemented
* @throws UnsupportedOperationException always
*/
public void setProxyUser(String proxyUser)
{
this.proxyUser = proxyUser;
// this.proxyUser = proxyUser;
throw new UnsupportedOperationException("proxyUser setting not implemented. Please raise a JIRA request.");
}
public String getProxyUser()

View File

@@ -0,0 +1,143 @@
/*
* Copyright (C) 2005-2011 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.opencmis;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import junit.framework.TestCase;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.content.filestore.FileContentWriter;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.util.ApplicationContextHelper;
import org.alfresco.util.GUID;
import org.alfresco.util.TempFileProvider;
import org.apache.chemistry.opencmis.client.api.Folder;
import org.apache.chemistry.opencmis.client.api.Repository;
import org.apache.chemistry.opencmis.client.api.Session;
import org.apache.chemistry.opencmis.client.api.SessionFactory;
import org.apache.chemistry.opencmis.client.runtime.SessionFactoryImpl;
import org.apache.chemistry.opencmis.commons.PropertyIds;
import org.apache.chemistry.opencmis.commons.SessionParameter;
import org.apache.chemistry.opencmis.commons.enums.BindingType;
import org.apache.chemistry.opencmis.commons.enums.VersioningState;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl;
import org.apache.chemistry.opencmis.commons.server.CallContext;
import org.apache.chemistry.opencmis.commons.server.CmisService;
import org.apache.chemistry.opencmis.commons.server.CmisServiceFactory;
import org.springframework.context.ApplicationContext;
/**
* Tests basic local CMIS interaction
*
* @author Derek Hulley
* @since 4.0
*/
public class OpenCmisLocalTest extends TestCase
{
private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
/**
* Test class to provide the service factory
*
* @author Derek Hulley
* @since 4.0
*/
public static class TestCmisServiceFactory implements CmisServiceFactory
{
private static AlfrescoCmisServiceFactory serviceFactory = (AlfrescoCmisServiceFactory) ctx.getBean("CMISServiceFactory");
@Override
public void init(Map<String, String> parameters)
{
}
@Override
public void destroy()
{
}
@Override
public CmisService getService(CallContext context)
{
return serviceFactory.getService(context);
}
}
private Repository getRepository(String user, String password)
{
// default factory implementation
SessionFactory sessionFactory = SessionFactoryImpl.newInstance();
Map<String, String> parameters = new HashMap<String, String>();
// user credentials
parameters.put(SessionParameter.USER, "admin");
parameters.put(SessionParameter.PASSWORD, "admin");
// connection settings
parameters.put(SessionParameter.BINDING_TYPE, BindingType.LOCAL.value());
parameters.put(SessionParameter.LOCAL_FACTORY, "org.alfresco.opencmis.OpenCmisLocalTest$TestCmisServiceFactory");
// create session
List<Repository> repositories = sessionFactory.getRepositories(parameters);
return repositories.size() > 0 ? repositories.get(0) : null;
}
public void setUp() throws Exception
{
}
public void testSetUp() throws Exception
{
Repository repository = getRepository("admin", "admin");
assertNotNull("No repository available for testing", repository);
}
public void testBasicFileOps()
{
Repository repository = getRepository("admin", "admin");
Session session = repository.createSession();
Folder rootFolder = session.getRootFolder();
// create folder
Map<String,String> folderProps = new HashMap<String, String>();
{
folderProps.put(PropertyIds.OBJECT_TYPE_ID, "cmis:folder");
folderProps.put(PropertyIds.NAME, getName() + "-" + GUID.generate());
}
Folder folder = rootFolder.createFolder(folderProps, null, null, null, session.getDefaultContext());
Map<String, String> fileProps = new HashMap<String, String>();
{
fileProps.put(PropertyIds.OBJECT_TYPE_ID, "cmis:document");
fileProps.put(PropertyIds.NAME, "mydoc-" + GUID.generate() + ".txt");
}
ContentStreamImpl fileContent = new ContentStreamImpl();
{
ContentWriter writer = new FileContentWriter(TempFileProvider.createTempFile(getName(), ".txt"));
writer.putContent("Ipsum and so on");
ContentReader reader = writer.getReader();
fileContent.setMimeType(MimetypeMap.MIMETYPE_TEXT_PLAIN);
fileContent.setStream(reader.getContentInputStream());
}
folder.createDocument(fileProps, fileContent, VersioningState.MAJOR);
}
}