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 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 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 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 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 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 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 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 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 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 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 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 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 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); + } +}