001/*
002 * The contents of this file are subject to the license and copyright
003 * detailed in the LICENSE and NOTICE files at the root of the source
004 * tree.
005 */
006package org.fcrepo.integration.auth.webac;
007
008import static java.util.Arrays.stream;
009import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
010import static javax.ws.rs.core.Response.Status.CONFLICT;
011import static javax.ws.rs.core.Response.Status.CREATED;
012import static javax.ws.rs.core.Response.Status.FORBIDDEN;
013import static javax.ws.rs.core.Response.Status.NO_CONTENT;
014import static javax.ws.rs.core.Response.Status.OK;
015import static org.apache.http.HttpHeaders.CONTENT_TYPE;
016import static org.apache.http.HttpStatus.SC_CREATED;
017import static org.apache.http.HttpStatus.SC_FORBIDDEN;
018import static org.apache.http.HttpStatus.SC_NOT_FOUND;
019import static org.apache.http.HttpStatus.SC_NO_CONTENT;
020import static org.apache.jena.vocabulary.DC_11.title;
021import static org.fcrepo.http.commons.session.TransactionConstants.ATOMIC_ID_HEADER;
022import static org.fcrepo.kernel.api.FedoraTypes.FCR_METADATA;
023import static org.fcrepo.kernel.api.FedoraTypes.FCR_TX;
024import static org.fcrepo.kernel.api.RdfLexicon.DIRECT_CONTAINER;
025import static org.fcrepo.kernel.api.RdfLexicon.EMBED_CONTAINED;
026import static org.fcrepo.kernel.api.RdfLexicon.INDIRECT_CONTAINER;
027import static org.fcrepo.kernel.api.RdfLexicon.MEMBERSHIP_RESOURCE;
028import static org.junit.Assert.assertEquals;
029import static org.junit.Assert.assertTrue;
030
031import java.io.IOException;
032import java.io.InputStream;
033import java.io.UnsupportedEncodingException;
034import java.nio.file.Paths;
035import java.util.Arrays;
036import java.util.Optional;
037import java.util.regex.Pattern;
038
039import javax.ws.rs.core.Link;
040import javax.ws.rs.core.Response;
041
042import org.fcrepo.auth.webac.WebACRolesProvider;
043import org.fcrepo.http.commons.test.util.CloseableDataset;
044import org.fcrepo.integration.http.api.AbstractResourceIT;
045import org.fcrepo.integration.http.api.TestIsolationExecutionListener;
046
047import org.apache.commons.codec.binary.Base64;
048import org.apache.http.Header;
049import org.apache.http.HeaderElement;
050import org.apache.http.HttpEntity;
051import org.apache.http.HttpResponse;
052import org.apache.http.HttpStatus;
053import org.apache.http.NameValuePair;
054import org.apache.http.client.config.RequestConfig;
055import org.apache.http.client.methods.CloseableHttpResponse;
056import org.apache.http.client.methods.HttpDelete;
057import org.apache.http.client.methods.HttpGet;
058import org.apache.http.client.methods.HttpHead;
059import org.apache.http.client.methods.HttpOptions;
060import org.apache.http.client.methods.HttpPatch;
061import org.apache.http.client.methods.HttpPost;
062import org.apache.http.client.methods.HttpPut;
063import org.apache.http.entity.ContentType;
064import org.apache.http.entity.InputStreamEntity;
065import org.apache.http.entity.StringEntity;
066import org.apache.http.message.AbstractHttpMessage;
067import org.apache.jena.graph.Node;
068import org.apache.jena.graph.NodeFactory;
069import org.apache.jena.sparql.core.DatasetGraph;
070import org.glassfish.grizzly.utils.Charsets;
071import org.junit.Before;
072import org.junit.Ignore;
073import org.junit.Rule;
074import org.junit.Test;
075import org.junit.contrib.java.lang.system.RestoreSystemProperties;
076import org.slf4j.Logger;
077import org.slf4j.LoggerFactory;
078import org.springframework.test.context.TestExecutionListeners;
079
080/**
081 * @author Peter Eichman
082 * @author whikloj
083 * @since September 4, 2015
084 */
085@TestExecutionListeners(
086        listeners = { TestIsolationExecutionListener.class },
087        mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
088public class WebACRecipesIT extends AbstractResourceIT {
089
090    private static final Logger logger = LoggerFactory.getLogger(WebACRecipesIT.class);
091
092    @Rule
093    public final RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();
094
095    private final ContentType turtleContentType = ContentType.create("text/turtle", "UTF-8");
096
097    private final ContentType sparqlContentType = ContentType.create("application/sparql-update", "UTF-8");
098
099    private WebACRolesProvider rolesProvider;
100
101    @Before
102    public void setup() {
103        this.rolesProvider = getBean(WebACRolesProvider.class);
104        authPropsConfig.setRootAuthAclPath(null);
105        rolesProvider.setGroupBaseUri(null);
106        rolesProvider.setUserBaseUri(null);
107    }
108
109    /**
110     * Convenience method to create an ACL with 0 or more authorization resources in the repository.
111     */
112    private String ingestAcl(final String username,
113            final String aclFilePath, final String aclResourcePath) throws IOException {
114
115        // create the ACL
116        final HttpResponse aclResponse = ingestTurtleResource(username, aclFilePath, aclResourcePath);
117
118        // return the URI to the newly created resource
119        return aclResponse.getFirstHeader("Location").getValue();
120    }
121
122    /**
123     * Convenience method to POST the contents of a Turtle file to the repository to create a new resource. Returns
124     * the HTTP response from that request. Throws an IOException if the server responds with anything other than a
125     * 201 Created response code.
126     */
127    private HttpResponse ingestTurtleResource(final String username, final String path, final String requestURI)
128            throws IOException {
129        final HttpPut request = new HttpPut(requestURI);
130
131        logger.debug("PUT to {} to create {}", requestURI, path);
132
133        setAuth(request, username);
134
135        final InputStream file = this.getClass().getResourceAsStream(path);
136        final InputStreamEntity fileEntity = new InputStreamEntity(file);
137        request.setEntity(fileEntity);
138        request.setHeader("Content-Type", "text/turtle");
139
140        try (final CloseableHttpResponse response = execute(request)) {
141            assertEquals("Didn't get a CREATED response!", CREATED.getStatusCode(), getStatus(response));
142            return response;
143        }
144
145    }
146
147    /**
148     * Convenience method to set up a regular FedoraResource
149     *
150     * @param path Path to put the resource under
151     * @return the Location of the newly created resource
152     * @throws IOException on error
153     */
154    private String ingestObj(final String path) throws IOException {
155        final HttpPut request = putObjMethod(path.replace(serverAddress, ""));
156        setAuth(request, "fedoraAdmin");
157        try (final CloseableHttpResponse response = execute(request)) {
158            assertEquals(HttpStatus.SC_CREATED, response.getStatusLine().getStatusCode());
159            return response.getFirstHeader("Location").getValue();
160        }
161    }
162
163    private String ingestBinary(final String path, final HttpEntity body) throws IOException {
164        logger.info("Ingesting {} binary to {}", body.getContentType().getValue(), path);
165        final HttpPut request = new HttpPut(serverAddress + path);
166        setAuth(request, "fedoraAdmin");
167        request.setEntity(body);
168        request.setHeader(body.getContentType());
169        final CloseableHttpResponse response = execute(request);
170        assertEquals(HttpStatus.SC_CREATED, response.getStatusLine().getStatusCode());
171        final String location = response.getFirstHeader("Location").getValue();
172        logger.info("Created binary at {}", location);
173        return location;
174
175    }
176
177    private String ingestDatastream(final String path, final String ds) throws IOException {
178        final HttpPut request = putDSMethod(path, ds, "some not so random content");
179        setAuth(request, "fedoraAdmin");
180        try (final CloseableHttpResponse response = execute(request)) {
181            assertEquals(HttpStatus.SC_CREATED, response.getStatusLine().getStatusCode());
182            return response.getFirstHeader("Location").getValue();
183        }
184    }
185
186    /**
187     * Convenience method for applying credentials to a request
188     *
189     * @param method the request to add the credentials to
190     * @param username the username to add
191     */
192    private static void setAuth(final AbstractHttpMessage method, final String username) {
193        final String creds = username + ":password";
194        final String encCreds = new String(Base64.encodeBase64(creds.getBytes()));
195        final String basic = "Basic " + encCreds;
196        method.setHeader("Authorization", basic);
197    }
198
199    @Test
200    public void scenario1() throws IOException {
201        final String testObj = ingestObj("/rest/webacl_box1");
202
203        final String acl1 = ingestAcl("fedoraAdmin", "/acls/01/acl.ttl",
204                                      testObj + "/fcr:acl");
205        final String aclLink = Link.fromUri(acl1).rel("acl").build().toString();
206
207        final HttpGet request = getObjMethod(testObj.replace(serverAddress, ""));
208        assertEquals("Anonymous can read " + testObj, HttpStatus.SC_FORBIDDEN, getStatus(request));
209
210        setAuth(request, "user01");
211        try (final CloseableHttpResponse response = execute(request)) {
212            assertEquals("User 'user01' can't read" + testObj, HttpStatus.SC_OK, getStatus(response));
213            // This gets the Link headers and filters for the correct one (aclLink::equals) defined above.
214            final Optional<String> header = stream(response.getHeaders("Link")).map(Header::getValue)
215                    .filter(aclLink::equals).findFirst();
216            // So you either have the correct Link header or you get nothing.
217            assertTrue("Missing Link header", header.isPresent());
218        }
219
220        final String childObj = ingestObj("/rest/webacl_box1/child");
221        final HttpGet getReq = getObjMethod(childObj.replace(serverAddress, ""));
222        setAuth(getReq, "user01");
223        try (final CloseableHttpResponse response = execute(getReq)) {
224            assertEquals("User 'user01' can't read child of " + testObj, HttpStatus.SC_OK, getStatus(response));
225        }
226    }
227
228    @Test
229    public void scenario2() throws IOException {
230        final String id = "/rest/box/bag/collection";
231        final String testObj = ingestObj(id);
232        ingestAcl("fedoraAdmin", "/acls/02/acl.ttl", testObj + "/fcr:acl");
233
234        logger.debug("Anonymous can not read " + testObj);
235        final HttpGet requestGet = getObjMethod(id);
236        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestGet));
237
238        logger.debug("GroupId 'Editors' can read " + testObj);
239        final HttpGet requestGet2 = getObjMethod(id);
240        setAuth(requestGet2, "jones");
241        requestGet2.setHeader("some-header", "Editors");
242        assertEquals(HttpStatus.SC_OK, getStatus(requestGet2));
243
244        logger.debug("Anonymous cannot write " + testObj);
245        final HttpPatch requestPatch = patchObjMethod(id);
246        requestPatch.setEntity(new StringEntity("INSERT { <> <" + title.getURI() + "> \"Test title\" . } WHERE {}"));
247        requestPatch.setHeader("Content-type", "application/sparql-update");
248        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestPatch));
249
250        logger.debug("Editors can write " + testObj);
251        final HttpPatch requestPatch2 = patchObjMethod(id);
252        setAuth(requestPatch2, "jones");
253        requestPatch2.setHeader("some-header", "Editors");
254        requestPatch2.setEntity(
255                new StringEntity("INSERT { <> <" + title.getURI() + "> \"Different title\" . } WHERE {}"));
256        requestPatch2.setHeader("Content-type", "application/sparql-update");
257        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(requestPatch2));
258    }
259
260    @Test
261    public void scenario3() throws IOException {
262        final String idDark = "/rest/dark/archive";
263        final String idLight = "/rest/dark/archive/sunshine";
264        final String testObj = ingestObj(idDark);
265        final String testObj2 = ingestObjWithACL(idLight, "/acls/03/acl.ttl");
266        ingestAcl("fedoraAdmin", "/acls/03/acl.ttl", testObj + "/fcr:acl");
267
268        logger.debug("Anonymous can't read " + testObj);
269        final HttpGet requestGet = getObjMethod(idDark);
270        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestGet));
271
272        logger.debug("Restricted can read " + testObj);
273        final HttpGet requestGet2 = getObjMethod(idDark);
274        setAuth(requestGet2, "jones");
275        requestGet2.setHeader("some-header", "Restricted");
276        assertEquals(HttpStatus.SC_OK, getStatus(requestGet2));
277
278        logger.debug("Anonymous can read " + testObj2);
279        final HttpGet requestGet3 = getObjMethod(idLight);
280        assertEquals(HttpStatus.SC_OK, getStatus(requestGet3));
281
282        logger.debug("Restricted can read " + testObj2);
283        final HttpGet requestGet4 = getObjMethod(idLight);
284        setAuth(requestGet4, "jones");
285        requestGet4.setHeader("some-header", "Restricted");
286        assertEquals(HttpStatus.SC_OK, getStatus(requestGet4));
287    }
288
289    @Test
290    public void scenario4() throws IOException {
291        final String id = "/rest/public_collection";
292        final String testObj = ingestObjWithACL(id, "/acls/04/acl.ttl");
293
294        logger.debug("Anonymous can read " + testObj);
295        final HttpGet requestGet = getObjMethod(id);
296        assertEquals(HttpStatus.SC_OK, getStatus(requestGet));
297
298        logger.debug("Editors can read " + testObj);
299        final HttpGet requestGet2 = getObjMethod(id);
300        setAuth(requestGet2, "jones");
301        requestGet2.setHeader("some-header", "Editors");
302        assertEquals(HttpStatus.SC_OK, getStatus(requestGet2));
303
304        logger.debug("Smith can access " + testObj);
305        final HttpGet requestGet3 = getObjMethod(id);
306        setAuth(requestGet3, "smith");
307        assertEquals(HttpStatus.SC_OK, getStatus(requestGet3));
308
309        logger.debug("Anonymous can't write " + testObj);
310        final HttpPatch requestPatch = patchObjMethod(id);
311        requestPatch.setHeader("Content-type", "application/sparql-update");
312        requestPatch.setEntity(new StringEntity("INSERT { <> <" + title.getURI() + "> \"Change title\" . } WHERE {}"));
313        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestPatch));
314
315        logger.debug("Editors can write " + testObj);
316        final HttpPatch requestPatch2 = patchObjMethod(id);
317        requestPatch2.setHeader("Content-type", "application/sparql-update");
318        requestPatch2.setEntity(new StringEntity("INSERT { <> <" + title.getURI() + "> \"New title\" . } WHERE {}"));
319        setAuth(requestPatch2, "jones");
320        requestPatch2.setHeader("some-header", "Editors");
321        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(requestPatch2));
322
323        logger.debug("Editors can create (PUT) child objects of " + testObj);
324        final HttpPut requestPut1 = putObjMethod(id + "/child1");
325        setAuth(requestPut1, "jones");
326        requestPut1.setHeader("some-header", "Editors");
327        assertEquals(HttpStatus.SC_CREATED, getStatus(requestPut1));
328
329        final HttpGet requestGet4 = getObjMethod(id + "/child1");
330        setAuth(requestGet4, "jones");
331        requestGet4.setHeader("some-header", "Editors");
332        assertEquals(HttpStatus.SC_OK, getStatus(requestGet4));
333
334        logger.debug("Editors can create (POST) child objects of " + testObj);
335        final HttpPost requestPost1 = postObjMethod(id);
336        requestPost1.addHeader("Slug", "child2");
337        setAuth(requestPost1, "jones");
338        requestPost1.setHeader("some-header", "Editors");
339        assertEquals(HttpStatus.SC_CREATED, getStatus(requestPost1));
340
341        final HttpGet requestGet5 = getObjMethod(id + "/child2");
342        setAuth(requestGet5, "jones");
343        requestGet5.setHeader("some-header", "Editors");
344        assertEquals(HttpStatus.SC_OK, getStatus(requestGet5));
345
346        logger.debug("Editors can create nested child objects of " + testObj);
347        final HttpPut requestPut2 = putObjMethod(id + "/a/b/c/child");
348        setAuth(requestPut2, "jones");
349        requestPut2.setHeader("some-header", "Editors");
350        assertEquals(HttpStatus.SC_CREATED, getStatus(requestPut2));
351
352        final HttpGet requestGet6 = getObjMethod(id + "/a/b/c/child");
353        setAuth(requestGet6, "jones");
354        requestGet6.setHeader("some-header", "Editors");
355        assertEquals(HttpStatus.SC_OK, getStatus(requestGet6));
356
357        logger.debug("Smith can't write " + testObj);
358        final HttpPatch requestPatch3 = patchObjMethod(id);
359        requestPatch3.setHeader("Content-type", "application/sparql-update");
360        requestPatch3.setEntity(
361                new StringEntity("INSERT { <> <" + title.getURI() + "> \"Different title\" . } WHERE {}"));
362        setAuth(requestPatch3, "smith");
363        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestPatch3));
364    }
365
366    @Test
367    public void scenario5() throws IOException {
368        final String idPublic = "/rest/mixedCollection/publicObj";
369        final String idPrivate = "/rest/mixedCollection/privateObj";
370        ingestObjWithACL("/rest/mixedCollection", "/acls/05/acl.ttl");
371        final String publicObj = ingestObj(idPublic);
372        final String privateObj = ingestObj(idPrivate);
373        final HttpPatch patch = patchObjMethod(idPublic);
374
375        setAuth(patch, "fedoraAdmin");
376        patch.setHeader("Content-type", "application/sparql-update");
377        patch.setEntity(new StringEntity("INSERT { <> a <http://example.com/terms#publicImage> . } WHERE {}"));
378        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(patch));
379
380
381        logger.debug("Anonymous can see eg:publicImage " + publicObj);
382        final HttpGet requestGet = getObjMethod(idPublic);
383        assertEquals(HttpStatus.SC_OK, getStatus(requestGet));
384
385        logger.debug("Anonymous can't see other resource " + privateObj);
386        final HttpGet requestGet2 = getObjMethod(idPrivate);
387        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestGet2));
388
389        logger.debug("Admins can see eg:publicImage " + publicObj);
390        final HttpGet requestGet3 = getObjMethod(idPublic);
391        setAuth(requestGet3, "jones");
392        requestGet3.setHeader("some-header", "Admins");
393        assertEquals(HttpStatus.SC_OK, getStatus(requestGet3));
394
395        logger.debug("Admins can see others" + privateObj);
396        final HttpGet requestGet4 = getObjMethod(idPrivate);
397        setAuth(requestGet4, "jones");
398        requestGet4.setHeader("some-header", "Admins");
399        assertEquals(HttpStatus.SC_OK, getStatus(requestGet4));
400    }
401
402    @Ignore("Content-type with charset causes it to be a binary - FCREPO-3312")
403    @Test
404    public void scenario9() throws IOException {
405        final String idPublic = "/rest/anotherCollection/publicObj";
406        final String groups = "/rest/group";
407        final String fooGroup = groups + "/foo";
408        final String testObj = ingestObj("/rest/anotherCollection");
409        final String publicObj = ingestObj(idPublic);
410
411        final HttpPut request = putObjMethod(fooGroup);
412        setAuth(request, "fedoraAdmin");
413
414        final InputStream file = this.getClass().getResourceAsStream("/acls/09/group.ttl");
415        final InputStreamEntity fileEntity = new InputStreamEntity(file);
416        request.setEntity(fileEntity);
417        request.setHeader("Content-Type", "text/turtle;charset=UTF-8");
418
419        assertEquals("Didn't get a CREATED response!", CREATED.getStatusCode(), getStatus(request));
420
421        ingestAcl("fedoraAdmin", "/acls/09/acl.ttl", testObj + "/fcr:acl");
422
423        logger.debug("Person1 can see object " + publicObj);
424        final HttpGet requestGet1 = getObjMethod(idPublic);
425        setAuth(requestGet1, "person1");
426        assertEquals(HttpStatus.SC_OK, getStatus(requestGet1));
427
428        logger.debug("Person2 can see object " + publicObj);
429        final HttpGet requestGet2 = getObjMethod(idPublic);
430        setAuth(requestGet2, "person2");
431        assertEquals(HttpStatus.SC_OK, getStatus(requestGet2));
432
433        logger.debug("Person3 user cannot see object " + publicObj);
434        final HttpGet requestGet3 = getObjMethod(idPublic);
435        setAuth(requestGet3, "person3");
436        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestGet3));
437    }
438
439    /**
440     * Test cases to verify authorization with only acl:Append mode configured
441     * in the acl authorization of an resource.
442     * Tests:
443     *  1. Deny(403) on GET.
444     *  2. Allow(204) on PATCH.
445     *  3. Deny(403) on DELETE.
446     *  4. Deny(403) on PATCH with SPARQL DELETE statements.
447     *  5. Allow(400) on PATCH with empty SPARQL content.
448     *  6. Deny(403) on PATCH with non-SPARQL content.
449     *
450     * @throws IOException thrown from injestObj() or *ObjMethod() calls
451     */
452    @Test
453    public void scenario18Test1() throws IOException {
454        final String testObj = ingestObj("/rest/append_only_resource");
455        final String id = "/rest/append_only_resource/" + getRandomUniqueId();
456        ingestObj(id);
457
458        logger.debug("user18 can read (has ACL:READ): {}", id);
459        final HttpGet requestGet = getObjMethod(id);
460        setAuth(requestGet, "user18");
461        assertEquals(HttpStatus.SC_OK, getStatus(requestGet));
462
463        logger.debug("user18 can't append (no ACL): {}", id);
464        final HttpPatch requestPatch = patchObjMethod(id);
465        setAuth(requestPatch, "user18");
466        requestPatch.setHeader("Content-type", "application/sparql-update");
467        requestPatch.setEntity(new StringEntity("INSERT { <> <" + title.getURI() + "> \"Test title\" . } WHERE {}"));
468        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestPatch));
469
470        logger.debug("user18 can't delete (no ACL): {}", id);
471        final HttpDelete requestDelete = deleteObjMethod(id);
472        setAuth(requestDelete, "user18");
473        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestDelete));
474
475        ingestAcl("fedoraAdmin", "/acls/18/append-only-acl.ttl", testObj + "/fcr:acl");
476
477        logger.debug("user18 still can't read (ACL append): {}", id);
478        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestGet));
479
480        logger.debug("user18 can patch - SPARQL INSERTs (ACL append): {}", id);
481        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(requestPatch));
482
483        // Alter the Content-type to include a character set, to ensure correct matching.
484        requestPatch.setHeader("Content-type", "application/sparql-update; charset=UTF-8");
485        logger.debug("user18 can patch - SPARQL INSERTs (ACL append with charset): {}", id);
486        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(requestPatch));
487
488        logger.debug("user18 still can't delete (ACL append): {}", id);
489        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestDelete));
490
491        requestPatch.setEntity(new StringEntity("DELETE { <> <" + title.getURI() + "> \"Test title\" . } WHERE {}"));
492
493        logger.debug("user18 can not patch - SPARQL DELETEs (ACL append): {}", id);
494        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestPatch));
495
496        requestPatch.setEntity(null);
497
498        logger.debug("user18 can patch (is authorized, but bad request) - Empty SPARQL (ACL append): {}", id);
499        assertEquals(HttpStatus.SC_BAD_REQUEST, getStatus(requestPatch));
500
501        requestPatch.setHeader("Content-type", null);
502
503        logger.debug("user18 can not patch - Non SPARQL (ACL append): {}", id);
504        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestPatch));
505
506    }
507
508    /**
509     * Test cases to verify authorization with acl:Read and acl:Append modes
510     * configured in the acl authorization of an resource.
511     * Tests:
512     *  1. Allow(200) on GET.
513     *  2. Allow(204) on PATCH.
514     *  3. Deny(403) on DELETE.
515     *
516     * @throws IOException thrown from called functions within this function
517     */
518    @Test
519    public void scenario18Test2() throws IOException {
520        final String testObj = ingestObj("/rest/read_append_resource");
521
522        final String id = "/rest/read_append_resource/" + getRandomUniqueId();
523        ingestObj(id);
524
525        logger.debug("user18 can read (has ACL:READ): {}", id);
526        final HttpGet requestGet = getObjMethod(id);
527        setAuth(requestGet, "user18");
528        assertEquals(HttpStatus.SC_OK, getStatus(requestGet));
529
530        logger.debug("user18 can't append (no ACL): {}", id);
531        final HttpPatch requestPatch = patchObjMethod(id);
532        setAuth(requestPatch, "user18");
533        requestPatch.setHeader("Content-type", "application/sparql-update");
534        requestPatch.setEntity(new StringEntity(
535                "INSERT { <> <" + title.getURI() + "> \"some title\" . } WHERE {}"));
536        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestPatch));
537
538        ingestAcl("fedoraAdmin", "/acls/18/read-append-acl.ttl", testObj + "/fcr:acl");
539
540        logger.debug("user18 can't delete (no ACL): {}", id);
541        final HttpDelete requestDelete = deleteObjMethod(id);
542        setAuth(requestDelete, "user18");
543        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestDelete));
544
545        logger.debug("user18 can read (ACL read, append): {}", id);
546        assertEquals(HttpStatus.SC_OK, getStatus(requestGet));
547
548        logger.debug("user18 can append (ACL read, append): {}", id);
549        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(requestPatch));
550
551        logger.debug("user18 still can't delete (ACL read, append): {}", id);
552        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestDelete));
553    }
554
555    /**
556     * Test cases to verify authorization with acl:Read, acl:Append and
557     * acl:Write modes configured in the acl authorization of an resource.
558     * Tests:
559     *  1. Allow(200) on GET.
560     *  2. Allow(204) on PATCH.
561     *  3. Allow(204) on DELETE.
562     *
563     * @throws IOException from functions called from this function
564     */
565    @Test
566    public void scenario18Test3() throws IOException {
567        final String testObj = ingestObj("/rest/read_append_write_resource");
568
569        final String id = "/rest/read_append_write_resource/" + getRandomUniqueId();
570        ingestObj(id);
571
572        logger.debug("user18 can read (has ACL:READ): {}", id);
573        final HttpGet requestGet = getObjMethod(id);
574        setAuth(requestGet, "user18");
575        assertEquals(HttpStatus.SC_OK, getStatus(requestGet));
576
577        logger.debug("user18 can't append (no ACL): {}", id);
578        final HttpPatch requestPatch = patchObjMethod(id);
579        setAuth(requestPatch, "user18");
580        requestPatch.setHeader("Content-type", "application/sparql-update");
581        requestPatch.setEntity(new StringEntity(
582                "INSERT { <> <http://purl.org/dc/elements/1.1/title> \"some title\" . } WHERE {}"));
583        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestPatch));
584
585        logger.debug("user18 can't delete (no ACL): {}", id);
586        final HttpDelete requestDelete = deleteObjMethod(id);
587        setAuth(requestDelete, "user18");
588        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestDelete));
589
590        ingestAcl("fedoraAdmin", "/acls/18/read-append-write-acl.ttl", testObj + "/fcr:acl");
591
592        logger.debug("user18 can read (ACL read, append, write): {}", id);
593        assertEquals(HttpStatus.SC_OK, getStatus(requestGet));
594
595        logger.debug("user18 can append (ACL read, append, write): {}", id);
596        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(requestPatch));
597
598        logger.debug("user18 can delete (ACL read, append, write): {}", id);
599        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(requestDelete));
600    }
601
602    @Test
603    public void testAccessToRoot() throws IOException {
604        final String id = "/rest/" + getRandomUniqueId();
605        final String testObj = ingestObj(id);
606
607        logger.debug("Anonymous can read (has ACL:READ): {}", id);
608        final HttpGet requestGet1 = getObjMethod(id);
609        assertEquals(HttpStatus.SC_OK, getStatus(requestGet1));
610
611        logger.debug("Can username 'user06a' read {} (has ACL:READ)", id);
612        final HttpGet requestGet2 = getObjMethod(id);
613        setAuth(requestGet2, "user06a");
614        assertEquals(HttpStatus.SC_OK, getStatus(requestGet2));
615
616        logger.debug("Can username 'notuser06b' read {} (has ACL:READ)", id);
617        final HttpGet requestGet3 = getObjMethod(id);
618        setAuth(requestGet3, "user06b");
619        assertEquals(HttpStatus.SC_OK, getStatus(requestGet3));
620
621        authPropsConfig.setRootAuthAclPath(Paths.get("./target/test-classes/test-root-authorization2.ttl"));
622        logger.debug("Can username 'user06a' read {} (overridden system ACL)", id);
623        final HttpGet requestGet4 = getObjMethod(id);
624        setAuth(requestGet4, "user06a");
625        assertEquals(HttpStatus.SC_OK, getStatus(requestGet4));
626        authPropsConfig.setRootAuthAclPath(null);
627
628        // Add ACL to root
629        final String rootURI = getObjMethod("/rest").getURI().toString();
630        ingestAcl("fedoraAdmin", "/acls/06/acl.ttl", rootURI + "/fcr:acl");
631
632        logger.debug("Anonymous still can't read (ACL present)");
633        final HttpGet requestGet5 = getObjMethod(id);
634        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestGet5));
635
636        logger.debug("Can username 'user06a' read {} (ACL present)", testObj);
637        final HttpGet requestGet6 = getObjMethod(id);
638        setAuth(requestGet6, "user06a");
639        assertEquals(HttpStatus.SC_OK, getStatus(requestGet6));
640
641        logger.debug("Can username 'user06b' read {} (ACL present)", testObj);
642        final HttpGet requestGet7 = getObjMethod(id);
643        setAuth(requestGet7, "user06b");
644        assertEquals(HttpStatus.SC_OK, getStatus(requestGet7));
645    }
646
647    @Test
648    public void scenario21TestACLNotForInheritance() throws IOException {
649        final String parentPath = "/rest/resource_acl_no_inheritance";
650        // Ingest ACL with no acl:default statement to the parent resource
651        ingestObjWithACL(parentPath, "/acls/21/acl.ttl");
652
653        final String id = parentPath + "/" + getRandomUniqueId();
654        final String testObj = ingestObj(id);
655
656
657        // Test the parent ACL with no acl:default is applied for the parent resource authorization.
658        final HttpGet requestGet1 = getObjMethod(parentPath);
659        setAuth(requestGet1, "user21");
660        assertEquals("Agent user21 can't read resource " + parentPath + " with its own ACL!",
661                HttpStatus.SC_OK, getStatus(requestGet1));
662
663        final HttpGet requestGet2 = getObjMethod(id);
664        assertEquals("Agent user21 inherits read permission from parent ACL to read resource " + testObj + "!",
665                HttpStatus.SC_OK, getStatus(requestGet2));
666
667        // Test the default root ACL is inherited for authorization while the parent ACL with no acl:default is ignored
668        authPropsConfig.setRootAuthAclPath(Paths.get("./target/test-classes/test-root-authorization2.ttl"));
669        final HttpGet requestGet3 = getObjMethod(id);
670        setAuth(requestGet3, "user06a");
671        assertEquals("Agent user06a can't inherit read persmssion from root ACL to read resource " + testObj + "!",
672                HttpStatus.SC_OK, getStatus(requestGet3));
673    }
674
675    @Test
676    public void scenario22TestACLAuthorizationNotForInheritance() throws IOException {
677        final String parentPath = "/rest/resource_mix_acl_default";
678        final String parentObj = ingestObj(parentPath);
679
680        final String id = parentPath + "/" + getRandomUniqueId();
681        final String testObj = ingestObj(id);
682
683        // Ingest ACL with mix acl:default authorization to the parent resource
684        ingestAcl("fedoraAdmin", "/acls/22/acl.ttl", parentObj + "/fcr:acl");
685
686        // Test the parent ACL is applied for the parent resource authorization.
687        final HttpGet requestGet1 = getObjMethod(parentPath);
688        setAuth(requestGet1, "user22a");
689        assertEquals("Agent user22a can't read resource " + parentPath + " with its own ACL!",
690                HttpStatus.SC_OK, getStatus(requestGet1));
691
692        final HttpGet requestGet2 = getObjMethod(parentPath);
693        setAuth(requestGet2, "user22b");
694        assertEquals("Agent user22b can't read resource " + parentPath + " with its own ACL!",
695                HttpStatus.SC_OK, getStatus(requestGet1));
696
697        // Test the parent ACL is applied for the parent resource authorization.
698        final HttpGet requestGet3 = getObjMethod(id);
699        setAuth(requestGet3, "user22a");
700        assertEquals("Agent user22a inherits read permission from parent ACL to read resource " + testObj + "!",
701                HttpStatus.SC_FORBIDDEN, getStatus(requestGet3));
702
703        final HttpGet requestGet4 = getObjMethod(id);
704        setAuth(requestGet4, "user22b");
705        assertEquals("Agent user22b can't inherits read permission from parent ACL to read resource " + testObj + "!",
706                HttpStatus.SC_OK, getStatus(requestGet4));
707    }
708
709    @Test
710    public void testAccessToBinary() throws IOException {
711        // Block access to "book"
712        final String idBook = "/rest/book";
713        final String bookURI = ingestObj(idBook);
714
715        // Open access datastream, "file"
716        final String id = idBook + "/file";
717        final String testObj = ingestDatastream(idBook, "file");
718        ingestAcl("fedoraAdmin", "/acls/07/acl.ttl", bookURI + "/fcr:acl");
719
720        logger.debug("Anonymous can't read");
721        final HttpGet requestGet1 = getObjMethod(id);
722        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestGet1));
723
724        logger.debug("Can username 'user07' read {}", testObj);
725        final HttpGet requestGet2 = getObjMethod(id);
726
727        setAuth(requestGet2, "user07");
728        assertEquals(HttpStatus.SC_OK, getStatus(requestGet2));
729    }
730
731    @Test
732    public void testAccessToVersionedResources() throws IOException {
733        final String idVersion = "/rest/versionResource";
734        final String idVersionUri = ingestObj(idVersion);
735
736        final HttpPatch requestPatch1 = patchObjMethod(idVersion);
737        setAuth(requestPatch1, "fedoraAdmin");
738        requestPatch1.addHeader("Content-type", "application/sparql-update");
739        requestPatch1.setEntity(
740                new StringEntity("PREFIX pcdm: <http://pcdm.org/models#> INSERT { <> a pcdm:Object } WHERE {}"));
741        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(requestPatch1));
742
743        ingestAcl("fedoraAdmin", "/acls/10/acl.ttl", idVersionUri + "/fcr:acl");
744
745        final HttpGet requestGet1 = getObjMethod(idVersion);
746        setAuth(requestGet1, "user10");
747        assertEquals("user10 can't read object", HttpStatus.SC_OK, getStatus(requestGet1));
748
749        final HttpPost requestPost1 = postObjMethod(idVersion + "/fcr:versions");
750        setAuth(requestPost1, "fedoraAdmin");
751        assertEquals("Unable to create a new version", HttpStatus.SC_CREATED, getStatus(requestPost1));
752
753        final HttpGet requestGet2 = getObjMethod(idVersion);
754        setAuth(requestGet2, "user10");
755        assertEquals("user10 can't read versioned object", HttpStatus.SC_OK, getStatus(requestGet2));
756    }
757
758    @Test
759    public void testDelegatedUserAccess() throws IOException {
760        logger.debug("testing delegated authentication");
761        final String targetPath = "/rest/foo";
762        final String targetResource = ingestObj(targetPath);
763
764        ingestAcl("fedoraAdmin", "/acls/11/acl.ttl", targetResource + "/fcr:acl");
765
766        final HttpGet adminGet = getObjMethod(targetPath);
767        setAuth(adminGet, "fedoraAdmin");
768        assertEquals("admin can read object", HttpStatus.SC_OK, getStatus(adminGet));
769
770        final HttpGet adminDelegatedGet = getObjMethod(targetPath);
771        setAuth(adminDelegatedGet, "fedoraAdmin");
772        adminDelegatedGet.addHeader("On-Behalf-Of", "user11");
773        assertEquals("delegated user can read object", HttpStatus.SC_OK, getStatus(adminDelegatedGet));
774
775        final HttpGet adminUnauthorizedDelegatedGet = getObjMethod(targetPath);
776        setAuth(adminUnauthorizedDelegatedGet, "fedoraAdmin");
777        adminUnauthorizedDelegatedGet.addHeader("On-Behalf-Of", "fakeuser");
778        assertEquals("delegated fakeuser cannot read object", HttpStatus.SC_FORBIDDEN,
779                getStatus(adminUnauthorizedDelegatedGet));
780
781        final HttpGet adminDelegatedGet2 = getObjMethod(targetPath);
782        setAuth(adminDelegatedGet2, "fedoraAdmin");
783        adminDelegatedGet2.addHeader("On-Behalf-Of", "info:user/user2");
784        assertEquals("delegated user can read object", HttpStatus.SC_OK, getStatus(adminDelegatedGet2));
785
786        final HttpGet adminUnauthorizedDelegatedGet2 = getObjMethod(targetPath);
787        setAuth(adminUnauthorizedDelegatedGet2, "fedoraAdmin");
788        adminUnauthorizedDelegatedGet2.addHeader("On-Behalf-Of", "info:user/fakeuser");
789        assertEquals("delegated fakeuser cannot read object", HttpStatus.SC_FORBIDDEN,
790                getStatus(adminUnauthorizedDelegatedGet2));
791
792        // Now test with the system property in effect
793        rolesProvider.setUserBaseUri("info:user/");
794        rolesProvider.setGroupBaseUri("info:group/");
795
796        final HttpGet adminDelegatedGet3 = getObjMethod(targetPath);
797        setAuth(adminDelegatedGet3, "fedoraAdmin");
798        adminDelegatedGet3.addHeader("On-Behalf-Of", "info:user/user2");
799        assertEquals("delegated user can read object", HttpStatus.SC_OK, getStatus(adminDelegatedGet3));
800
801        final HttpGet adminUnauthorizedDelegatedGet3 = getObjMethod(targetPath);
802        setAuth(adminUnauthorizedDelegatedGet3, "fedoraAdmin");
803        adminUnauthorizedDelegatedGet3.addHeader("On-Behalf-Of", "info:user/fakeuser");
804        assertEquals("delegated fakeuser cannot read object", HttpStatus.SC_FORBIDDEN,
805                getStatus(adminUnauthorizedDelegatedGet3));
806    }
807
808    @Test
809    public void testAccessByUriToVersionedResources() throws IOException {
810        final String idVersionPath = "rest/versionResourceUri";
811        final String idVersionResource = ingestObj(idVersionPath);
812
813        ingestAcl("fedoraAdmin", "/acls/12/acl.ttl", idVersionResource + "/fcr:acl");
814
815        final HttpGet requestGet1 = getObjMethod(idVersionPath);
816        setAuth(requestGet1, "user12");
817        assertEquals("testuser can't read object", HttpStatus.SC_OK, getStatus(requestGet1));
818
819        final HttpPost requestPost1 = postObjMethod(idVersionPath + "/fcr:versions");
820        setAuth(requestPost1, "user12");
821        final String mementoLocation;
822        try (final CloseableHttpResponse response = execute(requestPost1)) {
823            assertEquals("Unable to create a new version", HttpStatus.SC_CREATED, getStatus(response));
824            mementoLocation = getLocation(response);
825        }
826
827        final HttpGet requestGet2 = new HttpGet(mementoLocation);
828        setAuth(requestGet2, "user12");
829        assertEquals("testuser can't read versioned object", HttpStatus.SC_OK, getStatus(requestGet2));
830    }
831
832    @Test
833    public void testAgentAsUri() throws IOException {
834        final String id = "/rest/" + getRandomUniqueId();
835        final String testObj = ingestObj(id);
836
837        logger.debug("Anonymous can read (has ACL:READ): {}", id);
838        final HttpGet requestGet1 = getObjMethod(id);
839        assertEquals(HttpStatus.SC_OK, getStatus(requestGet1));
840
841        logger.debug("Can username 'smith123' read {} (no ACL)", id);
842        final HttpGet requestGet2 = getObjMethod(id);
843        setAuth(requestGet2, "smith123");
844        assertEquals(HttpStatus.SC_OK, getStatus(requestGet2));
845
846        rolesProvider.setUserBaseUri("info:user/");
847        rolesProvider.setGroupBaseUri("info:group/");
848
849        logger.debug("Can username 'smith123' read {} (overridden system ACL)", id);
850        final HttpGet requestGet3 = getObjMethod(id);
851        setAuth(requestGet3, "smith123");
852        assertEquals(HttpStatus.SC_OK, getStatus(requestGet3));
853
854        logger.debug("Can username 'group123' read {} (overridden system ACL)", id);
855        final HttpGet requestGet4 = getObjMethod(id);
856        setAuth(requestGet4, "group123");
857        assertEquals(HttpStatus.SC_OK, getStatus(requestGet4));
858
859        rolesProvider.setUserBaseUri(null);
860        rolesProvider.setGroupBaseUri(null);
861
862        // Add ACL to object
863        ingestAcl("fedoraAdmin", "/acls/16/acl.ttl", testObj + "/fcr:acl");
864
865        logger.debug("Anonymous still can't read (ACL present)");
866        final HttpGet requestGet5 = getObjMethod(id);
867        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestGet5));
868
869        logger.debug("Can username 'smith123' read {} (ACL present, no system properties)", testObj);
870        final HttpGet requestGet6 = getObjMethod(id);
871        setAuth(requestGet6, "smith123");
872        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestGet6));
873
874        rolesProvider.setUserBaseUri("info:user/");
875        rolesProvider.setGroupBaseUri("info:group/");
876
877        logger.debug("Can username 'smith123' read {} (ACL, system properties present)", id);
878        final HttpGet requestGet7 = getObjMethod(id);
879        setAuth(requestGet7, "smith123");
880        assertEquals(HttpStatus.SC_OK, getStatus(requestGet7));
881
882        logger.debug("Can groupname 'group123' read {} (ACL, system properties present)", id);
883        final HttpGet requestGet8 = getObjMethod(id);
884        setAuth(requestGet8, "group123");
885        assertEquals(HttpStatus.SC_OK, getStatus(requestGet8));
886    }
887
888    @Test
889    public void testRegisterNamespace() throws IOException {
890        final String testObj = ingestObj("/rest/test_namespace");
891        ingestAcl("fedoraAdmin", "/acls/13/acl.ttl", testObj + "/fcr:acl");
892
893        final String id = "/rest/test_namespace/" + getRandomUniqueId();
894        ingestObj(id);
895
896        final HttpPatch patchReq = patchObjMethod(id);
897        setAuth(patchReq, "user13");
898        patchReq.addHeader("Content-type", "application/sparql-update");
899        patchReq.setEntity(new StringEntity("PREFIX novel: <info://" + getRandomUniqueId() + ">\n"
900                + "INSERT DATA { <> novel:value 'test' }"));
901        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(patchReq));
902    }
903
904    @Test
905    public void testRegisterNodeType() throws IOException {
906        final String testObj = ingestObj("/rest/test_nodetype");
907        ingestAcl("fedoraAdmin", "/acls/14/acl.ttl", testObj + "/fcr:acl");
908
909        final String id = "/rest/test_nodetype/" + getRandomUniqueId();
910        ingestObj(id);
911
912        final HttpPatch patchReq = patchObjMethod(id);
913        setAuth(patchReq, "user14");
914        patchReq.addHeader("Content-type", "application/sparql-update");
915        patchReq.setEntity(new StringEntity("PREFIX dc: <http://purl.org/dc/elements/1.1/>\n"
916                + "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n"
917                + "INSERT DATA { <> rdf:type dc:type }"));
918        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(patchReq));
919    }
920
921
922    @Test
923    public void testDeletePropertyAsUser() throws IOException {
924        final String testObj = ingestObj("/rest/test_delete");
925        ingestAcl("fedoraAdmin", "/acls/15/acl.ttl", testObj + "/fcr:acl");
926
927        final String id = "/rest/test_delete/" + getRandomUniqueId();
928        ingestObj(id);
929
930        HttpPatch patchReq = patchObjMethod(id);
931        setAuth(patchReq, "user15");
932        patchReq.addHeader("Content-type", "application/sparql-update");
933        patchReq.setEntity(new StringEntity("PREFIX dc: <http://purl.org/dc/elements/1.1/>\n"
934                + "INSERT DATA { <> dc:title 'title' . " +
935                "                <> dc:rights 'rights' . }"));
936        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(patchReq));
937
938        patchReq = patchObjMethod(id);
939        setAuth(patchReq, "user15");
940        patchReq.addHeader("Content-type", "application/sparql-update");
941        patchReq.setEntity(new StringEntity("PREFIX dc: <http://purl.org/dc/elements/1.1/>\n"
942                + "DELETE { <> dc:title ?any . } WHERE { <> dc:title ?any . }"));
943        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(patchReq));
944
945        patchReq = patchObjMethod(id);
946        setAuth(patchReq, "notUser15");
947        patchReq.addHeader("Content-type", "application/sparql-update");
948        patchReq.setEntity(new StringEntity("PREFIX dc: <http://purl.org/dc/elements/1.1/>\n"
949                + "DELETE { <> dc:rights ?any . } WHERE { <> dc:rights ?any . }"));
950        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(patchReq));
951    }
952
953    @Test
954    public void testHeadWithReadOnlyUser() throws IOException {
955        final String testObj = ingestObj("/rest/test_head");
956        ingestAcl("fedoraAdmin", "/acls/19/acl.ttl", testObj + "/fcr:acl");
957
958        final HttpHead headReq = new HttpHead(testObj);
959        setAuth(headReq, "user19");
960        assertEquals(HttpStatus.SC_OK, getStatus(headReq));
961    }
962
963    @Test
964    public void testOptionsWithReadOnlyUser() throws IOException {
965        final String testObj = ingestObj("/rest/test_options");
966        ingestAcl("fedoraAdmin", "/acls/20/acl.ttl", testObj + "/fcr:acl");
967
968        final HttpOptions optionsReq = new HttpOptions(testObj);
969        setAuth(optionsReq, "user20");
970        assertEquals(HttpStatus.SC_OK, getStatus(optionsReq));
971    }
972
973    private static HttpResponse HEAD(final String requestURI) throws IOException {
974        return HEAD(requestURI, "fedoraAdmin");
975    }
976
977    private static HttpResponse HEAD(final String requestURI, final String username) throws IOException {
978        final HttpHead req = new HttpHead(requestURI);
979        setAuth(req, username);
980        return execute(req);
981    }
982
983    private static HttpResponse PUT(final String requestURI) throws IOException {
984        return PUT(requestURI, "fedoraAdmin");
985    }
986
987    private static HttpResponse PUT(final String requestURI, final String username) throws IOException {
988        final HttpPut req = new HttpPut(requestURI);
989        setAuth(req, username);
990        return execute(req);
991    }
992
993    private static HttpResponse DELETE(final String requestURI, final String username) throws IOException {
994        final HttpDelete req = new HttpDelete(requestURI);
995        setAuth(req, username);
996        return execute(req);
997    }
998
999    private static HttpResponse GET(final String requestURI, final String username) throws IOException {
1000        final HttpGet req = new HttpGet(requestURI);
1001        setAuth(req, username);
1002        return execute(req);
1003    }
1004
1005    private static HttpResponse PATCH(final String requestURI, final HttpEntity body, final String username)
1006            throws IOException {
1007        final HttpPatch req = new HttpPatch(requestURI);
1008        setAuth(req, username);
1009        if (body != null) {
1010            req.setEntity(body);
1011        }
1012        return execute(req);
1013    }
1014
1015    private static String getLink(final HttpResponse res) {
1016        for (final Header h : res.getHeaders("Link")) {
1017            final HeaderElement link = h.getElements()[0];
1018            for (final NameValuePair param : link.getParameters()) {
1019                if (param.getName().equals("rel") && param.getValue().equals("acl")) {
1020                    return link.getName().replaceAll("^<|>$", "");
1021                }
1022            }
1023        }
1024        return null;
1025    }
1026
1027    private String ingestObjWithACL(final String path, final String aclResourcePath) throws IOException {
1028        final String newURI = ingestObj(path);
1029        final HttpResponse res = HEAD(newURI);
1030        final String aclURI = getLink(res);
1031
1032        logger.debug("Creating ACL at {}", aclURI);
1033        ingestAcl("fedoraAdmin", aclResourcePath, aclURI);
1034
1035        return newURI;
1036    }
1037
1038    @Test
1039    public void testControl() throws IOException {
1040        final String controlObj = ingestObjWithACL("/rest/control", "/acls/25/control.ttl");
1041        final String readwriteObj = ingestObjWithACL("/rest/readwrite", "/acls/25/readwrite.ttl");
1042
1043        final String rwChildACL = getLink(PUT(readwriteObj + "/child"));
1044        assertEquals(SC_FORBIDDEN, getStatus(HEAD(rwChildACL, "testuser")));
1045        assertEquals(SC_FORBIDDEN, getStatus(GET(rwChildACL, "testuser")));
1046        assertEquals(SC_FORBIDDEN, getStatus(PUT(rwChildACL, "testuser")));
1047        assertEquals(SC_FORBIDDEN, getStatus(DELETE(rwChildACL, "testuser")));
1048
1049        final String controlChildACL = getLink(PUT(controlObj + "/child"));
1050        assertEquals(SC_NOT_FOUND, getStatus(HEAD(controlChildACL, "testuser")));
1051        assertEquals(SC_NOT_FOUND, getStatus(GET(controlChildACL, "testuser")));
1052
1053        ingestAcl("testuser", "/acls/25/child-control.ttl", controlChildACL);
1054        final StringEntity sparqlUpdate = new StringEntity(
1055                "PREFIX acl: <http://www.w3.org/ns/auth/acl#>  INSERT { <#restricted> acl:mode acl:Read } WHERE { }",
1056                ContentType.create("application/sparql-update"));
1057        assertEquals(SC_NO_CONTENT, getStatus(PATCH(controlChildACL, sparqlUpdate, "testuser")));
1058
1059        assertEquals(SC_NO_CONTENT, getStatus(DELETE(controlChildACL, "testuser")));
1060    }
1061
1062    @Test
1063    public void testAppendOnlyToContainer() throws IOException {
1064        final String testObj = ingestObj("/rest/test_append");
1065        ingestAcl("fedoraAdmin", "/acls/23/acl.ttl", testObj + "/fcr:acl");
1066        final String username = "user23";
1067
1068        final HttpOptions optionsReq = new HttpOptions(testObj);
1069        setAuth(optionsReq, username);
1070        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(optionsReq));
1071
1072        final HttpHead headReq = new HttpHead(testObj);
1073        setAuth(headReq, username);
1074        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(headReq));
1075
1076        final HttpGet getReq = new HttpGet(testObj);
1077        setAuth(getReq, username);
1078        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(getReq));
1079
1080        final HttpPut putReq = new HttpPut(testObj);
1081        setAuth(putReq, username);
1082        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(putReq));
1083
1084        final HttpDelete deleteReq = new HttpDelete(testObj);
1085        setAuth(deleteReq, username);
1086        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(deleteReq));
1087
1088        final HttpPost postReq = new HttpPost(testObj);
1089        setAuth(postReq, username);
1090        assertEquals(HttpStatus.SC_CREATED, getStatus(postReq));
1091
1092        final String[] legalSPARQLQueries = new String[] {
1093            "INSERT DATA { <> <http://purl.org/dc/terms/title> \"Test23\" . }",
1094            "INSERT { <> <http://purl.org/dc/terms/alternative> \"Test XXIII\" . } WHERE {}",
1095            "DELETE {} INSERT { <> <http://purl.org/dc/terms/description> \"Test append only\" . } WHERE {}"
1096        };
1097        for (final String query : legalSPARQLQueries) {
1098            final HttpPatch patchReq = new HttpPatch(testObj);
1099            setAuth(patchReq, username);
1100            patchReq.setEntity(new StringEntity(query));
1101            patchReq.setHeader("Content-Type", "application/sparql-update");
1102            logger.debug("Testing SPARQL update: {}", query);
1103            assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(patchReq));
1104        }
1105
1106        final String[] illegalSPARQLQueries = new String[] {
1107            "DELETE DATA { <> <http://purl.org/dc/terms/title> \"Test23\" . }",
1108            "DELETE { <> <http://purl.org/dc/terms/alternative> \"Test XXIII\" . } WHERE {}",
1109            "DELETE { <> <http://purl.org/dc/terms/description> \"Test append only\" . } INSERT {} WHERE {}"
1110        };
1111        for (final String query : illegalSPARQLQueries) {
1112            final HttpPatch patchReq = new HttpPatch(testObj);
1113            setAuth(patchReq, username);
1114            patchReq.setEntity(new StringEntity(query));
1115            patchReq.setHeader("Content-Type", "application/sparql-update");
1116            logger.debug("Testing SPARQL update: {}", query);
1117            assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(patchReq));
1118        }
1119        final String[] allowedDeleteSPARQLQueries = new String[] {
1120            "DELETE DATA {}",
1121            "DELETE { } WHERE {}",
1122            "DELETE { } INSERT {} WHERE {}"
1123        };
1124        for (final String query : allowedDeleteSPARQLQueries) {
1125            final HttpPatch patchReq = new HttpPatch(testObj);
1126            setAuth(patchReq, username);
1127            patchReq.setEntity(new StringEntity(query));
1128            patchReq.setHeader("Content-Type", "application/sparql-update");
1129            logger.debug("Testing SPARQL update: {}", query);
1130            assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(patchReq));
1131        }
1132
1133    }
1134
1135    @Test
1136    public void testAppendOnlyToBinary() throws IOException {
1137        final String testObj = ingestBinary("/rest/test_append_binary", new StringEntity("foo"));
1138        ingestAcl("fedoraAdmin", "/acls/24/acl.ttl", testObj + "/fcr:acl");
1139        final String username = "user24";
1140
1141        final HttpOptions optionsReq = new HttpOptions(testObj);
1142        setAuth(optionsReq, username);
1143        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(optionsReq));
1144
1145        final HttpHead headReq = new HttpHead(testObj);
1146        setAuth(headReq, username);
1147        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(headReq));
1148
1149        final HttpGet getReq = new HttpGet(testObj);
1150        setAuth(getReq, username);
1151        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(getReq));
1152
1153        final HttpPut putReq = new HttpPut(testObj);
1154        setAuth(putReq, username);
1155        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(putReq));
1156
1157        final HttpDelete deleteReq = new HttpDelete(testObj);
1158        setAuth(deleteReq, username);
1159        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(deleteReq));
1160
1161        final HttpPost postReq = new HttpPost(testObj);
1162        setAuth(postReq, username);
1163        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(postReq));
1164    }
1165
1166    @Test
1167    public void testFoafAgent() throws IOException {
1168        final String path = ingestObj("/rest/foaf-agent");
1169        ingestAcl("fedoraAdmin", "/acls/26/foaf-agent.ttl", path + "/fcr:acl");
1170        final String username = "user1";
1171
1172        final HttpGet req = new HttpGet(path);
1173
1174        //NB: Actually no authentication headers should be set for this test
1175        //since the point of foaf:Agent is to allow unauthenticated access for everyone.
1176        //However at this time the test integration test server requires callers to
1177        //authenticate.
1178        setAuth(req, username);
1179
1180        assertEquals(HttpStatus.SC_OK, getStatus(req));
1181    }
1182
1183    @Test
1184    public void testAuthenticatedAgent() throws IOException {
1185        final String path = ingestObj("/rest/authenticated-agent");
1186        ingestAcl("fedoraAdmin", "/acls/26/authenticated-agent.ttl", path + "/fcr:acl");
1187        final String username = "user1";
1188
1189        final HttpGet darkReq = new HttpGet(path);
1190        setAuth(darkReq, username);
1191        assertEquals(HttpStatus.SC_OK, getStatus(darkReq));
1192    }
1193
1194    @Test
1195    public void testAgentGroupWithHashUris() throws Exception {
1196        ingestTurtleResource("fedoraAdmin", "/acls/agent-group-list.ttl",
1197                             serverAddress + "/rest/agent-group-list");
1198        //check that the authorized are authorized.
1199        final String authorized = ingestObj("/rest/agent-group-with-hash-uri-authorized");
1200        ingestAcl("fedoraAdmin", "/acls/agent-group-with-hash-uri-authorized.ttl", authorized + "/fcr:acl");
1201
1202        final HttpGet getAuthorized = new HttpGet(authorized);
1203        setAuth(getAuthorized, "testuser");
1204        assertEquals(HttpStatus.SC_OK, getStatus(getAuthorized));
1205
1206        //check that the unauthorized are unauthorized.
1207        final String unauthorized = ingestObj("/rest/agent-group-with-hash-uri-unauthorized");
1208        ingestAcl("fedoraAdmin", "/acls/agent-group-with-hash-uri-unauthorized.ttl", unauthorized + "/fcr:acl");
1209
1210        final HttpGet getUnauthorized = new HttpGet(unauthorized);
1211        setAuth(getUnauthorized, "testuser");
1212        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(getUnauthorized));
1213    }
1214
1215    @Test
1216    public void testAgentGroupWithMembersAsURIs() throws Exception {
1217        rolesProvider.setUserBaseUri("http://example.com/");
1218        ingestTurtleResource("fedoraAdmin", "/acls/agent-group-list-with-member-uris.ttl",
1219                             serverAddress + "/rest/agent-group-list-with-member-uris");
1220        final String authorized = ingestObj("/rest/agent-group-with-vcard-member-as-uri");
1221        ingestAcl("fedoraAdmin", "/acls/agent-group-with-vcard-member-as-uri.ttl", authorized + "/fcr:acl");
1222        //check that test user is authorized to write
1223        final HttpPut childPut = new HttpPut(authorized + "/child");
1224        setAuth(childPut, "testuser");
1225        assertEquals(HttpStatus.SC_CREATED, getStatus(childPut));
1226    }
1227
1228    @Test
1229    public void testAgentGroup() throws Exception {
1230        ingestTurtleResource("fedoraAdmin", "/acls/agent-group-list-flat.ttl",
1231                             serverAddress + "/rest/agent-group-list-flat");
1232        //check that the authorized are authorized.
1233        final String flat = ingestObj("/rest/agent-group-flat");
1234        ingestAcl("fedoraAdmin", "/acls/agent-group-flat.ttl", flat + "/fcr:acl");
1235
1236        final HttpGet getFlat = new HttpGet(flat);
1237        setAuth(getFlat, "testuser");
1238        assertEquals(HttpStatus.SC_OK, getStatus(getFlat));
1239    }
1240
1241    @Test
1242    public void testAclAppendPermissions() throws Exception {
1243        final String testObj = ingestBinary("/rest/test-read-append", new StringEntity("foo"));
1244        ingestAcl("fedoraAdmin", "/acls/27/read-append.ttl", testObj + "/fcr:acl");
1245        final String username = "user27";
1246
1247        final HttpOptions optionsReq = new HttpOptions(testObj);
1248        setAuth(optionsReq, username);
1249        assertEquals(HttpStatus.SC_OK, getStatus(optionsReq));
1250
1251        final HttpHead headReq = new HttpHead(testObj);
1252        setAuth(headReq, username);
1253        assertEquals(HttpStatus.SC_OK, getStatus(headReq));
1254
1255        final HttpGet getReq = new HttpGet(testObj);
1256        setAuth(getReq, username);
1257        final String descriptionUri;
1258        try (final CloseableHttpResponse response = execute(getReq)) {
1259            assertEquals(HttpStatus.SC_OK, getStatus(response));
1260            descriptionUri = Arrays.stream(response.getHeaders("Link"))
1261                    .flatMap(header -> Arrays.stream(header.getValue().split(","))).map(linkStr -> Link.valueOf(
1262                            linkStr))
1263                    .filter(link -> link.getRels().contains("describedby")).map(link -> link.getUri().toString())
1264                    .findFirst().orElse(null);
1265        }
1266
1267
1268        final HttpPut putReq = new HttpPut(testObj);
1269        setAuth(putReq, username);
1270        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(putReq));
1271
1272        final HttpDelete deleteReq = new HttpDelete(testObj);
1273        setAuth(deleteReq, username);
1274        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(deleteReq));
1275
1276        final HttpPost postReq = new HttpPost(testObj);
1277        setAuth(postReq, username);
1278        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(postReq));
1279
1280        if (descriptionUri != null) {
1281            final HttpOptions optionsDescReq = new HttpOptions(descriptionUri);
1282            setAuth(optionsDescReq, username);
1283            assertEquals(HttpStatus.SC_OK, getStatus(optionsDescReq));
1284
1285            final HttpHead headDescReq = new HttpHead(descriptionUri);
1286            setAuth(headDescReq, username);
1287            assertEquals(HttpStatus.SC_OK, getStatus(headDescReq));
1288
1289            final HttpGet getDescReq = new HttpGet(descriptionUri);
1290            setAuth(getDescReq, username);
1291            assertEquals(HttpStatus.SC_OK, getStatus(getDescReq));
1292
1293            final HttpPut putDescReq = new HttpPut(descriptionUri);
1294            setAuth(putDescReq, username);
1295            assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(putDescReq));
1296
1297            final HttpDelete deleteDescReq = new HttpDelete(descriptionUri);
1298            setAuth(deleteDescReq, username);
1299            assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(deleteDescReq));
1300
1301            final HttpPost postDescReq = new HttpPost(descriptionUri);
1302            setAuth(postDescReq, username);
1303            assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(postDescReq));
1304        }
1305    }
1306
1307    @Test
1308    public void testCreateAclWithAccessToClassForBinary() throws Exception {
1309        final String id = getRandomUniqueId();
1310        final String subjectUri = serverAddress + id;
1311        ingestObj(subjectUri);
1312        ingestAcl("fedoraAdmin", "/acls/agent-access-to-class.ttl", subjectUri + "/fcr:acl");
1313
1314        final String binaryUri = ingestBinary("/rest/" + id + "/binary", new StringEntity("foo"));
1315
1316        final HttpHead headBinary = new HttpHead(binaryUri);
1317        setAuth(headBinary, "testuser");
1318        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(headBinary));
1319
1320        final HttpHead headDesc = new HttpHead(binaryUri + "/fcr:metadata");
1321        setAuth(headDesc, "testuser");
1322        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(headDesc));
1323
1324        // Add type to binary
1325        final HttpPatch requestPatch = patchObjMethod(id + "/binary/fcr:metadata");
1326        setAuth(requestPatch, "fedoraAdmin");
1327        final String sparql = "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> \n" +
1328                "PREFIX foaf: <http://xmlns.com/foaf/0.1/>  \n" +
1329                "INSERT { <> rdf:type foaf:Document } WHERE {}";
1330        requestPatch.setEntity(new StringEntity(sparql));
1331        requestPatch.setHeader("Content-type", "application/sparql-update");
1332        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(requestPatch));
1333
1334        final HttpHead headBinary2 = new HttpHead(binaryUri);
1335        setAuth(headBinary2, "testuser");
1336        assertEquals(HttpStatus.SC_OK, getStatus(headBinary2));
1337
1338        final HttpHead headDesc2 = new HttpHead(binaryUri + "/fcr:metadata");
1339        setAuth(headDesc2, "testuser");
1340        assertEquals(HttpStatus.SC_OK, getStatus(headDesc2));
1341    }
1342
1343    @Ignore("Until FCREPO-3310 and FCREPO-3311 are resolved")
1344    @Test
1345    public void testIndirectRelationshipForbidden() throws IOException {
1346        final String targetResource = "/rest/" + getRandomUniqueId();
1347        final String writeableResource = "/rest/" + getRandomUniqueId();
1348        final String username = "user28";
1349
1350        final String targetUri = ingestObj(targetResource);
1351
1352        final String readonlyString = "@prefix acl: <http://www.w3.org/ns/auth/acl#> .\n" +
1353                "<#readauthz> a acl:Authorization ;\n" +
1354                "   acl:agent \"" + username + "\" ;\n" +
1355                "   acl:mode acl:Read ;\n" +
1356                "   acl:accessTo <" + targetResource + "> .";
1357        ingestAclString(targetUri, readonlyString, "fedoraAdmin");
1358
1359        // User can read target resource.
1360        final HttpGet get1 = getObjMethod(targetResource);
1361        setAuth(get1, username);
1362        assertEquals(HttpStatus.SC_OK, getStatus(get1));
1363
1364        // User can't patch target resource.
1365        final String patch = "INSERT DATA { <> <http://purl.org/dc/elements/1.1/title> \"Changed it\"}";
1366        final HttpEntity patchEntity = new StringEntity(patch, sparqlContentType);
1367        try (final CloseableHttpResponse resp = (CloseableHttpResponse) PATCH(targetUri, patchEntity,
1368                username)) {
1369            assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(resp));
1370        }
1371
1372        // Make a user writable container.
1373        final String writeableUri = ingestObj(writeableResource);
1374        final String writeableAcl = "@prefix acl: <http://www.w3.org/ns/auth/acl#> .\n" +
1375                "<#writeauth> a acl:Authorization ;\n" +
1376                "   acl:agent \"" + username + "\" ;\n" +
1377                "   acl:mode acl:Read, acl:Write ;\n" +
1378                "   acl:accessTo <" + writeableResource + "> ;\n" +
1379                "   acl:default <" + writeableResource + "> .";
1380        ingestAclString(writeableUri, writeableAcl, "fedoraAdmin");
1381
1382        // Ensure we can still POST/PUT to writeable resource.
1383        testCanWrite(writeableResource, username);
1384
1385        // Try to create indirect container referencing readonly resource with POST.
1386        final HttpPost userPost = postObjMethod(writeableResource);
1387        setAuth(userPost, username);
1388        userPost.addHeader("Link", "<" + INDIRECT_CONTAINER.toString() + ">; rel=type");
1389        final String indirect = "@prefix ldp: <http://www.w3.org/ns/ldp#> .\n" +
1390                "@prefix example: <http://www.example.org/example1#> .\n" +
1391                "@prefix dc: <http://purl.org/dc/elements/1.1/> .\n" +
1392                "<> ldp:insertedContentRelation <http://example.org/test#something> ;\n" +
1393                "ldp:membershipResource <" + targetResource + "> ;\n" +
1394                "ldp:hasMemberRelation <http://example.org/test#predicateToCreate> ;\n" +
1395                "dc:title \"The indirect container\" .";
1396        final HttpEntity indirectEntity = new StringEntity(indirect, turtleContentType);
1397        userPost.setEntity(indirectEntity);
1398        userPost.setHeader(CONTENT_TYPE, "text/turtle");
1399        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(userPost));
1400
1401        // Try to create indirect container referencing readonly resource with PUT.
1402        final String indirectString = getRandomUniqueId();
1403        final HttpPut userPut = putObjMethod(writeableResource + "/" + indirectString);
1404        setAuth(userPut, username);
1405        userPut.addHeader("Link", "<" + INDIRECT_CONTAINER.toString() + ">; rel=type");
1406        userPut.setEntity(indirectEntity);
1407        userPut.setHeader(CONTENT_TYPE, "text/turtle");
1408        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(userPut));
1409
1410        // Create an user writeable resource.
1411        final HttpPost targetPost = postObjMethod(writeableResource);
1412        setAuth(targetPost, username);
1413        final String tempTarget;
1414        try (final CloseableHttpResponse resp = execute(targetPost)) {
1415            assertEquals(HttpStatus.SC_CREATED, getStatus(resp));
1416            tempTarget = getLocation(resp);
1417        }
1418
1419        // Try to create indirect container referencing an available resource.
1420        final String indirect_ok = "@prefix ldp: <http://www.w3.org/ns/ldp#> .\n" +
1421                "@prefix example: <http://www.example.org/example1#> .\n" +
1422                "@prefix dc: <http://purl.org/dc/elements/1.1/> .\n" +
1423                "<> ldp:insertedContentRelation <http://example.org/test#something> ;\n" +
1424                "ldp:membershipResource <" + tempTarget + "> ;\n" +
1425                "ldp:hasMemberRelation <http://example.org/test#predicateToCreate> ;\n" +
1426                "dc:title \"The indirect container\" .";
1427        final HttpPost userPatchPost = postObjMethod(writeableResource);
1428        setAuth(userPatchPost, username);
1429        userPatchPost.addHeader("Link", "<" + INDIRECT_CONTAINER.toString() + ">; rel=type");
1430        final HttpEntity in_ok = new StringEntity(indirect_ok, turtleContentType);
1431        userPatchPost.setEntity(in_ok);
1432        userPatchPost.setHeader(CONTENT_TYPE, "text/turtle");
1433        final String indirectUri;
1434        try (final CloseableHttpResponse resp = execute(userPatchPost)) {
1435            assertEquals(HttpStatus.SC_CREATED, getStatus(resp));
1436            indirectUri = getLocation(resp);
1437        }
1438
1439        // Then PATCH to the readonly resource.
1440        final HttpPatch patchIndirect = new HttpPatch(indirectUri);
1441        setAuth(patchIndirect, username);
1442        final String patch_text = "prefix ldp: <http://www.w3.org/ns/ldp#> \n" +
1443                "DELETE { <> ldp:membershipResource ?o } \n" +
1444                "INSERT { <> ldp:membershipResource <" + targetResource + "> } \n" +
1445                "WHERE { <> ldp:membershipResource ?o }";
1446        patchIndirect.setEntity(new StringEntity(patch_text, sparqlContentType));
1447        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(patchIndirect));
1448
1449        // Delete the ldp:membershipRelation and add it with INSERT DATA {}
1450        final String patch_delete_relation = "prefix ldp: <http://www.w3.org/ns/ldp#> \n" +
1451                "DELETE DATA { <> ldp:membershipResource <" + tempTarget + "> }";
1452        final HttpPatch patchIndirect2 = new HttpPatch(indirectUri);
1453        setAuth(patchIndirect2, username);
1454        patchIndirect2.setEntity(new StringEntity(patch_delete_relation, sparqlContentType));
1455        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(patchIndirect2));
1456
1457        final String patch_insert_relation = "prefix ldp: <http://www.w3.org/ns/ldp#> \n" +
1458                "INSERT DATA { <> ldp:membershipResource <" + targetResource + "> }";
1459        final HttpPatch patchIndirect3 = new HttpPatch(indirectUri);
1460        setAuth(patchIndirect3, username);
1461        patchIndirect3.setEntity(new StringEntity(patch_insert_relation, sparqlContentType));
1462        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(patchIndirect3));
1463
1464        // Patch the indirect to the readonly target as admin
1465        final HttpPatch patchAsAdmin = new HttpPatch(indirectUri);
1466        setAuth(patchAsAdmin, "fedoraAdmin");
1467        patchAsAdmin.setEntity(new StringEntity(patch_text, sparqlContentType));
1468        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(patchAsAdmin));
1469
1470        // Ensure the patching happened.
1471        final HttpGet verifyGet = new HttpGet(indirectUri);
1472        setAuth(verifyGet, "fedoraAdmin");
1473        try (final CloseableHttpResponse response = execute(verifyGet)) {
1474            final CloseableDataset dataset = getDataset(response);
1475            final DatasetGraph graph = dataset.asDatasetGraph();
1476            assertTrue("Can't find " + targetUri + " in graph",
1477                    graph.contains(
1478                            Node.ANY,
1479                            NodeFactory.createURI(indirectUri),
1480                            MEMBERSHIP_RESOURCE.asNode(),
1481                            NodeFactory.createURI(targetUri)
1482                    )
1483            );
1484        }
1485
1486        // Try to POST a child as user
1487        final HttpPost postChild = new HttpPost(indirectUri);
1488        final String postTarget = "@prefix ldp: <http://www.w3.org/ns/ldp#> .\n" +
1489                "@prefix test: <http://example.org/test#> .\n\n" +
1490                "<> test:something <" + tempTarget + "> .";
1491        final HttpEntity putPostChild = new StringEntity(postTarget, turtleContentType);
1492        setAuth(postChild, username);
1493        postChild.setEntity(putPostChild);
1494        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(postChild));
1495
1496        // Try to PUT a child as user
1497        final String id = getRandomUniqueId();
1498        final HttpPut putChild = new HttpPut(indirectUri + "/" + id);
1499        setAuth(putChild, username);
1500        putChild.setEntity(putPostChild);
1501        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(putChild));
1502
1503        // Put the child as Admin
1504        setAuth(putChild, "fedoraAdmin");
1505        assertEquals(HttpStatus.SC_CREATED, getStatus(putChild));
1506
1507        // Try to delete the child as user
1508        final HttpDelete deleteChild = new HttpDelete(indirectUri + "/" + id);
1509        setAuth(deleteChild, username);
1510        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(deleteChild));
1511
1512        // Try to delete the indirect container
1513        final HttpDelete deleteIndirect = new HttpDelete(indirectUri);
1514        setAuth(deleteIndirect, username);
1515        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(deleteIndirect));
1516
1517        // Ensure we can still write to the writeable resource.
1518        testCanWrite(writeableResource, username);
1519
1520    }
1521
1522    @Test
1523    public void testIndirectRelationshipOK() throws IOException {
1524        final String targetResource = "/rest/" + getRandomUniqueId();
1525        final String writeableResource = "/rest/" + getRandomUniqueId();
1526        final String username = "user28";
1527
1528        final String targetUri = ingestObj(targetResource);
1529
1530        final String readwriteString = "@prefix acl: <http://www.w3.org/ns/auth/acl#> .\n" +
1531                "<#readauthz> a acl:Authorization ;\n" +
1532                "   acl:agent \"" + username + "\" ;\n" +
1533                "   acl:mode acl:Read, acl:Write ;\n" +
1534                "   acl:accessTo <" + targetResource + "> .";
1535        ingestAclString(targetUri, readwriteString, "fedoraAdmin");
1536
1537        // User can read target resource.
1538        final HttpGet get1 = getObjMethod(targetResource);
1539        setAuth(get1, username);
1540        assertEquals(HttpStatus.SC_OK, getStatus(get1));
1541
1542        // User can patch target resource.
1543        final String patch = "INSERT DATA { <> <http://purl.org/dc/elements/1.1/title> \"Changed it\"}";
1544        final HttpEntity patchEntity = new StringEntity(patch, sparqlContentType);
1545        try (final CloseableHttpResponse resp = (CloseableHttpResponse) PATCH(targetUri, patchEntity,
1546                username)) {
1547            assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(resp));
1548        }
1549
1550        // Make a user writable container.
1551        final String writeableUri = ingestObj(writeableResource);
1552        final String writeableAcl = "@prefix acl: <http://www.w3.org/ns/auth/acl#> .\n" +
1553                "<#writeauth> a acl:Authorization ;\n" +
1554                "   acl:agent \"" + username + "\" ;\n" +
1555                "   acl:mode acl:Read, acl:Write ;\n" +
1556                "   acl:accessTo <" + writeableResource + "> ;\n" +
1557                "   acl:default <" + writeableResource + "> .";
1558        ingestAclString(writeableUri, writeableAcl, "fedoraAdmin");
1559
1560        // Ensure we can write to the writeable resource.
1561        testCanWrite(writeableResource, username);
1562
1563        // Try to create indirect container referencing writeable resource with POST.
1564        final HttpPost userPost = postObjMethod(writeableResource);
1565        setAuth(userPost, username);
1566        userPost.addHeader("Link", "<" + INDIRECT_CONTAINER.toString() + ">; rel=type");
1567        final String indirect = "@prefix ldp: <http://www.w3.org/ns/ldp#> .\n" +
1568                "@prefix test: <http://example.org/test#> .\n\n" +
1569                "<> ldp:insertedContentRelation test:something ;" +
1570                "ldp:membershipResource <" + targetResource + "> ;" +
1571                "ldp:hasMemberRelation test:predicateToCreate .";
1572        final HttpEntity indirectEntity = new StringEntity(indirect, turtleContentType);
1573        userPost.setEntity(new StringEntity(indirect, turtleContentType));
1574        userPost.setHeader("Content-type", "text/turtle");
1575        assertEquals(HttpStatus.SC_CREATED, getStatus(userPost));
1576
1577        // Try to create indirect container referencing writeable resource with PUT.
1578        final String indirectString = getRandomUniqueId();
1579        final HttpPut userPut = putObjMethod(writeableResource + "/" + indirectString);
1580        setAuth(userPut, username);
1581        userPut.addHeader("Link", "<" + INDIRECT_CONTAINER.toString() + ">; rel=type");
1582        userPut.setEntity(indirectEntity);
1583        userPut.setHeader("Content-type", "text/turtle");
1584        assertEquals(HttpStatus.SC_CREATED, getStatus(userPut));
1585
1586        // Create an user writeable resource.
1587        final HttpPost targetPost = postObjMethod(writeableResource);
1588        setAuth(targetPost, username);
1589        final String tempTarget;
1590        try (final CloseableHttpResponse resp = execute(targetPost)) {
1591            assertEquals(HttpStatus.SC_CREATED, getStatus(resp));
1592            tempTarget = getLocation(resp);
1593        }
1594
1595        // Try to create indirect container referencing an available resource.
1596        final String indirect_ok = "@prefix ldp: <http://www.w3.org/ns/ldp#> .\n" +
1597                "@prefix test: <http://example.org/test#> .\n\n" +
1598                "<> ldp:insertedContentRelation test:something ;" +
1599                "ldp:membershipResource <" + tempTarget + "> ;" +
1600                "ldp:hasMemberRelation test:predicateToCreate .";
1601        final HttpPost userPatchPost = postObjMethod(writeableResource);
1602        setAuth(userPatchPost, username);
1603        userPatchPost.addHeader("Link", "<" + INDIRECT_CONTAINER.toString() + ">; rel=type");
1604        userPatchPost.setEntity(new StringEntity(indirect_ok, turtleContentType));
1605        userPatchPost.setHeader("Content-type", "text/turtle");
1606        final String indirectUri;
1607        try (final CloseableHttpResponse resp = execute(userPatchPost)) {
1608            assertEquals(HttpStatus.SC_CREATED, getStatus(resp));
1609            indirectUri = getLocation(resp);
1610        }
1611
1612        // Then PATCH to the writeable resource.
1613        final HttpPatch patchIndirect = new HttpPatch(indirectUri);
1614        setAuth(patchIndirect, username);
1615        final String patch_text = "prefix ldp: <http://www.w3.org/ns/ldp#> \n" +
1616                "DELETE { <> ldp:membershipResource ?o } \n" +
1617                "INSERT { <> ldp:membershipResource <" + targetResource + "> } \n" +
1618                "WHERE { <> ldp:membershipResource ?o }";
1619        patchIndirect.setEntity(new StringEntity(patch_text, sparqlContentType));
1620        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(patchIndirect));
1621
1622        // Delete the ldp:membershipRelation and add it back
1623        final String patch_delete_relation = "prefix ldp: <http://www.w3.org/ns/ldp#> \n" +
1624                "DELETE DATA { <> ldp:membershipResource <" + targetResource + "> }";
1625        final HttpPatch patchIndirect2 = new HttpPatch(indirectUri);
1626        setAuth(patchIndirect2, username);
1627        patchIndirect2.setEntity(new StringEntity(patch_delete_relation, sparqlContentType));
1628        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(patchIndirect2));
1629
1630        // Cannot insert membershipResource without deleting the default value
1631        final String patch_insert_relation = "prefix ldp: <http://www.w3.org/ns/ldp#> \n" +
1632                "DELETE { <> ldp:membershipResource ?o } \n" +
1633                "INSERT { <> ldp:membershipResource <" + targetResource + "> } \n" +
1634                "WHERE { <> ldp:membershipResource ?o }";
1635        final HttpPatch patchIndirect3 = new HttpPatch(indirectUri);
1636        setAuth(patchIndirect3, username);
1637        patchIndirect3.setEntity(new StringEntity(patch_insert_relation, sparqlContentType));
1638        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(patchIndirect3));
1639
1640        // Ensure we can still write to the writeable resource.
1641        testCanWrite(writeableResource, username);
1642
1643    }
1644
1645    @Ignore("Until FCREPO-3310 and FCREPO-3311 are resolved")
1646    @Test
1647    public void testDirectRelationshipForbidden() throws IOException {
1648        final String targetResource = "/rest/" + getRandomUniqueId();
1649        final String writeableResource = "/rest/" + getRandomUniqueId();
1650        final String username = "user28";
1651
1652        final String targetUri = ingestObj(targetResource);
1653
1654        final String readonlyString = "@prefix acl: <http://www.w3.org/ns/auth/acl#> .\n" +
1655                "<#readauthz> a acl:Authorization ;\n" +
1656                "   acl:agent \"" + username + "\" ;\n" +
1657                "   acl:mode acl:Read ;\n" +
1658                "   acl:accessTo <" + targetResource + "> .";
1659        ingestAclString(targetUri, readonlyString, "fedoraAdmin");
1660
1661        // User can read target resource.
1662        final HttpGet get1 = getObjMethod(targetResource);
1663        setAuth(get1, username);
1664        assertEquals(HttpStatus.SC_OK, getStatus(get1));
1665
1666        // User can't patch target resource.
1667        final String patch = "INSERT DATA { <> <http://purl.org/dc/elements/1.1/title> \"Changed it\"}";
1668        final HttpEntity patchEntity = new StringEntity(patch, sparqlContentType);
1669        try (final CloseableHttpResponse resp = (CloseableHttpResponse) PATCH(targetUri, patchEntity,
1670                username)) {
1671            assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(resp));
1672        }
1673
1674        // Make a user writable container.
1675        final String writeableUri = ingestObj(writeableResource);
1676        final String writeableAcl = "@prefix acl: <http://www.w3.org/ns/auth/acl#> .\n" +
1677                "<#writeauth> a acl:Authorization ;\n" +
1678                "   acl:agent \"" + username + "\" ;\n" +
1679                "   acl:mode acl:Read, acl:Write ;\n" +
1680                "   acl:accessTo <" + writeableResource + "> ;\n" +
1681                "   acl:default <" + writeableResource + "> .";
1682        ingestAclString(writeableUri, writeableAcl, "fedoraAdmin");
1683
1684        // Ensure we can write to writeable resource.
1685        testCanWrite(writeableResource, username);
1686
1687        // Try to create direct container referencing readonly resource with POST.
1688        final HttpPost userPost = postObjMethod(writeableResource);
1689        setAuth(userPost, username);
1690        userPost.addHeader("Link", "<" + DIRECT_CONTAINER.toString() + ">; rel=type");
1691        final String direct = "@prefix ldp: <http://www.w3.org/ns/ldp#> .\n" +
1692                "@prefix test: <http://example.org/test#> .\n\n" +
1693                "<> ldp:membershipResource <" + targetResource + "> ;" +
1694                "ldp:hasMemberRelation test:predicateToCreate .";
1695        final HttpEntity directEntity = new StringEntity(direct, turtleContentType);
1696        userPost.setEntity(directEntity);
1697        userPost.setHeader("Content-type", "text/turtle");
1698        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(userPost));
1699
1700        // Try to create direct container referencing readonly resource with PUT.
1701        final String indirectString = getRandomUniqueId();
1702        final HttpPut userPut = putObjMethod(writeableResource + "/" + indirectString);
1703        setAuth(userPut, username);
1704        userPut.addHeader("Link", "<" + DIRECT_CONTAINER.toString() + ">; rel=type");
1705        userPut.setEntity(directEntity);
1706        userPut.setHeader("Content-type", "text/turtle");
1707        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(userPut));
1708
1709        // Create an user writeable resource.
1710        final HttpPost targetPost = postObjMethod(writeableResource);
1711        setAuth(targetPost, username);
1712        final String tempTarget;
1713        try (final CloseableHttpResponse resp = execute(targetPost)) {
1714            assertEquals(HttpStatus.SC_CREATED, getStatus(resp));
1715            tempTarget = getLocation(resp);
1716        }
1717
1718        // Try to create direct container referencing an available resource.
1719        final String direct_ok = "@prefix ldp: <http://www.w3.org/ns/ldp#> .\n" +
1720                "@prefix test: <http://example.org/test#> .\n\n" +
1721                "<> ldp:membershipResource <" + tempTarget + "> ;\n" +
1722                "ldp:hasMemberRelation test:predicateToCreate .";
1723        final HttpPost userPatchPost = postObjMethod(writeableResource);
1724        setAuth(userPatchPost, username);
1725        userPatchPost.addHeader("Link", "<" + DIRECT_CONTAINER.toString() + ">; rel=type");
1726        userPatchPost.setEntity(new StringEntity(direct_ok, turtleContentType));
1727        userPatchPost.setHeader("Content-type", "text/turtle");
1728        final String directUri;
1729        try (final CloseableHttpResponse resp = execute(userPatchPost)) {
1730            assertEquals(HttpStatus.SC_CREATED, getStatus(resp));
1731            directUri = getLocation(resp);
1732        }
1733
1734        // Then PATCH to the readonly resource.
1735        final HttpPatch patchDirect = new HttpPatch(directUri);
1736        setAuth(patchDirect, username);
1737        final String patch_text = "prefix ldp: <http://www.w3.org/ns/ldp#> \n" +
1738                "DELETE { <> ldp:membershipResource ?o } \n" +
1739                "INSERT { <> ldp:membershipResource <" + targetResource + "> } \n" +
1740                "WHERE { <> ldp:membershipResource ?o }";
1741        patchDirect.setEntity(new StringEntity(patch_text, sparqlContentType));
1742        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(patchDirect));
1743
1744        // Delete the ldp:membershipRelation and add it with INSERT DATA {}
1745        final String patch_delete_relation = "prefix ldp: <http://www.w3.org/ns/ldp#> \n" +
1746                "DELETE DATA { <> ldp:membershipResource <" + tempTarget + "> }";
1747        final HttpPatch patchDirect2 = new HttpPatch(directUri);
1748        setAuth(patchDirect2, username);
1749        patchDirect2.setEntity(new StringEntity(patch_delete_relation, sparqlContentType));
1750        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(patchDirect2));
1751
1752        final String patch_insert_relation = "prefix ldp: <http://www.w3.org/ns/ldp#> \n" +
1753                "INSERT DATA { <> ldp:membershipResource <" + targetResource + "> }";
1754        final HttpPatch patchDirect3 = new HttpPatch(directUri);
1755        setAuth(patchDirect3, username);
1756        patchDirect3.setEntity(new StringEntity(patch_insert_relation, sparqlContentType));
1757        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(patchDirect3));
1758
1759        // Patch the indirect to the readonly target as admin
1760        final HttpPatch patchAsAdmin = new HttpPatch(directUri);
1761        setAuth(patchAsAdmin, "fedoraAdmin");
1762        patchAsAdmin.setEntity(new StringEntity(patch_text, sparqlContentType));
1763        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(patchAsAdmin));
1764
1765        // Ensure the patching happened.
1766        final HttpGet verifyGet = new HttpGet(directUri);
1767        setAuth(verifyGet, "fedoraAdmin");
1768        try (final CloseableHttpResponse response = execute(verifyGet)) {
1769            final CloseableDataset dataset = getDataset(response);
1770            final DatasetGraph graph = dataset.asDatasetGraph();
1771            assertTrue("Can't find " + targetUri + " in graph",
1772                    graph.contains(
1773                        Node.ANY,
1774                        NodeFactory.createURI(directUri),
1775                        MEMBERSHIP_RESOURCE.asNode(),
1776                        NodeFactory.createURI(targetUri)
1777                    )
1778            );
1779        }
1780
1781        // Try to POST a child as user
1782        final HttpPost postChild = new HttpPost(directUri);
1783        final String postTarget = "@prefix ldp: <http://www.w3.org/ns/ldp#> .\n" +
1784                "@prefix test: <http://example.org/test#> .\n" +
1785                "<> test:something <" + tempTarget + "> .";
1786        final HttpEntity putPostChild = new StringEntity(postTarget, turtleContentType);
1787        setAuth(postChild, username);
1788        postChild.setEntity(putPostChild);
1789        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(postChild));
1790
1791        // Try to PUT a child as user
1792        final String id = getRandomUniqueId();
1793        final HttpPut putChild = new HttpPut(directUri + "/" + id);
1794        setAuth(putChild, username);
1795        putChild.setEntity(putPostChild);
1796        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(putChild));
1797
1798        // Put the child as Admin
1799        setAuth(putChild, "fedoraAdmin");
1800        assertEquals(HttpStatus.SC_CREATED, getStatus(putChild));
1801
1802        // Try to delete the child as user
1803        final HttpDelete deleteChild = new HttpDelete(directUri + "/" + id);
1804        setAuth(deleteChild, username);
1805        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(deleteChild));
1806
1807        // Try to delete the indirect container
1808        final HttpDelete deleteIndirect = new HttpDelete(directUri);
1809        setAuth(deleteIndirect, username);
1810        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(deleteIndirect));
1811
1812        // Ensure we can still write to the writeable resource.
1813        testCanWrite(writeableResource, username);
1814
1815    }
1816
1817    @Test
1818    public void testDirectRelationshipsOk() throws IOException {
1819        final String targetResource = "/rest/" + getRandomUniqueId();
1820        final String writeableResource = "/rest/" + getRandomUniqueId();
1821        final String username = "user28";
1822
1823        final String targetUri = ingestObj(targetResource);
1824
1825        final String readwriteString = "@prefix acl: <http://www.w3.org/ns/auth/acl#> .\n" +
1826                "<#readauthz> a acl:Authorization ;\n" +
1827                "   acl:agent \"" + username + "\" ;\n" +
1828                "   acl:mode acl:Read, acl:Write ;\n" +
1829                "   acl:accessTo <" + targetResource + "> .";
1830        ingestAclString(targetUri, readwriteString, "fedoraAdmin");
1831
1832        // User can read target resource.
1833        final HttpGet get1 = getObjMethod(targetResource);
1834        setAuth(get1, username);
1835        assertEquals(HttpStatus.SC_OK, getStatus(get1));
1836
1837        // User can patch target resource.
1838        final String patch = "INSERT DATA { <> <http://purl.org/dc/elements/1.1/title> \"Changed it\"}";
1839        final HttpEntity patchEntity = new StringEntity(patch, sparqlContentType);
1840        try (final CloseableHttpResponse resp = (CloseableHttpResponse) PATCH(targetUri, patchEntity,
1841                username)) {
1842            assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(resp));
1843        }
1844
1845        // Make a user writable container.
1846        final String writeableUri = ingestObj(writeableResource);
1847        final String writeableAcl = "@prefix acl: <http://www.w3.org/ns/auth/acl#> .\n" +
1848                "<#writeauth> a acl:Authorization ;\n" +
1849                "   acl:agent \"" + username + "\" ;\n" +
1850                "   acl:mode acl:Read, acl:Write ;\n" +
1851                "   acl:accessTo <" + writeableResource + "> ;\n" +
1852                "   acl:default <" + writeableResource + "> .";
1853        ingestAclString(writeableUri, writeableAcl, "fedoraAdmin");
1854
1855        // Ensure we can write to the writeable resource.
1856        testCanWrite(writeableResource, username);
1857
1858        // Try to create direct container referencing writeable resource with POST.
1859        final HttpPost userPost = postObjMethod(writeableResource);
1860        setAuth(userPost, username);
1861        userPost.addHeader("Link", "<" + DIRECT_CONTAINER.toString() + ">; rel=type");
1862        final String indirect = "@prefix ldp: <http://www.w3.org/ns/ldp#> .\n" +
1863                "@prefix test: <http://example.org/test#> .\n\n" +
1864                "<> ldp:membershipResource <" + targetResource + "> ;\n" +
1865                "ldp:hasMemberRelation test:predicateToCreate .";
1866        final HttpEntity directEntity = new StringEntity(indirect, turtleContentType);
1867        userPost.setEntity(new StringEntity(indirect, turtleContentType));
1868        userPost.setHeader("Content-type", "text/turtle");
1869        assertEquals(HttpStatus.SC_CREATED, getStatus(userPost));
1870
1871        // Try to create direct container referencing writeable resource with PUT.
1872        final String directString = getRandomUniqueId();
1873        final HttpPut userPut = putObjMethod(writeableResource + "/" + directString);
1874        setAuth(userPut, username);
1875        userPut.addHeader("Link", "<" + DIRECT_CONTAINER.toString() + ">; rel=type");
1876        userPut.setEntity(directEntity);
1877        userPut.setHeader("Content-type", "text/turtle");
1878        assertEquals(HttpStatus.SC_CREATED, getStatus(userPut));
1879
1880        // Create an user writeable resource.
1881        final HttpPost targetPost = postObjMethod(writeableResource);
1882        setAuth(targetPost, username);
1883        final String tempTarget;
1884        try (final CloseableHttpResponse resp = execute(targetPost)) {
1885            assertEquals(HttpStatus.SC_CREATED, getStatus(resp));
1886            tempTarget = getLocation(resp);
1887        }
1888
1889        // Try to create direct container referencing an available resource.
1890        final String direct_ok = "@prefix ldp: <http://www.w3.org/ns/ldp#> .\n" +
1891                "@prefix test: <http://example.org/test#> .\n\n" +
1892                "<> ldp:membershipResource <" + tempTarget + "> ;\n" +
1893                "ldp:hasMemberRelation test:predicateToCreate .";
1894        final HttpPost userPatchPost = postObjMethod(writeableResource);
1895        setAuth(userPatchPost, username);
1896        userPatchPost.addHeader("Link", "<" + DIRECT_CONTAINER.toString() + ">; rel=type");
1897        userPatchPost.setEntity(new StringEntity(direct_ok, turtleContentType));
1898        userPatchPost.setHeader("Content-type", "text/turtle");
1899        final String directUri;
1900        try (final CloseableHttpResponse resp = execute(userPatchPost)) {
1901            assertEquals(HttpStatus.SC_CREATED, getStatus(resp));
1902            directUri = getLocation(resp);
1903        }
1904
1905        // Then PATCH to the readonly resource.
1906        final HttpPatch patchDirect = new HttpPatch(directUri);
1907        setAuth(patchDirect, username);
1908        final String patch_text = "prefix ldp: <http://www.w3.org/ns/ldp#> \n" +
1909                "DELETE { <> ldp:membershipResource ?o } \n" +
1910                "INSERT { <> ldp:membershipResource <" + targetResource + "> } \n" +
1911                "WHERE { <> ldp:membershipResource ?o }";
1912        patchDirect.setEntity(new StringEntity(patch_text, sparqlContentType));
1913        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(patchDirect));
1914
1915        // Delete the ldp:membershipRelation and add it with INSERT
1916        final String patch_delete_relation = "prefix ldp: <http://www.w3.org/ns/ldp#> \n" +
1917                "DELETE DATA { <> ldp:membershipResource <" + targetResource + "> }";
1918        final HttpPatch patchDirect2 = new HttpPatch(directUri);
1919        setAuth(patchDirect2, username);
1920        patchDirect2.setEntity(new StringEntity(patch_delete_relation, sparqlContentType));
1921        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(patchDirect2));
1922
1923        // Cannot insert membershipResource without deleting the default value
1924        final String patch_insert_relation = "prefix ldp: <http://www.w3.org/ns/ldp#> \n" +
1925                "DELETE { <> ldp:membershipResource ?o } \n" +
1926                "INSERT { <> ldp:membershipResource <" + targetResource + "> } \n" +
1927                "WHERE { <> ldp:membershipResource ?o }";
1928        final HttpPatch patchDirect3 = new HttpPatch(directUri);
1929        setAuth(patchDirect3, username);
1930        patchDirect3.setEntity(new StringEntity(patch_insert_relation, sparqlContentType));
1931        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(patchDirect3));
1932
1933        // Ensure we can write to the writeable resource.
1934        testCanWrite(writeableResource, username);
1935    }
1936
1937    @Test
1938    public void testSameInTransaction() throws Exception {
1939        final String targetResource = "/rest/" + getRandomUniqueId();
1940        final String username = "user28";
1941        // Make a basic container.
1942        final String targetUri = ingestObj(targetResource);
1943        final String readwriteString = "@prefix acl: <http://www.w3.org/ns/auth/acl#> .\n" +
1944                "<#readauthz> a acl:Authorization ;\n" +
1945                "   acl:agent \"" + username + "\" ;\n" +
1946                "   acl:mode acl:Read, acl:Write ;\n" +
1947                "   acl:accessTo <" + targetResource + "> .";
1948        // Allow user28 to read and write this object.
1949        ingestAclString(targetUri, readwriteString, "fedoraAdmin");
1950        // Test that user28 can read target resource.
1951        final HttpGet getAllowed1 = getObjMethod(targetResource);
1952        setAuth(getAllowed1, username);
1953        assertEquals(HttpStatus.SC_OK, getStatus(getAllowed1));
1954        // Test that user28 can patch target resource.
1955        final HttpPatch patchAllowed1 = patchObjMethod(targetResource);
1956        final String patchString = "prefix dc: <http://purl.org/dc/elements/1.1/> INSERT { <> dc:title " +
1957            "\"new title\" } WHERE {}";
1958        final StringEntity patchEntity = new StringEntity(patchString, Charsets.UTF8_CHARSET);
1959        patchAllowed1.setEntity(patchEntity);
1960        patchAllowed1.setHeader(CONTENT_TYPE, "application/sparql-update");
1961        setAuth(patchAllowed1, username);
1962        assertEquals(SC_NO_CONTENT, getStatus(patchAllowed1));
1963        // Test that user28 can post to target resource.
1964        final HttpPost postAllowed1 = postObjMethod(targetResource);
1965        setAuth(postAllowed1, username);
1966        final String childResource;
1967        try (final CloseableHttpResponse response = execute(postAllowed1)) {
1968            assertEquals(SC_CREATED, getStatus(postAllowed1));
1969            childResource = getLocation(response);
1970        }
1971        // Test that user28 cannot patch the child resource (ACL is not acl:default).
1972        final HttpPatch patchDisallowed1 = new HttpPatch(childResource);
1973        patchDisallowed1.setEntity(patchEntity);
1974        patchDisallowed1.setHeader(CONTENT_TYPE, "application/sparql-update");
1975        setAuth(patchDisallowed1, username);
1976        assertEquals(SC_FORBIDDEN, getStatus(patchDisallowed1));
1977        // Test that user28 cannot post to a child resource.
1978        final HttpPost postDisallowed1 = new HttpPost(childResource);
1979        setAuth(postDisallowed1, username);
1980        assertEquals(SC_FORBIDDEN, getStatus(postDisallowed1));
1981        // Test another user cannot access the target resource.
1982        final HttpGet getDisallowed1 = getObjMethod(targetResource);
1983        setAuth(getDisallowed1, "user400");
1984        assertEquals(SC_FORBIDDEN, getStatus(getDisallowed1));
1985        // Get the transaction endpoint.
1986        final HttpGet getTransactionEndpoint = getObjMethod("/rest");
1987        setAuth(getTransactionEndpoint, "fedoraAdmin");
1988        final String transactionEndpoint;
1989        final Pattern linkHeaderMatcher = Pattern.compile("<([^>]+)>");
1990        try (final CloseableHttpResponse response = execute(getTransactionEndpoint)) {
1991            final var linkheaders = getLinkHeaders(response);
1992            transactionEndpoint = linkheaders.stream()
1993                    .filter(t -> t.contains("http://fedora.info/definitions/v4/transaction#endpoint"))
1994                    .map(t -> {
1995                        final var matches = linkHeaderMatcher.matcher(t);
1996                        matches.find();
1997                        return matches.group(1);
1998                    })
1999                    .findFirst()
2000                    .orElseThrow(Exception::new);
2001        }
2002        // Create a transaction.
2003        final HttpPost postTransaction = new HttpPost(transactionEndpoint);
2004        setAuth(postTransaction, "fedoraAdmin");
2005        final String transactionId;
2006        try (final CloseableHttpResponse response = execute(postTransaction)) {
2007            assertEquals(SC_CREATED, getStatus(response));
2008            transactionId = getLocation(response);
2009        }
2010        // Test user28 can post to  target resource in a transaction.
2011        final HttpPost postChildInTx = postObjMethod(targetResource);
2012        setAuth(postChildInTx, username);
2013        postChildInTx.setHeader(ATOMIC_ID_HEADER, transactionId);
2014        final String txChild;
2015        try (final CloseableHttpResponse response = execute(postChildInTx)) {
2016            assertEquals(SC_CREATED, getStatus(response));
2017            txChild = getLocation(response);
2018        }
2019        // Test user28 cannot post to the child in a transaction.
2020        final HttpPost postDisallowed2 = new HttpPost(txChild);
2021        setAuth(postDisallowed2, username);
2022        postDisallowed2.setHeader(ATOMIC_ID_HEADER, transactionId);
2023        assertEquals(SC_FORBIDDEN, getStatus(postDisallowed2));
2024    }
2025
2026    @Test
2027    public void testBinaryAndDescriptionAllowed() throws Exception {
2028        final String targetResource = "/rest/" + getRandomUniqueId();
2029        final String username = "user88";
2030        // Make a basic container.
2031        final String targetUri = ingestObj(targetResource);
2032        final String readwriteString = "@prefix acl: <http://www.w3.org/ns/auth/acl#> .\n" +
2033                "<#readauthz> a acl:Authorization ;\n" +
2034                "   acl:agent \"" + username + "\" ;\n" +
2035                "   acl:mode acl:Read, acl:Write ;\n" +
2036                "   acl:default <" + targetResource + "> ;" +
2037                "   acl:accessTo <" + targetResource + "> .";
2038        // Allow user to read and write this object.
2039        ingestAclString(targetUri, readwriteString, "fedoraAdmin");
2040        // user creates a binary
2041        final HttpPost newBinary = postObjMethod(targetResource);
2042        setAuth(newBinary, username);
2043        newBinary.setHeader(CONTENT_TYPE, "text/plain");
2044        final StringEntity stringData = new StringEntity("This is some data", Charsets.UTF8_CHARSET);
2045        newBinary.setEntity(stringData);
2046        final String binaryLocation;
2047        try (final CloseableHttpResponse response = execute(newBinary)) {
2048            assertEquals(SC_CREATED, getStatus(response));
2049            binaryLocation = getLocation(response);
2050        }
2051        // Try PUTting a new binary
2052        final HttpPut putAgain = new HttpPut(binaryLocation);
2053        setAuth(putAgain, username);
2054        putAgain.setHeader(CONTENT_TYPE, "text/plain");
2055        final StringEntity newStringData = new StringEntity("Some other data", Charsets.UTF8_CHARSET);
2056        putAgain.setEntity(newStringData);
2057        assertEquals(SC_NO_CONTENT, getStatus(putAgain));
2058        // Try PUTting to binary description
2059        final HttpPut putDesc = new HttpPut(binaryLocation + "/" + FCR_METADATA);
2060        setAuth(putDesc, username);
2061        putDesc.setHeader(CONTENT_TYPE, "text/turtle");
2062        final StringEntity putDescData = new StringEntity("<> <http://purl.org/dc/elements/1.1/title> \"Some title\".",
2063                Charsets.UTF8_CHARSET);
2064        putDesc.setEntity(putDescData);
2065        assertEquals(SC_NO_CONTENT, getStatus(putDesc));
2066        // Check the title
2067        assertPredicateValue(binaryLocation + "/" + FCR_METADATA, "http://purl.org/dc/elements/1.1/title",
2068                "Some title");
2069        // Try PATCHing to binary description
2070        final HttpPatch patchDesc = new HttpPatch(binaryLocation + "/" + FCR_METADATA);
2071        setAuth(patchDesc, username);
2072        patchDesc.setHeader(CONTENT_TYPE, "application/sparql-update");
2073        final StringEntity patchDescData = new StringEntity("PREFIX dc: <http://purl.org/dc/elements/1.1/> " +
2074                "DELETE { <> dc:title ?o } INSERT { <> dc:title \"Some different title\" } WHERE { <> dc:title ?o }",
2075                Charsets.UTF8_CHARSET);
2076        patchDesc.setEntity(patchDescData);
2077        assertEquals(SC_NO_CONTENT, getStatus(patchDesc));
2078        // Check the title
2079        assertPredicateValue(binaryLocation + "/" + FCR_METADATA, "http://purl.org/dc/elements/1.1/title",
2080                "Some different title");
2081
2082    }
2083
2084    @Test
2085    public void testRequestWithEmptyPath() throws Exception {
2086        // Ensure HttpClient does not remove empty paths
2087        final RequestConfig config = RequestConfig.custom().setNormalizeUri(false).build();
2088
2089        final String username = "testUser92";
2090        final String parent = getRandomUniqueId();
2091        final HttpPost postParent = postObjMethod();
2092        postParent.setHeader("Slug", parent);
2093        setAuth(postParent, "fedoraAdmin");
2094        final String parentUri;
2095        try (final CloseableHttpResponse response = execute(postParent)) {
2096            assertEquals(CREATED.getStatusCode(), getStatus(response));
2097            parentUri = getLocation(response);
2098        }
2099        // Make parent only accessible to fedoraAdmin
2100        final String parentAcl = "@prefix acl: <http://www.w3.org/ns/auth/acl#> .\n" +
2101                "<#readauthz> a acl:Authorization ;\n" +
2102                "   acl:agent \"fedoraAdmin\" ;\n" +
2103                "   acl:mode acl:Read, acl:Write ;\n" +
2104                "   acl:accessTo <" + parentUri + "> .";
2105        ingestAclString(parentUri, parentAcl, "fedoraAdmin");
2106        // Admin can see parent
2107        final HttpGet getAdminParent = getObjMethod(parent);
2108        setAuth(getAdminParent, "fedoraAdmin");
2109        assertEquals(OK.getStatusCode(), getStatus(getAdminParent));
2110        final HttpGet getParent = getObjMethod(parent);
2111        setAuth(getParent, username);
2112        // testUser92 cannot see parent.
2113        assertEquals(FORBIDDEN.getStatusCode(), getStatus(getParent));
2114
2115        final String child = getRandomUniqueId();
2116        final HttpPost postChild = postObjMethod(parent);
2117        postChild.setHeader("Slug", child);
2118        setAuth(postChild, "fedoraAdmin");
2119        final String childUri;
2120        try (final CloseableHttpResponse response = execute(postChild)) {
2121            assertEquals(CREATED.getStatusCode(), getStatus(response));
2122            childUri = getLocation(response);
2123        }
2124        // Make child accessible to testUser92
2125        final String childAcl = "@prefix acl: <http://www.w3.org/ns/auth/acl#> .\n" +
2126                "<#readauthz> a acl:Authorization ;\n" +
2127                "   acl:agent \"" + username + "\" ;\n" +
2128                "   acl:mode acl:Read, acl:Write ;\n" +
2129                "   acl:accessTo <" + childUri + "> .";
2130        ingestAclString(childUri, childAcl, "fedoraAdmin");
2131        // Admin can see child.
2132        final HttpGet getAdminChild = getObjMethod(parent + "/" + child);
2133        setAuth(getAdminChild, "fedoraAdmin");
2134        assertEquals(OK.getStatusCode(), getStatus(getAdminChild));
2135
2136        // testUser92 can see child.
2137        final HttpGet getChild = getObjMethod(parent + "/" + child);
2138        setAuth(getChild, username);
2139        assertEquals(OK.getStatusCode(), getStatus(getChild));
2140
2141        // Admin bypasses ACL resolution gets 409.
2142        final HttpGet getAdminRequest = getObjMethod(parent + "//" + child);
2143        setAuth(getAdminRequest, "fedoraAdmin");
2144        getAdminRequest.setConfig(config);
2145        assertEquals(BAD_REQUEST.getStatusCode(), getStatus(getAdminRequest));
2146        // User
2147        final HttpGet getUserRequest = getObjMethod(parent + "//" + child);
2148        setAuth(getUserRequest, username);
2149        getUserRequest.setConfig(config);
2150        assertEquals(BAD_REQUEST.getStatusCode(), getStatus(getUserRequest));
2151    }
2152
2153    @Test
2154    public void testGetWithEmbeddedResourcesOk() throws Exception {
2155        final String targetResource = "/rest/" + getRandomUniqueId();
2156        final String childResource = targetResource + "/" + getRandomUniqueId();
2157        final String username = "user88";
2158        // Make a basic container.
2159        final String targetUri = ingestObj(targetResource);
2160        ingestObj(childResource);
2161        final String readwriteString = "@prefix acl: <http://www.w3.org/ns/auth/acl#> .\n" +
2162                "<#readauthz> a acl:Authorization ;\n" +
2163                "   acl:agent \"" + username + "\" ;\n" +
2164                "   acl:mode acl:Read, acl:Write ;\n" +
2165                "   acl:default <" + targetResource + "> ;" +
2166                "   acl:accessTo <" + targetResource + "> .";
2167        // Allow user to read and write this object.
2168        ingestAclString(targetUri, readwriteString, "fedoraAdmin");
2169
2170        final HttpGet getAdminChild = new HttpGet(targetUri);
2171        setAuth(getAdminChild, username);
2172        getAdminChild.addHeader("Prefer", "return=representation; include=\"" + EMBED_CONTAINED + "\"");
2173        assertEquals(OK.getStatusCode(), getStatus(getAdminChild));
2174    }
2175
2176    @Test
2177    public void testGetWithEmbeddedResourceDenied() throws Exception {
2178        final String targetResource = "/rest/" + getRandomUniqueId();
2179        final String childResource = targetResource + "/" + getRandomUniqueId();
2180        final String username = "user88";
2181        // Make a basic container.
2182        final String targetUri = ingestObj(targetResource);
2183        final String readwriteString = "@prefix acl: <http://www.w3.org/ns/auth/acl#> .\n" +
2184                "<#readauthz> a acl:Authorization ;\n" +
2185                "   acl:agent \"" + username + "\" ;\n" +
2186                "   acl:mode acl:Read, acl:Write ;\n" +
2187                "   acl:accessTo <" + targetResource + "> .";
2188        // Allow user to read and write this object.
2189        ingestAclString(targetUri, readwriteString, "fedoraAdmin");
2190
2191        final String childUri = ingestObj(childResource);
2192        final String noAccessString = "@prefix acl: <http://www.w3.org/ns/auth/acl#> .\n" +
2193                "<#readauthz> a acl:Authorization ;\n" +
2194                "   acl:agent \"fedoraAdmin\" ;\n" +
2195                "   acl:mode acl:Read, acl:Write ;\n" +
2196                "   acl:accessTo <" + childResource + "> .";
2197        ingestAclString(childUri, noAccessString, "fedoraAdmin");
2198
2199        // Can get the target.
2200        final HttpGet getTarget = new HttpGet(targetUri);
2201        setAuth(getTarget, username);
2202        assertEquals(OK.getStatusCode(), getStatus(getTarget));
2203
2204        // Can't get the child.
2205        final HttpGet getChild = new HttpGet(childUri);
2206        setAuth(getChild, username);
2207        assertEquals(FORBIDDEN.getStatusCode(), getStatus(getChild));
2208
2209        // So you can't get the target with embedded resources.
2210        final HttpGet getAdminChild = new HttpGet(targetUri);
2211        setAuth(getAdminChild, username);
2212        getAdminChild.addHeader("Prefer", "return=representation; include=\"" + EMBED_CONTAINED + "\"");
2213        assertEquals(FORBIDDEN.getStatusCode(), getStatus(getAdminChild));
2214    }
2215
2216    @Test
2217    public void testDeepDeleteAllowed() throws Exception {
2218        final String targetResource = "/rest/" + getRandomUniqueId();
2219        final String username = "user88";
2220        // Make a basic container.
2221        final String targetUri = ingestObj(targetResource);
2222        final String readwriteString = "@prefix acl: <http://www.w3.org/ns/auth/acl#> .\n" +
2223                "<#readauthz> a acl:Authorization ;\n" +
2224                "   acl:agent \"" + username + "\" ;\n" +
2225                "   acl:mode acl:Read, acl:Write ;\n" +
2226                "   acl:accessTo <" + targetResource + "> ;\n" +
2227                "   acl:default <" + targetResource + "> .";
2228        // Allow user to read and write this object.
2229        ingestAclString(targetUri, readwriteString, "fedoraAdmin");
2230
2231        final String child1 = targetResource + "/" + getRandomUniqueId();
2232        final String child2 = targetResource + "/" + getRandomUniqueId();
2233        final String child1_1 = child1 + "/" + getRandomUniqueId();
2234        final String child2_1 = child2 + "/" + getRandomUniqueId();
2235        ingestObj(child1);
2236        ingestObj(child2);
2237        ingestObj(child1_1);
2238        ingestObj(child2_1);
2239
2240        assertGetRequest(targetUri, username, OK);
2241        assertGetRequest(serverAddress + child1, username, OK);
2242        assertGetRequest(serverAddress + child2, username, OK);
2243        assertGetRequest(serverAddress + child1_1, username, OK);
2244        assertGetRequest(serverAddress + child2_1, username, OK);
2245
2246        final var delete = new HttpDelete(targetUri);
2247        setAuth(delete, username);
2248        assertEquals(NO_CONTENT.getStatusCode(), getStatus(delete));
2249    }
2250
2251    @Test
2252    public void testDeepDeleteFailed() throws Exception {
2253        final String targetResource = "/rest/" + getRandomUniqueId();
2254        final String username = "user88";
2255        // Make a basic container.
2256        final String targetUri = ingestObj(targetResource);
2257        final String readwriteString = "@prefix acl: <http://www.w3.org/ns/auth/acl#> .\n" +
2258                "<#readauthz> a acl:Authorization ;\n" +
2259                "   acl:agent \"" + username + "\" ;\n" +
2260                "   acl:mode acl:Read, acl:Write ;\n" +
2261                "   acl:accessTo <" + targetResource + "> ;\n" +
2262                "   acl:default <" + targetResource + "> .";
2263        // Allow user to read and write this object.
2264        ingestAclString(targetUri, readwriteString, "fedoraAdmin");
2265
2266        final String child1 = targetResource + "/" + getRandomUniqueId();
2267        final String child2 = targetResource + "/" + getRandomUniqueId();
2268        final String child1_1 = child1 + "/" + getRandomUniqueId();
2269        final String child2_1 = child2 + "/" + getRandomUniqueId();
2270        ingestObj(child1);
2271        ingestObj(child2);
2272        ingestObj(child1_1);
2273        final String child2_1_URI = ingestObj(child2_1);
2274        final String noAccessString = "@prefix acl: <http://www.w3.org/ns/auth/acl#> .\n" +
2275                "<#readauthz> a acl:Authorization ;\n" +
2276                "   acl:agent \"fedoraAdmin\" ;\n" +
2277                "   acl:mode acl:Read, acl:Write ;\n" +
2278                "   acl:accessTo <" + child2_1 + "> .";
2279        ingestAclString(child2_1_URI, noAccessString, "fedoraAdmin");
2280
2281        assertGetRequest(targetUri, username, OK);
2282        assertGetRequest(serverAddress + child1, username, OK);
2283        assertGetRequest(serverAddress + child2, username, OK);
2284        assertGetRequest(serverAddress + child1_1, username, OK);
2285        assertGetRequest(serverAddress + child2_1, username, FORBIDDEN);
2286
2287        final var delete = new HttpDelete(targetUri);
2288        setAuth(delete, username);
2289        assertEquals(FORBIDDEN.getStatusCode(), getStatus(delete));
2290    }
2291
2292    @Test
2293    public void testTransactionExceptions() throws Exception {
2294        // Ensure both admin and users get 409 for invalid transaction ids.
2295        final var invalidIx = serverAddress + FCR_TX + "/fake";
2296        assertGetRequest(serverAddress, "fedoraAdmin", invalidIx, CONFLICT);
2297        assertGetRequest(serverAddress, "testuser", invalidIx, CONFLICT);
2298        // Ensure both admin and users get 409 for a non-existant transactions.
2299        final var fakeTx = serverAddress + FCR_TX + "/" + getRandomUniqueId();
2300        assertGetRequest(serverAddress, "fedoraAdmin", fakeTx, CONFLICT);
2301        assertGetRequest(serverAddress, "testuser", fakeTx, CONFLICT);
2302        // Create a transaction.
2303        final var postTx = postObjMethod(FCR_TX);
2304        setAuth(postTx, "fedoraAdmin");
2305        final String txId;
2306        try (final var response = execute(postTx)) {
2307            assertEquals(SC_CREATED, getStatus(response));
2308            txId = getLocation(response);
2309        }
2310        // Create an object in the transaction.
2311        final var postObj = postObjMethod();
2312        setAuth(postObj, "fedoraAdmin");
2313        addTxTo(postObj, txId);
2314        final String targetUri;
2315        try (final var response = execute(postObj)) {
2316            assertEquals(SC_CREATED, getStatus(response));
2317            targetUri = getLocation(response);
2318        }
2319        // Test the transaction works.
2320        assertGetRequest(targetUri, "testuser", txId, OK);
2321        // Commit the transaction
2322        final var commit = new HttpPut(txId);
2323        setAuth(commit, "fedoraAdmin");
2324        assertEquals(SC_NO_CONTENT, getStatus(commit));
2325        // Now try to get the transaction again, expect 409 Conflict..
2326        assertGetRequest(targetUri, "fedoraAdmin", txId, CONFLICT);
2327        assertGetRequest(targetUri, "testuser", txId, CONFLICT);
2328    }
2329
2330    private void assertGetRequest(final String uri, final String username, final Response.Status expectedResponse) {
2331        assertGetRequest(uri, username, null, expectedResponse);
2332    }
2333
2334    private void assertGetRequest(final String uri, final String username, final String txId,
2335                                  final Response.Status expectedResponse) {
2336        final var getTarget = new HttpGet(uri);
2337        setAuth(getTarget, username);
2338        if (txId != null) {
2339            addTxTo(getTarget, txId);
2340        }
2341        assertEquals(expectedResponse.getStatusCode(), getStatus(getTarget));
2342    }
2343
2344    /**
2345     * Check the graph has the predicate with the value.
2346     * @param targetUri Full URI of the resource to check.
2347     * @param predicateUri Full URI of the predicate to check.
2348     * @param predicateValue Literal value to look for.
2349     * @throws Exception if problems performing the GET.
2350     */
2351    private void assertPredicateValue(final String targetUri, final String predicateUri, final String predicateValue)
2352            throws Exception {
2353        final HttpGet verifyGet = new HttpGet(targetUri);
2354        setAuth(verifyGet, "fedoraAdmin");
2355        try (final CloseableHttpResponse response = execute(verifyGet)) {
2356            final CloseableDataset dataset = getDataset(response);
2357            final DatasetGraph graph = dataset.asDatasetGraph();
2358            assertTrue("Can't find " + predicateValue + " for predicate " + predicateUri + " in graph",
2359                    graph.contains(
2360                            Node.ANY,
2361                            Node.ANY,
2362                            NodeFactory.createURI(predicateUri),
2363                            NodeFactory.createLiteral(predicateValue)
2364                    )
2365            );
2366        }
2367    }
2368
2369
2370    /**
2371     * Utility function to ingest a ACL from a string.
2372     *
2373     * @param resourcePath Path to the resource if doesn't end with "/fcr:acl" it is added.
2374     * @param acl the text/turtle ACL as a string
2375     * @param username user to ingest as
2376     * @return the response from the ACL ingest.
2377     * @throws IOException on StringEntity encoding or client execute
2378     */
2379    private HttpResponse ingestAclString(final String resourcePath, final String acl, final String username)
2380            throws IOException {
2381        final String aclPath = (resourcePath.endsWith("/fcr:acl") ? resourcePath : resourcePath + "/fcr:acl");
2382        final HttpPut putReq = new HttpPut(aclPath);
2383        setAuth(putReq, username);
2384        putReq.setHeader("Content-type", "text/turtle");
2385        putReq.setEntity(new StringEntity(acl, turtleContentType));
2386        return execute(putReq);
2387    }
2388
2389    /**
2390     * Ensure that a writeable resource is still writeable
2391     *
2392     * @param writeableResource the URI of the writeable resource.
2393     * @param username the user will write access.
2394     * @throws UnsupportedEncodingException if default charset for String Entity is unsupported
2395     */
2396    private void testCanWrite(final String writeableResource, final String username)
2397            throws UnsupportedEncodingException {
2398        // Try to create a basic container inside the writeable resource with POST.
2399        final HttpPost okPost = postObjMethod(writeableResource);
2400        setAuth(okPost, username);
2401        assertEquals(HttpStatus.SC_CREATED, getStatus(okPost));
2402
2403        // Try to PATCH the writeableResource
2404        final HttpPatch okPatch = patchObjMethod(writeableResource);
2405        final String patchString = "PREFIX dc: <http://purl.org/dc/elements/1.1/> DELETE { <> dc:title ?o1 } " +
2406                "INSERT { <> dc:title \"Changed title\" }  WHERE { <> dc:title ?o1 }";
2407        final HttpEntity patchEntity = new StringEntity(patchString, sparqlContentType);
2408        setAuth(okPatch, username);
2409        okPatch.setHeader("Content-type", "application/sparql-update");
2410        okPatch.setEntity(patchEntity);
2411        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(okPatch));
2412    }
2413
2414    @Test
2415    public void testAuthenticatedUserCanCreateTransaction() {
2416        final HttpPost txnCreatePost = postObjMethod("rest/fcr:tx");
2417        setAuth(txnCreatePost, "testUser92");
2418        assertEquals(SC_CREATED, getStatus(txnCreatePost));
2419    }
2420}