Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Mark the hash-uri container as a pairtree node, exclude it from Resou…
…rce#getChildren() requests, and ensure clients can't create 'deep' hash uri resources from a request body
  • Loading branch information
cbeer committed Oct 22, 2014
1 parent 075d5b6 commit 161af27
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 38 deletions.
Expand Up @@ -174,7 +174,8 @@ public boolean apply(final Node n) {
try {
return isInternalNode.apply(n)
|| n.getName().equals(JCR_CONTENT)
|| TombstoneImpl.hasMixin(n);
|| TombstoneImpl.hasMixin(n)
|| n.getName().equals("#");
} catch (final RepositoryException e) {
throw new RepositoryRuntimeException(e);
}
Expand Down Expand Up @@ -217,7 +218,8 @@ public FedoraResource getContainer() {

Node container = getNode().getParent();
while (container.getDepth() > 0) {
if (container.isNodeType(FEDORA_PAIRTREE) || container.isNodeType(FEDORA_DATASTREAM)) {
if (container.isNodeType(FEDORA_PAIRTREE)
|| container.isNodeType(FEDORA_DATASTREAM)) {
container = container.getParent();
} else {
return nodeConverter.convert(container);
Expand Down
Expand Up @@ -21,17 +21,21 @@
import static javax.jcr.PropertyType.UNDEFINED;
import static javax.jcr.PropertyType.URI;
import static javax.jcr.PropertyType.WEAKREFERENCE;
import static org.fcrepo.jcr.FedoraJcrTypes.FEDORA_PAIRTREE;
import static org.fcrepo.jcr.FedoraJcrTypes.FEDORA_RESOURCE;
import static org.fcrepo.kernel.RdfLexicon.JCR_NAMESPACE;
import static org.fcrepo.kernel.RdfLexicon.isManagedPredicate;
import static org.fcrepo.kernel.impl.identifiers.NodeResourceConverter.nodeToResource;
import static org.fcrepo.kernel.impl.rdf.converters.PropertyConverter.getPropertyNameFromPredicate;
import static org.fcrepo.kernel.impl.utils.FedoraTypesUtils.getClosestExistingAncestor;
import static org.modeshape.jcr.api.JcrConstants.NT_FOLDER;
import static org.slf4j.LoggerFactory.getLogger;

import java.util.HashMap;
import java.util.Map;

import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
Expand Down Expand Up @@ -336,29 +340,46 @@ public Statement skolemize(final IdentifierConverter<Resource, FedoraResource> g
t.getPredicate(),
t.getObject());
} else if (graphSubjects.inDomain(t.getSubject()) && t.getSubject().getURI().contains("#")) {
final String absPath = graphSubjects.asString(t.getSubject());

if (!absPath.isEmpty() && !session.nodeExists(absPath)) {
final Node orCreateNode = jcrTools.findOrCreateNode(session, absPath, NT_FOLDER);
orCreateNode.addMixin("fedora:resource");
}
findOrCreateHashUri(graphSubjects, t.getSubject());
}

if (t.getObject().isAnon()) {
skolemized = t.changeObject(getSkolemizedResource(graphSubjects, skolemized.getObject()));
} else if (t.getObject().isResource()
&& graphSubjects.inDomain(t.getObject().asResource())
&& t.getObject().asResource().getURI().contains("#")) {
findOrCreateHashUri(graphSubjects, t.getObject().asResource());
}

return skolemized;
}

private void findOrCreateHashUri(final IdentifierConverter<Resource, FedoraResource> graphSubjects,
final Resource s) throws RepositoryException {
final String absPath = graphSubjects.asString(s);

if (!absPath.isEmpty() && !session.nodeExists(absPath)) {
final Node closestExistingAncestor = getClosestExistingAncestor(session, absPath);

final String absPath = graphSubjects.asString(t.getObject().asResource());
final Node orCreateNode = jcrTools.findOrCreateNode(session, absPath, NT_FOLDER);
orCreateNode.addMixin(FEDORA_RESOURCE);

if (!absPath.isEmpty() && !session.nodeExists(absPath)) {
final Node orCreateNode = jcrTools.findOrCreateNode(session, absPath, NT_FOLDER);
orCreateNode.addMixin("fedora:resource");
final Node parent = orCreateNode.getParent();

if (!parent.getName().equals("#")) {
throw new AssertionError("Hash URI resource created with too much hierarchy: " + s);
}
}

return skolemized;
// We require the closest node to be either "#" resource, or its parent.
if (!parent.equals(closestExistingAncestor)
&& !parent.getParent().equals(closestExistingAncestor)) {
throw new PathNotFoundException("Unexpected request to create new resource " + s);
}

if (parent.isNew()) {
parent.addMixin(FEDORA_PAIRTREE);
}
}
}

private Resource getSkolemizedResource(final IdentifierConverter<Resource, FedoraResource> graphSubjects,
Expand Down
Expand Up @@ -25,6 +25,7 @@
import javax.jcr.Session;

import static org.fcrepo.jcr.FedoraJcrTypes.FEDORA_PAIRTREE;
import static org.fcrepo.kernel.impl.utils.FedoraTypesUtils.getClosestExistingAncestor;
import static org.modeshape.jcr.api.JcrConstants.NT_FOLDER;


Expand All @@ -39,7 +40,7 @@ protected Node findOrCreateNode(final Session session,
final String path,
final String finalNodeType) throws RepositoryException {

final Node preexistingNode = getClosestPreexistingNode(session, path);
final Node preexistingNode = getClosestExistingAncestor(session, path);

if (TombstoneImpl.hasMixin(preexistingNode)) {
throw new TombstoneException(new TombstoneImpl(preexistingNode));
Expand All @@ -64,25 +65,4 @@ private void tagHierarchyWithPairtreeMixin(final Node baseNode,
}
}

private Node getClosestPreexistingNode(final Session session,
final String path) throws RepositoryException {
final String[] pathSegments = path.replaceAll("^/+", "").replaceAll("/+$", "").split("/");

Node node = session.getRootNode();

final int len = pathSegments.length;
for (int i = 0; i != len; ++i) {
final String pathSegment = pathSegments[i];

if (node.hasNode(pathSegment)) {
// Find the existing node ...
node = node.getNode(pathSegment);
} else {
return node;
}

}

return node;
}
}
Expand Up @@ -25,6 +25,7 @@
import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.RepositoryException;
import javax.jcr.Session;

import static com.google.common.base.Preconditions.checkNotNull;
import static javax.jcr.PropertyType.REFERENCE;
Expand Down Expand Up @@ -145,4 +146,34 @@ public boolean apply(final Node n) {
}
};

/**
* Get the closest ancestor that current exists
*
* @param session
* @param path
* @return
* @throws RepositoryException
*/
public static Node getClosestExistingAncestor(final Session session,
final String path) throws RepositoryException {
final String[] pathSegments = path.replaceAll("^/+", "").replaceAll("/+$", "").split("/");

Node node = session.getRootNode();

final int len = pathSegments.length;
for (int i = 0; i != len; ++i) {
final String pathSegment = pathSegments[i];

if (node.hasNode(pathSegment)) {
// Find the existing node ...
node = node.getNode(pathSegment);
} else {
return node;
}

}

return node;
}

}
Expand Up @@ -646,6 +646,14 @@ public void testGetChildrenTombstonesAreHidden() throws RepositoryException {
final FedoraResource resource = objectService.findOrCreateObject(session, "/" + pid + "/a");

resource.delete();
assertFalse(container.getChildren().hasNext());
}

@Test
public void testGetChildrenHidesHashUris() throws RepositoryException {
final String pid = getRandomPid();
final FedoraObject container = objectService.findOrCreateObject(session, "/" + pid);
final FedoraResource resource = objectService.findOrCreateObject(session, "/" + pid + "/#/a");

assertFalse(container.getChildren().hasNext());
}
Expand Down
Expand Up @@ -51,6 +51,7 @@

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.PropertyIterator;
import javax.jcr.PropertyType;
import javax.jcr.Repository;
Expand Down Expand Up @@ -126,6 +127,7 @@ private void buildMockNodeAndSurroundings() throws RepositoryException {
when(mockNode.getSession()).thenReturn(mockSession);
when(mockSession.getRepository()).thenReturn(mockRepository);
when(mockSession.getWorkspace()).thenReturn(mockWorkspace);
when(mockSession.getRootNode()).thenReturn(mockRootNode);
when(mockParent.getPath()).thenReturn("/test");
when(mockParent.getPrimaryNodeType()).thenReturn(mockNodeType);
when(mockNode.getPath()).thenReturn("/test/jcr");
Expand Down Expand Up @@ -157,6 +159,7 @@ private void buildMockNodeAndSurroundings() throws RepositoryException {
when(mockParentProperties.hasNext()).thenReturn(false);
when(mockNodeType.getSupertypes()).thenReturn(new NodeType[] {mockNodeType});
when(mockSession.getValueFactory()).thenReturn(mockValueFactory);
when(mockHashNode.getName()).thenReturn("#");
}

@Test
Expand Down Expand Up @@ -393,11 +396,54 @@ public void shouldCreateHashUriSubjects() throws RepositoryException {
createProperty("info:x"),
testSubjects.toDomain("/"));
testObj.jcrTools = mock(JcrTools.class);
when(mockNode.getParent()).thenReturn(mockHashNode);
when(mockHashNode.getParent()).thenReturn(mockChildNode);
when(mockRootNode.hasNode("some")).thenReturn(true);
when(mockRootNode.getNode("some")).thenReturn(mockChildNode);
when(mockChildNode.isNew()).thenReturn(false);
when(testObj.jcrTools.findOrCreateNode(mockSession, "/some/#/abc", NT_FOLDER)).thenReturn(mockNode);
when(mockHashNode.isNew()).thenReturn(true);
final Statement statement = testObj.skolemize(testSubjects, x);
assertEquals(x, statement);
verify(testObj.jcrTools).findOrCreateNode(mockSession, "/some/#/abc", NT_FOLDER);
verify(mockNode).addMixin("fedora:resource");
verify(mockNode).addMixin(FEDORA_RESOURCE);
verify(mockHashNode).addMixin(FEDORA_PAIRTREE);
}

@Test
public void shouldCreateHashUriSubjectsWithExistingHashUri() throws RepositoryException {
final Model m = createDefaultModel();
final Statement x = m.createStatement(testSubjects.toDomain("/some/#/abc"),
createProperty("info:x"),
testSubjects.toDomain("/"));
testObj.jcrTools = mock(JcrTools.class);
when(mockNode.getParent()).thenReturn(mockHashNode);
when(mockHashNode.getParent()).thenReturn(mockChildNode);
when(mockRootNode.hasNode("some")).thenReturn(true);
when(mockRootNode.getNode("some")).thenReturn(mockChildNode);
when(mockChildNode.isNew()).thenReturn(false);
when(mockChildNode.hasNode("#")).thenReturn(true);
when(mockChildNode.getNode("#")).thenReturn(mockHashNode);
when(mockHashNode.isNew()).thenReturn(false);
when(testObj.jcrTools.findOrCreateNode(mockSession, "/some/#/abc", NT_FOLDER)).thenReturn(mockNode);
final Statement statement = testObj.skolemize(testSubjects, x);
assertEquals(x, statement);
verify(testObj.jcrTools).findOrCreateNode(mockSession, "/some/#/abc", NT_FOLDER);
verify(mockNode).addMixin(FEDORA_RESOURCE);
}

@Test(expected = PathNotFoundException.class)
public void shouldNotAllowHashUriSubjectsForResourcesThatDontExist() throws RepositoryException {
final Model m = createDefaultModel();
final Statement x = m.createStatement(testSubjects.toDomain("/some/#/abc"),
createProperty("info:x"),
testSubjects.toDomain("/"));
testObj.jcrTools = mock(JcrTools.class);
when(mockNode.getParent()).thenReturn(mockHashNode);
when(mockHashNode.getParent()).thenReturn(mockChildNode);
when(mockRootNode.hasNode("some")).thenReturn(false);
when(testObj.jcrTools.findOrCreateNode(mockSession, "/some/#/abc", NT_FOLDER)).thenReturn(mockNode);
testObj.skolemize(testSubjects, x);
}

@Test
Expand All @@ -408,11 +454,15 @@ public void shouldCreateHashUriObjects() throws RepositoryException {
createProperty("info:x"),
testSubjects.toDomain("/some/#/abc"));
testObj.jcrTools = mock(JcrTools.class);
when(mockNode.getParent()).thenReturn(mockHashNode);
when(mockHashNode.getParent()).thenReturn(mockChildNode);
when(mockRootNode.hasNode("some")).thenReturn(true);
when(mockRootNode.getNode("some")).thenReturn(mockChildNode);
when(testObj.jcrTools.findOrCreateNode(mockSession, "/some/#/abc", NT_FOLDER)).thenReturn(mockNode);
final Statement statement = testObj.skolemize(testSubjects, x);
assertEquals(x, statement);
verify(testObj.jcrTools).findOrCreateNode(mockSession, "/some/#/abc", NT_FOLDER);
verify(mockNode).addMixin("fedora:resource");
verify(mockNode).addMixin(FEDORA_RESOURCE);
}

@Test
Expand Down Expand Up @@ -565,6 +615,12 @@ private static void logRDF(final Model rdf) throws IOException {
@Mock
private Node mockFullChildNode;

@Mock
private Node mockRootNode;

@Mock
private Node mockHashNode;

@Mock
private Counter mockCounter;

Expand Down
Expand Up @@ -15,6 +15,7 @@
*/
package org.fcrepo.kernel.impl.utils;

import static org.fcrepo.kernel.impl.utils.FedoraTypesUtils.getClosestExistingAncestor;
import static org.fcrepo.kernel.services.functions.JcrPropertyFunctions.isBinaryContentProperty;
import static org.fcrepo.kernel.impl.utils.FedoraTypesUtils.isReferenceProperty;
import static org.fcrepo.kernel.impl.utils.FedoraTypesUtils.isInternalProperty;
Expand Down Expand Up @@ -137,6 +138,12 @@ public class FedoraTypesUtilsTest {
@Mock
private Property mockProperty;

@Mock
private Node mockRootNode;

@Mock
private Node mockContainer;

@Before
public void setUp() {
initMocks(this);
Expand Down Expand Up @@ -293,4 +300,35 @@ public void testProperty2values() throws RepositoryException {

}

@Test
public void testGetClosestExistingAncestorRoot() throws RepositoryException {
when(mockSession.getRootNode()).thenReturn(mockRootNode);
when(mockRootNode.hasNode(anyString())).thenReturn(false);

final Node closestExistingAncestor = getClosestExistingAncestor(mockSession, "/some/path");
assertEquals(mockRootNode, closestExistingAncestor);
}

@Test
public void testGetClosestExistingAncestorContainer() throws RepositoryException {
when(mockSession.getRootNode()).thenReturn(mockRootNode);
when(mockRootNode.hasNode("some")).thenReturn(true);
when(mockRootNode.getNode("some")).thenReturn(mockContainer);

final Node closestExistingAncestor = getClosestExistingAncestor(mockSession, "/some/path");
assertEquals(mockContainer, closestExistingAncestor);
}

@Test
public void testGetClosestExistingAncestorNode() throws RepositoryException {
when(mockSession.getRootNode()).thenReturn(mockRootNode);
when(mockRootNode.hasNode("some")).thenReturn(true);
when(mockRootNode.getNode("some")).thenReturn(mockContainer);
when(mockContainer.hasNode("path")).thenReturn(true);
when(mockContainer.getNode("path")).thenReturn(mockNode);

final Node closestExistingAncestor = getClosestExistingAncestor(mockSession, "/some/path");
assertEquals(mockNode, closestExistingAncestor);
}

}

0 comments on commit 161af27

Please sign in to comment.