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