Skip to content

Commit 36d0fd2

Browse files
author
Lloyd Watkin
committed
Test for validity of <target/> element
1 parent bb7a7da commit 36d0fd2

4 files changed

Lines changed: 238 additions & 18 deletions

File tree

src/main/java/org/buddycloud/channelserver/channel/ValidateEntry.java

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import org.apache.log4j.Logger;
99
import org.buddycloud.channelserver.db.exception.NodeStoreException;
1010
import org.buddycloud.channelserver.pubsub.model.NodeItem;
11+
import org.buddycloud.channelserver.pubsub.model.impl.GlobalItemIDImpl;
1112
import org.dom4j.Element;
1213
import org.dom4j.dom.DOMElement;
1314
import org.xmpp.packet.JID;
@@ -20,7 +21,11 @@ public class ValidateEntry {
2021
public static final String UNSUPPORTED_CONTENT_TYPE = "unsupported-content-type";
2122
public static final String MAX_THREAD_DEPTH_EXCEEDED = "max-thread-depth-exceeded";
2223
public static final String PARENT_ITEM_NOT_FOUND = "parent-item-not-found";
23-
24+
public static final String TARGETED_ITEM_NOT_FOUND = "targeted-item-not-found";
25+
public static final String IN_REPLY_TO_MISSING = "missing-in-reply-to";
26+
public static final String MISSING_TARGET_ID = "missing-target-id";
27+
public static final String TARGET_MUST_BE_IN_SAME_THREAD = "target-outside-thread";
28+
2429
public static final String CONTENT_TEXT = "text";
2530
public static final String CONTENT_XHTML = "xhtml";
2631

@@ -36,20 +41,22 @@ public class ValidateEntry {
3641
public static final String POST_TYPE_COMMENT = "comment";
3742

3843
public static final String ACTIVITY_VERB_POST = "post";
39-
44+
4045
private static Logger LOGGER = Logger.getLogger(ValidateEntry.class);
4146

4247
private Element entry;
4348

4449
private String errorMessage = "";
4550
private String inReplyTo;
51+
private String targetId;
4652
private Element meta;
4753
private Element media;
4854

4955
private JID jid;
5056
private String channelServerDomain;
5157
private String node;
5258
private ChannelManager channelManager;
59+
private NodeItem replyingToItem;
5360

5461
Map<String, String> params = new HashMap<String, String>();
5562

@@ -129,9 +136,11 @@ public boolean isValid() throws NodeStoreException {
129136

130137
this.geoloc = this.entry.element("geoloc");
131138

132-
Element reply = this.entry.element("in-reply-to");
133-
134-
if ((null != reply) && (false == validateInReplyToElement(reply))) {
139+
if (false == validateInReplyToElement(this.entry.element("in-reply-to"))) {
140+
return false;
141+
}
142+
143+
if (false == validateTargetElement(this.entry.element("target"))) {
135144
return false;
136145
}
137146

@@ -224,27 +233,61 @@ public void setTo(String channelServerDomain) {
224233

225234

226235
private boolean validateInReplyToElement(Element reply) throws NodeStoreException {
236+
if (null == reply) {
237+
return true;
238+
}
227239

228-
inReplyTo = reply.attributeValue("ref");
229-
if (-1 != inReplyTo.indexOf(",")) {
230-
String[] tokens = inReplyTo.split(",");
231-
inReplyTo = tokens[2];
240+
inReplyTo = reply.attributeValue("ref");
241+
if (true == GlobalItemIDImpl.isGlobalId(inReplyTo)) {
242+
inReplyTo = GlobalItemIDImpl.toLocalId(inReplyTo);
232243
}
233244

234-
String[] inReplyToParts = reply.attributeValue("ref").split(",");
235-
inReplyTo = inReplyToParts[inReplyToParts.length - 1];
236-
237-
NodeItem nodeItem = null;
238-
if (null == (nodeItem = channelManager.getNodeItem(node, inReplyTo))) {
245+
replyingToItem = null;
246+
if (null == (replyingToItem = channelManager.getNodeItem(node, inReplyTo))) {
239247
this.errorMessage = PARENT_ITEM_NOT_FOUND;
240248
return false;
241249
}
242-
if (null != nodeItem.getInReplyTo()) {
250+
if (null != replyingToItem.getInReplyTo()) {
243251
LOGGER.error("User is attempting to reply to a reply");
244252
this.errorMessage = MAX_THREAD_DEPTH_EXCEEDED;
245253
return false;
246254
}
247255
return true;
248256
}
249257

258+
private boolean validateTargetElement(Element target) throws NodeStoreException {
259+
if (null == target) {
260+
return true;
261+
}
262+
String targetId = target.elementText("id");
263+
if ((null == targetId) || (0 == targetId.length())) {
264+
this.errorMessage = MISSING_TARGET_ID;
265+
return false;
266+
}
267+
if (null == inReplyTo) {
268+
this.errorMessage = IN_REPLY_TO_MISSING;
269+
return false;
270+
}
271+
if (true == GlobalItemIDImpl.isGlobalId(targetId)) {
272+
targetId = GlobalItemIDImpl.toLocalId(targetId);
273+
}
274+
NodeItem targetItem;
275+
if (true == targetId.equals(replyingToItem.getId())) {
276+
targetItem = replyingToItem;
277+
} else {
278+
targetItem = channelManager.getNodeItem(node, targetId);
279+
}
280+
if (null == targetItem) {
281+
this.errorMessage = TARGETED_ITEM_NOT_FOUND;
282+
return false;
283+
}
284+
if (true == targetItem.getId().equals(targetId)) {
285+
return true;
286+
}
287+
if ((null == targetItem.getInReplyTo()) || (false == targetItem.getInReplyTo().equals(targetId))) {
288+
this.errorMessage = TARGET_MUST_BE_IN_SAME_THREAD;
289+
return false;
290+
}
291+
return true;
292+
}
250293
}

src/test/java/org/buddycloud/channelserver/channel/ValidateEntryTest.java

Lines changed: 151 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,14 @@
3131
public class ValidateEntryTest extends TestHandler {
3232

3333
private ValidateEntry validateEntry;
34+
3435
private IQ publishRequest;
3536
private Element publishEntry;
3637
private IQ replyRequest;
3738
private Element replyEntry;
39+
private IQ targetRequest;
40+
private Element targetEntry;
41+
3842
private ChannelManager channelManager;
3943

4044
JID jid = new JID("juliet@shakespeare.lit/balcony");
@@ -49,7 +53,10 @@ public void setUp() throws Exception {
4953
replyRequest = readStanzaAsIq("/iq/pubsub/publish/reply.stanza");
5054
replyEntry = replyRequest.getChildElement().element("publish")
5155
.element("item").element("entry");
52-
56+
targetRequest = readStanzaAsIq("/iq/pubsub/publish/target.stanza");
57+
targetEntry = targetRequest.getChildElement().element("publish")
58+
.element("item").element("entry");
59+
5360
channelManager = Mockito.mock(ChannelManager.class);
5461

5562
NodeItem item = new NodeItemImpl(node, "1", new Date(), "<entry/>");
@@ -214,7 +221,7 @@ public void geolocationEntryIsAdded() throws Exception {
214221
public void globalInReplyToIdIsMadeALocalId() throws Exception {
215222

216223
Assert.assertEquals(
217-
"null@channels.shakespeare.lit,/users/romeo@shakespeare.lit/posts,fc362eb42085f017ed9ccd9c4004b095",
224+
"tag:channels.shakespeare.lit,/users/romeo@shakespeare.lit/posts,fc362eb42085f017ed9ccd9c4004b095",
218225
replyEntry.element("in-reply-to").attributeValue("ref"));
219226

220227
Element entry = (Element) this.replyEntry.clone();
@@ -320,4 +327,146 @@ public void canNotReplyToAReply() throws Exception {
320327
Assert.assertEquals(ValidateEntry.MAX_THREAD_DEPTH_EXCEEDED,
321328
validateEntry.getErrorMessage());
322329
}
330+
331+
@Test
332+
public void targetElementWithoutInReplyToReturnsError() throws Exception {
333+
334+
NodeItem item = new NodeItemImpl(node, "2", new Date(), "<entry/>", "1");
335+
Mockito.when(
336+
channelManager.getNodeItem(Mockito.eq(node),
337+
Mockito.eq("1"))).thenReturn(item);
338+
Mockito.when(
339+
channelManager.getNodeItem(Mockito.eq(node),
340+
Mockito.eq("2"))).thenReturn(null);
341+
342+
Element entry = (Element) this.targetEntry.clone();
343+
344+
entry.element("in-reply-to").detach();
345+
validateEntry = getEntryObject(entry);
346+
347+
Assert.assertFalse(validateEntry.isValid());
348+
Assert.assertEquals(ValidateEntry.IN_REPLY_TO_MISSING,
349+
validateEntry.getErrorMessage());
350+
}
351+
352+
@Test
353+
public void missingTargetIdElementReturnsError() throws Exception {
354+
355+
NodeItem item = new NodeItemImpl(node, "2", new Date(), "<entry/>");
356+
Mockito.when(
357+
channelManager.getNodeItem(Mockito.eq(node),
358+
Mockito.eq("1"))).thenReturn(item);
359+
Mockito.when(
360+
channelManager.getNodeItem(Mockito.eq(node),
361+
Mockito.eq("2"))).thenReturn(null);
362+
363+
Element entry = (Element) this.targetEntry.clone();
364+
365+
entry.element("target").element("id").detach();
366+
validateEntry = getEntryObject(entry);
367+
368+
Assert.assertFalse(validateEntry.isValid());
369+
Assert.assertEquals(ValidateEntry.MISSING_TARGET_ID,
370+
validateEntry.getErrorMessage());
371+
}
372+
373+
@Test
374+
public void emptyTargetIdElementReturnsError() throws Exception {
375+
376+
NodeItem item = new NodeItemImpl(node, "2", new Date(), "<entry/>");
377+
Mockito.when(
378+
channelManager.getNodeItem(Mockito.eq(node),
379+
Mockito.eq("1"))).thenReturn(item);
380+
Mockito.when(
381+
channelManager.getNodeItem(Mockito.eq(node),
382+
Mockito.eq("2"))).thenReturn(null);
383+
384+
Element entry = (Element) this.targetEntry.clone();
385+
386+
entry.element("target").element("id").detach();
387+
entry.element("target").addElement("id");
388+
validateEntry = getEntryObject(entry);
389+
390+
Assert.assertFalse(validateEntry.isValid());
391+
Assert.assertEquals(ValidateEntry.MISSING_TARGET_ID,
392+
validateEntry.getErrorMessage());
393+
}
394+
395+
@Test
396+
public void ifTargetedPostDoesntExistErrorIsReturned() throws Exception {
397+
398+
NodeItem item = new NodeItemImpl(node, "1", new Date(), "<entry/>");
399+
Mockito.when(
400+
channelManager.getNodeItem(Mockito.eq(node),
401+
Mockito.eq("1"))).thenReturn(item);
402+
Mockito.when(
403+
channelManager.getNodeItem(Mockito.eq(node),
404+
Mockito.eq("2"))).thenReturn(null);
405+
406+
Element entry = (Element) this.targetEntry.clone();
407+
validateEntry = getEntryObject(entry);
408+
409+
Assert.assertFalse(validateEntry.isValid());
410+
Assert.assertEquals(ValidateEntry.TARGETED_ITEM_NOT_FOUND,
411+
validateEntry.getErrorMessage());
412+
}
413+
414+
@Test
415+
public void ifTargetedPostIsntInSameThreadErrorIsReturnedParentCheck() throws Exception {
416+
NodeItem item = new NodeItemImpl(node, "1", new Date(), "<entry/>");
417+
Mockito.when(
418+
channelManager.getNodeItem(Mockito.eq(node),
419+
Mockito.eq("1"))).thenReturn(item);
420+
Mockito.when(
421+
channelManager.getNodeItem(Mockito.eq(node),
422+
Mockito.eq("2"))).thenReturn(item);
423+
424+
Element entry = (Element) this.targetEntry.clone();
425+
validateEntry = getEntryObject(entry);
426+
427+
Assert.assertFalse(validateEntry.isValid());
428+
Assert.assertEquals(ValidateEntry.TARGET_MUST_BE_IN_SAME_THREAD,
429+
validateEntry.getErrorMessage());
430+
}
431+
432+
@Test
433+
public void ifTargetedPostIsntInSameThreadErrorIsReturnedThreadCheck() throws Exception {
434+
435+
NodeItem item1 = new NodeItemImpl(node, "1", new Date(), "<entry/>");
436+
NodeItem item2 = new NodeItemImpl(node, "B", new Date(), "<entry/>", "A");
437+
Mockito.when(
438+
channelManager.getNodeItem(Mockito.eq(node),
439+
Mockito.eq("1"))).thenReturn(item1);
440+
Mockito.when(
441+
channelManager.getNodeItem(Mockito.eq(node),
442+
Mockito.eq("2"))).thenReturn(item2);
443+
444+
Element entry = (Element) this.targetEntry.clone();
445+
entry.element("target").element("id").setText("B");
446+
validateEntry = getEntryObject(entry);
447+
448+
Assert.assertFalse(validateEntry.isValid());
449+
Assert.assertEquals(ValidateEntry.TARGET_MUST_BE_IN_SAME_THREAD,
450+
validateEntry.getErrorMessage());
451+
}
452+
453+
454+
@Test
455+
public void ifTargetedIdIsSameAsReplyToIdOnlyOneDatabaseLookupPerformed() throws Exception {
456+
NodeItem item = new NodeItemImpl(node, "1", new Date(), "<entry/>");
457+
Mockito.when(
458+
channelManager.getNodeItem(Mockito.eq(node),
459+
Mockito.eq("1"))).thenReturn(item);
460+
461+
Element entry = (Element) this.targetEntry.clone();
462+
entry.element("target").element("id").setText("1");
463+
464+
validateEntry = getEntryObject(entry);
465+
466+
validateEntry.isValid();
467+
468+
Mockito.verify(channelManager, Mockito.times(1)).getNodeItem(Mockito.eq(node),
469+
Mockito.eq("1"));
470+
}
471+
323472
}

src/test/resources/stanzas/iq/pubsub/publish/reply.stanza

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<activity:object>
1818
<activity:object-type>post</activity:object-type>
1919
</activity:object>
20-
<thr:in-reply-to ref="null@channels.shakespeare.lit,/users/romeo@shakespeare.lit/posts,fc362eb42085f017ed9ccd9c4004b095" />
20+
<thr:in-reply-to ref="tag:channels.shakespeare.lit,/users/romeo@shakespeare.lit/posts,fc362eb42085f017ed9ccd9c4004b095" />
2121
</entry>
2222
</item>
2323
</publish>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
<iq from="juliet@shakespeare.lit/balcony" to="channels.shakespeare.lit" type="set" id="publish:20">
3+
<pubsub xmlns="http://jabber.org/protocol/pubsub">
4+
<publish node="/user/romeo@shakespeare.lit/posts">
5+
<item>
6+
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:activity="http://activitystrea.ms/spec/1.0/">
7+
<id>96da02ee1baef61e767742844207bec4</id>
8+
<title>Post title</title>
9+
<published>2014-01-01T00:00:00.000Z</published>
10+
<updated>2014-01-01T00:00:00.000Z</updated>
11+
<author>
12+
<name>juliet@shakespeare.lit</name>
13+
<jid xmlns="http://buddycloud.com/atom-elements-0">juliet@shakespeare.lit</jid>
14+
</author>
15+
<content type="text">O Romeo, Romeo! wherefore art thou Romeo?</content>
16+
<activity:verb>post</activity:verb>
17+
<activity:object>
18+
<activity:object-type>post</activity:object-type>
19+
</activity:object>
20+
<thr:in-reply-to ref="1" />
21+
<activity:target>
22+
<id>tag:channels.shakespeare.lit,/users/romeo@shakespeare.lit/posts,2</id>
23+
</activity:target>
24+
</entry>
25+
</item>
26+
</publish>
27+
</pubsub>
28+
</iq>

0 commit comments

Comments
 (0)