Merged BRANCHES/DEV/V4.0-BUG-FIX to HEAD:

35637: RemoteCredentialsService and RemoteAlfrescoTicketService, with tests


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@35639 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Nick Burch
2012-04-24 16:12:47 +00:00
parent f7e9040218
commit 19d1da8cd8
2 changed files with 701 additions and 0 deletions

View File

@@ -0,0 +1,295 @@
/*
* 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.remoteconnector;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import net.sf.acegisecurity.Authentication;
import org.alfresco.service.cmr.remoteconnector.RemoteConnectorRequest;
import org.alfresco.service.cmr.remoteconnector.RemoteConnectorResponse;
import org.alfresco.service.cmr.remoteconnector.RemoteConnectorService;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.security.authentication.AuthenticationException;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.web.scripts.BaseWebScriptTest;
import org.alfresco.repo.web.scripts.servlet.BasicHttpAuthenticatorFactory;
import org.alfresco.repo.web.scripts.servlet.BasicHttpAuthenticatorFactory.BasicHttpAuthenticator;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.simple.JSONObject;
import org.json.simple.parser.ParseException;
import org.springframework.extensions.webscripts.Authenticator;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.TestWebScriptServer;
import org.springframework.extensions.webscripts.TestWebScriptServer.Request;
import org.springframework.extensions.webscripts.TestWebScriptServer.Response;
import org.springframework.extensions.webscripts.servlet.ServletAuthenticatorFactory;
import org.springframework.extensions.webscripts.servlet.WebScriptServletRequest;
import org.springframework.extensions.webscripts.servlet.WebScriptServletResponse;
/**
* Testing implementation of {@link RemoteConnectorService} which talks to
* the local webscripts only
*
* @author Nick Burch
* @since 4.0.2
*/
public class LocalWebScriptConnectorServiceImpl implements RemoteConnectorService
{
/**
* The logger
*/
private static Log logger = LogFactory.getLog(LocalWebScriptConnectorServiceImpl.class);
private WebScriptHelper helper;
private LocalAndRemoteAuthenticator auth;
public LocalWebScriptConnectorServiceImpl(BaseWebScriptTest webScriptTest) throws Exception
{
this.helper = new WebScriptHelper(webScriptTest);
this.auth = new LocalAndRemoteAuthenticator(webScriptTest);
}
/**
* Builds a new Request object
*/
public RemoteConnectorRequest buildRequest(String url, String method)
{
// Ensure we accept this URL
String local = "http://localhost:8080/alfresco/";
String service = "/service/";
if (url.startsWith(local))
{
// Good, that's probably us, make it a relative url
url = url.substring(local.length()-1);
// Make sure it's a service one
if (url.startsWith(service))
{
// Strip off and use
url = url.substring(service.length()-1);
}
else
{
throw new IllegalArgumentException("Only /service/ local URLs are supported, can't handle " + url);
}
}
else
{
throw new IllegalArgumentException("Not a local URL: " + url);
}
// Build and return
return new RemoteConnectorRequestImpl(url, method);
}
/**
* Builds a new Request object, using HttpClient method descriptions
*/
public RemoteConnectorRequest buildRequest(String url, Class<? extends HttpMethodBase> method)
{
// Get the method name
String methodName;
try
{
HttpMethodBase httpMethod = method.getConstructor(String.class).newInstance(url);
methodName = httpMethod.getName();
}
catch(Exception e)
{
throw new AlfrescoRuntimeException("Error identifying method name", e);
}
// Build and return
return buildRequest(url, methodName);
}
/**
* Executes the specified request, and return the response
*/
public RemoteConnectorResponse executeRequest(RemoteConnectorRequest request) throws IOException, AuthenticationException
{
// Convert the request object
RemoteConnectorRequestImpl requestImpl = (RemoteConnectorRequestImpl)request;
Request req = new Request(request.getMethod(), request.getURL());
req.setType(request.getContentType());
if (request.getRequestBody() != null)
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
requestImpl.getRequestBody().writeRequest(baos);
req.setBody(baos.toByteArray());
}
// Log
if (logger.isInfoEnabled())
logger.info("Performing local " + request.getMethod() + " request to " + request.getURL());
// Capture the user details, as they may be changed during the request processing
Authentication fullAuth = AuthenticationUtil.getFullAuthentication();
String runAsUser = AuthenticationUtil.getRunAsUser();
// If they've specified Authentication details in the request, clear our security context
// and switch to that user, to avoid our context confusing the real request
Header authHeader = null;
Map<String,String> headers = new HashMap<String, String>();
for (Header header : request.getRequestHeaders())
{
if (header.getName().equals("Authorization"))
{
authHeader = header;
}
headers.put(header.getName(), header.getValue());
}
if (authHeader != null)
{
AuthenticationUtil.clearCurrentSecurityContext();
if (logger.isDebugEnabled())
logger.debug("HTTP Authorization found for the request, clearing security context, Auth is " + authHeader);
}
req.setHeaders(headers);
// Execute the request against the WebScript Test Framework
Response resp;
try
{
resp = helper.sendRequest(req, -1);
}
catch (Exception e)
{
throw new AlfrescoRuntimeException("Problem requesting", e);
}
// Reset the user details, now we're done performing the request
AuthenticationUtil.setFullAuthentication(fullAuth);
if (runAsUser != null && !runAsUser.equals(fullAuth.getName()))
{
AuthenticationUtil.setRunAsUser(runAsUser);
}
// Log
if (logger.isInfoEnabled())
logger.info("Response to request was " + resp.getStatus() + " - " + resp);
// Check the status
if (resp.getStatus() == Status.STATUS_UNAUTHORIZED)
{
throw new AuthenticationException("Not Authorized to access this resource");
}
if (resp.getStatus() == Status.STATUS_FORBIDDEN)
{
throw new AuthenticationException("Forbidden to access this resource");
}
// TODO Handle others too
// Convert the response
String charset = null;
String contentType = resp.getContentType();
if (contentType != null && contentType.contains("charset="))
{
int splitAt = contentType.indexOf("charset=") + "charset=".length();
charset = contentType.substring(splitAt);
}
InputStream body = new ByteArrayInputStream(resp.getContentAsByteArray());
Header[] respHeaders = new Header[0]; // TODO Can't easily get the list...
RemoteConnectorResponse response = new RemoteConnectorResponseImpl(request, contentType, charset, respHeaders, body);
return response;
}
/**
* Executes the given request, requesting a JSON response, and
* returns the parsed JSON received back
*
* @throws ParseException If the response is not valid JSON
*/
public JSONObject executeJSONRequest(RemoteConnectorRequest request) throws ParseException, IOException, AuthenticationException
{
return RemoteConnectorServiceImpl.doExecuteJSONRequest(request, this);
}
private static class WebScriptHelper
{
private BaseWebScriptTest test;
private Method sendRequest;
private WebScriptHelper(BaseWebScriptTest test) throws Exception
{
this.test = test;
sendRequest = BaseWebScriptTest.class.getDeclaredMethod("sendRequest", Request.class, Integer.TYPE);
sendRequest.setAccessible(true);
}
private Response sendRequest(Request request, int expectedStatus) throws Exception
{
return (Response)sendRequest.invoke(test, request, expectedStatus);
}
}
/**
* A wrapper around {@link BasicHttpAuthenticator}, which uses the
* Authentication Context if present, otherwise HTTP Auth
*/
private static class LocalAndRemoteAuthenticator implements ServletAuthenticatorFactory
{
private BasicHttpAuthenticatorFactory httpAuthFactory;
private LocalAndRemoteAuthenticator(BaseWebScriptTest test) throws Exception
{
// Get the test server
Method getServer = BaseWebScriptTest.class.getDeclaredMethod("getServer");
getServer.setAccessible(true);
TestWebScriptServer server = (TestWebScriptServer)getServer.invoke(test);
// Grab the real auth factory from the context
httpAuthFactory = (BasicHttpAuthenticatorFactory)server.getApplicationContext().getBean("webscripts.authenticator.basic");
// Wire us into the test
server.setServletAuthenticatorFactory(this);
}
@Override
public Authenticator create(WebScriptServletRequest req, WebScriptServletResponse res)
{
// Do we have current details?
if (AuthenticationUtil.getFullyAuthenticatedUser() != null)
{
// There are already details existing
// Allow these to be kept and used
logger.debug("Existing Authentication found, remaining as " + AuthenticationUtil.getFullyAuthenticatedUser());
return null;
}
// Fall back to the http auth one
logger.debug("No existing Authentication found, using regular HTTP Auth");
return httpAuthFactory.create(req, res);
}
}
}