1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.module.exchangesimple;
35
36 import info.magnolia.cms.beans.runtime.Document;
37 import info.magnolia.cms.beans.runtime.MultipartForm;
38 import info.magnolia.cms.core.Content;
39 import info.magnolia.cms.core.HierarchyManager;
40 import info.magnolia.cms.core.ItemType;
41 import info.magnolia.cms.core.MgnlNodeType;
42 import info.magnolia.cms.core.NodeData;
43 import info.magnolia.cms.core.SystemProperty;
44 import info.magnolia.cms.exchange.ExchangeException;
45 import info.magnolia.cms.filters.AbstractMgnlFilter;
46 import info.magnolia.cms.security.AccessDeniedException;
47 import info.magnolia.cms.security.MgnlKeyPair;
48 import info.magnolia.cms.security.Permission;
49 import info.magnolia.cms.security.PermissionUtil;
50 import info.magnolia.cms.security.SecurityUtil;
51 import info.magnolia.cms.util.ContentUtil;
52 import info.magnolia.cms.util.Rule;
53 import info.magnolia.cms.util.RuleBasedContentFilter;
54 import info.magnolia.context.MgnlContext;
55 import info.magnolia.context.SystemContext;
56
57 import java.io.IOException;
58 import java.io.InputStream;
59 import java.security.DigestInputStream;
60 import java.security.InvalidParameterException;
61 import java.security.MessageDigest;
62 import java.security.NoSuchAlgorithmException;
63 import java.util.Iterator;
64 import java.util.List;
65 import java.util.zip.GZIPInputStream;
66
67 import javax.jcr.ImportUUIDBehavior;
68 import javax.jcr.ItemNotFoundException;
69 import javax.jcr.Node;
70 import javax.jcr.PathNotFoundException;
71 import javax.jcr.Property;
72 import javax.jcr.PropertyType;
73 import javax.jcr.RepositoryException;
74 import javax.jcr.Session;
75 import javax.jcr.UnsupportedRepositoryOperationException;
76 import javax.jcr.lock.LockException;
77 import javax.servlet.FilterChain;
78 import javax.servlet.ServletException;
79 import javax.servlet.http.HttpServletRequest;
80 import javax.servlet.http.HttpServletResponse;
81 import javax.servlet.http.HttpSession;
82
83 import org.apache.commons.io.IOUtils;
84 import org.apache.commons.lang.StringUtils;
85 import org.jdom.Element;
86 import org.jdom.JDOMException;
87 import org.jdom.input.SAXBuilder;
88 import org.safehaus.uuid.UUIDGenerator;
89 import org.slf4j.Logger;
90 import org.slf4j.LoggerFactory;
91
92 import com.google.inject.Inject;
93
94
95
96
97
98
99
100 public class ReceiveFilter extends AbstractMgnlFilter {
101
102 private static final Logger log = LoggerFactory.getLogger(ReceiveFilter.class);
103
104 public static final String SYSTEM_REPO = "mgnlSystem";
105
106 private int unlockRetries = 10;
107
108 private int retryWait = 2;
109
110 private final ExchangeSimpleModule module;
111 private final MessageDigest md;
112
113
114 @Inject
115 public ReceiveFilter(ExchangeSimpleModule module) {
116 this.module = module;
117 try {
118 md = MessageDigest.getInstance("MD5");
119 } catch (NoSuchAlgorithmException e) {
120 throw new SecurityException("In order to proceed with activation please run Magnolia CMS using Java version with MD5 support.", e);
121 }
122 }
123
124 public int getUnlockRetries() {
125 return unlockRetries;
126 }
127
128 public void setUnlockRetries(int unlockRetries) {
129 this.unlockRetries = unlockRetries;
130 }
131
132 public long getRetryWait() {
133 return retryWait;
134 }
135
136 public void setRetryWait(int retryWait) {
137 this.retryWait = retryWait;
138 }
139
140 @Override
141 public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
142 String statusMessage = "";
143 String status = "";
144 String result = null;
145 try {
146 final String utf8AuthorStatus = request.getHeader(BaseSyndicatorImpl.UTF8_STATUS);
147
148 if (utf8AuthorStatus != null && (Boolean.parseBoolean(utf8AuthorStatus) != SystemProperty.getBooleanProperty(SystemProperty.MAGNOLIA_UTF8_ENABLED))) {
149 throw new UnsupportedOperationException("Activation between instances with different UTF-8 setting is not supported.");
150 }
151 final String action = request.getHeader(BaseSyndicatorImpl.ACTION);
152 if (action == null) {
153 throw new InvalidParameterException("Activation action must be set for each activation request.");
154 }
155
156
157 if (!isAuthorAuthenticated(request, response)) {
158 status = BaseSyndicatorImpl.ACTIVATION_HANDSHAKE;
159 setResponseHeaders(response, statusMessage, status, result);
160 return;
161 }
162
163 applyLock(request);
164 }catch (Throwable e) {
165 log.error(e.getMessage(), e);
166
167 statusMessage = StringUtils.defaultIfEmpty(e.getMessage(), e.getClass().getSimpleName());
168 status = BaseSyndicatorImpl.ACTIVATION_FAILED;
169 setResponseHeaders(response, statusMessage, status, result);
170 return;
171 }
172
173 try{
174 result = receive(request);
175 status = BaseSyndicatorImpl.ACTIVATION_SUCCESSFUL;
176 }
177 catch (OutOfMemoryError e) {
178 Runtime rt = Runtime.getRuntime();
179 log.error("---------\nOutOfMemoryError caught during activation. Total memory = "
180 + rt.totalMemory()
181 + ", free memory = "
182 + rt.freeMemory()
183 + "\n---------");
184 statusMessage = e.getMessage();
185 status = BaseSyndicatorImpl.ACTIVATION_FAILED;
186 }
187 catch (PathNotFoundException e) {
188
189 log.error(e.getMessage(), e);
190 statusMessage = "Parent not found (not yet activated): " + e.getMessage();
191 status = BaseSyndicatorImpl.ACTIVATION_FAILED;
192 } catch (ExchangeException e) {
193 log.debug(e.getMessage(), e);
194 statusMessage = e.getMessage();
195 status = BaseSyndicatorImpl.ACTIVATION_FAILED;
196 } catch (Throwable e) {
197 log.error(e.getMessage(), e);
198
199 statusMessage = StringUtils.defaultIfEmpty(e.getMessage(), e.getClass().getSimpleName());
200 status = BaseSyndicatorImpl.ACTIVATION_FAILED;
201 }
202 finally {
203 cleanUp(request, status);
204 setResponseHeaders(response, statusMessage, status, result);
205 }
206 }
207
208 protected boolean isAuthorAuthenticated(HttpServletRequest request, HttpServletResponse response) throws NoSuchAlgorithmException, ExchangeException {
209 if (SecurityUtil.getPublicKey() == null) {
210 if (module.getTempKeys() == null) {
211
212 MgnlKeyPair tempKeys = SecurityUtil.generateKeyPair(module.getActivationKeyLength());
213
214 response.addHeader(BaseSyndicatorImpl.ACTIVATION_AUTH, tempKeys.getPublicKey());
215 module.setTempKeys(tempKeys);
216 return false;
217 } else {
218 try {
219
220 String authorsPublicKeyEncryptedByTempPublicKey = request.getHeader(BaseSyndicatorImpl.ACTIVATION_AUTH_KEY);
221
222 String publicKey = SecurityUtil.decrypt(authorsPublicKeyEncryptedByTempPublicKey, module.getTempKeys().getPrivateKey());
223 if (StringUtils.isNotBlank(publicKey)) {
224 String authString = SecurityUtil.decrypt(request.getHeader(BaseSyndicatorImpl.ACTIVATION_AUTH), publicKey);
225 String[] auth = authString.split(";");
226 checkTimestamp(auth);
227
228
229 SecurityUtil.updateKeys(new MgnlKeyPair(null, publicKey));
230 }
231 } finally {
232
233 module.setTempKeys(null);
234 }
235 if (SecurityUtil.getPublicKey() == null) {
236
237 try {
238 Thread.sleep(3000);
239 } catch (InterruptedException e) {
240 Thread.currentThread().interrupt();
241 }
242 if (SecurityUtil.getPublicKey() == null) {
243 throw new ExchangeException("Failed to negotiate encryption key between author and public instance. Please try again later or contact admin if error persists.");
244 }
245 }
246 }
247 }
248 return true;
249 }
250
251 protected void setResponseHeaders(HttpServletResponse response, String statusMessage, String status, String result) {
252 response.setHeader(BaseSyndicatorImpl.ACTIVATION_ATTRIBUTE_STATUS, status);
253 response.setHeader(BaseSyndicatorImpl.ACTIVATION_ATTRIBUTE_MESSAGE, statusMessage);
254 }
255
256
257
258
259
260
261 protected synchronized String receive(HttpServletRequest request) throws Exception {
262 String action = request.getHeader(BaseSyndicatorImpl.ACTION);
263 log.debug("action: " + action);
264
265 String[] auth = checkAuthenticated(request);
266
267 String user = auth[1];
268
269 String resourcesmd5 = auth[2];
270
271
272 String webapp = getWebappName();
273
274 if (action.equalsIgnoreCase(BaseSyndicatorImpl.ACTIVATE)) {
275 String name = update(request, resourcesmd5);
276
277 log.info("User {} successfuly activated {} on {}.", new Object[] { user, name, webapp });
278 }
279 else if (action.equalsIgnoreCase(BaseSyndicatorImpl.DEACTIVATE)) {
280 String name = remove(request, resourcesmd5);
281
282 log.info("User {} succeessfuly deactivated {} on {}.", new Object[] { user, name, webapp });
283 }
284 else {
285 throw new UnsupportedOperationException("Method not supported : " + action);
286 }
287 return null;
288 }
289
290 protected String[] checkAuthenticated(HttpServletRequest request) throws ExchangeException {
291 String encrypted = request.getHeader(BaseSyndicatorImpl.ACTIVATION_AUTH);
292 if (StringUtils.isBlank(encrypted)) {
293 log.debug("Attempt to access activation URL w/o proper information in request. Ignoring silently.");
294 throw new ExchangeException();
295 }
296
297 String decrypted = SecurityUtil.decrypt(encrypted);
298 if (StringUtils.isBlank(decrypted)) {
299 throw new SecurityException("Handshake information for activation was incorrect. Someone attempted to impersonate author instance. Incoming request was from " + request.getRemoteAddr());
300 }
301
302 String[] auth = decrypted.split(";");
303
304
305 if (auth.length != 3) {
306 throw new SecurityException("Handshake information for activation was incorrect. Someone attempted to impersonate author instance. Incoming request was from " + request.getRemoteAddr());
307 }
308
309 checkTimestamp(auth);
310 return auth;
311 }
312
313 private void checkTimestamp(String[] auth) {
314 long timestamp = System.currentTimeMillis();
315 long authorTimestamp = 0;
316 try {
317 authorTimestamp = Long.parseLong(auth[0]);
318 } catch (NumberFormatException e) {
319 throw new SecurityException("Handshake information for activation was incorrect. This might be an attempt to replay earlier activation request.");
320 }
321 if (Math.abs(timestamp - authorTimestamp) > module.getActivationDelayTolerance()) {
322 throw new SecurityException("Activation refused due to request arriving too late or time not synched between author and public instance. Please contact your administrator to ensure server times are synced or the tolerance is set high enough to counter the differences.");
323 }
324 }
325
326 protected String getWebappName() {
327 return SystemProperty.getProperty(SystemProperty.MAGNOLIA_WEBAPP);
328 }
329
330
331
332
333 @Deprecated
334 protected String getUser(HttpServletRequest request) {
335 return null;
336 }
337
338
339
340
341
342
343
344
345
346
347
348 protected synchronized String update(HttpServletRequest request, String resourcesmd5) throws Exception {
349 MultipartForm data = MgnlContext.getPostedForm();
350 if (null != data) {
351 String newParentPath = this.getParentPath(request);
352 String resourceFileName = request.getHeader(BaseSyndicatorImpl.RESOURCE_MAPPING_FILE);
353 HierarchyManager hm = getHierarchyManager(request);
354 Element rootElement = getImportedContentRoot(data, resourceFileName, resourcesmd5);
355 Element topContentElement = rootElement.getChild(BaseSyndicatorImpl.RESOURCE_MAPPING_FILE_ELEMENT);
356 Content content = null;
357 try {
358 String uuid = topContentElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_UUID_ATTRIBUTE);
359 content = hm.getContentByUUID(uuid);
360
361 newParentPath = handleMovedContent(newParentPath, hm, topContentElement, content);
362 handleChildren(request, content);
363 this.importOnExisting(topContentElement, data, hm, content);
364 }
365 catch (ItemNotFoundException e) {
366
367 importFresh(topContentElement, data, hm, newParentPath);
368 }
369
370 return orderImportedNode(newParentPath, hm, rootElement, topContentElement);
371 }
372 return null;
373 }
374
375 protected Element getImportedContentRoot(MultipartForm data, String resourceFileName, String resourcesmd5) throws JDOMException, IOException {
376 Document resourceDocument = data.getDocument(resourceFileName);
377 SAXBuilder builder = new SAXBuilder();
378 InputStream documentInputStream = new DigestInputStream(resourceDocument.getStream(), md);
379 org.jdom.Document jdomDocument = builder.build(documentInputStream);
380 IOUtils.closeQuietly(documentInputStream);
381 String sign = SecurityUtil.byteArrayToHex(md.digest());
382 if (!resourcesmd5.equals(sign)) {
383 throw new SecurityException("Signature of received resource (" + sign + ") doesn't match expected signature (" + resourcesmd5 + "). This might mean that the activation operation have been intercepted by a third party and content have been modified during transfer.");
384 }
385
386 return jdomDocument.getRootElement();
387 }
388
389 protected void handleChildren(HttpServletRequest request, Content content) {
390 String ruleString = request.getHeader(BaseSyndicatorImpl.CONTENT_FILTER_RULE);
391 Rule rule = new Rule(ruleString, ",");
392 RuleBasedContentFilter filter = new RuleBasedContentFilter(rule);
393
394 this.removeChildren(content, filter);
395 }
396
397 protected String handleMovedContent(String newParentPath, HierarchyManager hm, Element topContentElement, Content content) throws RepositoryException {
398 String currentParentPath = content.getHandle();
399 currentParentPath = currentParentPath.substring(0, currentParentPath.lastIndexOf('/'));
400 String newName = topContentElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_NAME_ATTRIBUTE);
401 if (!newParentPath.endsWith("/")) {
402 newParentPath += "/";
403 }
404 if (!currentParentPath.endsWith("/")) {
405 currentParentPath += "/";
406 }
407 if (!newParentPath.equals(currentParentPath) || !content.getName().equals(newName)) {
408 log.info("Moving content from {} to {} due to activation request.", new Object[] { content.getHandle(), newParentPath + newName});
409 hm.moveTo(content.getHandle(), newParentPath + newName);
410 }
411 return newParentPath;
412 }
413
414 protected String orderImportedNode(String newParentPath, HierarchyManager hm, Element rootElement, Element topContentElement) throws RepositoryException {
415 String name;
416
417 name = topContentElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_NAME_ATTRIBUTE);
418 Content parent = hm.getContent(newParentPath);
419 List siblings = rootElement.getChild(BaseSyndicatorImpl.SIBLINGS_ROOT_ELEMENT).getChildren(BaseSyndicatorImpl.SIBLINGS_ELEMENT);
420 Iterator siblingsIterator = siblings.iterator();
421 while (siblingsIterator.hasNext()) {
422 Element sibling = (Element) siblingsIterator.next();
423
424 try {
425 String siblingUUID = sibling.getAttributeValue(BaseSyndicatorImpl.SIBLING_UUID);
426 Content beforeContent = hm.getContentByUUID(siblingUUID);
427 log.debug("Ordering {} before {}", name, beforeContent.getName());
428 order(parent, name, beforeContent.getName());
429 break;
430 } catch (ItemNotFoundException e) {
431
432 } catch (RepositoryException re) {
433 if (log.isDebugEnabled()) {
434 log.debug("Failed to order node", re);
435 } else {
436 log.warn("Failed to order node");
437 }
438 }
439 }
440
441
442 if (siblings.isEmpty()) {
443 order(parent, name, null);
444 }
445 return name;
446 }
447
448
449 protected void order(Content parent, String name, String orderBefore) throws RepositoryException {
450 try {
451 parent.orderBefore(name, orderBefore);
452 } catch (UnsupportedRepositoryOperationException e) {
453
454 log.warn("Failed to order unorderable content {} at {} due to {}", new Object[] {name, parent.getHandle(), e.getMessage()});
455 }
456 parent.save();
457 }
458
459
460
461
462
463
464 protected synchronized void copyProperties(Content source, Content destination) throws RepositoryException {
465
466
467 Iterator nodeDataIterator = destination.getNodeDataCollection().iterator();
468 while (nodeDataIterator.hasNext()) {
469 NodeData nodeData = (NodeData) nodeDataIterator.next();
470
471
472 if (nodeData.getType() != PropertyType.BINARY) {
473 nodeData.delete();
474 }
475 }
476
477
478 Node destinationNode = destination.getJCRNode();
479 nodeDataIterator = source.getNodeDataCollection().iterator();
480 while (nodeDataIterator.hasNext()) {
481 NodeData nodeData = (NodeData) nodeDataIterator.next();
482 Property property = nodeData.getJCRProperty();
483 if (property.getDefinition().isMultiple()) {
484 if (destination.isGranted(Permission.WRITE)) {
485 destinationNode.setProperty(nodeData.getName(), property.getValues());
486 }
487 else {
488 throw new AccessDeniedException("User not allowed to " + Permission.PERMISSION_NAME_WRITE + " at [" + nodeData.getHandle() + "]");
489 }
490 }
491 else {
492 destination.createNodeData(nodeData.getName(), nodeData.getValue());
493 }
494 }
495 }
496
497
498
499
500
501
502 protected synchronized void removeChildren(Content content, Content.ContentFilter filter) {
503 Iterator children = content.getChildren(filter).iterator();
504
505
506 while (children.hasNext()) {
507 Content child = (Content) children.next();
508 try {
509 child.delete();
510 }
511 catch (Exception e) {
512 log.error("Failed to remove " + child.getHandle() + " | " + e.getMessage());
513 }
514 }
515 }
516
517
518
519
520
521
522
523
524
525
526 protected synchronized void importFresh(Element topContentElement, MultipartForm data, HierarchyManager hierarchyManager, String parentPath) throws ExchangeException, RepositoryException {
527
528
529 String path = parentPath + (parentPath.endsWith("/") ? "" : "/") + topContentElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_NAME_ATTRIBUTE);
530 if (hierarchyManager.isExist(path)) {
531 log.warn("Replacing {} due to name collision (but different UUIDs.). This operation could not be rolled back in case of failure and you need to reactivate the page manually.", path);
532 hierarchyManager.delete(path);
533 }
534 try {
535 importResource(data, topContentElement, hierarchyManager, parentPath);
536 hierarchyManager.save();
537 } catch (PathNotFoundException e) {
538 final String message = "Parent content " + parentPath + " is not yet activated or you do not have write access to it. Please activate the parent content before activating children and ensure you have appropriate rights";
539
540 log.debug(message, e);
541 hierarchyManager.refresh(false);
542 throw new ExchangeException(message);
543 } catch (Exception e) {
544 final String message = "Activation failed | " + e.getMessage();
545 log.error("Exception caught", e);
546 hierarchyManager.refresh(false);
547 throw new ExchangeException(message);
548 }
549 }
550
551
552
553
554
555
556
557
558
559
560 protected synchronized void importOnExisting(Element topContentElement, MultipartForm data,
561 final HierarchyManager hierarchyManager, Content existingContent) throws ExchangeException, RepositoryException {
562 final Iterator<Content> fileListIterator = topContentElement.getChildren(BaseSyndicatorImpl.RESOURCE_MAPPING_FILE_ELEMENT).iterator();
563 final String uuid = UUIDGenerator.getInstance().generateTimeBasedUUID().toString();
564 final String handle = existingContent.getHandle();
565
566 final HierarchyManager systemHM = MgnlContext.getSystemContext().getHierarchyManager(SYSTEM_REPO);
567 try {
568 while (fileListIterator.hasNext()) {
569 Element fileElement = (Element) fileListIterator.next();
570 importResource(data, fileElement, hierarchyManager, handle);
571 }
572
573 Content activationTmp = ContentUtil.getOrCreateContent(systemHM.getRoot(), "activation-tmp", ItemType.FOLDER, true);
574 final Content transientNode = activationTmp.createContent(uuid, MgnlNodeType.NT_PAGE);
575 final String transientStoreHandle = transientNode.getHandle();
576
577 final String fileName = topContentElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_ID_ATTRIBUTE);
578 final String md5 = topContentElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_MD_ATTRIBUTE);
579 final InputStream inputStream = new DigestInputStream(new GZIPInputStream(data.getDocument(fileName).getStream()), md);
580
581 systemHM.getWorkspace().getSession().importXML(transientStoreHandle, inputStream, ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW);
582 IOUtils.closeQuietly(inputStream);
583 final String calculatedMD5 = SecurityUtil.byteArrayToHex(md.digest());
584 if (!calculatedMD5.equals(md5)) {
585 throw new SecurityException(fileName + " signature is not valid. Resource might have been modified in transit.");
586 }
587
588 Content tmpContent = transientNode.getContent(topContentElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_NAME_ATTRIBUTE));
589 copyProperties(tmpContent, existingContent);
590 systemHM.delete(transientStoreHandle);
591 hierarchyManager.save();
592 systemHM.save();
593 } catch (Exception e) {
594
595 hierarchyManager.refresh(false);
596 systemHM.refresh(false);
597
598 log.error("Exception caught", e);
599 throw new ExchangeException("Activation failed : " + e.getMessage());
600 }
601 }
602
603
604
605
606
607
608
609
610
611 protected synchronized void importResource(MultipartForm data, Element resourceElement, HierarchyManager hm, String parentPath) throws Exception {
612
613
614 PermissionUtil.isGranted(hm.getWorkspace().getSession(), parentPath, Session.ACTION_ADD_NODE);
615
616 final String name = resourceElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_NAME_ATTRIBUTE);
617 final String fileName = resourceElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_ID_ATTRIBUTE);
618 final String md5 = resourceElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_MD_ATTRIBUTE);
619
620 final InputStream inputStream = new DigestInputStream(new GZIPInputStream(data.getDocument(fileName).getStream()), md);
621 log.debug("Importing {} into parent path {}", new Object[] {name, parentPath});
622 hm.getWorkspace().getSession().importXML(parentPath, inputStream, ImportUUIDBehavior.IMPORT_UUID_COLLISION_REMOVE_EXISTING);
623 IOUtils.closeQuietly(inputStream);
624 final String calculatedMD5 = SecurityUtil.byteArrayToHex(md.digest());
625 if (!calculatedMD5.equals(md5)) {
626 throw new SecurityException(fileName + " signature is not valid. Resource might have been modified in transit. Expected signature:" + md5 + ", actual signature found: " + calculatedMD5);
627 }
628 Iterator fileListIterator = resourceElement.getChildren(BaseSyndicatorImpl.RESOURCE_MAPPING_FILE_ELEMENT).iterator();
629
630 try {
631 parentPath = hm.getContentByUUID(resourceElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_UUID_ATTRIBUTE)).getHandle();
632 } catch (ItemNotFoundException e) {
633
634
635 parentPath = StringUtils.removeEnd(parentPath, "/") + "/" + name;
636 }
637 while (fileListIterator.hasNext()) {
638 Element fileElement = (Element) fileListIterator.next();
639 importResource(data, fileElement, hm, parentPath);
640 }
641 }
642
643
644
645
646
647
648 protected synchronized String remove(HttpServletRequest request, String md5) throws Exception {
649
650 if (!md5.equals(SecurityUtil.byteArrayToHex(md.digest(request.getHeader(BaseSyndicatorImpl.NODE_UUID).getBytes())))) {
651 throw new SecurityException("Signature of resource doesn't match. This seems like malicious attempt to delete content. Request was issued from " + request.getRemoteAddr());
652 }
653 HierarchyManager hm = getHierarchyManager(request);
654 String handle = null;
655 try {
656 Content node = this.getNode(request);
657 handle = node.getHandle();
658 hm.delete(handle);
659 hm.save();
660 } catch (ItemNotFoundException e) {
661 log.debug("Unable to delete node", e);
662 }
663 return handle;
664 }
665
666
667
668
669
670
671 protected HierarchyManager getHierarchyManager(HttpServletRequest request) throws ExchangeException {
672 String workspaceName = request.getHeader(BaseSyndicatorImpl.WORKSPACE_NAME);
673
674 if (StringUtils.isEmpty(workspaceName)) {
675 throw new ExchangeException("Repository or workspace name not sent, unable to activate. Workspace: " + workspaceName) ;
676 }
677 SystemContext sysCtx = MgnlContext.getSystemContext();
678 return sysCtx.getHierarchyManager(workspaceName);
679 }
680
681
682
683
684
685
686
687 protected void cleanUp(HttpServletRequest request, String status) {
688 if (!BaseSyndicatorImpl.DEACTIVATE.equalsIgnoreCase(request.getHeader(BaseSyndicatorImpl.ACTION))) {
689 MultipartForm data = MgnlContext.getPostedForm();
690 if (null != data) {
691 Iterator keys = data.getDocuments().keySet().iterator();
692 while (keys.hasNext()) {
693 String key = (String) keys.next();
694 data.getDocument(key).delete();
695 }
696 }
697 try {
698 final String parentPath = getParentPath(request);
699 if (StringUtils.isEmpty(parentPath) || this.getHierarchyManager(request).isExist(parentPath)) {
700 try {
701 Content content = this.getNode(request);
702 if (content.isLocked()) {
703 content.unlock();
704 }
705 }catch (ItemNotFoundException e) {
706
707 }
708 }
709 } catch (LockException le) {
710
711 log.debug(le.getMessage(), le);
712 } catch (RepositoryException re) {
713
714 log.warn("Exception caught", re);
715 } catch (ExchangeException e) {
716
717 log.warn("Exception caught", e);
718 }
719 }
720
721
722 try {
723 HttpSession httpSession = request.getSession(false);
724 if (httpSession != null) {
725 httpSession.invalidate();
726 }
727 } catch (Throwable t) {
728
729 log.error("failed to invalidate session", t);
730 }
731 }
732
733
734
735
736
737 protected void applyLock(HttpServletRequest request) throws ExchangeException {
738 try {
739 Content parent = waitForLock(request);
740 if (parent != null) {
741
742 parent.lock(true, true);
743 }
744 } catch (LockException le) {
745
746 log.debug(le.getMessage());
747 } catch (RepositoryException re) {
748
749 log.warn("Exception caught", re);
750 }
751 }
752
753
754
755
756
757
758
759
760
761 protected Content waitForLock(HttpServletRequest request) throws ExchangeException, RepositoryException {
762 int retries = getUnlockRetries();
763 long retryWait = getRetryWait() * 1000;
764 Content content = null;
765 try {
766 content = this.getNode(request);
767 while (content.isLocked() && retries > 0) {
768 log.info("Content " + content.getHandle() + " is locked. Will retry " + retries + " more times.");
769 try {
770 Thread.sleep(retryWait);
771 } catch (InterruptedException e) {
772
773 Thread.currentThread().interrupt();
774 }
775 retries--;
776 content = this.getNode(request);
777 }
778 if (content.isLocked()) {
779 throw new ExchangeException("Content " + content.getHandle() + " was locked while activating " + request.getHeader(BaseSyndicatorImpl.NODE_UUID) + ". This most likely means that content have been at the same time activated by some other user. Please try again and if problem persists contact administrator.");
780 }
781 } catch (ItemNotFoundException e) {
782
783 log.debug("Attempt to lock non existing content {} during (de)activation.", getUUID(request));
784 } catch (PathNotFoundException e) {
785
786 log.debug("Attempt to lock non existing content {}:{} during (de)activation.",getHierarchyManager(request).getName(), getParentPath(request));
787 }
788 return content;
789 }
790
791 protected Content getNode(HttpServletRequest request) throws ExchangeException, RepositoryException {
792 if (request.getHeader(BaseSyndicatorImpl.PARENT_PATH) != null) {
793 String parentPath = this.getParentPath(request);
794 log.debug("parent path:" + parentPath);
795 return this.getHierarchyManager(request).getContent(parentPath);
796 } else if (!StringUtils.isEmpty(getUUID(request))){
797 log.debug("node uuid:" + request.getHeader(BaseSyndicatorImpl.NODE_UUID));
798 return this.getHierarchyManager(request).getContentByUUID(request.getHeader(BaseSyndicatorImpl.NODE_UUID));
799 } else {
800 throw new ExchangeException("Request is missing mandatory content identifier.");
801 }
802 }
803
804 protected String getParentPath(HttpServletRequest request) {
805 String parentPath = request.getHeader(BaseSyndicatorImpl.PARENT_PATH);
806 if (StringUtils.isNotEmpty(parentPath)) {
807 return parentPath;
808 }
809 return "";
810 }
811
812 protected String getUUID(HttpServletRequest request) {
813 String parentPath = request.getHeader(BaseSyndicatorImpl.NODE_UUID);
814 if (StringUtils.isNotEmpty(parentPath)) {
815 return parentPath;
816 }
817 return "";
818 }
819
820
821 }