diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/doclink/doclink.post.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/doclink/doclink.post.desc.xml index e95ce9b7a4..1f9f6aea3e 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/doclink/doclink.post.desc.xml +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/doclink/doclink.post.desc.xml @@ -41,12 +41,23 @@ + + json + + + + json - + diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/doclink/doclink.post.json.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/doclink/doclink.post.json.ftl index 321d5972da..9c91406902 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/doclink/doclink.post.json.ftl +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/doclink/doclink.post.json.ftl @@ -1,5 +1,16 @@ <#escape x as jsonUtils.encodeJSONString(x)> { - "linkNodeRef": "${linkNodeRef}" + "linkNodes" : + [ + <#list results as result> + { + "nodeRef" : "${result.nodeRef}" + } + <#if result_has_next>, + + ], + "successCount": "${successCount}", + "failureCount": "${failureCount}", + "overallSuccess": "${overallSuccess?c}" } \ No newline at end of file diff --git a/config/alfresco/web-scripts-application-context.xml b/config/alfresco/web-scripts-application-context.xml index 5e2e150b76..6ce6f52f33 100644 --- a/config/alfresco/web-scripts-application-context.xml +++ b/config/alfresco/web-scripts-application-context.xml @@ -1846,6 +1846,7 @@ + + diff --git a/source/java/org/alfresco/repo/web/scripts/doclink/AbstractDocLink.java b/source/java/org/alfresco/repo/web/scripts/doclink/AbstractDocLink.java index 8e38144138..43a54bf75e 100644 --- a/source/java/org/alfresco/repo/web/scripts/doclink/AbstractDocLink.java +++ b/source/java/org/alfresco/repo/web/scripts/doclink/AbstractDocLink.java @@ -23,122 +23,171 @@ * along with Alfresco. If not, see . * #L% */ -package org.alfresco.repo.web.scripts.doclink; - -import java.util.Map; -import java.util.StringTokenizer; - -import org.alfresco.model.ContentModel; -import org.alfresco.service.cmr.repository.DocumentLinkService; -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.util.ParameterCheck; -import org.alfresco.util.PropertyCheck; -import org.springframework.extensions.webscripts.DeclarativeWebScript; -import org.springframework.extensions.webscripts.Status; -import org.springframework.extensions.webscripts.WebScriptException; - -/** - * This class contains common code for doclink webscripts controllers - * - * @author Ana Bozianu - * @since 5.1 - */ -public abstract class AbstractDocLink extends DeclarativeWebScript -{ - private static String PARAM_STORE_TYPE = "store_type"; - private static String PARAM_STORE_ID = "store_id"; - private static String PARAM_ID = "id"; - private static String PARAM_SITE = "site"; - private static String PARAM_CONTAINER = "container"; - private static String PARAM_PATH = "path"; - - protected NodeService nodeService; - protected SiteService siteService; - protected DocumentLinkService documentLinkService; - - protected NodeRef parseNodeRefFromTemplateArgs(Map templateVars) - { - if (templateVars == null) - { - return null; - } - - String storeTypeArg = templateVars.get(PARAM_STORE_TYPE); - String storeIdArg = templateVars.get(PARAM_STORE_ID); - String idArg = templateVars.get(PARAM_ID); - - if (storeTypeArg != null) - { - ParameterCheck.mandatoryString("storeTypeArg", storeTypeArg); - ParameterCheck.mandatoryString("storeIdArg", storeIdArg); - ParameterCheck.mandatoryString("idArg", idArg); - - /* - * NodeRef based request - * URL_BASE/{store_type}/{store_id}/{id} - */ - return new NodeRef(storeTypeArg, storeIdArg, idArg); - } - else - { - String siteArg = templateVars.get(PARAM_SITE); - String containerArg = templateVars.get(PARAM_CONTAINER); - String pathArg = templateVars.get(PARAM_PATH); - - if (siteArg != null) - { - ParameterCheck.mandatoryString("siteArg", siteArg); - ParameterCheck.mandatoryString("containerArg", containerArg); - - /* - * Site based request URL_BASE/{site}/{container} or - * URL_BASE/{site}/{container}/{path} - */ - SiteInfo site = siteService.getSite(siteArg); - PropertyCheck.mandatory(this, "site", site); - - NodeRef node = siteService.getContainer(site.getShortName(), containerArg); - if (node == null) - { - throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Invalid 'container' variable"); - } - - if (pathArg != null) - { - // URL_BASE/{site}/{container}/{path} - StringTokenizer st = new StringTokenizer(pathArg, "/"); - while (st.hasMoreTokens()) - { - String childName = st.nextToken(); - node = nodeService.getChildByName(node, ContentModel.ASSOC_CONTAINS, childName); - if (node == null) - { - throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Invalid 'path' variable"); - } - } - } - - return node; - } - } - return null; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setSiteService(SiteService siteService) - { - this.siteService = siteService; - } - - public void setDocumentLinkService(DocumentLinkService documentLinkService) - { - this.documentLinkService = documentLinkService; - } -} +package org.alfresco.repo.web.scripts.doclink; + +import java.io.StringWriter; +import java.util.Map; +import java.util.StringTokenizer; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.web.scripts.links.AbstractLinksWebScript; +import org.alfresco.service.cmr.activities.ActivityService; +import org.alfresco.service.cmr.links.LinkInfo; +import org.alfresco.service.cmr.repository.DocumentLinkService; +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.util.ParameterCheck; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.JSONStringer; +import org.json.simple.JSONObject; +import org.springframework.extensions.webscripts.DeclarativeWebScript; +import org.springframework.extensions.webscripts.Status; +import org.springframework.extensions.webscripts.WebScriptException; +import org.springframework.extensions.webscripts.WebScriptRequest; +import org.springframework.extensions.webscripts.json.JSONWriter; + +/** + * This class contains common code for doclink webscripts controllers + * + * @author Ana Bozianu + * @since 5.1 + */ +public abstract class AbstractDocLink extends DeclarativeWebScript +{ + private static String PARAM_STORE_TYPE = "store_type"; + private static String PARAM_STORE_ID = "store_id"; + private static String PARAM_ID = "id"; + private static String PARAM_SITE = "site"; + private static String PARAM_CONTAINER = "container"; + private static String PARAM_PATH = "path"; + + private static final String ACTIVITY_TOOL = "documentLinkService"; + + protected NodeService nodeService; + protected SiteService siteService; + protected DocumentLinkService documentLinkService; + protected ActivityService activityService; + + private static Log logger = LogFactory.getLog(AbstractDocLink.class); + + protected NodeRef parseNodeRefFromTemplateArgs(Map templateVars) + { + if (templateVars == null) + { + return null; + } + + String storeTypeArg = templateVars.get(PARAM_STORE_TYPE); + String storeIdArg = templateVars.get(PARAM_STORE_ID); + String idArg = templateVars.get(PARAM_ID); + + if (storeTypeArg != null) + { + ParameterCheck.mandatoryString("storeTypeArg", storeTypeArg); + ParameterCheck.mandatoryString("storeIdArg", storeIdArg); + ParameterCheck.mandatoryString("idArg", idArg); + + /* + * NodeRef based request + * URL_BASE/{store_type}/{store_id}/{id} + */ + return new NodeRef(storeTypeArg, storeIdArg, idArg); + } + else + { + String siteArg = templateVars.get(PARAM_SITE); + String containerArg = templateVars.get(PARAM_CONTAINER); + String pathArg = templateVars.get(PARAM_PATH); + + if (siteArg != null) + { + ParameterCheck.mandatoryString("siteArg", siteArg); + ParameterCheck.mandatoryString("containerArg", containerArg); + + /* + * Site based request URL_BASE/{site}/{container} or + * URL_BASE/{site}/{container}/{path} + */ + SiteInfo site = siteService.getSite(siteArg); + PropertyCheck.mandatory(this, "site", site); + + NodeRef node = siteService.getContainer(site.getShortName(), containerArg); + if (node == null) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Invalid 'container' variable"); + } + + if (pathArg != null) + { + // URL_BASE/{site}/{container}/{path} + StringTokenizer st = new StringTokenizer(pathArg, "/"); + while (st.hasMoreTokens()) + { + String childName = st.nextToken(); + node = nodeService.getChildByName(node, ContentModel.ASSOC_CONTAINS, childName); + if (node == null) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Invalid 'path' variable"); + } + } + } + + return node; + } + } + return null; + } + + /** + * Generates an activity entry for the link + */ + + protected void addActivityEntry(String activityType, String title, String nodeRef, String site) + { + try + { + StringWriter activityJson = new StringWriter(); + JSONWriter activity = new JSONWriter(activityJson); + activity.startObject(); + activity.writeValue("title", title); + activity.writeValue("nodeRef", nodeRef); + activity.writeValue("page", "document-details?nodeRef=" + nodeRef); + activity.endObject(); + + activityService.postActivity( + activityType, + site, + ACTIVITY_TOOL, + activityJson.toString()); + } + catch (Exception e) + { + // Warn, but carry on + logger.warn("Error adding link event to activities feed", e); + } + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setSiteService(SiteService siteService) + { + this.siteService = siteService; + } + + public void setDocumentLinkService(DocumentLinkService documentLinkService) + { + this.documentLinkService = documentLinkService; + } + + public void setActivityService(ActivityService activityService) + { + this.activityService = activityService; + } +} diff --git a/source/java/org/alfresco/repo/web/scripts/doclink/DocLinkPost.java b/source/java/org/alfresco/repo/web/scripts/doclink/DocLinkPost.java index d0feef5c06..4557f89469 100644 --- a/source/java/org/alfresco/repo/web/scripts/doclink/DocLinkPost.java +++ b/source/java/org/alfresco/repo/web/scripts/doclink/DocLinkPost.java @@ -23,95 +23,177 @@ * along with Alfresco. If not, see . * #L% */ -package org.alfresco.repo.web.scripts.doclink; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.repo.security.permissions.AccessDeniedException; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.util.ParameterCheck; -import org.json.simple.JSONObject; -import org.json.simple.JSONValue; -import org.json.simple.parser.ParseException; -import org.springframework.extensions.webscripts.Cache; -import org.springframework.extensions.webscripts.Status; -import org.springframework.extensions.webscripts.WebScriptException; -import org.springframework.extensions.webscripts.WebScriptRequest; - -/** - * This class is the controller for the doclink.post webscript doclink.post is a - * webscript for creating a link of a document within a target destination - * - * @author Ana Bozianu - * @since 5.1 - */ -public class DocLinkPost extends AbstractDocLink -{ - private static String PARAM_DESTINATION_NODE = "destinationNodeRef"; - - @Override - protected Map executeImpl(WebScriptRequest req, Status status, Cache cache) - { - NodeRef sourceNodeRef = null; - NodeRef destinationNodeRef = null; - - /* Parse the template vars */ - Map templateVars = req.getServiceMatch().getTemplateVars(); - sourceNodeRef = parseNodeRefFromTemplateArgs(templateVars); - - /* Parse the JSON content */ - JSONObject json = null; - String contentType = req.getContentType(); - if (contentType != null && contentType.indexOf(';') != -1) - { - contentType = contentType.substring(0, contentType.indexOf(';')); - } - if (MimetypeMap.MIMETYPE_JSON.equals(contentType)) - { - try - { - json = (JSONObject) JSONValue.parseWithException(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()); - } - } - else - { - throw new WebScriptException(Status.STATUS_BAD_REQUEST, "invalid request content type"); - } - - /* Parse the destination NodeRef parameter */ - String destinationNodeParam = (String) json.get(PARAM_DESTINATION_NODE); - ParameterCheck.mandatoryString("destinationNodeParam", destinationNodeParam); - destinationNodeRef = new NodeRef(destinationNodeParam); - - /* Create link */ - NodeRef linkNodeRef = null; - try - { - linkNodeRef = documentLinkService.createDocumentLink(sourceNodeRef, destinationNodeRef); - } - catch (IllegalArgumentException ex) - { - throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Invalid Arguments: " + ex.getMessage()); - } - catch (AccessDeniedException e) - { - throw new WebScriptException(Status.STATUS_FORBIDDEN, "You don't have permission to perform this operation"); - } - - /* Build response */ - Map model = new HashMap(); - model.put("linkNodeRef", linkNodeRef.toString()); - return model; - } -} +package org.alfresco.repo.web.scripts.doclink; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.activities.ActivityType; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.util.ParameterCheck; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.JSONValue; +import org.json.simple.parser.ParseException; +import org.springframework.extensions.webscripts.Cache; +import org.springframework.extensions.webscripts.Status; +import org.springframework.extensions.webscripts.WebScriptException; +import org.springframework.extensions.webscripts.WebScriptRequest; + +/** + * This class is the controller for the doclink.post webscript doclink.post is a + * webscript for creating a link of a document within a target destination + * + * @author Ana Bozianu + * @since 5.1 + */ +public class DocLinkPost extends AbstractDocLink +{ + private static final String PARAM_DESTINATION_NODE = "destinationNodeRef"; + private static final String PARAM_MULTIPLE_FILES = "multipleFiles"; + + @Override + protected Map executeImpl(WebScriptRequest req, Status status, Cache cache) + { + NodeRef sourceNodeRef = null; + NodeRef destinationNodeRef = null; + + /* Parse the template vars */ + Map templateVars = req.getServiceMatch().getTemplateVars(); + sourceNodeRef = parseNodeRefFromTemplateArgs(templateVars); + + /* Parse the JSON content */ + JSONObject json = null; + String contentType = req.getContentType(); + if (contentType != null && contentType.indexOf(';') != -1) + { + contentType = contentType.substring(0, contentType.indexOf(';')); + } + if (MimetypeMap.MIMETYPE_JSON.equals(contentType)) + { + try + { + json = (JSONObject) JSONValue.parseWithException(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()); + } + } + else + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "invalid request content type"); + } + + /* Parse the destination NodeRef parameter */ + String destinationNodeParam = (String) json.get(PARAM_DESTINATION_NODE); + ParameterCheck.mandatoryString("destinationNodeParam", destinationNodeParam); + destinationNodeRef = new NodeRef(destinationNodeParam); + + List nodeRefs = new ArrayList(); + if (json.containsKey(PARAM_MULTIPLE_FILES)) + { + JSONArray multipleFiles = (JSONArray) json.get(PARAM_MULTIPLE_FILES); + for (int i = 0; i < multipleFiles.size(); i++) + { + String nodeRefString = (String) multipleFiles.get(i); + if (nodeRefString != null) + { + try + { + NodeRef nodeRefToCreateLink = new NodeRef(nodeRefString); + nodeRefs.add(nodeRefToCreateLink); + } + catch (AlfrescoRuntimeException ex) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Invalid Arguments: " + ex.getMessage()); + } + + } + } + } + else + { + nodeRefs.add(sourceNodeRef); + } + + // getSite for destination folder + String siteName = siteService.getSiteShortName(destinationNodeRef); + + ArrayList linksResults = new ArrayList(); + Map linkResult = new HashMap(); + NodeRef linkNodeRef = null; + int successCount = 0; + int failureCount = 0; + + if (nodeRefs != null && nodeRefs.size() > 0) + { + for (NodeRef sourceNode : nodeRefs) + { + /* Create link */ + linkNodeRef = createLink(destinationNodeRef, sourceNode); + + if (linkNodeRef != null) + { + String sourceName = (String) nodeService.getProperty(sourceNode, ContentModel.PROP_NAME); + if (siteName != null) + { + addActivityEntry(ActivityType.DOCLINK_CREATED, sourceName, sourceNode.toString(), siteName); + } + + linkResult.put("nodeRef", linkNodeRef.toString()); + linksResults.add(linkResult); + successCount++; + } + } + } + + failureCount = nodeRefs.size() - successCount; + Map model = new HashMap(); + model.put("results", linksResults); + model.put("successCount", successCount); + model.put("failureCount", failureCount); + model.put("overallSuccess", failureCount == 0); + return model; + } + + /** + * Create link for sourceNodeRef in destinationNodeRef location + * + * @param destinationNodeRef + * @param sourceNodeRef + * @return + */ + private NodeRef createLink(NodeRef destinationNodeRef, NodeRef sourceNodeRef) + { + NodeRef linkNodeRef = null; + try + { + linkNodeRef = documentLinkService.createDocumentLink(sourceNodeRef, destinationNodeRef); + } + catch (IllegalArgumentException ex) + { + if (ex.getMessage().contains("filelink") || ex.getMessage().contains("folderLink")) + { + return null; + } + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Invalid Arguments: " + ex.getMessage()); + } + catch (AccessDeniedException e) + { + throw new WebScriptException(Status.STATUS_FORBIDDEN, "You don't have permission to perform this operation"); + } + return linkNodeRef; + } +} diff --git a/source/java/org/alfresco/repo/web/scripts/links/AbstractLinksWebScript.java b/source/java/org/alfresco/repo/web/scripts/links/AbstractLinksWebScript.java index 1af0ea3235..06865280c0 100644 --- a/source/java/org/alfresco/repo/web/scripts/links/AbstractLinksWebScript.java +++ b/source/java/org/alfresco/repo/web/scripts/links/AbstractLinksWebScript.java @@ -23,411 +23,411 @@ * along with Alfresco. If not, see . * #L% */ -package org.alfresco.repo.web.scripts.links; - -import java.io.IOException; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Pattern; - -import org.alfresco.query.PagingRequest; -import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.service.cmr.activities.ActivityService; -import org.alfresco.service.cmr.links.LinkInfo; -import org.alfresco.service.cmr.links.LinksService; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.security.NoSuchPersonException; -import org.alfresco.service.cmr.security.PersonService; -import org.alfresco.service.cmr.site.SiteInfo; -import org.alfresco.service.cmr.site.SiteService; -import org.alfresco.util.ScriptPagingDetails; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.json.simple.JSONArray; -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; -import org.springframework.extensions.webscripts.json.JSONWriter; - -/** - * @author Nick Burch - * @since 4.0 - */ -public abstract class AbstractLinksWebScript extends DeclarativeWebScript -{ - public static final String LINKS_SERVICE_ACTIVITY_APP_NAME = "links"; - - protected static final String PARAM_MESSAGE = "message"; - protected static final String PARAM_ITEM = "item"; - - private static Log logger = LogFactory.getLog(AbstractLinksWebScript.class); - - // Injected services - protected NodeService nodeService; - protected SiteService siteService; - protected LinksService linksService; - protected PersonService personService; - protected ActivityService activityService; - - private String protocolsWhiteList = "http,https,ftp,mailto"; - private ArrayList allowedProtocols; - private ArrayList xssPatterns; - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setSiteService(SiteService siteService) - { - this.siteService = siteService; - } - - public void setLinksService(LinksService linksService) - { - this.linksService = linksService; - } - - public void setPersonService(PersonService personService) - { - this.personService = personService; - } - - public void setActivityService(ActivityService activityService) - { - this.activityService = activityService; - } - - public void setProtocolsWhiteList(String protocolsWhiteList) - { - this.protocolsWhiteList = protocolsWhiteList; - } - - public void setXssRegexp(ArrayList xssRegexp) - { - xssPatterns = new ArrayList<>(xssRegexp.size()); - for (String xssRegexpStr : xssRegexp) - { - xssPatterns.add(Pattern.compile(xssRegexpStr)); - } - } - - private boolean isProtocolAllowed(String protocol) - { - // will be used default protocol prefix - if (protocol.length() == 0) - { - return true; - } - - if (allowedProtocols == null) - { - allowedProtocols = new ArrayList(); - for (String delimProtocol : protocolsWhiteList.split(",")) - { - if (delimProtocol.trim().length() == 0) - { - continue; - } - allowedProtocols.add(delimProtocol.trim()); - } - } - - return allowedProtocols.contains(protocol); - } - - private boolean isPossibleXSS(String url) - { - // check for null - if (xssPatterns == null) - { - return false; - } - - boolean result = false; - for (Pattern pattern : xssPatterns) - { - if (pattern.matcher(url).matches()) - { - result = true; - } - } - return result; - } - - private boolean isUrlCorrect(String url) - { - //default behavior if url absent - if (url == null) - { - return true; - } - - if (url.trim().length() == 0 || isPossibleXSS(url)) - { - return false; - } - - int colonPos = url.indexOf(":"); - colonPos = colonPos > 0 ? colonPos : 0; - String protocol = url.substring(0, colonPos); - - boolean result = isProtocolAllowed(protocol); - //check for record host:port e.g.: localhost:8080 - if (!result) - { - String secondUrlPart = url.substring(colonPos+1); - int slashPos = secondUrlPart.indexOf("/"); - slashPos = slashPos > 0 ? slashPos : secondUrlPart.length(); - String port = secondUrlPart.substring(0, slashPos); - - Pattern p = Pattern.compile("^[0-9]*$"); - if (p.matcher(port).matches()) - { - result = true; - } - } - return result; - } - - - protected String getOrNull(JSONObject json, String key) - { - if (json.containsKey(key)) - { - return (String)json.get(key); - } - return null; - } - - protected List getTags(JSONObject json) - { - List tags = null; - if (json.containsKey("tags")) - { - // Is it "tags":"" or "tags":[...] ? - if (json.get("tags") instanceof String) - { - // This is normally an empty string, skip - String tagsS = (String)json.get("tags"); - if ("".equals(tagsS)) - { - // No tags were given - return null; - } - else - { - // Log, and treat as empty - // (We don't support "tags":"a,b,c" in these webscripts) - logger.warn("Unexpected tag data: " + tagsS); - return null; - } - } - else - { - tags = new ArrayList(); - JSONArray jsTags = (JSONArray)json.get("tags"); - for (int i=0; i renderLink(LinkInfo link) - { - Map res = new HashMap(); - res.put("node", link.getNodeRef()); - res.put("name", link.getSystemName()); - res.put("title", link.getTitle()); - res.put("description", link.getDescription()); - res.put("url", link.getURL()); - res.put("createdOn", link.getCreatedAt()); - res.put("modifiedOn", link.getModifiedAt()); - res.put("tags", link.getTags()); - res.put("internal", link.isInternal()); - - // FTL needs a script node of the person, if available - String creator = link.getCreator(); - Object creatorO; - if ((null == creator) || !personService.personExists(creator)) - { - creatorO = ""; - } - else - { - NodeRef person = personService.getPerson(creator); - creatorO = person; - } - res.put("creator", creatorO); - - // We want blank instead of null - for (String key : res.keySet()) - { - if (res.get(key) == null) - { - res.put(key, ""); - } - } - - return res; - } - - @Override - protected Map executeImpl(WebScriptRequest req, - Status status, Cache cache) - { - Map templateVars = req.getServiceMatch().getTemplateVars(); - if (templateVars == null) - { - String error = "No parameters supplied"; - throw new WebScriptException(Status.STATUS_BAD_REQUEST, error); - } - - - // Parse the JSON, if supplied - JSONObject json = null; - String contentType = req.getContentType(); - if (contentType != null && contentType.indexOf(';') != -1) - { - contentType = contentType.substring(0, contentType.indexOf(';')); - } - if (MimetypeMap.MIMETYPE_JSON.equals(contentType)) - { - 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()); - } - } - - - // Get the site short name. Try quite hard to do so... - String siteName = templateVars.get("site"); - if (siteName == null) - { - siteName = req.getParameter("site"); - } - if (siteName == null && json != null) - { - if (json.containsKey("siteid")) - { - siteName = (String)json.get("siteid"); - } - else if (json.containsKey("site")) - { - siteName = (String)json.get("site"); - } - } - if (siteName == null) - { - String error = "No site given"; - throw new WebScriptException(Status.STATUS_BAD_REQUEST, error); - } - - // Grab the requested site - SiteInfo site = siteService.getSite(siteName); - if (site == null) - { - String error = "Could not find site: " + siteName; - throw new WebScriptException(Status.STATUS_NOT_FOUND, error); - } - - // Link name is optional - String linkName = templateVars.get("path"); - - //sanitise url - if (json != null) - { - String url = getOrNull(json, "url"); - if (!isUrlCorrect(url)) - { - String error = "Url not allowed"; - throw new WebScriptException(Status.STATUS_BAD_REQUEST, error); - } - } - - // Have the real work done - return executeImpl(site, linkName, req, json, status, cache); - } - - protected abstract Map executeImpl(SiteInfo site, - String linkName, WebScriptRequest req, JSONObject json, - Status status, Cache cache); - -} +package org.alfresco.repo.web.scripts.links; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import org.alfresco.query.PagingRequest; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.service.cmr.activities.ActivityService; +import org.alfresco.service.cmr.links.LinkInfo; +import org.alfresco.service.cmr.links.LinksService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.NoSuchPersonException; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.util.ScriptPagingDetails; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.simple.JSONArray; +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; +import org.springframework.extensions.webscripts.json.JSONWriter; + +/** + * @author Nick Burch + * @since 4.0 + */ +public abstract class AbstractLinksWebScript extends DeclarativeWebScript +{ + public static final String LINKS_SERVICE_ACTIVITY_APP_NAME = "links"; + + protected static final String PARAM_MESSAGE = "message"; + protected static final String PARAM_ITEM = "item"; + + private static Log logger = LogFactory.getLog(AbstractLinksWebScript.class); + + // Injected services + protected NodeService nodeService; + protected SiteService siteService; + protected LinksService linksService; + protected PersonService personService; + protected ActivityService activityService; + + private String protocolsWhiteList = "http,https,ftp,mailto"; + private ArrayList allowedProtocols; + private ArrayList xssPatterns; + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setSiteService(SiteService siteService) + { + this.siteService = siteService; + } + + public void setLinksService(LinksService linksService) + { + this.linksService = linksService; + } + + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + public void setActivityService(ActivityService activityService) + { + this.activityService = activityService; + } + + public void setProtocolsWhiteList(String protocolsWhiteList) + { + this.protocolsWhiteList = protocolsWhiteList; + } + + public void setXssRegexp(ArrayList xssRegexp) + { + xssPatterns = new ArrayList<>(xssRegexp.size()); + for (String xssRegexpStr : xssRegexp) + { + xssPatterns.add(Pattern.compile(xssRegexpStr)); + } + } + + private boolean isProtocolAllowed(String protocol) + { + // will be used default protocol prefix + if (protocol.length() == 0) + { + return true; + } + + if (allowedProtocols == null) + { + allowedProtocols = new ArrayList(); + for (String delimProtocol : protocolsWhiteList.split(",")) + { + if (delimProtocol.trim().length() == 0) + { + continue; + } + allowedProtocols.add(delimProtocol.trim()); + } + } + + return allowedProtocols.contains(protocol); + } + + private boolean isPossibleXSS(String url) + { + // check for null + if (xssPatterns == null) + { + return false; + } + + boolean result = false; + for (Pattern pattern : xssPatterns) + { + if (pattern.matcher(url).matches()) + { + result = true; + } + } + return result; + } + + private boolean isUrlCorrect(String url) + { + //default behavior if url absent + if (url == null) + { + return true; + } + + if (url.trim().length() == 0 || isPossibleXSS(url)) + { + return false; + } + + int colonPos = url.indexOf(":"); + colonPos = colonPos > 0 ? colonPos : 0; + String protocol = url.substring(0, colonPos); + + boolean result = isProtocolAllowed(protocol); + //check for record host:port e.g.: localhost:8080 + if (!result) + { + String secondUrlPart = url.substring(colonPos+1); + int slashPos = secondUrlPart.indexOf("/"); + slashPos = slashPos > 0 ? slashPos : secondUrlPart.length(); + String port = secondUrlPart.substring(0, slashPos); + + Pattern p = Pattern.compile("^[0-9]*$"); + if (p.matcher(port).matches()) + { + result = true; + } + } + return result; + } + + + protected String getOrNull(JSONObject json, String key) + { + if (json.containsKey(key)) + { + return (String)json.get(key); + } + return null; + } + + protected List getTags(JSONObject json) + { + List tags = null; + if (json.containsKey("tags")) + { + // Is it "tags":"" or "tags":[...] ? + if (json.get("tags") instanceof String) + { + // This is normally an empty string, skip + String tagsS = (String)json.get("tags"); + if ("".equals(tagsS)) + { + // No tags were given + return null; + } + else + { + // Log, and treat as empty + // (We don't support "tags":"a,b,c" in these webscripts) + logger.warn("Unexpected tag data: " + tagsS); + return null; + } + } + else + { + tags = new ArrayList(); + JSONArray jsTags = (JSONArray)json.get("tags"); + for (int i=0; i renderLink(LinkInfo link) + { + Map res = new HashMap(); + res.put("node", link.getNodeRef()); + res.put("name", link.getSystemName()); + res.put("title", link.getTitle()); + res.put("description", link.getDescription()); + res.put("url", link.getURL()); + res.put("createdOn", link.getCreatedAt()); + res.put("modifiedOn", link.getModifiedAt()); + res.put("tags", link.getTags()); + res.put("internal", link.isInternal()); + + // FTL needs a script node of the person, if available + String creator = link.getCreator(); + Object creatorO; + if ((null == creator) || !personService.personExists(creator)) + { + creatorO = ""; + } + else + { + NodeRef person = personService.getPerson(creator); + creatorO = person; + } + res.put("creator", creatorO); + + // We want blank instead of null + for (String key : res.keySet()) + { + if (res.get(key) == null) + { + res.put(key, ""); + } + } + + return res; + } + + @Override + protected Map executeImpl(WebScriptRequest req, + Status status, Cache cache) + { + Map templateVars = req.getServiceMatch().getTemplateVars(); + if (templateVars == null) + { + String error = "No parameters supplied"; + throw new WebScriptException(Status.STATUS_BAD_REQUEST, error); + } + + + // Parse the JSON, if supplied + JSONObject json = null; + String contentType = req.getContentType(); + if (contentType != null && contentType.indexOf(';') != -1) + { + contentType = contentType.substring(0, contentType.indexOf(';')); + } + if (MimetypeMap.MIMETYPE_JSON.equals(contentType)) + { + 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()); + } + } + + + // Get the site short name. Try quite hard to do so... + String siteName = templateVars.get("site"); + if (siteName == null) + { + siteName = req.getParameter("site"); + } + if (siteName == null && json != null) + { + if (json.containsKey("siteid")) + { + siteName = (String)json.get("siteid"); + } + else if (json.containsKey("site")) + { + siteName = (String)json.get("site"); + } + } + if (siteName == null) + { + String error = "No site given"; + throw new WebScriptException(Status.STATUS_BAD_REQUEST, error); + } + + // Grab the requested site + SiteInfo site = siteService.getSite(siteName); + if (site == null) + { + String error = "Could not find site: " + siteName; + throw new WebScriptException(Status.STATUS_NOT_FOUND, error); + } + + // Link name is optional + String linkName = templateVars.get("path"); + + //sanitise url + if (json != null) + { + String url = getOrNull(json, "url"); + if (!isUrlCorrect(url)) + { + String error = "Url not allowed"; + throw new WebScriptException(Status.STATUS_BAD_REQUEST, error); + } + } + + // Have the real work done + return executeImpl(site, linkName, req, json, status, cache); + } + + protected abstract Map executeImpl(SiteInfo site, + String linkName, WebScriptRequest req, JSONObject json, + Status status, Cache cache); + +}