diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/imap/attachments.get.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/imap/attachments.get.desc.xml new file mode 100644 index 0000000000..0b58d96e03 --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/imap/attachments.get.desc.xml @@ -0,0 +1,8 @@ + + Node Attachments Info + Node Attachments Info + /api/attachments?nodeRef={noderef} + + user + required + diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/imap/attachments.get.js b/config/alfresco/templates/webscripts/org/alfresco/repository/imap/attachments.get.js new file mode 100644 index 0000000000..705a9b3542 --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/imap/attachments.get.js @@ -0,0 +1,37 @@ +function main() +{ + var attachmentsAssocs = []; + + if (args["nodeRef"] != null) + { + var nodeRef = args["nodeRef"], + node = search.findNode(nodeRef); + + if (node != null) + { + var assocs = node.associations; + for (var assocName in assocs) + { + if(assocName.equalsIgnoreCase("{http://www.alfresco.org/model/imap/1.0}attachment")) + { + var associations = assocs[assocName]; + for (var j = 0, jj = associations.length; j < jj; j++) + { + + attachmentsAssocs.push( + { + nodeRef: associations[j].nodeRef.toString(), + name: associations[j].name, + assocname: assocName + } + ); + } + } + } + } + } + + model.attachmentsAssocs = attachmentsAssocs; +} + +main(); \ No newline at end of file diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/imap/attachments.get.json.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/imap/attachments.get.json.ftl new file mode 100644 index 0000000000..a9a7a460a7 --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/imap/attachments.get.json.ftl @@ -0,0 +1,11 @@ +<#escape x as jsonUtils.encodeJSONString(x)> +[ +<#list attachmentsAssocs as a> + { + "nodeRef": "${a.nodeRef}", + "name": "${a.name}", + "assocname": "${a.assocname}" + }<#if (a_has_next)>, + +] + \ No newline at end of file diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/node/folder.post.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/node/folder.post.desc.xml new file mode 100644 index 0000000000..41acadf89e --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/node/folder.post.desc.xml @@ -0,0 +1,41 @@ + + Create Child Folder + + By default the new folder will be of type cm:folder, but subtypes of cm:folder + may be specified instead. +
+ The new NodeRef will be returned if the folder can be created. +
+ The minimum request is of the form: +
+     { "name": "NewNodeName" }
+   
+
+ The full set of parameters accepted in the request is of the form: +
+    {  
+       "name": "NewNodeName",
+       "title": "New Node Title",
+       "description": "A shiny new node",
+       "type": "cm:folder"
+    }
+   
+
+ + ]]> +
+ /api/node/folder/{store_type}/{store_id}/{id} + /api/site/folder/{site}/{container}/{path} + /api/site/folder/{site}/{container} + argument + user + required + public_api +
diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/node/folder.post.json.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/node/folder.post.json.ftl new file mode 100644 index 0000000000..e07a675f7d --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/node/folder.post.json.ftl @@ -0,0 +1,10 @@ +<#escape x as jsonUtils.encodeJSONString(x)> + { + "nodeRef": "${nodeRef.nodeRef}" + <#if site??> + , + "site": "${site.shortName}", + "container": "${container}", + + } + diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/site/site.get.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/site/site.get.desc.xml index 6dce9e323b..c5514cd322 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/site/site.get.desc.xml +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/site/site.get.desc.xml @@ -11,4 +11,4 @@ user required draft_public_api - \ No newline at end of file + diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/site/sites.get.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/site/sites.get.desc.xml index c9dac37def..47be54e5f4 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/site/sites.get.desc.xml +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/site/sites.get.desc.xml @@ -5,4 +5,4 @@ argument user required - \ No newline at end of file + diff --git a/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary-v2/filters.lib.js b/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary-v2/filters.lib.js index 49b4c0f03e..7a452d4f60 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary-v2/filters.lib.js +++ b/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary-v2/filters.lib.js @@ -162,8 +162,8 @@ var Filters = case "editingOthers": filterQuery = this.constructPathQuery(parsedArgs); - filterQuery += " +ASPECT:\"workingcopy\""; - filterQuery += " +((-@cm\\:workingCopyOwner:\"" + person.properties.userName + '")'; + filterQuery += " +((+ASPECT:\"workingcopy\""; + filterQuery += " -@cm\\:workingCopyOwner:\"" + person.properties.userName + '")'; filterQuery += " OR (-@cm\\:lockOwner:\"" + person.properties.userName + '"'; filterQuery += " +@cm\\:lockType:\"WRITE_LOCK\"))"; filterParams.query = filterQuery; @@ -197,6 +197,18 @@ var Filters = filterParams.query = filterQuery; break; + case "synced": + filterQuery = this.constructPathQuery(parsedArgs); + filterQuery += " +ASPECT:\"sync:syncSetMemberNode\""; + filterParams.query = filterQuery; + break; + + case "syncedErrors": + filterQuery = this.constructPathQuery(parsedArgs); + filterQuery += " +ASPECT:\"sync:failed\""; + filterParams.query = filterQuery; + break; + case "node": filterParams.variablePath = false; filterParams.query = "+ID:\"" + parsedArgs.nodeRef + "\""; diff --git a/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary-v2/node.get.json.ftl b/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary-v2/node.get.json.ftl index fe8bebaa95..76c0d8074b 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary-v2/node.get.json.ftl +++ b/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary-v2/node.get.json.ftl @@ -8,7 +8,8 @@ <#if doclist.parent?? && doclist.parent.nodeJSON??>"parent": <#noescape>${doclist.parent.nodeJSON}, <#if doclist.customJSON??>"custom": <#noescape>${doclist.customJSON}, "onlineEditing": ${doclist.onlineEditing?string}, - "workingCopyLabel": "${workingCopyLabel}" + "workingCopyLabel": "${workingCopyLabel}", + "shareURL": "${site.getShareUrl()}" }, "item": { diff --git a/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/action/permissions.post.json.js b/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/action/permissions.post.json.js index a91ab67706..43077f1ab8 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/action/permissions.post.json.js +++ b/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/action/permissions.post.json.js @@ -90,7 +90,7 @@ function runAction(p_params) if (jsonPermissions[i].get("group") == "GROUP_EVERYONE" && !site.isPublic) { result.success = false; - status.setCode(status.STATUS_BAD_REQUEST, "Cannot give permissions to all other permissions on non-public site"); + status.setCode(status.STATUS_BAD_REQUEST, "Cannot give permissions to all other users on non-public site"); return; } } diff --git a/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/containers.get.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/containers.get.desc.xml index 07e7ceba2d..6fa87c4e4f 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/containers.get.desc.xml +++ b/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/containers.get.desc.xml @@ -6,4 +6,4 @@ user required internal - \ No newline at end of file + diff --git a/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/filters.lib.js b/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/filters.lib.js index eab0e1e5bd..a02c24104d 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/filters.lib.js +++ b/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/filters.lib.js @@ -161,8 +161,8 @@ var Filters = case "editingOthers": filterQuery = this.constructPathQuery(parsedArgs); - filterQuery += " +ASPECT:\"workingcopy\""; - filterQuery += " +((-@cm\\:workingCopyOwner:\"" + person.properties.userName + '")'; + filterQuery += " +((+ASPECT:\"workingcopy\""; + filterQuery += " -@cm\\:workingCopyOwner:\"" + person.properties.userName + '")'; filterQuery += " OR (-@cm\\:lockOwner:\"" + person.properties.userName + '"'; filterQuery += " +@cm\\:lockType:\"WRITE_LOCK\"))"; filterParams.query = filterQuery; @@ -196,6 +196,18 @@ var Filters = filterParams.query = filterQuery; break; + case "synced": + filterQuery = this.constructPathQuery(parsedArgs); + filterQuery += " +ASPECT:\"sync:syncSetMemberNode\""; + filterParams.query = filterQuery; + break; + + case "syncedErrors": + filterQuery = this.constructPathQuery(parsedArgs); + filterQuery += " +ASPECT:\"sync:failed\""; + filterParams.query = filterQuery; + break; + case "node": filterParams.variablePath = false; filterParams.query = "+ID:\"" + parsedArgs.nodeRef + "\""; diff --git a/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/treenode.get.js b/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/treenode.get.js index 5997b71bd2..7dd4be474f 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/treenode.get.js +++ b/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/treenode.get.js @@ -43,7 +43,8 @@ function getTreeNode() items.push( { node: item, - hasSubfolders: hasSubfolders + hasSubfolders: hasSubfolders, + aspects: jsonUtils.toObject(appUtils.toJSON(item, true)).aspects }); } diff --git a/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/treenode.get.json.ftl b/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/treenode.get.json.ftl index ef0411da01..67e7d0fa65 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/treenode.get.json.ftl +++ b/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/treenode.get.json.ftl @@ -27,7 +27,13 @@ "create": ${t.hasPermission("CreateChildren")?string}, "edit": ${t.hasPermission("Write")?string}, "delete": ${t.hasPermission("Delete")?string} - } + }, + "aspects": + [ + <#list item.aspects as aspect> + "${aspect}"<#if aspect_has_next>, + + ] }<#if item_has_next>, ] diff --git a/config/alfresco/web-scripts-application-context.xml b/config/alfresco/web-scripts-application-context.xml index 56348bfa81..6cd620697a 100644 --- a/config/alfresco/web-scripts-application-context.xml +++ b/config/alfresco/web-scripts-application-context.xml @@ -751,6 +751,18 @@ + + + + + + + + + + + + diff --git a/source/java/org/alfresco/repo/remoteconnector/LocalWebScriptConnectorServiceImpl.java b/source/java/org/alfresco/repo/remoteconnector/LocalWebScriptConnectorServiceImpl.java index 7837069698..882c42b92b 100644 --- a/source/java/org/alfresco/repo/remoteconnector/LocalWebScriptConnectorServiceImpl.java +++ b/source/java/org/alfresco/repo/remoteconnector/LocalWebScriptConnectorServiceImpl.java @@ -38,6 +38,7 @@ import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.web.scripts.BaseWebScriptTest; import org.alfresco.repo.web.scripts.servlet.BasicHttpAuthenticatorFactory; +import org.alfresco.repo.web.scripts.servlet.LocalTestRunAsAuthenticatorFactory.LocalTestRunAsAuthenticator; import org.alfresco.repo.web.scripts.servlet.BasicHttpAuthenticatorFactory.BasicHttpAuthenticator; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpMethodBase; @@ -211,17 +212,13 @@ public class LocalWebScriptConnectorServiceImpl implements RemoteConnectorServic throw new AuthenticationException("Forbidden to access this resource"); } - // Check for some general ones - if (resp.getStatus() >= 400 && resp.getStatus() <= 499) - { - throw new RemoteConnectorClientException(resp.getStatus(), "(not available)", null); - } + // Check for failures where we don't care about the response body if (resp.getStatus() >= 500 && resp.getStatus() <= 599) { throw new RemoteConnectorServerException(resp.getStatus(), "(not available)"); } - // Convert the response + // Convert the response into our required format String charset = null; String contentType = resp.getContentType(); if (contentType != null && contentType.contains("charset=")) @@ -235,6 +232,14 @@ public class LocalWebScriptConnectorServiceImpl implements RemoteConnectorServic RemoteConnectorResponse response = new RemoteConnectorResponseImpl( request, contentType, charset, resp.getStatus(), respHeaders, body); + + // If it's a client error, let them know what went wrong + if (resp.getStatus() >= 400 && resp.getStatus() <= 499) + { + throw new RemoteConnectorClientException(resp.getStatus(), "(not available)", response); + } + + // Otherwise return the response for processing return response; } @@ -287,6 +292,7 @@ public class LocalWebScriptConnectorServiceImpl implements RemoteConnectorServic httpAuthFactory = (BasicHttpAuthenticatorFactory)server.getApplicationContext().getBean("webscripts.authenticator.basic"); // Wire us into the test + test.setCustomAuthenticatorFactory(this); server.setServletAuthenticatorFactory(this); } @@ -298,8 +304,9 @@ public class LocalWebScriptConnectorServiceImpl implements RemoteConnectorServic { // There are already details existing // Allow these to be kept and used - logger.debug("Existing Authentication found, remaining as " + AuthenticationUtil.getFullyAuthenticatedUser()); - return null; + String fullUser = AuthenticationUtil.getFullyAuthenticatedUser(); + logger.debug("Existing Authentication found, remaining as " + fullUser); + return new LocalTestRunAsAuthenticator(fullUser); } // Fall back to the http auth one diff --git a/source/java/org/alfresco/repo/web/scripts/BaseWebScriptTest.java b/source/java/org/alfresco/repo/web/scripts/BaseWebScriptTest.java index 29c0847c75..767ac4d218 100644 --- a/source/java/org/alfresco/repo/web/scripts/BaseWebScriptTest.java +++ b/source/java/org/alfresco/repo/web/scripts/BaseWebScriptTest.java @@ -48,6 +48,7 @@ import org.apache.commons.httpclient.params.HttpClientParams; import org.springframework.extensions.webscripts.TestWebScriptServer; import org.springframework.extensions.webscripts.TestWebScriptServer.Request; import org.springframework.extensions.webscripts.TestWebScriptServer.Response; +import org.springframework.extensions.webscripts.servlet.ServletAuthenticatorFactory; /** * Base unit test class for web scripts. @@ -56,13 +57,13 @@ import org.springframework.extensions.webscripts.TestWebScriptServer.Response; */ public abstract class BaseWebScriptTest extends TestCase { - // Test Listener private WebScriptTestListener listener = null; private boolean traceReqRes = false; // Local Server access private String customContext = null; + private ServletAuthenticatorFactory customAuthenticatorFactory = null; // Remote Server access private String defaultRunAs = null; @@ -215,6 +216,25 @@ public abstract class BaseWebScriptTest extends TestCase return defaultRunAs; } + /** + * Returns the custom {@link ServletAuthenticatorFactory} which is injected into + * an {@link TestWebScriptServer} instances that are returned, if any. + * Default is not to alter the {@link ServletAuthenticatorFactory} + */ + public ServletAuthenticatorFactory getCustomAuthenticatorFactory() + { + return customAuthenticatorFactory; + } + + /** + * Sets that a custom {@link ServletAuthenticatorFactory} should be injected into + * instances of {@link TestWebScriptServer} returned from {@link #getServer()} + */ + public void setCustomAuthenticatorFactory(ServletAuthenticatorFactory customAuthenticatorFactory) + { + this.customAuthenticatorFactory = customAuthenticatorFactory; + } + @Override protected void setUp() throws Exception { @@ -236,14 +256,21 @@ public abstract class BaseWebScriptTest extends TestCase */ protected TestWebScriptServer getServer() { + TestWebScriptServer server; if (customContext == null) { - return TestWebScriptRepoServer.getTestServer(); + server = TestWebScriptRepoServer.getTestServer(); } else { - return TestWebScriptRepoServer.getTestServer(customContext); + server = TestWebScriptRepoServer.getTestServer(customContext); } + + if (customAuthenticatorFactory != null) + { + server.setServletAuthenticatorFactory(customAuthenticatorFactory); + } + return server; } @@ -344,7 +371,12 @@ public abstract class BaseWebScriptTest extends TestCase if (AuthenticationUtil.isMtEnabled()) { // MT repository container requires non-none authentication (ie. guest or higher) - tws.setServletAuthenticatorFactory(new LocalTestRunAsAuthenticatorFactory()); + // If the servlet authenticator is still the default, substitute in a custom one + // (If they test has already changed the authenticator, then stay with that) + if (customAuthenticatorFactory == null) + { + tws.setServletAuthenticatorFactory(new LocalTestRunAsAuthenticatorFactory()); + } } if (asUser == null) diff --git a/source/java/org/alfresco/repo/web/scripts/node/NodeFolderPost.java b/source/java/org/alfresco/repo/web/scripts/node/NodeFolderPost.java new file mode 100644 index 0000000000..c625b5b250 --- /dev/null +++ b/source/java/org/alfresco/repo/web/scripts/node/NodeFolderPost.java @@ -0,0 +1,198 @@ +package org.alfresco.repo.web.scripts.node; + +import java.io.IOException; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.StringTokenizer; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; +import org.springframework.extensions.webscripts.Cache; +import org.springframework.extensions.webscripts.DeclarativeWebScript; +import org.springframework.extensions.webscripts.Status; +import org.springframework.extensions.webscripts.WebScriptException; +import org.springframework.extensions.webscripts.WebScriptRequest; + +/** + * This class is the webscript for creating child folders, of either a Node or a Site Container. + * + * @since 4.1 + */ +public class NodeFolderPost extends DeclarativeWebScript +{ + private NodeService nodeService; + private SiteService siteService; + private NamespaceService namespaceService; + private DictionaryService dictionaryService; + + @Override + protected Map executeImpl(WebScriptRequest req, Status status, Cache cache) + { + // Identify the Node they want to create a child of + SiteInfo site = null; + String container = null; + NodeRef parentNodeRef = null; + + Map templateArgs = req.getServiceMatch().getTemplateVars(); + if (templateArgs.get("site") != null && templateArgs.get("container") != null) + { + // Site based request + site = siteService.getSite(templateArgs.get("site")); + if (site == null) + { + status.setCode(Status.STATUS_NOT_FOUND); + status.setRedirect(true); + return null; + } + + // Check the container exists + container = templateArgs.get("container"); + NodeRef containerNodeRef = siteService.getContainer(site.getShortName(), container); + if (containerNodeRef == null) + { + status.setCode(Status.STATUS_NOT_FOUND); + status.setRedirect(true); + return null; + } + + // Work out where to put it + if (templateArgs.get("path") != null) + { + // Nibble our way along the / delimited path, starting from the container + parentNodeRef = containerNodeRef; + StringTokenizer st = new StringTokenizer(templateArgs.get("path"), "/"); + while (st.hasMoreTokens()) + { + String childName = st.nextToken(); + parentNodeRef = nodeService.getChildByName(parentNodeRef, ContentModel.ASSOC_CONTAINS, childName); + if (parentNodeRef == null) + { + status.setCode(Status.STATUS_NOT_FOUND); + status.setRedirect(true); + return null; + } + } + } + else + { + // Direct child of the container + parentNodeRef = containerNodeRef; + } + } + else if (templateArgs.get("store_type") != null && templateArgs.get("store_id") != null + && templateArgs.get("id") != null) + { + // NodeRef based creation + parentNodeRef = new NodeRef(templateArgs.get("store_type"), + templateArgs.get("store_id"), templateArgs.get("id")); + if (! nodeService.exists(parentNodeRef)) + { + status.setCode(Status.STATUS_NOT_FOUND); + status.setRedirect(true); + return null; + } + } + else + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "No parent details found"); + } + + + // Process the JSON post details + JSONObject json = null; + JSONParser parser = new JSONParser(); + try + { + json = (JSONObject)parser.parse(req.getContent().getContent()); + } + catch (IOException io) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Invalid JSON: " + io.getMessage()); + } + catch (ParseException pe) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Invalid JSON: " + pe.getMessage()); + } + + + // Fetch the name, title and description + String name = (String)json.get("name"); + if (name == null) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Name is required"); + } + + String title = (String)json.get("title"); + if (title == null) + { + title = name; + } + String description = (String)json.get("description"); + + Map props = new HashMap(); + props.put(ContentModel.PROP_NAME, name); + props.put(ContentModel.PROP_TITLE, title); + props.put(ContentModel.PROP_DESCRIPTION, description); + + + // Verify the type is allowed + QName type = ContentModel.TYPE_FOLDER; + if (json.get("type") != null) + { + type = QName.createQName( (String)json.get("type"), namespaceService ); + if (! dictionaryService.isSubClass(type, ContentModel.TYPE_FOLDER)) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Specified type is not a folder"); + } + } + + + // Have the node created + NodeRef nodeRef = null; + try + { + nodeRef = nodeService.createNode(parentNodeRef, ContentModel.ASSOC_CONTAINS, + QName.createQName(name), type, props).getChildRef(); + } + catch (AccessDeniedException e) + { + throw new WebScriptException(Status.STATUS_FORBIDDEN, "You don't have permission to create the node"); + } + + // Report the details + Map model = new HashMap(); + model.put("nodeRef", nodeRef); + model.put("site", site); + model.put("container", container); + model.put("parentNodeRef", parentNodeRef); + return model; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + public void setSiteService(SiteService siteService) + { + this.siteService = siteService; + } + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } +} diff --git a/source/java/org/alfresco/repo/web/scripts/node/NodeWebScripTest.java b/source/java/org/alfresco/repo/web/scripts/node/NodeWebScripTest.java new file mode 100644 index 0000000000..5189a37eac --- /dev/null +++ b/source/java/org/alfresco/repo/web/scripts/node/NodeWebScripTest.java @@ -0,0 +1,344 @@ +package org.alfresco.repo.web.scripts.node; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.site.SiteModel; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.repo.web.scripts.BaseWebScriptTest; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.MutableAuthenticationService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.service.cmr.site.SiteVisibility; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.PropertyMap; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.springframework.context.support.AbstractRefreshableApplicationContext; +import org.springframework.extensions.webscripts.Status; +import org.springframework.extensions.webscripts.TestWebScriptServer.Request; +import org.springframework.extensions.webscripts.TestWebScriptServer.Response; + +/** + * Unit Tests for the Java-backed Node WebScripts + * + * @since 4.1 + */ +public class NodeWebScripTest extends BaseWebScriptTest +{ + private static Log logger = LogFactory.getLog(NodeWebScripTest.class); + + private String TEST_SITE_NAME = "TestNodeSite"; + private SiteInfo TEST_SITE; + + private MutableAuthenticationService authenticationService; + private RetryingTransactionHelper retryingTransactionHelper; + private PersonService personService; + private SiteService siteService; + private NodeService nodeService; + + private static final String USER_ONE = "UserOneSecondToo"; + private static final String USER_TWO = "UserTwoSecondToo"; + private static final String USER_THREE = "UserThreeStill"; + private static final String PASSWORD = "passwordTEST"; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + + AbstractRefreshableApplicationContext ctx = (AbstractRefreshableApplicationContext)getServer().getApplicationContext(); + this.retryingTransactionHelper = (RetryingTransactionHelper)ctx.getBean("retryingTransactionHelper"); + this.authenticationService = (MutableAuthenticationService)ctx.getBean("AuthenticationService"); + this.personService = (PersonService)ctx.getBean("PersonService"); + this.siteService = (SiteService)ctx.getBean("SiteService"); + this.nodeService = (NodeService)ctx.getBean("NodeService"); + + // Do the setup as admin + AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser(); + + // Create a site + TEST_SITE = createSite(TEST_SITE_NAME); + + // Create two users, one who's a site member + createUser(USER_ONE, true); + createUser(USER_TWO, false); + + // Do our tests by default as the first user who is a contributor + AuthenticationUtil.setFullyAuthenticatedUser(USER_ONE); + } + + @Override + protected void tearDown() throws Exception + { + super.tearDown(); + + // Admin user required to delete users and sites + AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser(); + + // Zap the site, and their contents + siteService.deleteSite(TEST_SITE.getShortName()); + + // Delete users + for (String user : new String[] {USER_ONE, USER_TWO, USER_THREE}) + { + // Delete the user, as admin + AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser(); + if(personService.personExists(user)) + { + personService.deletePerson(user); + } + if(this.authenticationService.authenticationExists(user)) + { + this.authenticationService.deleteAuthentication(user); + } + } + } + + private SiteInfo createSite(final String shortName) + { + return retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback() + { + @Override + public SiteInfo execute() throws Throwable + { + if (siteService.getSite(shortName) != null) + { + // Tidy up after failed earlier run + siteService.deleteSite(shortName); + } + + // Do the create + SiteInfo site = siteService.createSite("Testing", shortName, shortName, null, SiteVisibility.PUBLIC); + + // Ensure we have a doclib + siteService.createContainer(shortName, SiteService.DOCUMENT_LIBRARY, ContentModel.TYPE_FOLDER, null); + + // All done + return site; + } + }, false, true + ); + } + + private void createUser(final String userName, boolean contributor) + { + // Make sure a new user is created every time + // This ensures a predictable password etc + if(this.personService.personExists(userName)) + { + this.personService.deletePerson(userName); + } + if(this.authenticationService.authenticationExists(userName)) + { + this.authenticationService.deleteAuthentication(userName); + } + + + // Create a fresh user + authenticationService.createAuthentication(userName, PASSWORD.toCharArray()); + + // create person properties + PropertyMap personProps = new PropertyMap(); + personProps.put(ContentModel.PROP_USERNAME, userName); + personProps.put(ContentModel.PROP_FIRSTNAME, "First"); + personProps.put(ContentModel.PROP_LASTNAME, "Last"); + personProps.put(ContentModel.PROP_EMAIL, "FirstName123.LastName123@email.com"); + personProps.put(ContentModel.PROP_JOBTITLE, "JobTitle123"); + personProps.put(ContentModel.PROP_JOBTITLE, "Organisation123"); + + // create person node for user + personService.createPerson(personProps); + + // Set site permissions as needed + if (contributor) + { + this.siteService.setMembership(TEST_SITE_NAME, userName, SiteModel.SITE_CONTRIBUTOR); + } + else + { + this.siteService.setMembership(TEST_SITE_NAME, userName, SiteModel.SITE_CONSUMER); + } + } + + private JSONObject asJSON(Response response) throws Exception + { + String json = response.getContentAsString(); + JSONParser p = new JSONParser(); + Object o = p.parse(json); + + if (o instanceof JSONObject) + { + return (JSONObject)o; + } + throw new IllegalArgumentException("Expected JSONObject, got " + o + " from " + json); + } + + + @SuppressWarnings("unchecked") + public void testFolderCreation() throws Exception + { + // Create a folder within the DocLib + NodeRef siteDocLib = siteService.getContainer(TEST_SITE.getShortName(), SiteService.DOCUMENT_LIBRARY); + + String testFolderName = "testing"; + Map testFolderProps = new HashMap(); + testFolderProps.put(ContentModel.PROP_NAME, testFolderName); + NodeRef testFolder = nodeService.createNode(siteDocLib, ContentModel.ASSOC_CONTAINS, + QName.createQName("testing"), ContentModel.TYPE_FOLDER, testFolderProps).getChildRef(); + + String testNodeName = "aNEWfolder"; + String testNodeTitle = "aTITLEforAfolder"; + String testNodeDescription = "DESCRIPTIONofAfolder"; + JSONObject jsonReq = null; + JSONObject json = null; + NodeRef folder = null; + + + // By NodeID + Request req = new Request("POST", "/api/node/folder/"+testFolder.getStoreRef().getProtocol()+"/"+ + testFolder.getStoreRef().getIdentifier()+"/"+testFolder.getId()); + jsonReq = new JSONObject(); + jsonReq.put("name", testNodeName); + req.setBody(jsonReq.toString().getBytes()); + req.setType(MimetypeMap.MIMETYPE_JSON); + + json = asJSON( sendRequest(req, Status.STATUS_OK) ); + assertNotNull(json.get("nodeRef")); + + folder = new NodeRef((String)json.get("nodeRef")); + assertEquals(true, nodeService.exists(folder)); + assertEquals(testNodeName, nodeService.getProperty(folder, ContentModel.PROP_NAME)); + assertEquals(testNodeName, nodeService.getProperty(folder, ContentModel.PROP_TITLE)); + assertEquals(null, nodeService.getProperty(folder, ContentModel.PROP_DESCRIPTION)); + + assertEquals(testFolder, nodeService.getPrimaryParent(folder).getParentRef()); + assertEquals(ContentModel.TYPE_FOLDER, nodeService.getType(folder)); + + nodeService.deleteNode(folder); + + + // In a Site Container + req = new Request("POST", "/api/site/folder/"+TEST_SITE_NAME+"/"+SiteService.DOCUMENT_LIBRARY); + jsonReq = new JSONObject(); + jsonReq.put("name", testNodeName); + jsonReq.put("description", testNodeDescription); + req.setBody(jsonReq.toString().getBytes()); + + json = asJSON( sendRequest(req, Status.STATUS_OK) ); + assertNotNull(json.get("nodeRef")); + + folder = new NodeRef((String)json.get("nodeRef")); + assertEquals(true, nodeService.exists(folder)); + assertEquals(testNodeName, nodeService.getProperty(folder, ContentModel.PROP_NAME)); + assertEquals(testNodeName, nodeService.getProperty(folder, ContentModel.PROP_TITLE)); + assertEquals(testNodeDescription, nodeService.getProperty(folder, ContentModel.PROP_DESCRIPTION)); + + assertEquals(siteDocLib, nodeService.getPrimaryParent(folder).getParentRef()); + assertEquals(ContentModel.TYPE_FOLDER, nodeService.getType(folder)); + + nodeService.deleteNode(folder); + + + // A Child of a Site Container + req = new Request("POST", "/api/site/folder/"+TEST_SITE_NAME+"/"+SiteService.DOCUMENT_LIBRARY+"/"+testFolderName); + jsonReq = new JSONObject(); + jsonReq.put("name", testNodeName); + jsonReq.put("title", testNodeTitle); + jsonReq.put("description", testNodeDescription); + req.setBody(jsonReq.toString().getBytes()); + + json = asJSON( sendRequest(req, Status.STATUS_OK) ); + assertNotNull(json.get("nodeRef")); + + folder = new NodeRef((String)json.get("nodeRef")); + assertEquals(true, nodeService.exists(folder)); + assertEquals(testNodeName, nodeService.getProperty(folder, ContentModel.PROP_NAME)); + assertEquals(testNodeTitle, nodeService.getProperty(folder, ContentModel.PROP_TITLE)); + assertEquals(testNodeDescription, nodeService.getProperty(folder, ContentModel.PROP_DESCRIPTION)); + + assertEquals(testFolder, nodeService.getPrimaryParent(folder).getParentRef()); + assertEquals(ContentModel.TYPE_FOLDER, nodeService.getType(folder)); + + nodeService.deleteNode(folder); + + + // Type needs to be a subtype of folder + + // explicit cm:folder + req = new Request("POST", "/api/site/folder/"+TEST_SITE_NAME+"/"+SiteService.DOCUMENT_LIBRARY+"/"+testFolderName); + jsonReq = new JSONObject(); + jsonReq.put("name", testNodeName); + jsonReq.put("type", "cm:folder"); + req.setBody(jsonReq.toString().getBytes()); + + json = asJSON( sendRequest(req, Status.STATUS_OK) ); + assertNotNull(json.get("nodeRef")); + + folder = new NodeRef((String)json.get("nodeRef")); + assertEquals(true, nodeService.exists(folder)); + assertEquals(testNodeName, nodeService.getProperty(folder, ContentModel.PROP_NAME)); + assertEquals(ContentModel.TYPE_FOLDER, nodeService.getType(folder)); + + nodeService.deleteNode(folder); + + + // cm:systemfolder extends from cm:folder + req = new Request("POST", "/api/site/folder/"+TEST_SITE_NAME+"/"+SiteService.DOCUMENT_LIBRARY+"/"+testFolderName); + jsonReq = new JSONObject(); + jsonReq.put("name", testNodeName); + jsonReq.put("type", "cm:systemfolder"); + req.setBody(jsonReq.toString().getBytes()); + + json = asJSON( sendRequest(req, Status.STATUS_OK) ); + assertNotNull(json.get("nodeRef")); + + folder = new NodeRef((String)json.get("nodeRef")); + assertEquals(true, nodeService.exists(folder)); + assertEquals(testNodeName, nodeService.getProperty(folder, ContentModel.PROP_NAME)); + assertEquals(ContentModel.TYPE_SYSTEM_FOLDER, nodeService.getType(folder)); + + nodeService.deleteNode(folder); + + + // cm:content isn't allowed + req = new Request("POST", "/api/site/folder/"+TEST_SITE_NAME+"/"+SiteService.DOCUMENT_LIBRARY+"/"+testFolderName); + jsonReq = new JSONObject(); + jsonReq.put("name", testNodeName); + jsonReq.put("type", "cm:content"); + req.setBody(jsonReq.toString().getBytes()); + + sendRequest(req, Status.STATUS_BAD_REQUEST); + + + // Check permissions - need to be Contributor + AuthenticationUtil.setFullyAuthenticatedUser(USER_ONE); + req = new Request("POST", "/api/node/folder/"+testFolder.getStoreRef().getProtocol()+"/"+ + testFolder.getStoreRef().getIdentifier()+"/"+testFolder.getId()); + jsonReq = new JSONObject(); + jsonReq.put("name", testNodeName); + req.setBody(jsonReq.toString().getBytes()); + req.setType(MimetypeMap.MIMETYPE_JSON); + + json = asJSON( sendRequest(req, Status.STATUS_OK) ); + assertNotNull(json.get("nodeRef")); + + folder = new NodeRef((String)json.get("nodeRef")); + assertEquals(true, nodeService.exists(folder)); + nodeService.deleteNode(folder); + + + AuthenticationUtil.setFullyAuthenticatedUser(USER_TWO); + sendRequest(req, Status.STATUS_FORBIDDEN); + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/web/scripts/servlet/LocalTestRunAsAuthenticatorFactory.java b/source/java/org/alfresco/repo/web/scripts/servlet/LocalTestRunAsAuthenticatorFactory.java index 4c6d5a0bf8..f12896110e 100644 --- a/source/java/org/alfresco/repo/web/scripts/servlet/LocalTestRunAsAuthenticatorFactory.java +++ b/source/java/org/alfresco/repo/web/scripts/servlet/LocalTestRunAsAuthenticatorFactory.java @@ -57,7 +57,7 @@ public class LocalTestRunAsAuthenticatorFactory implements ServletAuthenticatorF return new LocalTestRunAsAuthenticator(runAsUser); } - public class LocalTestRunAsAuthenticator implements Authenticator + public static class LocalTestRunAsAuthenticator implements Authenticator { private String userName; diff --git a/source/java/org/alfresco/repo/web/scripts/workflow/AbstractWorkflowRestApiTest.java b/source/java/org/alfresco/repo/web/scripts/workflow/AbstractWorkflowRestApiTest.java index 09d43e85c5..1f45073b52 100644 --- a/source/java/org/alfresco/repo/web/scripts/workflow/AbstractWorkflowRestApiTest.java +++ b/source/java/org/alfresco/repo/web/scripts/workflow/AbstractWorkflowRestApiTest.java @@ -718,8 +718,8 @@ public abstract class AbstractWorkflowRestApiTest extends BaseWebScriptTest public void testWorkflowDefinitionsGet() throws Exception { - personManager.setUser(USER1); - + personManager.setUser(USER1); + Response response = sendRequest(new GetRequest(URL_WORKFLOW_DEFINITIONS), 200); assertEquals(Status.STATUS_OK, response.getStatus()); JSONObject json = new JSONObject(response.getContentAsString()); @@ -817,8 +817,8 @@ public abstract class AbstractWorkflowRestApiTest extends BaseWebScriptTest public void testWorkflowDefinitionGet() throws Exception { - personManager.setUser(USER1); - + personManager.setUser(USER1); + // Get the latest definition for the adhoc-workflow WorkflowDefinition wDef = workflowService.getDefinitionByName(getAdhocWorkflowDefinitionName()); @@ -1648,4 +1648,4 @@ public abstract class AbstractWorkflowRestApiTest extends BaseWebScriptTest protected abstract String getEngine(); -} \ No newline at end of file +}