diff --git a/config/alfresco/application-context-highlevel.xml b/config/alfresco/application-context-highlevel.xml
index 78833f5a65..1b76e0b06e 100644
--- a/config/alfresco/application-context-highlevel.xml
+++ b/config/alfresco/application-context-highlevel.xml
@@ -15,6 +15,8 @@
+
+
diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml
index 760c0c7523..d25a0e712d 100644
--- a/config/alfresco/core-services-context.xml
+++ b/config/alfresco/core-services-context.xml
@@ -764,6 +764,9 @@
alfresco/model/linksModel.xml
+
+
+ alfresco/model/remoteCredentialsModel.xml
alfresco/model/datalistModel.xml
diff --git a/config/alfresco/public-services-security-context.xml b/config/alfresco/public-services-security-context.xml
index 0ff8669192..eaecb4f732 100644
--- a/config/alfresco/public-services-security-context.xml
+++ b/config/alfresco/public-services-security-context.xml
@@ -1099,6 +1099,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.alfresco.service.cmr.remoteticket.RemoteAlfrescoTicketService.getRemoteCredentials=ACL_ALLOW
+ org.alfresco.service.cmr.remoteticket.RemoteAlfrescoTicketService.storeRemoteCredentials=ACL_ALLOW
+ org.alfresco.service.cmr.remoteticket.RemoteAlfrescoTicketService.deleteRemoteCredentials=ACL_ALLOW
+ org.alfresco.service.cmr.remoteticket.RemoteAlfrescoTicketService.getAlfrescoTicket=ACL_ALLOW
+ org.alfresco.service.cmr.remoteticket.RemoteAlfrescoTicketService.refetchAlfrescoTicket=ACL_ALLOW
+ org.alfresco.service.cmr.remoteticket.RemoteAlfrescoTicketService.registerRemoteSystem=ACL_METHOD.ROLE_ADMINISTRATOR
+ org.alfresco.service.cmr.remoteticket.RemoteAlfrescoTicketService.*=ACL_DENY
+
+
+
+
+
diff --git a/config/alfresco/remote-credentials-services-context.xml b/config/alfresco/remote-credentials-services-context.xml
new file mode 100644
index 0000000000..91c05886f8
--- /dev/null
+++ b/config/alfresco/remote-credentials-services-context.xml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+ org.alfresco.service.cmr.remotecredentials.RemoteCredentialsService
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ getPersonCredentials
+ listPersonRemoteSystems
+ listSharedRemoteSystems
+ listAllRemoteSystems
+ listPersonCredentials
+ listSharedCredentials
+ listAllCredentials
+
+
+
+
+
+
+
+
+
+ createPersonCredentials
+ createSharedCredentials
+ updateCredentials
+ updateCredentialsAuthenticationSucceeded
+ deleteCredentials
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/config/alfresco/remote-ticket-services-context.xml b/config/alfresco/remote-ticket-services-context.xml
new file mode 100644
index 0000000000..5609b4637d
--- /dev/null
+++ b/config/alfresco/remote-ticket-services-context.xml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ org.alfresco.service.cmr.remoteticket.RemoteAlfrescoTicketService
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ getRemoteCredentials
+ registerRemoteSystem
+
+
+ getAlfrescoTicket
+ refetchAlfrescoTicket
+
+
+
+
+
+
+
+
+
+ storeRemoteCredentials
+ deleteRemoteCredentials
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.alfresco.cache.remote.auth.ticketCache
+
+
+
+
+
diff --git a/source/java/org/alfresco/repo/remoteconnector/RemoteConnectorRequestImpl.java b/source/java/org/alfresco/repo/remoteconnector/RemoteConnectorRequestImpl.java
new file mode 100644
index 0000000000..b1a60b57f7
--- /dev/null
+++ b/source/java/org/alfresco/repo/remoteconnector/RemoteConnectorRequestImpl.java
@@ -0,0 +1,229 @@
+/*
+ * 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 .
+ */
+package org.alfresco.repo.remoteconnector;
+
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.service.cmr.remoteconnector.RemoteConnectorRequest;
+import org.alfresco.service.cmr.remoteconnector.RemoteConnectorService;
+import org.apache.commons.httpclient.Header;
+import org.apache.commons.httpclient.HttpMethodBase;
+import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
+import org.apache.commons.httpclient.methods.DeleteMethod;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.methods.InputStreamRequestEntity;
+import org.apache.commons.httpclient.methods.PostMethod;
+import org.apache.commons.httpclient.methods.PutMethod;
+import org.apache.commons.httpclient.methods.RequestEntity;
+import org.apache.commons.httpclient.methods.StringRequestEntity;
+
+/**
+ * Helper wrapper around a Remote Request, to be performed by the
+ * {@link RemoteConnectorService}.
+ *
+ * @author Nick Burch
+ * @since 4.0.2
+ */
+public class RemoteConnectorRequestImpl implements RemoteConnectorRequest
+{
+ public static final String HEADER_CONTENT_TYPE = "Content-Type";
+
+ private final String url;
+ private final String methodName;
+ private final HttpMethodBase method;
+ private final List headers = new ArrayList();
+ private RequestEntity requestBody;
+
+ public RemoteConnectorRequestImpl(String url, String methodName)
+ {
+ this(url, buildHttpClientMethod(url, methodName));
+ }
+ public RemoteConnectorRequestImpl(String url, Class extends HttpMethodBase> method)
+ {
+ this(url, buildHttpClientMethod(url, method));
+ }
+ private RemoteConnectorRequestImpl(String url, HttpMethodBase method)
+ {
+ this.url = url;
+ this.method = method;
+ this.methodName = method.getName();
+ }
+
+ protected static HttpMethodBase buildHttpClientMethod(String url, String method)
+ {
+ if ("GET".equals(method))
+ {
+ return new GetMethod(url);
+ }
+ if ("POST".equals(method))
+ {
+ return new PostMethod(url);
+ }
+ if ("PUT".equals(method))
+ {
+ return new PutMethod(url);
+ }
+ if ("DELETE".equals(method))
+ {
+ return new DeleteMethod(url);
+ }
+ if (TestingMethod.METHOD_NAME.equals(method))
+ {
+ return new TestingMethod(url);
+ }
+ throw new UnsupportedOperationException("Method '"+method+"' not supported");
+ }
+ protected static HttpMethodBase buildHttpClientMethod(String url, Class extends HttpMethodBase> method)
+ {
+ HttpMethodBase request = null;
+ try
+ {
+ request = method.getConstructor(String.class).newInstance(url);
+ }
+ catch(Exception e)
+ {
+ throw new AlfrescoRuntimeException("HttpClient broken", e);
+ }
+ return request;
+ }
+
+ public String getURL()
+ {
+ return url;
+ }
+ public String getMethod()
+ {
+ return methodName;
+ }
+ public HttpMethodBase getMethodInstance()
+ {
+ return method;
+ }
+
+ public String getContentType()
+ {
+ for (Header hdr : headers)
+ {
+ if (HEADER_CONTENT_TYPE.equals( hdr.getName() ))
+ {
+ return hdr.getValue();
+ }
+ }
+ return null;
+ }
+ public void setContentType(String contentType)
+ {
+ for (Header hdr : headers)
+ {
+ if (HEADER_CONTENT_TYPE.equals( hdr.getName() ))
+ {
+ hdr.setValue(contentType);
+ return;
+ }
+ }
+ headers.add(new Header(HEADER_CONTENT_TYPE, contentType));
+ }
+
+ public RequestEntity getRequestBody()
+ {
+ return requestBody;
+ }
+ public void setRequestBody(String body)
+ {
+ try
+ {
+ requestBody = new StringRequestEntity(body, getContentType(), "UTF-8");
+ }
+ catch (UnsupportedEncodingException e) {} // Can't occur
+ }
+ public void setRequestBody(byte[] body)
+ {
+ requestBody = new ByteArrayRequestEntity(body);
+ }
+ public void setRequestBody(InputStream body)
+ {
+ requestBody = new InputStreamRequestEntity(body);
+ }
+ public void setRequestBody(RequestEntity body)
+ {
+ requestBody = body;
+ }
+
+ public Header[] getRequestHeaders()
+ {
+ return headers.toArray(new Header[headers.size()]);
+ }
+ public void addRequestHeader(Header header)
+ {
+ addRequestHeaders(new Header[] {header});
+ }
+ public void addRequestHeader(String name, String value)
+ {
+ addRequestHeader(new Header(name,value));
+ }
+ public void addRequestHeaders(Header[] headers)
+ {
+ for (Header newHdr : headers)
+ {
+ // See if we already have one of these headers
+ Header existingHdr = null;
+ for (Header hdr : this.headers)
+ {
+ if (newHdr.getName().equals( hdr.getName() ))
+ {
+ existingHdr = hdr;
+ }
+ }
+
+ // Update or add as needed
+ if (existingHdr != null)
+ {
+ existingHdr.setValue(newHdr.getValue());
+ }
+ else
+ {
+ this.headers.add(newHdr);
+ }
+ }
+ }
+
+ /**
+ * An HttpClient Method implementation for the method "TESTING",
+ * which we use in certain unit tests
+ */
+ private static class TestingMethod extends GetMethod
+ {
+ private static final String METHOD_NAME = "TESTING";
+
+ private TestingMethod(String url)
+ {
+ super(url);
+ }
+
+ @Override
+ public String getName()
+ {
+ return METHOD_NAME;
+ }
+ }
+}
diff --git a/source/java/org/alfresco/repo/remoteconnector/RemoteConnectorResponseImpl.java b/source/java/org/alfresco/repo/remoteconnector/RemoteConnectorResponseImpl.java
new file mode 100644
index 0000000000..b926974784
--- /dev/null
+++ b/source/java/org/alfresco/repo/remoteconnector/RemoteConnectorResponseImpl.java
@@ -0,0 +1,141 @@
+/*
+ * 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 .
+ */
+package org.alfresco.repo.remoteconnector;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.alfresco.service.cmr.remoteconnector.RemoteConnectorRequest;
+import org.alfresco.service.cmr.remoteconnector.RemoteConnectorResponse;
+import org.alfresco.service.cmr.remoteconnector.RemoteConnectorService;
+import org.apache.commons.httpclient.Header;
+import org.apache.tika.io.IOUtils;
+
+
+/**
+ * Helper wrapper around a Remote Request, to be performed by the
+ * {@link RemoteConnectorService}.
+ *
+ * @author Nick Burch
+ * @since 4.0.2
+ */
+public class RemoteConnectorResponseImpl implements RemoteConnectorResponse
+{
+ private RemoteConnectorRequest request;
+ private String contentType;
+ private String charset;
+
+ private Header[] headers;
+
+ private InputStream bodyStream;
+ private byte[] bodyBytes;
+
+ /**
+ * Creates a new Response object with the data coming from a stream.
+ * Because of the HttpClient lifecycle, a HttpClient response
+ * InputStream shouldn't be used as cleanup is needed
+ */
+ public RemoteConnectorResponseImpl(RemoteConnectorRequest request, String contentType,
+ String charset, Header[] headers, InputStream response)
+ {
+ this.request = request;
+ this.contentType = contentType;
+ this.charset = charset;
+ this.headers = headers;
+ this.bodyStream = response;
+ this.bodyBytes = null;
+ }
+ public RemoteConnectorResponseImpl(RemoteConnectorRequest request, String contentType,
+ String charset, Header[] headers, byte[] response)
+ {
+ this(request, contentType, charset, headers, new ByteArrayInputStream(response));
+ this.bodyBytes = response;
+ }
+
+ @Override
+ public String getCharset()
+ {
+ return charset;
+ }
+
+ @Override
+ public String getContentType()
+ {
+ int split = contentType.indexOf(';');
+ if (split == -1)
+ {
+ return contentType;
+ }
+ else
+ {
+ return contentType.substring(0, split);
+ }
+ }
+
+ @Override
+ public String getRawContentType()
+ {
+ return contentType;
+ }
+
+ @Override
+ public RemoteConnectorRequest getRequest()
+ {
+ return request;
+ }
+
+ @Override
+ public Header[] getResponseHeaders()
+ {
+ return headers;
+ }
+
+ @Override
+ public byte[] getResponseBodyAsBytes() throws IOException
+ {
+ if (bodyBytes == null)
+ {
+ bodyBytes = IOUtils.toByteArray(bodyStream);
+ bodyStream.close();
+
+ // Build a new stream version in case they also want that
+ bodyStream = new ByteArrayInputStream(bodyBytes);
+ }
+ return bodyBytes;
+ }
+
+ @Override
+ public InputStream getResponseBodyAsStream()
+ {
+ return bodyStream;
+ }
+
+ @Override
+ public String getResponseBodyAsString() throws IOException
+ {
+ String charset = this.charset;
+ if (charset == null)
+ {
+ charset = "UTF-8";
+ }
+
+ return new String(getResponseBodyAsBytes(), charset);
+ }
+}
diff --git a/source/java/org/alfresco/repo/remoteconnector/RemoteConnectorServiceImpl.java b/source/java/org/alfresco/repo/remoteconnector/RemoteConnectorServiceImpl.java
new file mode 100644
index 0000000000..ff2fbf2911
--- /dev/null
+++ b/source/java/org/alfresco/repo/remoteconnector/RemoteConnectorServiceImpl.java
@@ -0,0 +1,229 @@
+/*
+ * 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 .
+ */
+package org.alfresco.repo.remoteconnector;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.alfresco.repo.content.MimetypeMap;
+import org.alfresco.repo.security.authentication.AuthenticationException;
+import org.alfresco.service.cmr.remoteconnector.RemoteConnectorRequest;
+import org.alfresco.service.cmr.remoteconnector.RemoteConnectorResponse;
+import org.alfresco.service.cmr.remoteconnector.RemoteConnectorService;
+import org.apache.commons.httpclient.Header;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpMethodBase;
+import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
+import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
+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.json.simple.parser.JSONParser;
+import org.springframework.extensions.webscripts.Status;
+
+/**
+ * HttpClient powered implementation of {@link RemoteConnectorService}, which
+ * performs requests to remote HTTP servers
+ *
+ * @author Nick Burch
+ * @since 4.0.2
+ */
+public class RemoteConnectorServiceImpl implements RemoteConnectorService
+{
+ /**
+ * The logger
+ */
+ private static Log logger = LogFactory.getLog(RemoteConnectorServiceImpl.class);
+ private static final long MAX_BUFFER_RESPONSE_SIZE = 10*1024*1024;
+
+ private HttpClient httpClient;
+
+ public RemoteConnectorServiceImpl()
+ {
+ httpClient = new HttpClient();
+ httpClient.setHttpConnectionManager(new MultiThreadedHttpConnectionManager());
+ }
+
+ /**
+ * Builds a new Request object
+ */
+ public RemoteConnectorRequest buildRequest(String url, String method)
+ {
+ return new RemoteConnectorRequestImpl(url, method);
+ }
+
+ /**
+ * Builds a new Request object, using HttpClient method descriptions
+ */
+ public RemoteConnectorRequest buildRequest(String url, Class extends HttpMethodBase> method)
+ {
+ return new RemoteConnectorRequestImpl(url, method);
+ }
+
+ /**
+ * Executes the specified request, and return the response
+ */
+ public RemoteConnectorResponse executeRequest(RemoteConnectorRequest request) throws IOException, AuthenticationException
+ {
+ RemoteConnectorRequestImpl reqImpl = (RemoteConnectorRequestImpl)request;
+ HttpMethodBase httpRequest = reqImpl.getMethodInstance();
+
+ // Attach the headers to the request
+ for (Header hdr : request.getRequestHeaders())
+ {
+ httpRequest.addRequestHeader(hdr);
+ }
+
+ // Attach the body, if possible
+ if (httpRequest instanceof EntityEnclosingMethod)
+ {
+ if (request.getRequestBody() != null)
+ {
+ ((EntityEnclosingMethod)httpRequest).setRequestEntity( reqImpl.getRequestBody() );
+ }
+ }
+
+ // Log what we're doing
+ if (logger.isDebugEnabled())
+ logger.debug("Performing " + request.getMethod() + " request to " + request.getURL());
+
+ // Perform the request
+ int status = httpClient.executeMethod(httpRequest);
+ String statusText = httpRequest.getStatusText();
+
+ Header[] responseHdrs = httpRequest.getResponseHeaders();
+ Header responseContentTypeH = httpRequest.getResponseHeader(RemoteConnectorRequestImpl.HEADER_CONTENT_TYPE);
+ String responseCharSet = httpRequest.getResponseCharSet();
+ String responseContentType = (responseContentTypeH != null ? responseContentTypeH.getValue() : null);
+
+
+ // Decide on how best to handle the response, based on the size
+ // Ideally, we want to close the HttpClient resources immediately, but
+ // that isn't possible for very large responses
+ RemoteConnectorResponse response = null;
+ if (httpRequest.getResponseContentLength() > MAX_BUFFER_RESPONSE_SIZE)
+ {
+ // Need to wrap the InputStream in something that'll close
+ InputStream wrappedStream = new HttpClientReleasingInputStream(httpRequest);
+
+ // Now build the response
+ response = new RemoteConnectorResponseImpl(request, responseContentType, responseCharSet,
+ responseHdrs, wrappedStream);
+ }
+ else
+ {
+ // Fairly small response, just keep the bytes and make life simple
+ response = new RemoteConnectorResponseImpl(request, responseContentType, responseCharSet,
+ responseHdrs, httpRequest.getResponseBody());
+
+ // Now we have the bytes, we can close the HttpClient resources
+ httpRequest.releaseConnection();
+ httpRequest = null;
+ }
+
+
+ // Log the response
+ if (logger.isDebugEnabled())
+ logger.debug("Response was " + status + " " + statusText);
+
+ // Decide if we should throw an exception
+ if (status == Status.STATUS_FORBIDDEN)
+ {
+ // Tidy if needed
+ if (httpRequest != null)
+ httpRequest.releaseConnection();
+ // Then report the error
+ throw new AuthenticationException(statusText);
+ }
+ if (status == Status.STATUS_INTERNAL_SERVER_ERROR)
+ {
+ // Tidy if needed
+ if (httpRequest != null)
+ httpRequest.releaseConnection();
+ // Then report the error
+ throw new IOException(statusText);
+ }
+ // TODO Handle the rest of the different status codes
+
+
+ // Return our created response
+ 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 doExecuteJSONRequest(request, this);
+ }
+
+ public static JSONObject doExecuteJSONRequest(RemoteConnectorRequest request, RemoteConnectorService service) throws ParseException, IOException, AuthenticationException
+ {
+ // Set as JSON
+ request.setContentType(MimetypeMap.MIMETYPE_JSON);
+
+ // Perform the request
+ RemoteConnectorResponse response = service.executeRequest(request);
+
+ // Parse this as JSON
+ JSONParser parser = new JSONParser();
+ String jsonText = response.getResponseBodyAsString();
+ Object json = parser.parse(jsonText);
+
+ // Check it's the right type and return
+ if (json instanceof JSONObject)
+ {
+ return (JSONObject)json;
+ }
+ else
+ {
+ throw new ParseException(0, json);
+ }
+ }
+
+ private static class HttpClientReleasingInputStream extends FilterInputStream
+ {
+ private HttpMethodBase httpRequest;
+ private HttpClientReleasingInputStream(HttpMethodBase httpRequest) throws IOException
+ {
+ super(httpRequest.getResponseBodyAsStream());
+ this.httpRequest = httpRequest;
+ }
+
+ @Override
+ public void close() throws IOException
+ {
+ // Tidy the main stream
+ super.close();
+
+ // Now release the underlying resources
+ if (httpRequest != null)
+ {
+ httpRequest.releaseConnection();
+ httpRequest = null;
+ }
+ }
+ }
+}
diff --git a/source/java/org/alfresco/repo/remotecredentials/AbstractCredentialsImpl.java b/source/java/org/alfresco/repo/remotecredentials/AbstractCredentialsImpl.java
new file mode 100644
index 0000000000..3a605b8be4
--- /dev/null
+++ b/source/java/org/alfresco/repo/remotecredentials/AbstractCredentialsImpl.java
@@ -0,0 +1,121 @@
+/*
+ * 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 .
+ */
+package org.alfresco.repo.remotecredentials;
+
+import org.alfresco.service.cmr.remotecredentials.BaseCredentialsInfo;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.namespace.QName;
+
+/**
+ * This class is the parent of a set of Remote Credentials
+ *
+ * @author Nick Burch
+ * @since Odin
+ */
+public abstract class AbstractCredentialsImpl implements BaseCredentialsInfo
+{
+ private static final long serialVersionUID = 1825070334051269906L;
+
+ private QName type;
+ private NodeRef nodeRef;
+ private String remoteSystemName;
+ private NodeRef remoteSystemContainerNodeRef;
+
+ private String remoteUsername;
+ private boolean lastAuthenticationSucceeded;
+
+ /**
+ * Creates a new, empty {@link AbstractCredentialsImpl} ready
+ * to be stored later
+ */
+ public AbstractCredentialsImpl(QName type)
+ {
+ this.type = type;
+
+ // Default is that the authentication worked, unless told otherwise
+ this.lastAuthenticationSucceeded = true;
+ }
+
+ public AbstractCredentialsImpl(NodeRef nodeRef, QName type, String remoteSystemName, NodeRef remoteSystemContainerNodeRef)
+ {
+ this(type);
+
+ // Record the node details
+ this.nodeRef = nodeRef;
+ this.remoteSystemName = remoteSystemName;
+ this.remoteSystemContainerNodeRef = remoteSystemContainerNodeRef;
+ }
+
+ /**
+ * @return the NodeRef of the underlying credentials
+ */
+ public NodeRef getNodeRef()
+ {
+ return nodeRef;
+ }
+
+ /**
+ * @return the Type of the underlying credentials
+ */
+ public QName getCredentialsType()
+ {
+ return type;
+ }
+
+ /**
+ * @return the Remote System Name the credentials belong to
+ */
+ public String getRemoteSystemName()
+ {
+ return remoteSystemName;
+ }
+
+ /**
+ * @return the NodeRef of the container for the Remote System
+ */
+ public NodeRef getRemoteSystemContainerNodeRef()
+ {
+ return remoteSystemContainerNodeRef;
+ }
+
+
+ /**
+ * @return the Remote Username
+ */
+ public String getRemoteUsername()
+ {
+ return remoteUsername;
+ }
+ public void setRemoteUsername(String username)
+ {
+ this.remoteUsername = username;
+ }
+
+ /**
+ * @return whether the last authentication attempt succeeded
+ */
+ public boolean getLastAuthenticationSucceeded()
+ {
+ return lastAuthenticationSucceeded;
+ }
+ public void setLastAuthenticationSucceeded(boolean succeeded)
+ {
+ this.lastAuthenticationSucceeded = succeeded;
+ }
+}
diff --git a/source/java/org/alfresco/repo/remotecredentials/OAuth1CredentialsFactory.java b/source/java/org/alfresco/repo/remotecredentials/OAuth1CredentialsFactory.java
new file mode 100644
index 0000000000..9a61976c75
--- /dev/null
+++ b/source/java/org/alfresco/repo/remotecredentials/OAuth1CredentialsFactory.java
@@ -0,0 +1,99 @@
+/*
+ * 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 .
+ */
+package org.alfresco.repo.remotecredentials;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.alfresco.repo.node.encryption.MetadataEncryptor;
+import org.alfresco.service.cmr.remotecredentials.BaseCredentialsInfo;
+import org.alfresco.service.cmr.remotecredentials.OAuth1CredentialsInfo;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.namespace.QName;
+
+/**
+ * The Factory for building {@link OAuth1CredentialsInfo} objects
+ *
+ * @author Nick Burch
+ * @since Odin
+ */
+public class OAuth1CredentialsFactory implements RemoteCredentialsInfoFactory
+{
+ private MetadataEncryptor metadataEncryptor;
+
+ public void setMetadataEncryptor(MetadataEncryptor metadataEncryptor)
+ {
+ this.metadataEncryptor = metadataEncryptor;
+ }
+
+ /**
+ * Creates a new {@link OAuth1CredentialsInfo} based on the details of the underlying node.
+ */
+ public OAuth1CredentialsInfo createCredentials(QName type, NodeRef nodeRef, String remoteSystemName,
+ NodeRef remoteSystemContainerNodeRef, Map properties)
+ {
+ // Decrypt the token and secret
+ String token = (String)metadataEncryptor.decrypt(
+ RemoteCredentialsModel.PROP_OAUTH1_TOKEN, properties.get(RemoteCredentialsModel.PROP_OAUTH1_TOKEN));
+ String secret = (String)metadataEncryptor.decrypt(
+ RemoteCredentialsModel.PROP_OAUTH1_TOKEN_SECRET, properties.get(RemoteCredentialsModel.PROP_OAUTH1_TOKEN_SECRET));
+
+ // Build the object
+ OAuth1CredentialsInfoImpl credentials =
+ new OAuth1CredentialsInfoImpl(nodeRef, remoteSystemName, remoteSystemContainerNodeRef);
+
+ // Populate
+ RemoteCredentialsInfoFactory.FactoryHelper.setCoreCredentials(credentials, properties);
+ credentials.setOAuthToken(token);
+ credentials.setOAuthSecret(secret);
+
+ // All done
+ return credentials;
+ }
+
+ /**
+ * Serializes the given {@link BaseCredentialsInfo} object to node properties.
+ *
+ * @param info The Credentials object to serialize
+ * @param coreProperties The core rc:credentialBase properties for the node
+ * @return The final set of properties to be serialized for the node
+ */
+ public Map serializeCredentials(BaseCredentialsInfo info)
+ {
+ if (! (info instanceof OAuth1CredentialsInfo))
+ {
+ throw new IllegalStateException("Incorrect registration, info must be a OAuth1CredentialsInfo");
+ }
+
+ // Encrypt the details
+ OAuth1CredentialsInfo credentials = (OAuth1CredentialsInfo)info;
+
+ Serializable tokenEncrypted = metadataEncryptor.encrypt(
+ RemoteCredentialsModel.PROP_OAUTH1_TOKEN, credentials.getOAuthToken());
+ Serializable secretEncrypted = metadataEncryptor.encrypt(
+ RemoteCredentialsModel.PROP_OAUTH1_TOKEN_SECRET, credentials.getOAuthSecret());
+
+ // Store our specific types and return
+ Map properties = new HashMap();
+ properties.put(RemoteCredentialsModel.PROP_OAUTH1_TOKEN, tokenEncrypted);
+ properties.put(RemoteCredentialsModel.PROP_OAUTH1_TOKEN_SECRET, secretEncrypted);
+ return properties;
+ }
+}
diff --git a/source/java/org/alfresco/repo/remotecredentials/OAuth1CredentialsInfoImpl.java b/source/java/org/alfresco/repo/remotecredentials/OAuth1CredentialsInfoImpl.java
new file mode 100644
index 0000000000..5557d56b92
--- /dev/null
+++ b/source/java/org/alfresco/repo/remotecredentials/OAuth1CredentialsInfoImpl.java
@@ -0,0 +1,72 @@
+/*
+ * 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 .
+ */
+package org.alfresco.repo.remotecredentials;
+
+import org.alfresco.service.cmr.remotecredentials.OAuth1CredentialsInfo;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.namespace.QName;
+
+/**
+ * This class represents an OAuth 1.0 based set of credentials
+ *
+ * @author Nick Burch
+ * @since Odin
+ */
+public class OAuth1CredentialsInfoImpl extends AbstractCredentialsImpl implements OAuth1CredentialsInfo
+{
+ private static final long serialVersionUID = 4739556616590284462L;
+ private static final QName TYPE = RemoteCredentialsModel.TYPE_OAUTH1_CREDENTIALS;
+
+ private String oauthToken;
+ private String oauthTokenSecret;
+
+ public OAuth1CredentialsInfoImpl()
+ {
+ super(TYPE);
+ }
+
+ public OAuth1CredentialsInfoImpl(NodeRef nodeRef, String remoteSystemName, NodeRef remoteSystemContainerNodeRef)
+ {
+ super(nodeRef, TYPE, remoteSystemName, remoteSystemContainerNodeRef);
+ }
+
+ /**
+ * @return the OAuth Token Identifier
+ */
+ public String getOAuthToken()
+ {
+ return oauthToken;
+ }
+ public void setOAuthToken(String token)
+ {
+ this.oauthToken = token;
+ }
+
+ /**
+ * @return the OAuth Token Secret
+ */
+ public String getOAuthSecret()
+ {
+ return oauthTokenSecret;
+ }
+ public void setOAuthSecret(String secret)
+ {
+ this.oauthTokenSecret = secret;
+ }
+}
diff --git a/source/java/org/alfresco/repo/remotecredentials/OAuth2CredentialsFactory.java b/source/java/org/alfresco/repo/remotecredentials/OAuth2CredentialsFactory.java
new file mode 100644
index 0000000000..e3fd4ff6be
--- /dev/null
+++ b/source/java/org/alfresco/repo/remotecredentials/OAuth2CredentialsFactory.java
@@ -0,0 +1,108 @@
+/*
+ * 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 .
+ */
+package org.alfresco.repo.remotecredentials;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.alfresco.repo.node.encryption.MetadataEncryptor;
+import org.alfresco.service.cmr.remotecredentials.BaseCredentialsInfo;
+import org.alfresco.service.cmr.remotecredentials.OAuth2CredentialsInfo;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.namespace.QName;
+
+/**
+ * The Factory for building {@link OAuth2CredentialsInfo} objects
+ *
+ * @author Nick Burch
+ * @since Odin
+ */
+public class OAuth2CredentialsFactory implements RemoteCredentialsInfoFactory
+{
+ private MetadataEncryptor metadataEncryptor;
+
+ public void setMetadataEncryptor(MetadataEncryptor metadataEncryptor)
+ {
+ this.metadataEncryptor = metadataEncryptor;
+ }
+
+ /**
+ * Creates a new {@link OAuth2CredentialsInfo} based on the details of the underlying node.
+ */
+ public OAuth2CredentialsInfo createCredentials(QName type, NodeRef nodeRef, String remoteSystemName,
+ NodeRef remoteSystemContainerNodeRef, Map properties)
+ {
+ // Decrypt the token details
+ String accessToken = (String)metadataEncryptor.decrypt(
+ RemoteCredentialsModel.PROP_OAUTH2_ACCESS_TOKEN, properties.get(RemoteCredentialsModel.PROP_OAUTH2_ACCESS_TOKEN));
+ String refreshToken = (String)metadataEncryptor.decrypt(
+ RemoteCredentialsModel.PROP_OAUTH2_REFRESH_TOKEN, properties.get(RemoteCredentialsModel.PROP_OAUTH2_REFRESH_TOKEN));
+
+ // Get the dates
+ Date tokenIssuedAt = (Date)properties.get(RemoteCredentialsModel.PROP_OAUTH2_TOKEN_ISSUED_AT);
+ Date tokenExpiresAt = (Date)properties.get(RemoteCredentialsModel.PROP_OAUTH2_TOKEN_EXPIRES_AT);
+
+ // Build the object
+ OAuth2CredentialsInfoImpl credentials =
+ new OAuth2CredentialsInfoImpl(nodeRef, remoteSystemName, remoteSystemContainerNodeRef);
+
+ // Populate
+ RemoteCredentialsInfoFactory.FactoryHelper.setCoreCredentials(credentials, properties);
+ credentials.setOauthAccessToken(accessToken);
+ credentials.setOauthRefreshToken(refreshToken);
+ credentials.setOauthTokenIssuedAt(tokenIssuedAt);
+ credentials.setOauthTokenExpiresAt(tokenExpiresAt);
+
+ // All done
+ return credentials;
+ }
+
+ /**
+ * Serializes the given {@link BaseCredentialsInfo} object to node properties.
+ *
+ * @param info The Credentials object to serialize
+ * @param coreProperties The core rc:credentialBase properties for the node
+ * @return The final set of properties to be serialized for the node
+ */
+ public Map serializeCredentials(BaseCredentialsInfo info)
+ {
+ if (! (info instanceof OAuth2CredentialsInfo))
+ {
+ throw new IllegalStateException("Incorrect registration, info must be a OAuth2CredentialsInfo");
+ }
+
+ // Encrypt the token details
+ OAuth2CredentialsInfo credentials = (OAuth2CredentialsInfo)info;
+
+ Serializable accessTokenEncrypted = metadataEncryptor.encrypt(
+ RemoteCredentialsModel.PROP_OAUTH2_ACCESS_TOKEN, credentials.getOAuthAccessToken());
+ Serializable refreshTokenEncrypted = metadataEncryptor.encrypt(
+ RemoteCredentialsModel.PROP_OAUTH2_REFRESH_TOKEN, credentials.getOAuthRefreshToken());
+
+ // Store our specific types and return
+ Map properties = new HashMap();
+ properties.put(RemoteCredentialsModel.PROP_OAUTH2_ACCESS_TOKEN, accessTokenEncrypted);
+ properties.put(RemoteCredentialsModel.PROP_OAUTH2_REFRESH_TOKEN, refreshTokenEncrypted);
+ properties.put(RemoteCredentialsModel.PROP_OAUTH2_TOKEN_ISSUED_AT, credentials.getOAuthTicketIssuedAt());
+ properties.put(RemoteCredentialsModel.PROP_OAUTH2_TOKEN_EXPIRES_AT, credentials.getOAuthTicketExpiresAt());
+ return properties;
+ }
+}
diff --git a/source/java/org/alfresco/repo/remotecredentials/OAuth2CredentialsInfoImpl.java b/source/java/org/alfresco/repo/remotecredentials/OAuth2CredentialsInfoImpl.java
new file mode 100644
index 0000000000..cf0ebcb0fe
--- /dev/null
+++ b/source/java/org/alfresco/repo/remotecredentials/OAuth2CredentialsInfoImpl.java
@@ -0,0 +1,100 @@
+/*
+ * 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 .
+ */
+package org.alfresco.repo.remotecredentials;
+
+import java.util.Date;
+
+import org.alfresco.service.cmr.remotecredentials.OAuth2CredentialsInfo;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.namespace.QName;
+
+/**
+ * This class represents an OAuth 2.0 based set of credentials
+ *
+ * @author Nick Burch
+ * @since Odin
+ */
+public class OAuth2CredentialsInfoImpl extends AbstractCredentialsImpl implements OAuth2CredentialsInfo
+{
+ private static final long serialVersionUID = 4739556616590284462L;
+ private static final QName TYPE = RemoteCredentialsModel.TYPE_OAUTH2_CREDENTIALS;
+
+ private String oauthAccessToken;
+ private String oauthRefreshToken;
+ private Date oauthTokenExpiresAt;
+ private Date oauthTokenIssuedAt;
+
+ public OAuth2CredentialsInfoImpl()
+ {
+ super(TYPE);
+ }
+
+ public OAuth2CredentialsInfoImpl(NodeRef nodeRef, String remoteSystemName, NodeRef remoteSystemContainerNodeRef)
+ {
+ super(nodeRef, TYPE, remoteSystemName, remoteSystemContainerNodeRef);
+ }
+
+ /**
+ * @return the OAuth Access Token
+ */
+ public String getOAuthAccessToken()
+ {
+ return oauthAccessToken;
+ }
+ public void setOauthAccessToken(String oauthAccessToken)
+ {
+ this.oauthAccessToken = oauthAccessToken;
+ }
+
+ /**
+ * @return the OAuth Refresh
+ */
+ public String getOAuthRefreshToken()
+ {
+ return oauthRefreshToken;
+ }
+ public void setOauthRefreshToken(String oauthRefreshToken)
+ {
+ this.oauthRefreshToken = oauthRefreshToken;
+ }
+
+ /**
+ * @return When the Access Token was Issued
+ */
+ public Date getOAuthTicketIssuedAt()
+ {
+ return oauthTokenIssuedAt;
+ }
+ public void setOauthTokenIssuedAt(Date oauthTokenIssuedAt)
+ {
+ this.oauthTokenIssuedAt = oauthTokenIssuedAt;
+ }
+
+ /**
+ * @return When the Access Token will Expire
+ */
+ public Date getOAuthTicketExpiresAt()
+ {
+ return oauthTokenExpiresAt;
+ }
+ public void setOauthTokenExpiresAt(Date oauthTokenExpiresAt)
+ {
+ this.oauthTokenExpiresAt = oauthTokenExpiresAt;
+ }
+}
diff --git a/source/java/org/alfresco/repo/remotecredentials/PasswordCredentialsFactory.java b/source/java/org/alfresco/repo/remotecredentials/PasswordCredentialsFactory.java
new file mode 100644
index 0000000000..c98764001f
--- /dev/null
+++ b/source/java/org/alfresco/repo/remotecredentials/PasswordCredentialsFactory.java
@@ -0,0 +1,92 @@
+/*
+ * 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 .
+ */
+package org.alfresco.repo.remotecredentials;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.alfresco.repo.node.encryption.MetadataEncryptor;
+import org.alfresco.service.cmr.remotecredentials.BaseCredentialsInfo;
+import org.alfresco.service.cmr.remotecredentials.PasswordCredentialsInfo;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.namespace.QName;
+
+/**
+ * The Factory for building {@link PasswordCredentialsInfo} objects
+ *
+ * @author Nick Burch
+ * @since Odin
+ */
+public class PasswordCredentialsFactory implements RemoteCredentialsInfoFactory
+{
+ private MetadataEncryptor metadataEncryptor;
+
+ public void setMetadataEncryptor(MetadataEncryptor metadataEncryptor)
+ {
+ this.metadataEncryptor = metadataEncryptor;
+ }
+
+ /**
+ * Creates a new {@link PasswordCredentialsInfo} based on the details of the underlying node.
+ */
+ public PasswordCredentialsInfo createCredentials(QName type, NodeRef nodeRef, String remoteSystemName,
+ NodeRef remoteSystemContainerNodeRef, Map properties)
+ {
+ // Decrypt the password
+ String password = (String)metadataEncryptor.decrypt(
+ RemoteCredentialsModel.PROP_REMOTE_PASSWORD, properties.get(RemoteCredentialsModel.PROP_REMOTE_PASSWORD));
+
+ // Build the object
+ PasswordCredentialsInfoImpl credentials =
+ new PasswordCredentialsInfoImpl(nodeRef, remoteSystemName, remoteSystemContainerNodeRef);
+
+ // Populate
+ RemoteCredentialsInfoFactory.FactoryHelper.setCoreCredentials(credentials, properties);
+ credentials.setRemotePassword(password);
+
+ // All done
+ return credentials;
+ }
+
+ /**
+ * Serializes the given {@link BaseCredentialsInfo} object to node properties.
+ *
+ * @param info The Credentials object to serialize
+ * @param coreProperties The core rc:credentialBase properties for the node
+ * @return The final set of properties to be serialized for the node
+ */
+ public Map serializeCredentials(BaseCredentialsInfo info)
+ {
+ if (! (info instanceof PasswordCredentialsInfo))
+ {
+ throw new IllegalStateException("Incorrect registration, info must be a PasswordCredentialsInfo");
+ }
+
+ // Encrypt the password
+ PasswordCredentialsInfo credentials = (PasswordCredentialsInfo)info;
+ Serializable passwordEncrypted = metadataEncryptor.encrypt(
+ RemoteCredentialsModel.PROP_REMOTE_PASSWORD, credentials.getRemotePassword());
+
+ // Store our specific types and return
+ Map properties = new HashMap();
+ properties.put(RemoteCredentialsModel.PROP_REMOTE_PASSWORD, passwordEncrypted);
+ return properties;
+ }
+}
\ No newline at end of file
diff --git a/source/java/org/alfresco/repo/remotecredentials/PasswordCredentialsInfoImpl.java b/source/java/org/alfresco/repo/remotecredentials/PasswordCredentialsInfoImpl.java
new file mode 100644
index 0000000000..efad24ff1d
--- /dev/null
+++ b/source/java/org/alfresco/repo/remotecredentials/PasswordCredentialsInfoImpl.java
@@ -0,0 +1,58 @@
+/*
+ * 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 .
+ */
+package org.alfresco.repo.remotecredentials;
+
+import org.alfresco.service.cmr.remotecredentials.PasswordCredentialsInfo;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.namespace.QName;
+
+/**
+ * This class represents a password based set of credentials
+ *
+ * @author Nick Burch
+ * @since Odin
+ */
+public class PasswordCredentialsInfoImpl extends AbstractCredentialsImpl implements PasswordCredentialsInfo
+{
+ private static final long serialVersionUID = -5351115540931076949L;
+ private static final QName TYPE = RemoteCredentialsModel.TYPE_PASSWORD_CREDENTIALS;
+
+ private String remotePassword;
+
+ public PasswordCredentialsInfoImpl()
+ {
+ super(TYPE);
+ }
+ public PasswordCredentialsInfoImpl(NodeRef nodeRef, String remoteSystemName, NodeRef remoteSystemContainerNodeRef)
+ {
+ super(nodeRef, TYPE, remoteSystemName, remoteSystemContainerNodeRef);
+ }
+
+ /**
+ * @return the Remote Password
+ */
+ public String getRemotePassword()
+ {
+ return remotePassword;
+ }
+ public void setRemotePassword(String password)
+ {
+ remotePassword = password;
+ }
+}
diff --git a/source/java/org/alfresco/repo/remotecredentials/RemoteCredentialsInfoFactory.java b/source/java/org/alfresco/repo/remotecredentials/RemoteCredentialsInfoFactory.java
new file mode 100644
index 0000000000..3c80addf77
--- /dev/null
+++ b/source/java/org/alfresco/repo/remotecredentials/RemoteCredentialsInfoFactory.java
@@ -0,0 +1,94 @@
+/*
+ * 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 .
+ */
+package org.alfresco.repo.remotecredentials;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.alfresco.service.cmr.remotecredentials.BaseCredentialsInfo;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.namespace.QName;
+
+/**
+ * The interface which controls how implementations of
+ * {@link BaseCredentialsInfo} are serialized
+ *
+ * @author Nick Burch
+ * @since Odin
+ */
+public interface RemoteCredentialsInfoFactory
+{
+ /**
+ * Creates a new {@link BaseCredentialsInfo} object of the appropriate
+ * type, based on the details of the underlying node.
+ *
+ * @param type The type of the credentials node, a child of rc:credentialBase
+ * @param nodeRef The NodeRef of the credentials node
+ * @param properties All the node properties
+ */
+ public BaseCredentialsInfo createCredentials(QName type, NodeRef nodeRef, String remoteSystemName,
+ NodeRef remoteSystemContainerNodeRef, Map properties);
+
+ /**
+ * Serializes the given {@link BaseCredentialsInfo} object to node properties.
+ *
+ * @param info The Credentials object to serialize
+ * @return The properties to be serialized for the node
+ */
+ public Map serializeCredentials(BaseCredentialsInfo info);
+
+ /**
+ * Helper class for implementations of {@link RemoteCredentialsInfoFactory}
+ */
+ public static class FactoryHelper
+ {
+ /**
+ * Sets the core properties on a {@link AbstractCredentialsImpl}
+ */
+ public static void setCoreCredentials(AbstractCredentialsImpl credentials, Map properties)
+ {
+ credentials.setRemoteUsername(
+ (String)properties.get(RemoteCredentialsModel.PROP_REMOTE_USERNAME)
+ );
+
+ Boolean succeeded = (Boolean)properties.get(RemoteCredentialsModel.PROP_LAST_AUTHENTICATION_SUCCEEDED);
+ if (succeeded != null)
+ {
+ credentials.setLastAuthenticationSucceeded(succeeded.booleanValue());
+ }
+ else
+ {
+ // Default is that it did
+ credentials.setLastAuthenticationSucceeded(true);
+ }
+ }
+
+ /**
+ * Generates the core properties for a {@link BaseCredentialsInfo}
+ */
+ public static Map getCoreCredentials(BaseCredentialsInfo credentials)
+ {
+ Map properties = new HashMap();
+ properties.put(RemoteCredentialsModel.PROP_REMOTE_USERNAME, credentials.getRemoteUsername());
+ properties.put(RemoteCredentialsModel.PROP_LAST_AUTHENTICATION_SUCCEEDED, credentials.getLastAuthenticationSucceeded());
+ return properties;
+ }
+ }
+}
\ No newline at end of file
diff --git a/source/java/org/alfresco/repo/remotecredentials/RemoteCredentialsModel.java b/source/java/org/alfresco/repo/remotecredentials/RemoteCredentialsModel.java
new file mode 100644
index 0000000000..e4ad888534
--- /dev/null
+++ b/source/java/org/alfresco/repo/remotecredentials/RemoteCredentialsModel.java
@@ -0,0 +1,63 @@
+/*
+ * 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 .
+ */
+package org.alfresco.repo.remotecredentials;
+
+import org.alfresco.service.namespace.QName;
+
+/**
+ * Remote Credentials models constants
+ *
+ * @author Nick Burch
+ * @since Odin
+ */
+public interface RemoteCredentialsModel
+{
+ /** Remote Credentials Model */
+ public static final String REMOTE_CREDENTIALS_MODEL_URL = "http://www.alfresco.org/model/remotecredentials/1.0";
+ public static final String REMOTE_CREDENTIALS_MODEL_PREFIX = "rc";
+
+ /** Applied to something that holds rc:remoteCredentialsSystem objects */
+ public static final QName ASPECT_REMOTE_CREDENTIALS_SYSTEM_CONTAINER = QName.createQName(REMOTE_CREDENTIALS_MODEL_URL, "remoteCredentialsSystemContainer");
+ public static final QName ASSOC_CREDENTIALS_SYSTEM = QName.createQName(REMOTE_CREDENTIALS_MODEL_URL, "credentialsSystem");
+
+ /** Remote Credentials Holder */
+ public static final QName TYPE_REMOTE_CREDENTIALS_SYSTEM = QName.createQName(REMOTE_CREDENTIALS_MODEL_URL, "remoteCredentialsSystem");
+ public static final QName ASSOC_CREDENTIALS = QName.createQName(REMOTE_CREDENTIALS_MODEL_URL, "credentials");
+
+ /** Credentials Base */
+ public static final QName TYPE_CREDENTIALS_BASE = QName.createQName(REMOTE_CREDENTIALS_MODEL_URL, "credentialBase");
+ public static final QName PROP_REMOTE_USERNAME = QName.createQName(REMOTE_CREDENTIALS_MODEL_URL, "remoteUsername");
+ public static final QName PROP_LAST_AUTHENTICATION_SUCCEEDED = QName.createQName(REMOTE_CREDENTIALS_MODEL_URL, "lastAuthenticationSucceeded");
+
+ /** Password Credentials */
+ public static final QName TYPE_PASSWORD_CREDENTIALS = QName.createQName(REMOTE_CREDENTIALS_MODEL_URL, "passwordCredentials");
+ public static final QName PROP_REMOTE_PASSWORD = QName.createQName(REMOTE_CREDENTIALS_MODEL_URL, "remotePassword");
+
+ /** OAuth 1.0 Credentials */
+ public static final QName TYPE_OAUTH1_CREDENTIALS = QName.createQName(REMOTE_CREDENTIALS_MODEL_URL, "oauth1Credentials");
+ public static final QName PROP_OAUTH1_TOKEN = QName.createQName(REMOTE_CREDENTIALS_MODEL_URL, "oauth1Token");
+ public static final QName PROP_OAUTH1_TOKEN_SECRET = QName.createQName(REMOTE_CREDENTIALS_MODEL_URL, "oauth1TokenSecret");
+
+ /** OAuth 2.0 Credentials */
+ public static final QName TYPE_OAUTH2_CREDENTIALS = QName.createQName(REMOTE_CREDENTIALS_MODEL_URL, "oauth2Credentials");
+ public static final QName PROP_OAUTH2_ACCESS_TOKEN = QName.createQName(REMOTE_CREDENTIALS_MODEL_URL, "oauth2AccessToken");
+ public static final QName PROP_OAUTH2_REFRESH_TOKEN = QName.createQName(REMOTE_CREDENTIALS_MODEL_URL, "oauth2RefreshToken");
+ public static final QName PROP_OAUTH2_TOKEN_ISSUED_AT = QName.createQName(REMOTE_CREDENTIALS_MODEL_URL, "oauth2TokenIssuedAt");
+ public static final QName PROP_OAUTH2_TOKEN_EXPIRES_AT = QName.createQName(REMOTE_CREDENTIALS_MODEL_URL, "oauth2TokenExpiresAt");
+}
\ No newline at end of file
diff --git a/source/java/org/alfresco/repo/remotecredentials/RemoteCredentialsServiceImpl.java b/source/java/org/alfresco/repo/remotecredentials/RemoteCredentialsServiceImpl.java
new file mode 100644
index 0000000000..94ed8126ba
--- /dev/null
+++ b/source/java/org/alfresco/repo/remotecredentials/RemoteCredentialsServiceImpl.java
@@ -0,0 +1,707 @@
+/*
+ * 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 .
+ */
+package org.alfresco.repo.remotecredentials;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.query.EmptyPagingResults;
+import org.alfresco.query.ListBackedPagingResults;
+import org.alfresco.query.PagingRequest;
+import org.alfresco.query.PagingResults;
+import org.alfresco.repo.model.Repository;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
+import org.alfresco.service.cmr.dictionary.DictionaryService;
+import org.alfresco.service.cmr.remotecredentials.BaseCredentialsInfo;
+import org.alfresco.service.cmr.remotecredentials.RemoteCredentialsService;
+import org.alfresco.service.cmr.repository.ChildAssociationRef;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.repository.datatype.TypeConversionException;
+import org.alfresco.service.namespace.NamespaceService;
+import org.alfresco.service.namespace.QName;
+import org.alfresco.service.namespace.RegexQNamePattern;
+import org.alfresco.util.GUID;
+import org.alfresco.util.Pair;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * An Implementation of the {@link RemoteCredentialsService}
+ *
+ * @author Nick Burch
+ * @since Odin
+ */
+public class RemoteCredentialsServiceImpl implements RemoteCredentialsService
+{
+ /**
+ * The logger
+ */
+ private static Log logger = LogFactory.getLog(RemoteCredentialsServiceImpl.class);
+
+ /**
+ * The name of the System Container used to hold Shared Credentials.
+ * This isn't final, as unit tests change it to avoid trampling on
+ * the real credentials.
+ */
+ private static String SHARED_CREDENTIALS_CONTAINER_NAME = "remote_credentials";
+
+ private Repository repositoryHelper;
+ private NodeService nodeService;
+ private NamespaceService namespaceService;
+ private DictionaryService dictionaryService;
+
+ /**
+ * Controls which Factory will be used to create {@link BaseCredentialsInfo}
+ * instances for a given node, based on the type.
+ * eg rc:passwordCredentials -> PasswordCredentialsFactory
+ */
+ private Map credentialsFactories = new HashMap();
+
+
+ public void setNodeService(NodeService nodeService)
+ {
+ this.nodeService = nodeService;
+ }
+
+ public void setNamespaceService(NamespaceService namespaceService)
+ {
+ this.namespaceService = namespaceService;
+ }
+
+ public void setDictionaryService(DictionaryService dictionaryService)
+ {
+ this.dictionaryService = dictionaryService;
+ }
+
+ public void setRepositoryHelper(Repository repositoryHelper)
+ {
+ this.repositoryHelper = repositoryHelper;
+ }
+
+ /**
+ * Registers a number of new factories
+ */
+ public void setCredentialsFactories(Map factories)
+ {
+ // Convert, eg rc:passwordCredentials -> qname version, then register
+ for (String type : factories.keySet())
+ {
+ RemoteCredentialsInfoFactory factory = factories.get(type);
+ QName typeQ = QName.createQName(type, namespaceService);
+ registerCredentialsFactory(typeQ, factory);
+ }
+ }
+
+ /**
+ * Registers a new Factory to produce {@link BaseCredentialsInfo} objects
+ * for a given data type.
+ * This provides an alternative to {@link #setCredentialsFactories(Map)}
+ * to allow the registering of a new type without overriding all of them.
+ *
+ * @param credentialsType The object type
+ * @param factory The Factory to use to create this type with
+ */
+ public void registerCredentialsFactory(QName credentialsType, RemoteCredentialsInfoFactory factory)
+ {
+ // Check the hierarchy is valid
+ if (! dictionaryService.isSubClass(credentialsType, RemoteCredentialsModel.TYPE_CREDENTIALS_BASE))
+ {
+ logger.warn("Unable to register credentials factory for " + credentialsType +
+ " as that type doesn't inherit from " + RemoteCredentialsModel.TYPE_CREDENTIALS_BASE);
+ return;
+ }
+
+ // Log the new type
+ if (logger.isDebugEnabled())
+ logger.debug("Registering credentials factory for " + credentialsType + " of " + factory);
+
+ // Store it
+ credentialsFactories.put(credentialsType, factory);
+ }
+
+ // --------------------------------------------------------
+
+ private static QName SYSTEM_FOLDER_QNAME =
+ QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "system");
+ private static QName SHARED_CREDENTIALS_CONTAINER_QNAME =
+ QName.createQName(RemoteCredentialsModel.REMOTE_CREDENTIALS_MODEL_URL, SHARED_CREDENTIALS_CONTAINER_NAME);
+ /**
+ * Gets the NodeRef of the holder of shared credentials remote systems.
+ *
+ * This is stored under system
+ *
+ * Protected, so that unit tests can make use of it
+ */
+ protected NodeRef getSharedContainerNodeRef(boolean required)
+ {
+ // Grab the root of the repository, for the current tennant
+ final NodeRef root = repositoryHelper.getRootHome();
+
+ // Locate the system folder, in the root
+ List sysRefs = nodeService.getChildAssocs(
+ root, ContentModel.ASSOC_CHILDREN, SYSTEM_FOLDER_QNAME);
+ if (sysRefs.size() != 1)
+ {
+ throw new IllegalStateException("System folder missing / duplicated! Found " + sysRefs);
+ }
+ final NodeRef system = sysRefs.get(0).getChildRef();
+
+ // Find the shared credentials container, under system
+ List containerRefs = nodeService.getChildAssocs(
+ system, ContentModel.ASSOC_CHILDREN, SHARED_CREDENTIALS_CONTAINER_QNAME);
+
+ if (containerRefs.size() > 0)
+ {
+ if (containerRefs.size() > 1)
+ logger.warn("Duplicate Shared Credentials Containers found: " + containerRefs);
+
+ NodeRef container = containerRefs.get(0).getChildRef();
+ return container;
+ }
+ else
+ {
+ if (required)
+ {
+ throw new IllegalStateException("Required System Folder " + SHARED_CREDENTIALS_CONTAINER_QNAME + " is missing!");
+ }
+ else
+ {
+ if (logger.isInfoEnabled())
+ logger.info("Required System Folder " + SHARED_CREDENTIALS_CONTAINER_QNAME + " missing, needed for writes");
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Gets, creating as needed, the person credentials container for the given system
+ */
+ private NodeRef getPersonContainer(String remoteSystem, boolean lazyCreate)
+ {
+ // Get the person node
+ NodeRef person = repositoryHelper.getPerson();
+ if (person == null)
+ {
+ // Something's rather broken, the service security ought to prevent this
+ throw new IllegalStateException("Person details required but none found! Running as " + AuthenticationUtil.getRunAsUser());
+ }
+
+ // If we're in edit mode, ensure the correct aspect is applied
+ if (lazyCreate)
+ {
+ ensureCredentialsSystemContainer(person);
+ }
+
+ // Find the container
+ return findRemoteSystemContainer(person, remoteSystem, lazyCreate);
+ }
+ /**
+ * Gets, creating as needed, the shared credentials container for the given system
+ */
+ private NodeRef getSharedContainer(String remoteSystem, boolean lazyCreate)
+ {
+ // Find the shared credentials container, under system
+ NodeRef systemContainer = getSharedContainerNodeRef(lazyCreate);
+ if (systemContainer == null) return null;
+
+ // If we're in edit mode, ensure the correct aspect is applied
+ if (lazyCreate)
+ {
+ ensureCredentialsSystemContainer(systemContainer);
+ }
+
+ // Find the container
+ return findRemoteSystemContainer(systemContainer, remoteSystem, lazyCreate);
+ }
+
+ /**
+ * Ensure the appropriate aspect is applied to the node which
+ * will hold the Remote Credentials System
+ */
+ private void ensureCredentialsSystemContainer(final NodeRef nodeRef)
+ {
+ AuthenticationUtil.runAsSystem(new RunAsWork() {
+ @Override
+ public Void doWork() throws Exception
+ {
+ if (!nodeService.hasAspect(nodeRef, RemoteCredentialsModel.ASPECT_REMOTE_CREDENTIALS_SYSTEM_CONTAINER))
+ {
+ // Add the aspect
+ nodeService.addAspect(nodeRef, RemoteCredentialsModel.ASPECT_REMOTE_CREDENTIALS_SYSTEM_CONTAINER, null);
+
+ if (logger.isDebugEnabled())
+ logger.debug("Added the Credentials Container aspect to " + nodeRef);
+ }
+ return null;
+ }
+ });
+ }
+ private NodeRef findRemoteSystemContainer(NodeRef nodeRef, String remoteSystem, boolean lazyCreate)
+ {
+ QName remoteSystemQName = QName.createQName(remoteSystem);
+ List systems = nodeService.getChildAssocs(
+ nodeRef, RemoteCredentialsModel.ASSOC_CREDENTIALS_SYSTEM, remoteSystemQName);
+
+ NodeRef system = null;
+ if (systems.size() > 0)
+ {
+ system = systems.get(0).getChildRef();
+
+ if (logger.isDebugEnabled())
+ logger.debug("Resolved Remote Credentials Container for " + remoteSystem + " of " + system + " in parent " + nodeRef);
+ }
+ else
+ {
+ if (lazyCreate)
+ {
+ // Create, as the current user
+ system = nodeService.createNode(
+ nodeRef, RemoteCredentialsModel.ASSOC_CREDENTIALS_SYSTEM,
+ QName.createQName(remoteSystem), RemoteCredentialsModel.TYPE_REMOTE_CREDENTIALS_SYSTEM
+ ).getChildRef();
+
+ if (logger.isDebugEnabled())
+ logger.debug("Lazy created Remote Credentials Container for " + remoteSystem +
+ " in parent " + nodeRef + ", new container is " + system);
+ }
+ else
+ {
+ if (logger.isDebugEnabled())
+ logger.debug("No Remote Credentials Container for " + remoteSystem + " found in " +
+ nodeRef + ", will be lazy created on write");
+ }
+ }
+
+ return system;
+ }
+
+ // --------------------------------------------------------
+
+ @Override
+ public void deleteCredentials(BaseCredentialsInfo credentialsInfo)
+ {
+ if (credentialsInfo.getNodeRef() == null)
+ {
+ throw new IllegalArgumentException("Cannot delete Credentials which haven't been persisted yet!");
+ }
+ nodeService.deleteNode(credentialsInfo.getNodeRef());
+
+ if (logger.isDebugEnabled())
+ logger.debug("Deleted credentials " + credentialsInfo + " from " + credentialsInfo.getNodeRef() +
+ " from Remote System " + credentialsInfo.getRemoteSystemName());
+
+ // Leave the Remote System Container, in case special permissions
+ // were previously applied to it that should be retained
+ }
+
+ @Override
+ public BaseCredentialsInfo createPersonCredentials(String remoteSystem, BaseCredentialsInfo credentials)
+ {
+ NodeRef personContainer = getPersonContainer(remoteSystem, true);
+ return createCredentials(remoteSystem, personContainer, credentials);
+ }
+ @Override
+ public BaseCredentialsInfo createSharedCredentials(String remoteSystem, BaseCredentialsInfo credentials)
+ {
+ NodeRef shared = getSharedContainer(remoteSystem, true);
+ return createCredentials(remoteSystem, shared, credentials);
+ }
+ private BaseCredentialsInfo createCredentials(String remoteSystem, NodeRef remoteSystemNodeRef, BaseCredentialsInfo credentials)
+ {
+ if (credentials.getNodeRef() != null)
+ {
+ throw new IllegalArgumentException("Cannot create Credentials which have already been persisted!");
+ }
+
+ // Check we know about the type
+ RemoteCredentialsInfoFactory factory = credentialsFactories.get(credentials.getCredentialsType());
+ if (factory == null)
+ {
+ throw new TypeConversionException("No Factory registered for type " + credentials.getCredentialsType());
+ }
+
+ // Build the properties
+ Map properties = RemoteCredentialsInfoFactory.FactoryHelper.getCoreCredentials(credentials);
+ properties.putAll( factory.serializeCredentials(credentials) );
+
+ // Generate a name for it, which will be unique and doesn't need updating
+ QName name = QName.createQName(GUID.generate());
+
+ // Add the node
+ NodeRef nodeRef = nodeService.createNode(
+ remoteSystemNodeRef, RemoteCredentialsModel.ASSOC_CREDENTIALS,
+ name, credentials.getCredentialsType(), properties
+ ).getChildRef();
+
+ if (logger.isDebugEnabled())
+ logger.debug("Created new credentials at " + nodeRef + " for " + remoteSystem + " in " +
+ remoteSystemNodeRef + " of " + credentials);
+
+ // Return the new object
+ return factory.createCredentials(
+ credentials.getCredentialsType(), nodeRef,
+ remoteSystem, remoteSystemNodeRef,
+ nodeService.getProperties(nodeRef)
+ );
+ }
+
+
+ @Override
+ public BaseCredentialsInfo getPersonCredentials(String remoteSystem)
+ {
+ NodeRef personContainer = getPersonContainer(remoteSystem, false);
+ if (personContainer == null) return null;
+
+ // Grab the children
+ List credentials =
+ nodeService.getChildAssocs(personContainer, RemoteCredentialsModel.ASSOC_CREDENTIALS, RegexQNamePattern.MATCH_ALL);
+ if (credentials.size() > 0)
+ {
+ NodeRef nodeRef = credentials.get(0).getChildRef();
+ return loadCredentials(remoteSystem, personContainer, nodeRef);
+ }
+ return null;
+ }
+ private BaseCredentialsInfo loadCredentials(String remoteSystem, NodeRef remoteSystemNodeRef, NodeRef credentialsNodeRef)
+ {
+ QName type = nodeService.getType(credentialsNodeRef);
+ RemoteCredentialsInfoFactory factory = credentialsFactories.get(type);
+ if (factory == null)
+ {
+ throw new TypeConversionException("No Factory registered for type " + type);
+ }
+
+ // Wrap as an object
+ return factory.createCredentials(
+ type, credentialsNodeRef,
+ remoteSystem, remoteSystemNodeRef,
+ nodeService.getProperties(credentialsNodeRef)
+ );
+ }
+
+
+ @Override
+ public BaseCredentialsInfo updateCredentials(BaseCredentialsInfo credentials)
+ {
+ if (credentials.getNodeRef() == null)
+ {
+ throw new IllegalArgumentException("Cannot update Credentials which haven't been persisted yet!");
+ }
+
+ RemoteCredentialsInfoFactory factory = credentialsFactories.get(credentials.getCredentialsType());
+ if (factory == null)
+ {
+ throw new TypeConversionException("No Factory registered for type " + credentials.getCredentialsType());
+ }
+
+ // Grab the current set of properties
+ Map oldProps = nodeService.getProperties(credentials.getNodeRef());
+
+ // Overwrite them with the credentials ones
+ Map props = new HashMap(oldProps);
+ props.putAll( RemoteCredentialsInfoFactory.FactoryHelper.getCoreCredentials(credentials) );
+ props.putAll( factory.serializeCredentials(credentials) );
+
+ // Store
+ nodeService.setProperties(credentials.getNodeRef(), props);
+
+ // For now, return as-is
+ return credentials;
+ }
+
+ @Override
+ public BaseCredentialsInfo updateCredentialsAuthenticationSucceeded(boolean succeeded, BaseCredentialsInfo credentials)
+ {
+ // We can't help with credentials that have never been stored
+ if (credentials.getNodeRef() == null)
+ {
+ throw new IllegalArgumentException("Cannot update Credentials which haven't been persisted yet!");
+ }
+
+ // Return quickly if the credentials are already in the correct state
+ if (succeeded == credentials.getLastAuthenticationSucceeded())
+ {
+ return credentials;
+ }
+
+
+ // Do the update
+ nodeService.setProperty(credentials.getNodeRef(), RemoteCredentialsModel.PROP_LAST_AUTHENTICATION_SUCCEEDED, succeeded);
+
+ // Update the object if we can
+ if (credentials instanceof AbstractCredentialsImpl)
+ {
+ ((AbstractCredentialsImpl)credentials).setLastAuthenticationSucceeded(succeeded);
+ return credentials;
+ }
+ else
+ {
+ // Need to re-load
+ return loadCredentials(credentials.getRemoteSystemName(),
+ credentials.getRemoteSystemContainerNodeRef(), credentials.getNodeRef());
+ }
+ }
+
+
+ @Override
+ public PagingResults listAllRemoteSystems(PagingRequest paging)
+ {
+ return listRemoteSystems(true, true, paging);
+ }
+ @Override
+ public PagingResults listPersonRemoteSystems(PagingRequest paging)
+ {
+ return listRemoteSystems(true, false, paging);
+ }
+ @Override
+ public PagingResults listSharedRemoteSystems(PagingRequest paging)
+ {
+ return listRemoteSystems(false, true, paging);
+ }
+ private PagingResults listRemoteSystems(boolean people, boolean shared, PagingRequest paging)
+ {
+ List search = new ArrayList();
+
+ if (people)
+ {
+ // Only search if it has the marker aspect
+ NodeRef person = repositoryHelper.getPerson();
+ if (nodeService.hasAspect(person, RemoteCredentialsModel.ASPECT_REMOTE_CREDENTIALS_SYSTEM_CONTAINER))
+ {
+ search.add(person);
+ }
+ }
+ if (shared)
+ {
+ NodeRef system = getSharedContainerNodeRef(false);
+ if (system != null)
+ {
+ search.add(system);
+ }
+ }
+
+ // If no suitable nodes were given, bail out
+ if (search.isEmpty())
+ {
+ return new EmptyPagingResults();
+ }
+
+ // Look for nodes
+ // Because all the information we need is held on the association, we don't
+ // really need to use a Canned Query for this
+ Set systems = new HashSet();
+ for (NodeRef nodeRef : search)
+ {
+ List refs =
+ nodeService.getChildAssocs(nodeRef, RemoteCredentialsModel.ASSOC_CREDENTIALS_SYSTEM, RegexQNamePattern.MATCH_ALL);
+ for (ChildAssociationRef ref : refs)
+ {
+ // System Name is the association name, no namespace
+ systems.add( ref.getQName().getLocalName() );
+ }
+ }
+
+ // Sort, then wrap as paged results
+ List sortedSystems = new ArrayList(systems);
+ Collections.sort(sortedSystems);
+ return new ListBackedPagingResults(sortedSystems, paging);
+ }
+
+ @Override
+ public PagingResults extends BaseCredentialsInfo> listSharedCredentials(String remoteSystem,
+ QName credentialsType, PagingRequest paging)
+ {
+ // Get the container for that system
+ NodeRef container = getSharedContainer(remoteSystem, false);
+ if (container == null)
+ {
+ return new EmptyPagingResults();
+ }
+ return listCredentials(new NodeRef[] {container}, remoteSystem, credentialsType, paging);
+ }
+ @Override
+ public PagingResults extends BaseCredentialsInfo> listPersonCredentials(String remoteSystem,
+ QName credentialsType, PagingRequest paging)
+ {
+ // Get the container for that system
+ NodeRef container = getPersonContainer(remoteSystem, false);
+ if (container == null)
+ {
+ return new EmptyPagingResults();
+ }
+ return listCredentials(new NodeRef[] {container}, remoteSystem, credentialsType, paging);
+ }
+ @Override
+ public PagingResults extends BaseCredentialsInfo> listAllCredentials(String remoteSystem, QName credentialsType,
+ PagingRequest paging)
+ {
+ NodeRef personContainer = getPersonContainer(remoteSystem, false);
+ NodeRef systemContainer = getSharedContainer(remoteSystem, false);
+ if (personContainer == null && systemContainer == null)
+ {
+ return new EmptyPagingResults();
+ }
+ return listCredentials(new NodeRef[] {personContainer, systemContainer}, remoteSystem, credentialsType, paging);
+ }
+ /**
+ * TODO This would probably be better done as a dedicated Canned Query
+ * We want to filter by Assoc Type and Child Node Type, and the node service
+ * currently only allows you to do one or the other
+ */
+ private PagingResults extends BaseCredentialsInfo> listCredentials(NodeRef[] containers, String remoteSystem,
+ QName credentialsType, PagingRequest paging)
+ {
+ // NodeService wants an exhaustive list of the types
+ // Expand our single Credentials Type to cover all subtypes of it too
+ Set types = null;
+ if (credentialsType != null)
+ {
+ types = new HashSet( dictionaryService.getSubTypes(credentialsType, true) );
+
+ if (logger.isDebugEnabled())
+ logger.debug("Searching for credentials of " + credentialsType + " as types " + types);
+ }
+
+ // Find all the credentials
+ List credentials = new ArrayList();
+ for (NodeRef nodeRef : containers)
+ {
+ if (nodeRef != null)
+ {
+ // Find the credentials in the node
+ List allCreds = nodeService.getChildAssocs(
+ nodeRef, RemoteCredentialsModel.ASSOC_CREDENTIALS, RegexQNamePattern.MATCH_ALL);
+
+ // Filter them by type, if needed
+ if (types == null || types.isEmpty())
+ {
+ // No type filtering needed
+ credentials.addAll(allCreds);
+ }
+ else
+ {
+ // Check the type of each one, and add if it matches
+ for (ChildAssociationRef ref : allCreds)
+ {
+ NodeRef credNodeRef = ref.getChildRef();
+ QName credType = nodeService.getType(credNodeRef);
+ if (types.contains(credType))
+ {
+ // Matching type, accept
+ credentials.add(ref);
+ }
+ }
+ }
+ }
+ }
+
+ // Did we find any?
+ if (credentials.isEmpty())
+ {
+ return new EmptyPagingResults();
+ }
+
+ // Excerpt
+ int start = paging.getSkipCount();
+ int end = Math.min(credentials.size(), start + paging.getMaxItems());
+ if (paging.getMaxItems() == 0)
+ {
+ end = credentials.size();
+ }
+ boolean hasMore = (end < credentials.size());
+
+ List wanted = credentials.subList(start, end);
+
+ // Wrap and return
+ return new CredentialsPagingResults(wanted, credentials.size(), hasMore, remoteSystem);
+ }
+
+ // --------------------------------------------------------
+
+ private class CredentialsPagingResults implements PagingResults
+ {
+ private List results;
+ private boolean hasMore;
+ private int size;
+
+ private CredentialsPagingResults(List refs, int size, boolean hasMore, String remoteSystem)
+ {
+ this.size = size;
+ this.hasMore = hasMore;
+
+ this.results = new ArrayList(refs.size());
+ for (ChildAssociationRef ref : refs)
+ {
+ this.results.add( loadCredentials(remoteSystem, ref.getParentRef(), ref.getChildRef()) );
+ }
+ }
+
+ @Override
+ public List getPage()
+ {
+ return results;
+ }
+
+ @Override
+ public Pair getTotalResultCount()
+ {
+ return new Pair(size,size);
+ }
+
+ @Override
+ public boolean hasMoreItems()
+ {
+ return hasMore;
+ }
+
+ @Override
+ public String getQueryExecutionId()
+ {
+ return null;
+ }
+ }
+
+ // --------------------------------------------------------
+
+ /** Unit testing use only! */
+ protected static String getSharedCredentialsSystemContainerName()
+ {
+ return SHARED_CREDENTIALS_CONTAINER_NAME;
+ }
+ protected static QName getSharedCredentialsSystemContainerQName()
+ {
+ return SHARED_CREDENTIALS_CONTAINER_QNAME;
+ }
+ /** Unit testing use only! Used to avoid tests affecting the real system container */
+ protected static void setSharedCredentialsSystemContainerName(String container)
+ {
+ SHARED_CREDENTIALS_CONTAINER_NAME = container;
+ SHARED_CREDENTIALS_CONTAINER_QNAME =
+ QName.createQName(RemoteCredentialsModel.REMOTE_CREDENTIALS_MODEL_URL, SHARED_CREDENTIALS_CONTAINER_NAME);
+ }
+}
\ No newline at end of file
diff --git a/source/java/org/alfresco/repo/remotecredentials/RemoteCredentialsServicesTest.java b/source/java/org/alfresco/repo/remotecredentials/RemoteCredentialsServicesTest.java
new file mode 100644
index 0000000000..28a31792b1
--- /dev/null
+++ b/source/java/org/alfresco/repo/remotecredentials/RemoteCredentialsServicesTest.java
@@ -0,0 +1,909 @@
+/*
+ * 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 .
+ */
+package org.alfresco.repo.remotecredentials;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.query.PagingRequest;
+import org.alfresco.query.PagingResults;
+import org.alfresco.repo.policy.BehaviourFilter;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.repo.transaction.RetryingTransactionHelper;
+import org.alfresco.service.cmr.dictionary.DictionaryService;
+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.repository.ChildAssociationRef;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.security.AuthorityService;
+import org.alfresco.service.cmr.security.MutableAuthenticationService;
+import org.alfresco.service.cmr.security.PermissionService;
+import org.alfresco.service.cmr.security.PersonService;
+import org.alfresco.service.namespace.NamespaceService;
+import org.alfresco.service.namespace.QName;
+import org.alfresco.service.transaction.TransactionService;
+import org.alfresco.util.PropertyMap;
+import org.alfresco.util.test.junitrules.ApplicationContextInit;
+import org.alfresco.util.test.junitrules.TemporaryNodes;
+import org.alfresco.util.test.junitrules.WellKnownNodes;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.springframework.context.ApplicationContext;
+
+/**
+ * Test cases for {@link RemoteCredentialsServiceImpl} and friends.
+ *
+ * Note - this test will zap any existing shared credentials!
+ *
+ * @author Nick Burch
+ * @since Odin
+ */
+public class RemoteCredentialsServicesTest
+{
+ private static final String TEST_REMOTE_SYSTEM_ONE = "TestRemoteSystemOne";
+ private static final String TEST_REMOTE_SYSTEM_TWO = "TestRemoteSystemTwo";
+ private static final String TEST_REMOTE_SYSTEM_THREE = "aAaAaTestRemoteSystemThree";
+
+ private static final String TEST_REMOTE_USERNAME_ONE = "test@example.com";
+ private static final String TEST_REMOTE_USERNAME_TWO = "test2@example.com";
+ private static final String TEST_REMOTE_USERNAME_THREE = "test3@example.com";
+
+ private static final String SHARED_SYSTEM_CONTAINER_NAME = "test-remote-credentials";
+
+ // Rule to initialise the default Alfresco spring configuration
+ @ClassRule public static ApplicationContextInit APP_CONTEXT_INIT = new ApplicationContextInit();
+
+ // A rule to help find well known nodes in the system
+ @ClassRule public static WellKnownNodes knownNodes = new WellKnownNodes(APP_CONTEXT_INIT);
+
+ // A rule to manage test nodes use in each test method
+ @ClassRule public static TemporaryNodes classTestNodes = new TemporaryNodes(APP_CONTEXT_INIT);
+ @Rule public TemporaryNodes testNodes = new TemporaryNodes(APP_CONTEXT_INIT);
+
+ // injected services
+ private static MutableAuthenticationService AUTHENTICATION_SERVICE;
+ private static BehaviourFilter BEHAVIOUR_FILTER;
+ private static RemoteCredentialsService REMOTE_CREDENTIALS_SERVICE;
+ private static RemoteCredentialsService PRIVATE_REMOTE_CREDENTIALS_SERVICE;
+ private static DictionaryService DICTIONARY_SERVICE;
+ private static NodeService NODE_SERVICE;
+ private static NodeService PUBLIC_NODE_SERVICE;
+ private static PersonService PERSON_SERVICE;
+ private static RetryingTransactionHelper TRANSACTION_HELPER;
+ private static TransactionService TRANSACTION_SERVICE;
+ private static PermissionService PERMISSION_SERVICE;
+
+ private static final String TEST_USER_ONE = RemoteCredentialsServicesTest.class.getSimpleName() + "_testuser1";
+ private static final String TEST_USER_TWO = RemoteCredentialsServicesTest.class.getSimpleName() + "_testuser2";
+ private static final String TEST_USER_THREE = RemoteCredentialsServicesTest.class.getSimpleName() + "_testuser3";
+ private static final String ADMIN_USER = AuthenticationUtil.getAdminUserName();
+
+ @BeforeClass public static void initTestsContext() throws Exception
+ {
+ ApplicationContext testContext = APP_CONTEXT_INIT.getApplicationContext();
+
+ PRIVATE_REMOTE_CREDENTIALS_SERVICE = (RemoteCredentialsService)testContext.getBean("remoteCredentialsService");
+ REMOTE_CREDENTIALS_SERVICE = (RemoteCredentialsService)testContext.getBean("RemoteCredentialsService");
+
+ AUTHENTICATION_SERVICE = (MutableAuthenticationService)testContext.getBean("authenticationService");
+ BEHAVIOUR_FILTER = (BehaviourFilter)testContext.getBean("policyBehaviourFilter");
+ DICTIONARY_SERVICE = (DictionaryService)testContext.getBean("dictionaryService");
+ NODE_SERVICE = (NodeService)testContext.getBean("nodeService");
+ PUBLIC_NODE_SERVICE = (NodeService)testContext.getBean("NodeService");
+ PERSON_SERVICE = (PersonService)testContext.getBean("personService");
+ TRANSACTION_HELPER = (RetryingTransactionHelper)testContext.getBean("retryingTransactionHelper");
+ TRANSACTION_SERVICE = (TransactionService)testContext.getBean("TransactionService");
+ PERMISSION_SERVICE = (PermissionService)testContext.getBean("permissionService");
+
+ // Switch to a test shared system container
+ RemoteCredentialsServiceImpl.setSharedCredentialsSystemContainerName(SHARED_SYSTEM_CONTAINER_NAME);
+
+ // Have the test shared system container created
+ AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.SYSTEM_USER_NAME);
+ NodeRef system = knownNodes.getSystemRoot();
+ NodeRef container = PUBLIC_NODE_SERVICE.createNode(system, ContentModel.ASSOC_CHILDREN,
+ RemoteCredentialsServiceImpl.getSharedCredentialsSystemContainerQName(),
+ ContentModel.TYPE_CONTAINER
+ ).getChildRef();
+ PERMISSION_SERVICE.setPermission(container, PermissionService.ALL_AUTHORITIES, PermissionService.FULL_CONTROL, true);
+ classTestNodes.addNodeRef(container);
+ }
+
+ @Before public void setupUsers() throws Exception
+ {
+ // Do the setup as admin
+ AuthenticationUtil.setFullyAuthenticatedUser(ADMIN_USER);
+ createUser(TEST_USER_ONE);
+ createUser(TEST_USER_TWO);
+ createUser(TEST_USER_THREE);
+
+ // We need to create the test site as the test user so that they can contribute content to it in tests below.
+ AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER_ONE);
+ }
+
+
+ /**
+ * Tests that read only methods don't create the shared credentials
+ * container, but that write ones will do.
+ */
+ @Test public void testSharedCredentialsContainer() throws Exception
+ {
+ // Run as a test user
+ AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER_ONE);
+
+
+ // To start with, the container is there, but empty
+ NodeRef container = ((RemoteCredentialsServiceImpl)PRIVATE_REMOTE_CREDENTIALS_SERVICE).getSharedContainerNodeRef(false);
+ assertNotNull(container);
+ assertEquals(0, PUBLIC_NODE_SERVICE.getChildAssocs(container).size());
+
+ // Ask for the list of shared remote systems
+ REMOTE_CREDENTIALS_SERVICE.listSharedRemoteSystems(new PagingRequest(10));
+
+ // Won't have been affected by a read
+ container = ((RemoteCredentialsServiceImpl)PRIVATE_REMOTE_CREDENTIALS_SERVICE).getSharedContainerNodeRef(false);
+ assertNotNull(container);
+ assertEquals(0, PUBLIC_NODE_SERVICE.getChildAssocs(container).size());
+
+
+ // Try to store some credentials
+ PasswordCredentialsInfo credentials = new PasswordCredentialsInfoImpl();
+ REMOTE_CREDENTIALS_SERVICE.createSharedCredentials(TEST_REMOTE_SYSTEM_ONE, credentials);
+
+ // It will now exist
+ container = ((RemoteCredentialsServiceImpl)PRIVATE_REMOTE_CREDENTIALS_SERVICE).getSharedContainerNodeRef(false);
+ assertNotNull(container);
+
+ // Should have a marker aspect
+ Set cAspects = PUBLIC_NODE_SERVICE.getAspects(container);
+ assertEquals("Aspect missing, found " + cAspects, true,
+ cAspects.contains(RemoteCredentialsModel.ASPECT_REMOTE_CREDENTIALS_SYSTEM_CONTAINER));
+
+ // Should have single node in it
+ assertEquals(1, PUBLIC_NODE_SERVICE.getChildAssocs(container).size());
+ }
+
+ /**
+ * Creating shared and personal credentials, then checking how this
+ * affects the listing of Remote Systems
+ */
+ @Test public void testCreateCredentialsAndSystemListing() throws Exception
+ {
+ PagingResults systems = null;
+
+ // Run as a test user
+ AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER_ONE);
+
+
+ // Initially both should be empty
+ systems = REMOTE_CREDENTIALS_SERVICE.listPersonRemoteSystems(new PagingRequest(10));
+ assertEquals("No systems should be found, got " + systems.getPage(),
+ 0, systems.getPage().size());
+
+ systems = REMOTE_CREDENTIALS_SERVICE.listSharedRemoteSystems(new PagingRequest(10));
+ assertEquals("No systems should be found, got " + systems.getPage(),
+ 0, systems.getPage().size());
+
+ systems = REMOTE_CREDENTIALS_SERVICE.listAllRemoteSystems(new PagingRequest(10));
+ assertEquals("No systems should be found, got " + systems.getPage(),
+ 0, systems.getPage().size());
+
+
+ // Create one for the person
+ PasswordCredentialsInfoImpl credentials = new PasswordCredentialsInfoImpl();
+ credentials.setRemoteUsername(TEST_REMOTE_USERNAME_ONE);
+ REMOTE_CREDENTIALS_SERVICE.createPersonCredentials(TEST_REMOTE_SYSTEM_ONE, credentials);
+
+ // Check it shows up
+ systems = REMOTE_CREDENTIALS_SERVICE.listPersonRemoteSystems(new PagingRequest(10));
+ assertEquals("Unexpected systems " + systems.getPage(),
+ 1, systems.getPage().size());
+
+ systems = REMOTE_CREDENTIALS_SERVICE.listSharedRemoteSystems(new PagingRequest(10));
+ assertEquals("Unexpected systems " + systems.getPage(),
+ 0, systems.getPage().size());
+
+ systems = REMOTE_CREDENTIALS_SERVICE.listAllRemoteSystems(new PagingRequest(10));
+ assertEquals("Unexpected systems " + systems.getPage(),
+ 1, systems.getPage().size());
+
+
+ // Switch to another user, check it doesn't
+ AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER_TWO);
+
+ systems = REMOTE_CREDENTIALS_SERVICE.listPersonRemoteSystems(new PagingRequest(10));
+ assertEquals(0, systems.getPage().size());
+
+ systems = REMOTE_CREDENTIALS_SERVICE.listSharedRemoteSystems(new PagingRequest(10));
+ assertEquals(0, systems.getPage().size());
+
+ systems = REMOTE_CREDENTIALS_SERVICE.listAllRemoteSystems(new PagingRequest(10));
+ assertEquals(0, systems.getPage().size());
+
+
+ // Create both personal and shared ones as the current user
+ credentials = new PasswordCredentialsInfoImpl();
+ credentials.setRemoteUsername(TEST_REMOTE_USERNAME_TWO);
+ REMOTE_CREDENTIALS_SERVICE.createPersonCredentials(TEST_REMOTE_SYSTEM_TWO, credentials);
+
+ credentials = new PasswordCredentialsInfoImpl();
+ credentials.setRemoteUsername(TEST_REMOTE_USERNAME_THREE);
+ REMOTE_CREDENTIALS_SERVICE.createPersonCredentials(TEST_REMOTE_SYSTEM_THREE, credentials);
+
+ credentials = new PasswordCredentialsInfoImpl();
+ credentials.setRemoteUsername(TEST_REMOTE_USERNAME_THREE);
+ BaseCredentialsInfo cc = REMOTE_CREDENTIALS_SERVICE.createSharedCredentials(TEST_REMOTE_SYSTEM_THREE, credentials);
+
+ // Check as the user who created these
+ systems = REMOTE_CREDENTIALS_SERVICE.listPersonRemoteSystems(new PagingRequest(10));
+ assertEquals(2, systems.getPage().size());
+
+ systems = REMOTE_CREDENTIALS_SERVICE.listSharedRemoteSystems(new PagingRequest(10));
+ assertEquals(1, systems.getPage().size());
+
+ systems = REMOTE_CREDENTIALS_SERVICE.listAllRemoteSystems(new PagingRequest(10));
+ assertEquals(2, systems.getPage().size());
+
+
+ // Check as the first user, they should see the shared one
+ AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER_ONE);
+
+ systems = REMOTE_CREDENTIALS_SERVICE.listPersonRemoteSystems(new PagingRequest(10));
+ assertEquals(1, systems.getPage().size());
+
+ systems = REMOTE_CREDENTIALS_SERVICE.listSharedRemoteSystems(new PagingRequest(10));
+ assertEquals(1, systems.getPage().size());
+
+ systems = REMOTE_CREDENTIALS_SERVICE.listAllRemoteSystems(new PagingRequest(10));
+ assertEquals(2, systems.getPage().size());
+
+
+ // Change the shared permissions, see it goes away
+ AuthenticationUtil.setFullyAuthenticatedUser(ADMIN_USER);
+ PERMISSION_SERVICE.setInheritParentPermissions(cc.getRemoteSystemContainerNodeRef(), false);
+
+
+ // Check as the owning user, will still see all of them
+ AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER_TWO);
+ systems = REMOTE_CREDENTIALS_SERVICE.listPersonRemoteSystems(new PagingRequest(10));
+ assertEquals(2, systems.getPage().size());
+
+ systems = REMOTE_CREDENTIALS_SERVICE.listSharedRemoteSystems(new PagingRequest(10));
+ assertEquals(1, systems.getPage().size());
+
+ systems = REMOTE_CREDENTIALS_SERVICE.listAllRemoteSystems(new PagingRequest(10));
+ assertEquals(2, systems.getPage().size());
+
+
+ // Check as the other user, shared will have gone as we lost read permissions
+ AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER_ONE);
+
+ systems = REMOTE_CREDENTIALS_SERVICE.listPersonRemoteSystems(new PagingRequest(10));
+ assertEquals(1, systems.getPage().size());
+
+ systems = REMOTE_CREDENTIALS_SERVICE.listSharedRemoteSystems(new PagingRequest(10));
+ assertEquals(0, systems.getPage().size());
+
+ systems = REMOTE_CREDENTIALS_SERVICE.listAllRemoteSystems(new PagingRequest(10));
+ assertEquals(1, systems.getPage().size());
+
+
+ // Finally, check the listings have the correct things in them
+ AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER_TWO);
+ systems = REMOTE_CREDENTIALS_SERVICE.listPersonRemoteSystems(new PagingRequest(10));
+ assertEquals(2, systems.getPage().size());
+ assertEquals(true, systems.getPage().contains(TEST_REMOTE_SYSTEM_TWO));
+ assertEquals(true, systems.getPage().contains(TEST_REMOTE_SYSTEM_THREE));
+
+ systems = REMOTE_CREDENTIALS_SERVICE.listSharedRemoteSystems(new PagingRequest(10));
+ assertEquals(1, systems.getPage().size());
+ assertEquals(true, systems.getPage().contains(TEST_REMOTE_SYSTEM_THREE));
+
+ systems = REMOTE_CREDENTIALS_SERVICE.listAllRemoteSystems(new PagingRequest(10));
+ assertEquals(2, systems.getPage().size());
+ assertEquals(true, systems.getPage().contains(TEST_REMOTE_SYSTEM_TWO));
+ assertEquals(true, systems.getPage().contains(TEST_REMOTE_SYSTEM_THREE));
+ }
+
+ /** Test CRUD on person credentials, with listing */
+ @Test public void testPersonCredentialsCRUD() throws Exception
+ {
+ PagingResults systems = null;
+ PagingResults extends BaseCredentialsInfo> creds = null;
+
+
+ // Run as a test user
+ AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER_ONE);
+
+ // Initially both should be empty empty
+ systems = REMOTE_CREDENTIALS_SERVICE.listPersonRemoteSystems(new PagingRequest(10));
+ assertEquals("No systems should be found, got " + systems.getPage(),
+ 0, systems.getPage().size());
+ systems = REMOTE_CREDENTIALS_SERVICE.listSharedRemoteSystems(new PagingRequest(10));
+ assertEquals("No systems should be found, got " + systems.getPage(),
+ 0, systems.getPage().size());
+
+
+ // Create for a person
+ PasswordCredentialsInfoImpl pwCred = new PasswordCredentialsInfoImpl();
+ pwCred.setRemoteUsername(TEST_REMOTE_USERNAME_ONE);
+ BaseCredentialsInfo credentials = REMOTE_CREDENTIALS_SERVICE.createPersonCredentials(TEST_REMOTE_SYSTEM_ONE, pwCred);
+
+ // Check the new object was populated properly
+ assertNotNull(credentials);
+ assertNotNull(credentials.getNodeRef());
+ assertNotNull(credentials.getRemoteSystemContainerNodeRef());
+ assertEquals(TEST_REMOTE_SYSTEM_ONE, credentials.getRemoteSystemName());
+ assertEquals(TEST_REMOTE_USERNAME_ONE, credentials.getRemoteUsername());
+ assertEquals(RemoteCredentialsModel.TYPE_PASSWORD_CREDENTIALS, credentials.getCredentialsType());
+
+
+ // Fetch and re-check
+ credentials = REMOTE_CREDENTIALS_SERVICE.getPersonCredentials(TEST_REMOTE_SYSTEM_ONE);
+ assertNotNull(credentials);
+ assertNotNull(credentials.getNodeRef());
+ assertNotNull(credentials.getRemoteSystemContainerNodeRef());
+ assertEquals(TEST_REMOTE_SYSTEM_ONE, credentials.getRemoteSystemName());
+ assertEquals(TEST_REMOTE_USERNAME_ONE, credentials.getRemoteUsername());
+ assertEquals(RemoteCredentialsModel.TYPE_PASSWORD_CREDENTIALS, credentials.getCredentialsType());
+
+ // Won't be there for non-existent systems
+ credentials = REMOTE_CREDENTIALS_SERVICE.getPersonCredentials(TEST_REMOTE_SYSTEM_TWO);
+ assertEquals(null, credentials);
+ credentials = REMOTE_CREDENTIALS_SERVICE.getPersonCredentials(TEST_REMOTE_SYSTEM_THREE);
+ assertEquals(null, credentials);
+
+
+ // Update
+ credentials = REMOTE_CREDENTIALS_SERVICE.getPersonCredentials(TEST_REMOTE_SYSTEM_ONE);
+ assertEquals(PasswordCredentialsInfoImpl.class, credentials.getClass());
+ pwCred = (PasswordCredentialsInfoImpl)credentials;
+
+ pwCred.setRemoteUsername(TEST_REMOTE_USERNAME_TWO);
+ pwCred.setRemotePassword("testing");
+
+ credentials = REMOTE_CREDENTIALS_SERVICE.updateCredentials(pwCred);
+ assertNotNull(credentials);
+ assertEquals(TEST_REMOTE_USERNAME_TWO, credentials.getRemoteUsername());
+
+ // Fetch and re-check
+ pwCred = (PasswordCredentialsInfoImpl)REMOTE_CREDENTIALS_SERVICE.getPersonCredentials(TEST_REMOTE_SYSTEM_ONE);
+ assertNotNull(pwCred);
+ assertEquals(TEST_REMOTE_USERNAME_TWO, pwCred.getRemoteUsername());
+ assertEquals("testing", pwCred.getRemotePassword());
+
+
+ // Update the auth worked flag
+ credentials = REMOTE_CREDENTIALS_SERVICE.getPersonCredentials(TEST_REMOTE_SYSTEM_ONE);
+ assertEquals(true, credentials.getLastAuthenticationSucceeded());
+
+ // To the same thing
+ credentials = REMOTE_CREDENTIALS_SERVICE.updateCredentialsAuthenticationSucceeded(true, credentials);
+ assertEquals(true, credentials.getLastAuthenticationSucceeded());
+
+ credentials = REMOTE_CREDENTIALS_SERVICE.getPersonCredentials(TEST_REMOTE_SYSTEM_ONE);
+ assertEquals(true, credentials.getLastAuthenticationSucceeded());
+
+ // To a different things
+ credentials = REMOTE_CREDENTIALS_SERVICE.updateCredentialsAuthenticationSucceeded(false, credentials);
+ assertEquals(false, credentials.getLastAuthenticationSucceeded());
+
+ credentials = REMOTE_CREDENTIALS_SERVICE.getPersonCredentials(TEST_REMOTE_SYSTEM_ONE);
+ assertEquals(false, credentials.getLastAuthenticationSucceeded());
+
+ // And back
+ credentials = REMOTE_CREDENTIALS_SERVICE.updateCredentialsAuthenticationSucceeded(true, credentials);
+ assertEquals(true, credentials.getLastAuthenticationSucceeded());
+
+ credentials = REMOTE_CREDENTIALS_SERVICE.getPersonCredentials(TEST_REMOTE_SYSTEM_ONE);
+ assertEquals(true, credentials.getLastAuthenticationSucceeded());
+
+
+ // List remote systems
+ systems = REMOTE_CREDENTIALS_SERVICE.listPersonRemoteSystems(new PagingRequest(10));
+ assertEquals(1, systems.getPage().size());
+
+ systems = REMOTE_CREDENTIALS_SERVICE.listSharedRemoteSystems(new PagingRequest(10));
+ assertEquals(0, systems.getPage().size());
+
+ systems = REMOTE_CREDENTIALS_SERVICE.listAllRemoteSystems(new PagingRequest(10));
+ assertEquals(1, systems.getPage().size());
+
+
+ // List the credentials
+ creds = REMOTE_CREDENTIALS_SERVICE.listPersonCredentials(TEST_REMOTE_SYSTEM_ONE, null, new PagingRequest(10));
+ assertEquals(1, creds.getPage().size());
+ assertEquals(TEST_REMOTE_USERNAME_TWO, creds.getPage().get(0).getRemoteUsername());
+
+ creds = REMOTE_CREDENTIALS_SERVICE.listSharedCredentials(TEST_REMOTE_SYSTEM_ONE, null, new PagingRequest(10));
+ assertEquals(0, creds.getPage().size());
+
+ creds = REMOTE_CREDENTIALS_SERVICE.listAllCredentials(TEST_REMOTE_SYSTEM_ONE, null, new PagingRequest(10));
+ assertEquals(1, creds.getPage().size());
+ assertEquals(TEST_REMOTE_USERNAME_TWO, creds.getPage().get(0).getRemoteUsername());
+
+
+ // Delete
+ REMOTE_CREDENTIALS_SERVICE.deleteCredentials(credentials);
+
+ credentials = REMOTE_CREDENTIALS_SERVICE.getPersonCredentials(TEST_REMOTE_SYSTEM_ONE);
+ assertEquals(null, credentials);
+
+
+ // List again - credentials should have gone, but system remains
+ creds = REMOTE_CREDENTIALS_SERVICE.listPersonCredentials(TEST_REMOTE_SYSTEM_ONE, null, new PagingRequest(10));
+ assertEquals(0, creds.getPage().size());
+ creds = REMOTE_CREDENTIALS_SERVICE.listSharedCredentials(TEST_REMOTE_SYSTEM_ONE, null, new PagingRequest(10));
+ assertEquals(0, creds.getPage().size());
+ creds = REMOTE_CREDENTIALS_SERVICE.listAllCredentials(TEST_REMOTE_SYSTEM_ONE, null, new PagingRequest(10));
+ assertEquals(0, creds.getPage().size());
+
+ systems = REMOTE_CREDENTIALS_SERVICE.listPersonRemoteSystems(new PagingRequest(10));
+ assertEquals(1, systems.getPage().size());
+ systems = REMOTE_CREDENTIALS_SERVICE.listSharedRemoteSystems(new PagingRequest(10));
+ assertEquals(0, systems.getPage().size());
+ systems = REMOTE_CREDENTIALS_SERVICE.listAllRemoteSystems(new PagingRequest(10));
+ assertEquals(1, systems.getPage().size());
+
+
+ // Create credentials of Password, OAuth1 and OAuth2 types
+ pwCred = new PasswordCredentialsInfoImpl();
+ pwCred.setRemoteUsername(TEST_REMOTE_USERNAME_ONE);
+ REMOTE_CREDENTIALS_SERVICE.createPersonCredentials(TEST_REMOTE_SYSTEM_TWO, pwCred);
+
+ OAuth1CredentialsInfoImpl oa1Cred = new OAuth1CredentialsInfoImpl();
+ oa1Cred.setRemoteUsername(TEST_REMOTE_USERNAME_TWO);
+ oa1Cred.setOAuthToken("test");
+ REMOTE_CREDENTIALS_SERVICE.createPersonCredentials(TEST_REMOTE_SYSTEM_TWO, oa1Cred);
+
+ OAuth2CredentialsInfoImpl oa2Cred = new OAuth2CredentialsInfoImpl();
+ oa2Cred.setRemoteUsername(TEST_REMOTE_USERNAME_THREE);
+ oa2Cred.setOauthAccessToken("testA");
+ oa2Cred.setOauthRefreshToken("testR");
+ REMOTE_CREDENTIALS_SERVICE.createPersonCredentials(TEST_REMOTE_SYSTEM_TWO, oa2Cred);
+
+
+ // List, should see all three sets of credentials
+ creds = REMOTE_CREDENTIALS_SERVICE.listPersonCredentials(TEST_REMOTE_SYSTEM_TWO, null, new PagingRequest(10));
+ assertEquals(3, creds.getPage().size());
+ creds = REMOTE_CREDENTIALS_SERVICE.listSharedCredentials(TEST_REMOTE_SYSTEM_TWO, null, new PagingRequest(10));
+ assertEquals(0, creds.getPage().size());
+ creds = REMOTE_CREDENTIALS_SERVICE.listAllCredentials(TEST_REMOTE_SYSTEM_TWO, null, new PagingRequest(10));
+ assertEquals(3, creds.getPage().size());
+
+ // List the systems, still only system one and two
+ systems = REMOTE_CREDENTIALS_SERVICE.listPersonRemoteSystems(new PagingRequest(10));
+ assertEquals(2, systems.getPage().size());
+ systems = REMOTE_CREDENTIALS_SERVICE.listSharedRemoteSystems(new PagingRequest(10));
+ assertEquals(0, systems.getPage().size());
+ systems = REMOTE_CREDENTIALS_SERVICE.listAllRemoteSystems(new PagingRequest(10));
+ assertEquals(2, systems.getPage().size());
+
+
+ // Check we can filter credentials by type
+ creds = REMOTE_CREDENTIALS_SERVICE.listPersonCredentials(TEST_REMOTE_SYSTEM_TWO, null, new PagingRequest(10));
+ assertEquals(3, creds.getPage().size());
+
+ creds = REMOTE_CREDENTIALS_SERVICE.listPersonCredentials(TEST_REMOTE_SYSTEM_TWO, RemoteCredentialsModel.TYPE_PASSWORD_CREDENTIALS, new PagingRequest(10));
+ assertEquals(1, creds.getPage().size());
+ assertEquals(TEST_REMOTE_USERNAME_ONE, creds.getPage().get(0).getRemoteUsername());
+
+ creds = REMOTE_CREDENTIALS_SERVICE.listPersonCredentials(TEST_REMOTE_SYSTEM_TWO, RemoteCredentialsModel.TYPE_OAUTH1_CREDENTIALS, new PagingRequest(10));
+ assertEquals(1, creds.getPage().size());
+ assertEquals(TEST_REMOTE_USERNAME_TWO, creds.getPage().get(0).getRemoteUsername());
+
+ creds = REMOTE_CREDENTIALS_SERVICE.listPersonCredentials(TEST_REMOTE_SYSTEM_TWO, RemoteCredentialsModel.TYPE_OAUTH2_CREDENTIALS, new PagingRequest(10));
+ assertEquals(1, creds.getPage().size());
+ assertEquals(TEST_REMOTE_USERNAME_THREE, creds.getPage().get(0).getRemoteUsername());
+ }
+
+ /** Test CRUD on shared credentials, with listing */
+ @Test public void testSharedCredentialsCRUD() throws Exception
+ {
+ PagingResults systems = null;
+ PagingResults extends BaseCredentialsInfo> creds = null;
+
+
+ // Run as a test user
+ AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER_ONE);
+
+ // Initially both should be empty empty
+ systems = REMOTE_CREDENTIALS_SERVICE.listPersonRemoteSystems(new PagingRequest(10));
+ assertEquals("No systems should be found, got " + systems.getPage(),
+ 0, systems.getPage().size());
+ systems = REMOTE_CREDENTIALS_SERVICE.listSharedRemoteSystems(new PagingRequest(10));
+ assertEquals("No systems should be found, got " + systems.getPage(),
+ 0, systems.getPage().size());
+
+
+ // Create shared
+ PasswordCredentialsInfoImpl pwCred = new PasswordCredentialsInfoImpl();
+ pwCred.setRemoteUsername(TEST_REMOTE_USERNAME_ONE);
+ BaseCredentialsInfo credentials = REMOTE_CREDENTIALS_SERVICE.createSharedCredentials(TEST_REMOTE_SYSTEM_ONE, pwCred);
+
+ // Check the new object was populated properly
+ assertNotNull(credentials);
+ assertNotNull(credentials.getNodeRef());
+ assertNotNull(credentials.getRemoteSystemContainerNodeRef());
+ assertEquals(TEST_REMOTE_SYSTEM_ONE, credentials.getRemoteSystemName());
+ assertEquals(TEST_REMOTE_USERNAME_ONE, credentials.getRemoteUsername());
+ assertEquals(RemoteCredentialsModel.TYPE_PASSWORD_CREDENTIALS, credentials.getCredentialsType());
+
+
+ // Update
+ pwCred = (PasswordCredentialsInfoImpl)credentials;
+ pwCred.setRemoteUsername(TEST_REMOTE_USERNAME_TWO);
+ credentials = REMOTE_CREDENTIALS_SERVICE.updateCredentials(pwCred);
+
+ assertNotNull(credentials);
+ assertNotNull(credentials.getNodeRef());
+ assertNotNull(credentials.getRemoteSystemContainerNodeRef());
+ assertEquals(TEST_REMOTE_SYSTEM_ONE, credentials.getRemoteSystemName());
+ assertEquals(TEST_REMOTE_USERNAME_TWO, credentials.getRemoteUsername());
+ assertEquals(RemoteCredentialsModel.TYPE_PASSWORD_CREDENTIALS, credentials.getCredentialsType());
+
+
+ // List
+ systems = REMOTE_CREDENTIALS_SERVICE.listPersonRemoteSystems(new PagingRequest(10));
+ assertEquals(0, systems.getPage().size());
+ systems = REMOTE_CREDENTIALS_SERVICE.listSharedRemoteSystems(new PagingRequest(10));
+ assertEquals(1, systems.getPage().size());
+ systems = REMOTE_CREDENTIALS_SERVICE.listAllRemoteSystems(new PagingRequest(10));
+ assertEquals(1, systems.getPage().size());
+
+ creds = REMOTE_CREDENTIALS_SERVICE.listPersonCredentials(TEST_REMOTE_SYSTEM_ONE, null, new PagingRequest(10));
+ assertEquals(0, creds.getPage().size());
+
+ creds = REMOTE_CREDENTIALS_SERVICE.listSharedCredentials(TEST_REMOTE_SYSTEM_ONE, null, new PagingRequest(10));
+ assertEquals(1, creds.getPage().size());
+ assertEquals(TEST_REMOTE_USERNAME_TWO, creds.getPage().get(0).getRemoteUsername());
+
+ creds = REMOTE_CREDENTIALS_SERVICE.listAllCredentials(TEST_REMOTE_SYSTEM_ONE, null, new PagingRequest(10));
+ assertEquals(1, creds.getPage().size());
+ assertEquals(TEST_REMOTE_USERNAME_TWO, creds.getPage().get(0).getRemoteUsername());
+
+
+ // Delete
+ REMOTE_CREDENTIALS_SERVICE.deleteCredentials(credentials);
+
+
+ // List, system remains, credentials gone
+ systems = REMOTE_CREDENTIALS_SERVICE.listPersonRemoteSystems(new PagingRequest(10));
+ assertEquals(0, systems.getPage().size());
+ systems = REMOTE_CREDENTIALS_SERVICE.listSharedRemoteSystems(new PagingRequest(10));
+ assertEquals(1, systems.getPage().size());
+ systems = REMOTE_CREDENTIALS_SERVICE.listAllRemoteSystems(new PagingRequest(10));
+ assertEquals(1, systems.getPage().size());
+
+ creds = REMOTE_CREDENTIALS_SERVICE.listPersonCredentials(TEST_REMOTE_SYSTEM_ONE, null, new PagingRequest(10));
+ assertEquals(0, creds.getPage().size());
+ creds = REMOTE_CREDENTIALS_SERVICE.listSharedCredentials(TEST_REMOTE_SYSTEM_ONE, null, new PagingRequest(10));
+ assertEquals(0, creds.getPage().size());
+ creds = REMOTE_CREDENTIALS_SERVICE.listAllCredentials(TEST_REMOTE_SYSTEM_ONE, null, new PagingRequest(10));
+ assertEquals(0, creds.getPage().size());
+ }
+
+ /** Dedicated permissions and paging tests */
+ @Test public void testListingPermissionsAndPaging() throws Exception
+ {
+ PagingResults systems = null;
+ PagingResults extends BaseCredentialsInfo> creds = null;
+
+
+ // Run as a test user
+ AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER_ONE);
+
+ // Initially both should be empty empty
+ systems = REMOTE_CREDENTIALS_SERVICE.listPersonRemoteSystems(new PagingRequest(10));
+ assertEquals("No systems should be found, got " + systems.getPage(),
+ 0, systems.getPage().size());
+ systems = REMOTE_CREDENTIALS_SERVICE.listSharedRemoteSystems(new PagingRequest(10));
+ assertEquals("No systems should be found, got " + systems.getPage(),
+ 0, systems.getPage().size());
+
+
+ // Create some credentials as the first user, for systems One and Two
+ PasswordCredentialsInfoImpl pwCred = new PasswordCredentialsInfoImpl();
+ pwCred.setRemoteUsername(TEST_REMOTE_USERNAME_ONE);
+ REMOTE_CREDENTIALS_SERVICE.createSharedCredentials(TEST_REMOTE_SYSTEM_ONE, pwCred);
+
+ pwCred = new PasswordCredentialsInfoImpl();
+ pwCred.setRemoteUsername(TEST_REMOTE_USERNAME_TWO);
+ REMOTE_CREDENTIALS_SERVICE.createSharedCredentials(TEST_REMOTE_SYSTEM_ONE, pwCred);
+
+ pwCred = new PasswordCredentialsInfoImpl();
+ pwCred.setRemoteUsername(TEST_REMOTE_USERNAME_TWO);
+ REMOTE_CREDENTIALS_SERVICE.createSharedCredentials(TEST_REMOTE_SYSTEM_TWO, pwCred);
+
+ pwCred = new PasswordCredentialsInfoImpl();
+ pwCred.setRemoteUsername(TEST_REMOTE_USERNAME_TWO);
+ REMOTE_CREDENTIALS_SERVICE.createPersonCredentials(TEST_REMOTE_SYSTEM_TWO, pwCred);
+
+
+ // Switch to the second user, create some credentials on Two and Three
+ AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER_TWO);
+
+ pwCred = new PasswordCredentialsInfoImpl();
+ pwCred.setRemoteUsername(TEST_REMOTE_USERNAME_TWO);
+ REMOTE_CREDENTIALS_SERVICE.createSharedCredentials(TEST_REMOTE_SYSTEM_TWO, pwCred);
+
+ pwCred = new PasswordCredentialsInfoImpl();
+ pwCred.setRemoteUsername(TEST_REMOTE_USERNAME_THREE);
+ REMOTE_CREDENTIALS_SERVICE.createSharedCredentials(TEST_REMOTE_SYSTEM_THREE, pwCred);
+
+ pwCred = new PasswordCredentialsInfoImpl();
+ pwCred.setRemoteUsername(TEST_REMOTE_USERNAME_THREE);
+ REMOTE_CREDENTIALS_SERVICE.createPersonCredentials(TEST_REMOTE_SYSTEM_THREE, pwCred);
+
+
+ // Check the listings of remote systems for each user
+ AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER_ONE);
+ systems = REMOTE_CREDENTIALS_SERVICE.listPersonRemoteSystems(new PagingRequest(10));
+ assertEquals(1, systems.getPage().size());
+ assertEquals(TEST_REMOTE_SYSTEM_TWO, systems.getPage().get(0));
+
+ AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER_TWO);
+ systems = REMOTE_CREDENTIALS_SERVICE.listPersonRemoteSystems(new PagingRequest(10));
+ assertEquals(1, systems.getPage().size());
+ assertEquals(TEST_REMOTE_SYSTEM_THREE, systems.getPage().get(0));
+
+
+ // Check the listings of remote systems that are shared - shouldn't matter which user
+ AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER_ONE);
+ systems = REMOTE_CREDENTIALS_SERVICE.listSharedRemoteSystems(new PagingRequest(10));
+ assertEquals(3, systems.getPage().size());
+
+ AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER_TWO);
+ systems = REMOTE_CREDENTIALS_SERVICE.listSharedRemoteSystems(new PagingRequest(10));
+ assertEquals(3, systems.getPage().size());
+
+
+ // Check the listings of the credentials by user
+ AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER_ONE);
+ creds = REMOTE_CREDENTIALS_SERVICE.listPersonCredentials(TEST_REMOTE_SYSTEM_ONE, null, new PagingRequest(10));
+ assertEquals(0, creds.getPage().size());
+ creds = REMOTE_CREDENTIALS_SERVICE.listPersonCredentials(TEST_REMOTE_SYSTEM_TWO, null, new PagingRequest(10));
+ assertEquals(1, creds.getPage().size());
+ assertEquals(TEST_REMOTE_SYSTEM_TWO, creds.getPage().get(0).getRemoteSystemName());
+ creds = REMOTE_CREDENTIALS_SERVICE.listPersonCredentials(TEST_REMOTE_SYSTEM_THREE, null, new PagingRequest(10));
+ assertEquals(0, creds.getPage().size());
+
+ AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER_TWO);
+ creds = REMOTE_CREDENTIALS_SERVICE.listPersonCredentials(TEST_REMOTE_SYSTEM_ONE, null, new PagingRequest(10));
+ assertEquals(0, creds.getPage().size());
+ creds = REMOTE_CREDENTIALS_SERVICE.listPersonCredentials(TEST_REMOTE_SYSTEM_TWO, null, new PagingRequest(10));
+ assertEquals(0, creds.getPage().size());
+ creds = REMOTE_CREDENTIALS_SERVICE.listPersonCredentials(TEST_REMOTE_SYSTEM_THREE, null, new PagingRequest(10));
+ assertEquals(1, creds.getPage().size());
+ assertEquals(TEST_REMOTE_SYSTEM_THREE, creds.getPage().get(0).getRemoteSystemName());
+
+
+ // Check the shared listing of credentials, same for both users
+ AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER_ONE);
+ creds = REMOTE_CREDENTIALS_SERVICE.listSharedCredentials(TEST_REMOTE_SYSTEM_ONE, null, new PagingRequest(10));
+ assertEquals(2, creds.getPage().size());
+ creds = REMOTE_CREDENTIALS_SERVICE.listSharedCredentials(TEST_REMOTE_SYSTEM_TWO, null, new PagingRequest(10));
+ assertEquals(2, creds.getPage().size());
+ creds = REMOTE_CREDENTIALS_SERVICE.listSharedCredentials(TEST_REMOTE_SYSTEM_THREE, null, new PagingRequest(10));
+ assertEquals(1, creds.getPage().size());
+
+ AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER_TWO);
+ creds = REMOTE_CREDENTIALS_SERVICE.listSharedCredentials(TEST_REMOTE_SYSTEM_ONE, null, new PagingRequest(10));
+ assertEquals(2, creds.getPage().size());
+ creds = REMOTE_CREDENTIALS_SERVICE.listSharedCredentials(TEST_REMOTE_SYSTEM_TWO, null, new PagingRequest(10));
+ assertEquals(2, creds.getPage().size());
+ creds = REMOTE_CREDENTIALS_SERVICE.listSharedCredentials(TEST_REMOTE_SYSTEM_THREE, null, new PagingRequest(10));
+ assertEquals(1, creds.getPage().size());
+
+
+ // Check the paging of remote systems
+ systems = REMOTE_CREDENTIALS_SERVICE.listSharedRemoteSystems(new PagingRequest(10));
+ assertEquals(3, systems.getPage().size());
+ assertEquals(false, systems.hasMoreItems());
+ assertEquals(3, systems.getTotalResultCount().getFirst().intValue());
+
+ systems = REMOTE_CREDENTIALS_SERVICE.listSharedRemoteSystems(new PagingRequest(1));
+ assertEquals(1, systems.getPage().size());
+ assertEquals(true, systems.hasMoreItems());
+ assertEquals(3, systems.getTotalResultCount().getFirst().intValue());
+
+ systems = REMOTE_CREDENTIALS_SERVICE.listSharedRemoteSystems(new PagingRequest(1, 2));
+ assertEquals(2, systems.getPage().size());
+ assertEquals(false, systems.hasMoreItems());
+ assertEquals(3, systems.getTotalResultCount().getFirst().intValue());
+
+ systems = REMOTE_CREDENTIALS_SERVICE.listSharedRemoteSystems(new PagingRequest(2, 2));
+ assertEquals(1, systems.getPage().size());
+ assertEquals(false, systems.hasMoreItems());
+ assertEquals(3, systems.getTotalResultCount().getFirst().intValue());
+
+
+ // Check the paging of credentials
+ creds = REMOTE_CREDENTIALS_SERVICE.listSharedCredentials(TEST_REMOTE_SYSTEM_ONE, null, new PagingRequest(10));
+ assertEquals(2, creds.getPage().size());
+ assertEquals(false, creds.hasMoreItems());
+ assertEquals(2, creds.getTotalResultCount().getFirst().intValue());
+
+ creds = REMOTE_CREDENTIALS_SERVICE.listSharedCredentials(TEST_REMOTE_SYSTEM_ONE, null, new PagingRequest(1));
+ assertEquals(1, creds.getPage().size());
+ assertEquals(true, creds.hasMoreItems());
+ assertEquals(2, creds.getTotalResultCount().getFirst().intValue());
+
+ creds = REMOTE_CREDENTIALS_SERVICE.listSharedCredentials(TEST_REMOTE_SYSTEM_ONE, null, new PagingRequest(1,1));
+ assertEquals(1, creds.getPage().size());
+ assertEquals(false, creds.hasMoreItems());
+ assertEquals(2, creds.getTotalResultCount().getFirst().intValue());
+
+
+ // Tweak shared permissions
+ AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER_TWO);
+ creds = REMOTE_CREDENTIALS_SERVICE.listSharedCredentials(TEST_REMOTE_SYSTEM_ONE, null, new PagingRequest(10));
+ assertEquals(2, creds.getPage().size());
+ creds = REMOTE_CREDENTIALS_SERVICE.listSharedCredentials(TEST_REMOTE_SYSTEM_TWO, null, new PagingRequest(10));
+ assertEquals(2, creds.getPage().size());
+ creds = REMOTE_CREDENTIALS_SERVICE.listSharedCredentials(TEST_REMOTE_SYSTEM_THREE, null, new PagingRequest(10));
+ assertEquals(1, creds.getPage().size());
+
+ // Systems One and Two were created by users one
+ creds = REMOTE_CREDENTIALS_SERVICE.listSharedCredentials(TEST_REMOTE_SYSTEM_ONE, null, new PagingRequest(1));
+ NodeRef sharedS1 = creds.getPage().get(0).getRemoteSystemContainerNodeRef();
+ creds = REMOTE_CREDENTIALS_SERVICE.listSharedCredentials(TEST_REMOTE_SYSTEM_TWO, null, new PagingRequest(1));
+ NodeRef sharedS2 = creds.getPage().get(0).getRemoteSystemContainerNodeRef();
+
+ AuthenticationUtil.setFullyAuthenticatedUser(ADMIN_USER);
+ PERMISSION_SERVICE.setInheritParentPermissions(sharedS1, false);
+ PERMISSION_SERVICE.setInheritParentPermissions(sharedS2, false);
+
+
+ // Should then only be able to see Three, the one they created
+ AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER_TWO);
+ creds = REMOTE_CREDENTIALS_SERVICE.listSharedCredentials(TEST_REMOTE_SYSTEM_ONE, null, new PagingRequest(10));
+ assertEquals(0, creds.getPage().size());
+ creds = REMOTE_CREDENTIALS_SERVICE.listSharedCredentials(TEST_REMOTE_SYSTEM_TWO, null, new PagingRequest(10));
+ assertEquals(0, creds.getPage().size());
+ creds = REMOTE_CREDENTIALS_SERVICE.listSharedCredentials(TEST_REMOTE_SYSTEM_THREE, null, new PagingRequest(10));
+ assertEquals(1, creds.getPage().size());
+
+ // User One won't be able to see User Two's shared credentials under S2 under the new permissions
+ // They can still see their own credentials for S1 and S2, plus all in S3 (permissions unchanged)
+ AuthenticationUtil.setFullyAuthenticatedUser(TEST_USER_ONE);
+ creds = REMOTE_CREDENTIALS_SERVICE.listSharedCredentials(TEST_REMOTE_SYSTEM_ONE, null, new PagingRequest(10));
+ assertEquals(2, creds.getPage().size());
+ creds = REMOTE_CREDENTIALS_SERVICE.listSharedCredentials(TEST_REMOTE_SYSTEM_TWO, null, new PagingRequest(10));
+ assertEquals(1, creds.getPage().size());
+ creds = REMOTE_CREDENTIALS_SERVICE.listSharedCredentials(TEST_REMOTE_SYSTEM_THREE, null, new PagingRequest(10));
+ assertEquals(1, creds.getPage().size());
+ }
+
+
+ // --------------------------------------------------------------------------------
+
+ /**
+ * By default, all tests are run as the admin user.
+ */
+ @Before public void setAdminUser()
+ {
+ AuthenticationUtil.setFullyAuthenticatedUser(ADMIN_USER);
+ }
+
+ @After public void deleteTestNodes() throws Exception
+ {
+ AuthenticationUtil.setFullyAuthenticatedUser(ADMIN_USER);
+
+ // Find the shared system container, and zap contents
+ NodeRef container = ((RemoteCredentialsServiceImpl)PRIVATE_REMOTE_CREDENTIALS_SERVICE).getSharedContainerNodeRef(false);
+ if (container != null)
+ {
+ List children = new ArrayList();
+ for (ChildAssociationRef child : PUBLIC_NODE_SERVICE.getChildAssocs(container))
+ {
+ children.add(child.getChildRef());
+ }
+ performDeletionOfNodes(children);
+ }
+
+ // Zap the users, including any credentials stored for them
+ deleteUser(TEST_USER_ONE);
+ deleteUser(TEST_USER_TWO);
+ deleteUser(TEST_USER_THREE);
+ }
+
+ /**
+ * Deletes the specified NodeRefs, if they exist.
+ * @param nodesToDelete
+ */
+ private static void performDeletionOfNodes(final List nodesToDelete)
+ {
+ TRANSACTION_HELPER.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
+ {
+ @Override
+ public Void execute() throws Throwable
+ {
+ AuthenticationUtil.setFullyAuthenticatedUser(ADMIN_USER);
+
+ for (NodeRef node : nodesToDelete)
+ {
+ if (NODE_SERVICE.exists(node))
+ {
+ NODE_SERVICE.deleteNode(node);
+ }
+ }
+
+ return null;
+ }
+ });
+ }
+
+ private static void createUser(final String userName)
+ {
+ TRANSACTION_HELPER.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
+ {
+ @Override
+ public Void execute() throws Throwable
+ {
+ if (!AUTHENTICATION_SERVICE.authenticationExists(userName))
+ {
+ AUTHENTICATION_SERVICE.createAuthentication(userName, "PWD".toCharArray());
+ }
+
+ if (!PERSON_SERVICE.personExists(userName))
+ {
+ PropertyMap ppOne = new PropertyMap();
+ ppOne.put(ContentModel.PROP_USERNAME, userName);
+ ppOne.put(ContentModel.PROP_FIRSTNAME, "firstName");
+ ppOne.put(ContentModel.PROP_LASTNAME, "lastName");
+ ppOne.put(ContentModel.PROP_EMAIL, "email@email.com");
+ ppOne.put(ContentModel.PROP_JOBTITLE, "jobTitle");
+
+ PERSON_SERVICE.createPerson(ppOne);
+ }
+
+ return null;
+ }
+ });
+ }
+
+ private static void deleteUser(final String userName)
+ {
+ TRANSACTION_HELPER.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
+ {
+ @Override
+ public Void execute() throws Throwable
+ {
+ if (PERSON_SERVICE.personExists(userName))
+ {
+ PERSON_SERVICE.deletePerson(userName);
+ }
+
+ return null;
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/source/java/org/alfresco/repo/remoteticket/AbstractRemoteAlfrescoTicketImpl.java b/source/java/org/alfresco/repo/remoteticket/AbstractRemoteAlfrescoTicketImpl.java
new file mode 100644
index 0000000000..17fff349ef
--- /dev/null
+++ b/source/java/org/alfresco/repo/remoteticket/AbstractRemoteAlfrescoTicketImpl.java
@@ -0,0 +1,84 @@
+/*
+ * 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 .
+ */
+package org.alfresco.repo.remoteticket;
+
+import java.nio.charset.Charset;
+
+import org.alfresco.service.cmr.remoteticket.RemoteAlfrescoTicketInfo;
+import org.alfresco.util.Pair;
+import org.apache.commons.httpclient.Credentials;
+import org.apache.commons.httpclient.UsernamePasswordCredentials;
+import org.springframework.extensions.surf.util.Base64;
+import org.springframework.extensions.surf.util.URLEncoder;
+
+/**
+ * Parent class for implementations of {@link RemoteAlfrescoTicketInfo},
+ * which provides common helpers for working with tickets
+ *
+ * @author Nick Burch
+ * @since 4.0.2
+ */
+public abstract class AbstractRemoteAlfrescoTicketImpl implements RemoteAlfrescoTicketInfo
+{
+ protected static final Charset utf8 = Charset.forName("UTF-8");
+
+ /**
+ * Returns the Ticket as a URL Parameter fragment, such as
+ * "ticket=123&sig=13". No escaping is done
+ */
+ public abstract String getAsUrlParameters();
+
+ /**
+ * Returns the Ticket as a URL Escaped Parameter fragment, such as
+ * "ticket=12%20xx&sig=2". Special characters in the URL are escaped
+ * suitable for using as full URL, but any ampersands are not escaped
+ * (it's not HTML escaped)
+ */
+ public String getAsEscapedUrlParameters()
+ {
+ String unescaped = getAsUrlParameters();
+ return URLEncoder.encodeUri(unescaped);
+ }
+
+ /**
+ * Returns the Ticket in the form used for HTTP Basic Authentication.
+ * This should be added as the value to a HTTP Request Header with
+ * key Authorization
+ */
+ public String getAsHTTPAuthorization()
+ {
+ // Build from the Username and Password
+ Pair userPass = getAsUsernameAndPassword();
+ Credentials credentials = new UsernamePasswordCredentials(userPass.getFirst(), userPass.getSecond());
+
+ // Encode it into the required format
+ String credentialsEncoded = Base64.encodeBytes(
+ credentials.toString().getBytes(utf8), Base64.DONT_BREAK_LINES );
+
+ // Mark it as Basic, and we're done
+ return "Basic " + credentialsEncoded;
+ }
+
+ /**
+ * Returns the Ticket in the form of a pseudo username and password.
+ * The Username is normally a special ticket identifier, and the password
+ * is the ticket in a suitably encoded form.
+ */
+ public abstract Pair getAsUsernameAndPassword();
+}
diff --git a/source/java/org/alfresco/repo/remoteticket/AlfTicketRemoteAlfrescoTicketImpl.java b/source/java/org/alfresco/repo/remoteticket/AlfTicketRemoteAlfrescoTicketImpl.java
new file mode 100644
index 0000000000..ff544798b5
--- /dev/null
+++ b/source/java/org/alfresco/repo/remoteticket/AlfTicketRemoteAlfrescoTicketImpl.java
@@ -0,0 +1,70 @@
+/*
+ * 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 .
+ */
+package org.alfresco.repo.remoteticket;
+
+import org.alfresco.repo.security.authentication.Authorization;
+import org.alfresco.util.Pair;
+
+/**
+ * An implementation of {@link RemoteAlfrescoTicketInfo} which works
+ * with the regular Alfresco alf_ticket ticket system
+ *
+ * @author Nick Burch
+ * @since 4.0.2
+ */
+public class AlfTicketRemoteAlfrescoTicketImpl extends AbstractRemoteAlfrescoTicketImpl
+{
+ private static final String TICKET_USERNAME = Authorization.TICKET_USERID;
+ private static final String TICKET_URL_PARAM = "alf_ticket";
+
+ private final String ticket;
+
+ public AlfTicketRemoteAlfrescoTicketImpl(String ticket)
+ {
+ this.ticket = ticket;
+ }
+
+ /**
+ * Returns the Ticket as a URL Parameter fragment, of the form
+ * "alf_ticket=XXXX"
+ */
+ public String getAsUrlParameters()
+ {
+ return TICKET_URL_PARAM + "=" + ticket;
+ }
+
+ /**
+ * Returns the Ticket as a URL Escaped Parameter fragment, which is the
+ * same as the un-escaped due to the format of Alfresco Tickets
+ */
+ public String getAsEscapedUrlParameters()
+ {
+ return getAsUrlParameters();
+ }
+
+ /**
+ * Returns the Ticket in the form of a pseudo username and password.
+ * The Username is a special ticket identifier, and the password
+ * is the ticket
+ */
+ public Pair getAsUsernameAndPassword()
+ {
+ return new Pair(TICKET_USERNAME, ticket);
+ }
+}
diff --git a/source/java/org/alfresco/repo/remoteticket/GuestRemoteAlfrescoTicketImpl.java b/source/java/org/alfresco/repo/remoteticket/GuestRemoteAlfrescoTicketImpl.java
new file mode 100644
index 0000000000..d5102b3a38
--- /dev/null
+++ b/source/java/org/alfresco/repo/remoteticket/GuestRemoteAlfrescoTicketImpl.java
@@ -0,0 +1,64 @@
+/*
+ * 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 .
+ */
+package org.alfresco.repo.remoteticket;
+
+import org.alfresco.util.Pair;
+
+/**
+ * An implementation of {@link RemoteAlfrescoTicketInfo} which authenticates
+ * as the Guest user
+ *
+ * @author Nick Burch
+ * @since 4.0.2
+ */
+public class GuestRemoteAlfrescoTicketImpl extends AbstractRemoteAlfrescoTicketImpl
+{
+ private static final String GUEST_USERNAME = "guest";
+ private static final String GUEST_URL_PARAM = "guest=true";
+
+ public GuestRemoteAlfrescoTicketImpl() {}
+
+ /**
+ * Returns the Ticket as a URL Parameter fragment, of the form
+ * "guest=true"
+ */
+ public String getAsUrlParameters()
+ {
+ return GUEST_URL_PARAM;
+ }
+
+ /**
+ * Returns the Ticket as a URL Escaped Parameter fragment, which is the
+ * same as the un-escaped due to the format of Alfresco Tickets
+ */
+ public String getAsEscapedUrlParameters()
+ {
+ return getAsUrlParameters();
+ }
+
+ /**
+ * Returns the Ticket in the form of a pseudo username and password.
+ * The Username is a special ticket identifier, and the password
+ * is the ticket
+ */
+ public Pair getAsUsernameAndPassword()
+ {
+ return new Pair(GUEST_USERNAME, "");
+ }
+}
diff --git a/source/java/org/alfresco/repo/remoteticket/RemoteAlfrescoTicketServiceImpl.java b/source/java/org/alfresco/repo/remoteticket/RemoteAlfrescoTicketServiceImpl.java
new file mode 100644
index 0000000000..5151529807
--- /dev/null
+++ b/source/java/org/alfresco/repo/remoteticket/RemoteAlfrescoTicketServiceImpl.java
@@ -0,0 +1,413 @@
+/*
+ * 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 .
+ */
+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.EhCacheAdapter;
+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 EhCacheAdapter ticketsCache;
+
+ private Map remoteSystemsUrls = new HashMap();
+ private Map> remoteSystemsReqHeaders = new HashMap>();
+
+ /**
+ * 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 EhCache to be used to cache remote tickets in
+ */
+ public void setTicketsCache(EhCacheAdapter 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 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());
+
+ // 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()
+ {
+ 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())
+ {
+ remoteCredentialsService.updateCredentialsAuthenticationSucceeded(true, credentials);
+ }
+
+ // Wrap and return
+ return new AlfTicketRemoteAlfrescoTicketImpl(ticket);
+ }
+}
diff --git a/source/java/org/alfresco/service/cmr/remoteconnector/RemoteConnectorRequest.java b/source/java/org/alfresco/service/cmr/remoteconnector/RemoteConnectorRequest.java
new file mode 100644
index 0000000000..d00a429112
--- /dev/null
+++ b/source/java/org/alfresco/service/cmr/remoteconnector/RemoteConnectorRequest.java
@@ -0,0 +1,70 @@
+/*
+ * 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 .
+ */
+package org.alfresco.service.cmr.remoteconnector;
+
+import java.io.InputStream;
+
+import org.apache.commons.httpclient.Header;
+import org.apache.commons.httpclient.methods.RequestEntity;
+
+/**
+ * Helper wrapper around a Remote Request, to be performed by the
+ * {@link RemoteConnectorService}.
+ * To have one of these created for you, use
+ * {@link RemoteConnectorService#buildRequest(String, String)}
+ *
+ * @author Nick Burch
+ * @since 4.0.2
+ */
+public interface RemoteConnectorRequest
+{
+ /**
+ * @return the URL this request is for
+ */
+ String getURL();
+ /**
+ * @return the HTTP Method this request will execute (eg POST, GET)
+ */
+ String getMethod();
+
+ /**
+ * @return The Content Type of the request
+ */
+ String getContentType();
+ /**
+ * Sets the Content Type to send for the request
+ */
+ void setContentType(String contentType);
+
+ /**
+ * Returns the Request Body, for use by the {@link RemoteConnectorService}
+ * which created this
+ */
+ Object getRequestBody();
+
+ void setRequestBody(String body);
+ void setRequestBody(byte[] body);
+ void setRequestBody(InputStream body);
+ void setRequestBody(RequestEntity body);
+
+ Header[] getRequestHeaders();
+ void addRequestHeader(String name, String value);
+ void addRequestHeader(Header header);
+ void addRequestHeaders(Header[] headers);
+}
diff --git a/source/java/org/alfresco/service/cmr/remoteconnector/RemoteConnectorResponse.java b/source/java/org/alfresco/service/cmr/remoteconnector/RemoteConnectorResponse.java
new file mode 100644
index 0000000000..21f6f835ba
--- /dev/null
+++ b/source/java/org/alfresco/service/cmr/remoteconnector/RemoteConnectorResponse.java
@@ -0,0 +1,70 @@
+/*
+ * 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 .
+ */
+package org.alfresco.service.cmr.remoteconnector;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.commons.httpclient.Header;
+
+/**
+ * Helper wrapper around a Remote Response, for a request that
+ * was executed by {@link RemoteConnectorService}.
+ *
+ * @author Nick Burch
+ * @since 4.0.2
+ */
+public interface RemoteConnectorResponse
+{
+ /**
+ * @return The request that generated this response
+ */
+ RemoteConnectorRequest getRequest();
+
+ /**
+ * @return The raw response content type, if available
+ */
+ String getRawContentType();
+ /**
+ * @return The mimetype of the response content, if available
+ */
+ String getContentType();
+ /**
+ * @return The charset of the response content, if available
+ */
+ String getCharset();
+
+ /**
+ * @return All of the response headers
+ */
+ Header[] getResponseHeaders();
+
+ /**
+ * @return The response data, as a stream
+ */
+ InputStream getResponseBodyAsStream() throws IOException;
+ /**
+ * @return The response data, as a byte array
+ */
+ byte[] getResponseBodyAsBytes() throws IOException;
+ /**
+ * @return The response as a string, based on the response content type charset
+ */
+ String getResponseBodyAsString() throws IOException;
+}
diff --git a/source/java/org/alfresco/service/cmr/remoteconnector/RemoteConnectorService.java b/source/java/org/alfresco/service/cmr/remoteconnector/RemoteConnectorService.java
new file mode 100644
index 0000000000..458b802de2
--- /dev/null
+++ b/source/java/org/alfresco/service/cmr/remoteconnector/RemoteConnectorService.java
@@ -0,0 +1,58 @@
+/*
+ * 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 .
+ */
+package org.alfresco.service.cmr.remoteconnector;
+
+import java.io.IOException;
+
+import org.alfresco.repo.security.authentication.AuthenticationException;
+import org.json.simple.JSONObject;
+import org.json.simple.parser.ParseException;
+
+/**
+ * Helper Service for performing remote web requests from within
+ * the repository tier.
+ *
+ * The default implementation of the service works with HttpClient
+ * internally, but other implementations (such as testing loopback)
+ * can be used.
+ *
+ * @author Nick Burch
+ * @since 4.0.2
+ */
+public interface RemoteConnectorService
+{
+ /**
+ * Builds a new Request object, to talk to the given URL
+ * with the supplied method
+ */
+ RemoteConnectorRequest buildRequest(String url, String method);
+
+ /**
+ * Executes the specified request, and return the response
+ */
+ RemoteConnectorResponse executeRequest(RemoteConnectorRequest request) throws IOException, AuthenticationException;
+
+ /**
+ * Executes the given request, requesting a JSON response, and
+ * returns the parsed JSON received back
+ *
+ * @throws ParseException If the response is not valid JSON
+ */
+ JSONObject executeJSONRequest(RemoteConnectorRequest request) throws IOException, AuthenticationException, ParseException;
+}
\ No newline at end of file
diff --git a/source/java/org/alfresco/service/cmr/remotecredentials/BaseCredentialsInfo.java b/source/java/org/alfresco/service/cmr/remotecredentials/BaseCredentialsInfo.java
new file mode 100644
index 0000000000..e9e3dc69f0
--- /dev/null
+++ b/source/java/org/alfresco/service/cmr/remotecredentials/BaseCredentialsInfo.java
@@ -0,0 +1,64 @@
+/*
+ * 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 .
+ */
+package org.alfresco.service.cmr.remotecredentials;
+
+import java.io.Serializable;
+
+import org.alfresco.repo.security.permissions.PermissionCheckValue;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.namespace.QName;
+
+/**
+ * This class is the parent of a set of Remote Credentials
+ *
+ * @author Nick Burch
+ * @since 4.0.2
+ */
+public interface BaseCredentialsInfo extends Serializable, PermissionCheckValue
+{
+ /**
+ * @return the NodeRef of the underlying credentials
+ */
+ NodeRef getNodeRef();
+
+ /**
+ * @return the Type of the underlying credentials
+ */
+ QName getCredentialsType();
+
+ /**
+ * @return the Remote System Name the credentials belong to
+ */
+ String getRemoteSystemName();
+
+ /**
+ * @return the NodeRef of the container for the Remote System
+ */
+ NodeRef getRemoteSystemContainerNodeRef();
+
+ /**
+ * @return the Remote Username
+ */
+ String getRemoteUsername();
+
+ /**
+ * @return whether the last authentication attempt succeeded
+ */
+ boolean getLastAuthenticationSucceeded();
+}
diff --git a/source/java/org/alfresco/service/cmr/remotecredentials/OAuth1CredentialsInfo.java b/source/java/org/alfresco/service/cmr/remotecredentials/OAuth1CredentialsInfo.java
new file mode 100644
index 0000000000..0511731304
--- /dev/null
+++ b/source/java/org/alfresco/service/cmr/remotecredentials/OAuth1CredentialsInfo.java
@@ -0,0 +1,38 @@
+/*
+ * 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 .
+ */
+package org.alfresco.service.cmr.remotecredentials;
+
+/**
+ * This class represents an OAuth 1.0 based set of credentials
+ *
+ * @author Nick Burch
+ * @since 4.0.2
+ */
+public interface OAuth1CredentialsInfo extends BaseCredentialsInfo
+{
+ /**
+ * @return the OAuth Token Identifier
+ */
+ String getOAuthToken();
+
+ /**
+ * @return the OAuth Token Secret
+ */
+ String getOAuthSecret();
+}
diff --git a/source/java/org/alfresco/service/cmr/remotecredentials/OAuth2CredentialsInfo.java b/source/java/org/alfresco/service/cmr/remotecredentials/OAuth2CredentialsInfo.java
new file mode 100644
index 0000000000..2f25039b00
--- /dev/null
+++ b/source/java/org/alfresco/service/cmr/remotecredentials/OAuth2CredentialsInfo.java
@@ -0,0 +1,50 @@
+/*
+ * 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 .
+ */
+package org.alfresco.service.cmr.remotecredentials;
+
+import java.util.Date;
+
+/**
+ * This class represents an OAuth 2.0 based set of credentials
+ *
+ * @author Nick Burch
+ * @since 4.0.2
+ */
+public interface OAuth2CredentialsInfo extends BaseCredentialsInfo
+{
+ /**
+ * @return the OAuth Access Token
+ */
+ String getOAuthAccessToken();
+
+ /**
+ * @return the OAuth Refresh
+ */
+ String getOAuthRefreshToken();
+
+ /**
+ * @return When the Access Token was Issued
+ */
+ Date getOAuthTicketIssuedAt();
+
+ /**
+ * @return When the Access Token will Expire
+ */
+ Date getOAuthTicketExpiresAt();
+}
diff --git a/source/java/org/alfresco/service/cmr/remotecredentials/PasswordCredentialsInfo.java b/source/java/org/alfresco/service/cmr/remotecredentials/PasswordCredentialsInfo.java
new file mode 100644
index 0000000000..e59dc05ee9
--- /dev/null
+++ b/source/java/org/alfresco/service/cmr/remotecredentials/PasswordCredentialsInfo.java
@@ -0,0 +1,34 @@
+/*
+ * 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 .
+ */
+package org.alfresco.service.cmr.remotecredentials;
+
+
+/**
+ * This class represents a password based set of credentials
+ *
+ * @author Nick Burch
+ * @since 4.0.2
+ */
+public interface PasswordCredentialsInfo extends BaseCredentialsInfo
+{
+ /**
+ * @return the Remote Password
+ */
+ String getRemotePassword();
+}
diff --git a/source/java/org/alfresco/service/cmr/remotecredentials/RemoteCredentialsService.java b/source/java/org/alfresco/service/cmr/remotecredentials/RemoteCredentialsService.java
new file mode 100644
index 0000000000..3ab3518dd6
--- /dev/null
+++ b/source/java/org/alfresco/service/cmr/remotecredentials/RemoteCredentialsService.java
@@ -0,0 +1,129 @@
+/*
+ * 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 .
+ */
+package org.alfresco.service.cmr.remotecredentials;
+
+import org.alfresco.query.PagingRequest;
+import org.alfresco.query.PagingResults;
+import org.alfresco.service.namespace.QName;
+
+/**
+ * The core Remote Credentials service.
+ *
+ * This provides low level support for storing, retrieving
+ * and finding remote credentials. Most users will want
+ * something built on top of this, eg to do the OAuth Dance.
+ *
+ * The "Remote System" name chosen by systems built on top of
+ * this need to be unique, to avoid clashes. Where there is
+ * only one thing that is talked to (eg Twitter, Flickr,
+ * Alfresco Cloud), then the "Remote System Name" should be
+ * the name of the system. Where one service can talk to
+ * multiple systems, the system hostname should be used as
+ * a suffix, such as "OpenID-livejournal.com" and
+ * "OpenID-stackexchange.net", so they can be differentiated.
+ *
+ * @author Nick Burch
+ * @since 4.0.2
+ */
+public interface RemoteCredentialsService
+{
+ /**
+ * Stores a new {@link BaseCredentialsInfo} for the current user
+ */
+ BaseCredentialsInfo createPersonCredentials(String remoteSystem, BaseCredentialsInfo credentials);
+
+ /**
+ * Stores a new {@link BaseCredentialsInfo} for shared use.
+ * Permissions should then be set to control access to these.
+ */
+ BaseCredentialsInfo createSharedCredentials(String remoteSystem, BaseCredentialsInfo credentials);
+
+ /**
+ * Updates an existing {@link BaseCredentialsInfo}. The type
+ * must not change.
+ */
+ BaseCredentialsInfo updateCredentials(BaseCredentialsInfo credentials);
+
+ /**
+ * Records if the most recent Authentication attempt with a given
+ * set of credentials worked or not.
+ */
+ BaseCredentialsInfo updateCredentialsAuthenticationSucceeded(boolean succeeded, BaseCredentialsInfo credentialsInfo);
+
+ /**
+ * Deletes an existing {@link BaseCredentialsInfo} from the repository
+ */
+ void deleteCredentials(BaseCredentialsInfo credentialsInfo);
+
+
+ /**
+ * Lists all Remote Systems for which credentials are
+ * stored for the current user
+ */
+ PagingResults listPersonRemoteSystems(PagingRequest paging);
+
+ /**
+ * Lists all Remote Systems for which the user has access
+ * to shared credentials
+ */
+ PagingResults listSharedRemoteSystems(PagingRequest paging);
+
+ /**
+ * Lists all the Remote Systems for which the user has credentials,
+ * either personal ones or shared ones
+ */
+ PagingResults listAllRemoteSystems(PagingRequest paging);
+
+
+ /**
+ * Fetches the credentials for the current user for the specified
+ * System. If multiple credentials exist, the first is returned, so
+ * this should only be used for systems where a user is restricted
+ * to only one set of credentials per system.
+ * @return The Credentials, or Null if none exist for the current user
+ */
+ BaseCredentialsInfo getPersonCredentials(String remoteSystem);
+
+
+ /**
+ * Lists all Credentials for the current user for the given Remote System
+ *
+ * @param remoteSystem The Remote System to return credentials for
+ * @param credentialsType Optional type (including child subtypes) of the credentials to filter by
+ */
+ PagingResults extends BaseCredentialsInfo> listPersonCredentials(String remoteSystem, QName credentialsType, PagingRequest paging);
+
+ /**
+ * Lists all Credentials that are shared with the current user for
+ * the given Remote System
+ *
+ * @param remoteSystem The Remote System to return credentials for
+ * @param credentialsType Optional type (including child subtypes) of the credentials to filter by
+ */
+ PagingResults extends BaseCredentialsInfo> listSharedCredentials(String remoteSystem, QName credentialsType, PagingRequest paging);
+
+ /**
+ * Lists all Credentials that the user has access to
+ * for the given Remote System
+ *
+ * @param remoteSystem The Remote System to return credentials for
+ * @param credentialsType Optional type (including child subtypes) of the credentials to filter by
+ */
+ PagingResults extends BaseCredentialsInfo> listAllCredentials(String remoteSystem, QName credentialsType, PagingRequest paging);
+}
diff --git a/source/java/org/alfresco/service/cmr/remoteticket/NoCredentialsFoundException.java b/source/java/org/alfresco/service/cmr/remoteticket/NoCredentialsFoundException.java
new file mode 100644
index 0000000000..42d3e5bf9c
--- /dev/null
+++ b/source/java/org/alfresco/service/cmr/remoteticket/NoCredentialsFoundException.java
@@ -0,0 +1,45 @@
+/*
+ * 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 .
+ */
+package org.alfresco.service.cmr.remoteticket;
+
+import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+
+/**
+ * Exception thrown if no credentials could be found when
+ * attempting to perform an authentication request to
+ * a remote system.
+ *
+ * @author Nick Burch
+ * @since 4.0.2
+ */
+public class NoCredentialsFoundException extends AlfrescoRuntimeException
+{
+ private static final long serialVersionUID = -1167368337984937185L;
+
+ public NoCredentialsFoundException()
+ {
+ super("No Credentials Found");
+ }
+
+ public NoCredentialsFoundException(String remoteSystemId)
+ {
+ super("No Credentials Found for " + AuthenticationUtil.getRunAsUser() + " for Remote System '" + remoteSystemId + "'");
+ }
+}
\ No newline at end of file
diff --git a/source/java/org/alfresco/service/cmr/remoteticket/NoSuchSystemException.java b/source/java/org/alfresco/service/cmr/remoteticket/NoSuchSystemException.java
new file mode 100644
index 0000000000..7abb32506b
--- /dev/null
+++ b/source/java/org/alfresco/service/cmr/remoteticket/NoSuchSystemException.java
@@ -0,0 +1,46 @@
+/*
+ * 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 .
+ */
+package org.alfresco.service.cmr.remoteticket;
+
+import org.alfresco.error.AlfrescoRuntimeException;
+
+/**
+ * Exception thrown if a request is made, to work on
+ * authentication for a Remote System, where the
+ * System is not known to the service.
+ *
+ * @author Nick Burch
+ * @since 4.0.2
+ */
+public class NoSuchSystemException extends AlfrescoRuntimeException
+{
+ private static final long serialVersionUID = 282472917033620185L;
+ private String system;
+
+ public NoSuchSystemException(String system)
+ {
+ super("No Remote System defined with ID '" + system + "'");
+ this.system = system;
+ }
+
+ public String getSystem()
+ {
+ return system;
+ }
+}
\ No newline at end of file
diff --git a/source/java/org/alfresco/service/cmr/remoteticket/RemoteAlfrescoTicketInfo.java b/source/java/org/alfresco/service/cmr/remoteticket/RemoteAlfrescoTicketInfo.java
new file mode 100644
index 0000000000..5ddaa6735e
--- /dev/null
+++ b/source/java/org/alfresco/service/cmr/remoteticket/RemoteAlfrescoTicketInfo.java
@@ -0,0 +1,63 @@
+/*
+ * 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 .
+ */
+package org.alfresco.service.cmr.remoteticket;
+
+import org.alfresco.util.Pair;
+
+/**
+ * Holds details on a Ticket from a Remote Alfresco System,
+ * and provides ways to get it into different forms
+ * suitable for sending back to the Remote System.
+ *
+ * Currently, only regular Tickets are supported, but this
+ * is designed to handle things like OAuth later
+ *
+ * @author Nick Burch
+ * @since 4.0.2
+ */
+public interface RemoteAlfrescoTicketInfo
+{
+ /**
+ * Returns the Ticket as a URL Parameter fragment, such as
+ * "ticket=123&sig=13". No escaping is done
+ */
+ String getAsUrlParameters();
+
+ /**
+ * Returns the Ticket as a URL Escaped Parameter fragment, such as
+ * "ticket=12%20xx&sig=2". Special characters in the URL are escaped
+ * suitable for using as full URL, but any ampersands are not escaped
+ * (it's not HTML escaped)
+ */
+ String getAsEscapedUrlParameters();
+
+ /**
+ * Returns the Ticket in the form used for HTTP Basic Authentication.
+ * This should be added as the value to a HTTP Request Header with
+ * key Authorization
+ */
+ String getAsHTTPAuthorization();
+
+ /**
+ * Returns the Ticket in the form of a pseudo username and password.
+ * The Username is normally a special ticket identifier, and the password
+ * is the ticket in a suitably encoded form.
+ */
+ Pair getAsUsernameAndPassword();
+}
diff --git a/source/java/org/alfresco/service/cmr/remoteticket/RemoteAlfrescoTicketService.java b/source/java/org/alfresco/service/cmr/remoteticket/RemoteAlfrescoTicketService.java
new file mode 100644
index 0000000000..fe5752bfa1
--- /dev/null
+++ b/source/java/org/alfresco/service/cmr/remoteticket/RemoteAlfrescoTicketService.java
@@ -0,0 +1,126 @@
+/*
+ * 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 .
+ */
+package org.alfresco.service.cmr.remoteticket;
+
+import java.util.Map;
+
+import org.alfresco.repo.security.authentication.AuthenticationException;
+import org.alfresco.service.cmr.remotecredentials.BaseCredentialsInfo;
+import org.alfresco.service.cmr.remotecredentials.RemoteCredentialsService;
+
+/**
+ * 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.
+ *
+ * Currently only Username+Password credentials, exchanged for a
+ * regular alf_ticket Alfresco Ticket are supported, but
+ * things like OAuth should be supportable too later.
+ *
+ * All Remote Systems must be registered with this service before
+ * use, supplying details of where to find the remote Alfresco
+ * for a given Remote System ID. The Remote System names should
+ * follow the system naming convention from {@link RemoteCredentialsService}
+ *
+ * TODO OAuth support
+ *
+ * @author Nick Burch
+ * @since 4.0.2
+ */
+public interface RemoteAlfrescoTicketService
+{
+ /**
+ * Validates and stores the remote credentials for the current user
+ *
+ * @param remoteSystemId The ID of the remote system, as registered with the service
+ *
+ * @throws AuthenticationException If the credentials are invalid
+ * @throws RemoteSystemUnavailableException If the remote system is unavailable
+ * @throws NoSuchSystemException If no system has been registered with the given ID
+ */
+ BaseCredentialsInfo storeRemoteCredentials(String remoteSystemId, String username, String password)
+ throws AuthenticationException, RemoteSystemUnavailableException, NoSuchSystemException;
+
+ /**
+ * 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
+ * @throws NoSuchSystemException If no system has been registered with the given ID
+ */
+ BaseCredentialsInfo getRemoteCredentials(String remoteSystemId) throws NoSuchSystemException;
+
+ /**
+ * Deletes the remote credentials (if any) for the current user
+ *
+ * @param remoteSystemId The ID of the remote system, as registered with the service
+ * @return Whether credentials were found to delete
+ * @throws NoSuchSystemException If no system has been registered with the given ID
+ */
+ boolean deleteRemoteCredentials(String remoteSystemId) throws NoSuchSystemException;
+
+ /**
+ * Returns the current Alfresco Ticket for the current user on
+ * the remote system, fetching if it isn't already cached.
+ *
+ * Note that because tickets are cached, it is possible that a
+ * ticket has become invalid (due to timeout or server restart).
+ * If the ticket is rejected by the remote server, you should
+ * call {@link #refetchAlfrescoTicket(String)} to ensure you have
+ * the latest ticket, and re-try the request.
+ *
+ * @param remoteSystemId The ID of the remote system, as registered with the service
+ * @return The Alfresco Ticket for the current user on the remote system
+ *
+ * @throws AuthenticationException If the stored remote credentials are now invalid
+ * @throws NoCredentialsFoundException If the user has no stored credentials for the remote system
+ * @throws NoSuchSystemException If no system has been registered with the given ID
+ * @throws RemoteSystemUnavailableException If it was not possible to talk to the remote system
+ */
+ RemoteAlfrescoTicketInfo getAlfrescoTicket(String remoteSystemId)
+ throws AuthenticationException, NoCredentialsFoundException, NoSuchSystemException, RemoteSystemUnavailableException;
+
+ /**
+ * Forces a re-fetch of the Alfresco Ticket for the current user,
+ * if possible, and marks the credentials as failing if not.
+ *
+ * Normally {@link #getAlfrescoTicket(String)} should be used initially, with
+ * this only used if the ticket received is rejected by the remote server.
+ *
+ * @param remoteSystemId The ID of the remote system, as registered with the service
+ * @return The Alfresco Ticket for the current user on the remote system
+ *
+ * @throws AuthenticationException If the stored remote credentials are now invalid
+ * @throws NoCredentialsFoundException If the user has no stored credentials for the remote system
+ * @throws NoSuchSystemException If no system has been registered with the given ID
+ * @throws RemoteSystemUnavailableException If it was not possible to talk to the remote system
+ */
+ RemoteAlfrescoTicketInfo refetchAlfrescoTicket(String remoteSystemId)
+ throws AuthenticationException, NoCredentialsFoundException, NoSuchSystemException, RemoteSystemUnavailableException;
+
+ /**
+ * Registers the details of a new Remote System with the service.
+ *
+ * @param remoteSystemId The ID to be used to identify the system
+ * @param baseUrl The base URL of Alfresco Services on the remote system, eg http://localhost:8080/alfresco/service/
+ * @param requestHeaders Any HTTP headers that must be sent with the request when talking to the server
+ */
+ void registerRemoteSystem(String remoteSystemId, String baseUrl, Map requestHeaders);
+}
\ No newline at end of file
diff --git a/source/java/org/alfresco/service/cmr/remoteticket/RemoteSystemUnavailableException.java b/source/java/org/alfresco/service/cmr/remoteticket/RemoteSystemUnavailableException.java
new file mode 100644
index 0000000000..ef423c095e
--- /dev/null
+++ b/source/java/org/alfresco/service/cmr/remoteticket/RemoteSystemUnavailableException.java
@@ -0,0 +1,43 @@
+/*
+ * 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 .
+ */
+package org.alfresco.service.cmr.remoteticket;
+
+import org.alfresco.error.AlfrescoRuntimeException;
+
+/**
+ * Exception thrown if an error was received when attempting
+ * to talk with a remote system, meaning that it is unavailable.
+ *
+ * @author Nick Burch
+ * @since 4.0.2
+ */
+public class RemoteSystemUnavailableException extends AlfrescoRuntimeException
+{
+ private static final long serialVersionUID = 5346482391129538502L;
+
+ public RemoteSystemUnavailableException(String message)
+ {
+ super(message);
+ }
+
+ public RemoteSystemUnavailableException(String message, Throwable source)
+ {
+ super(message, source);
+ }
+}