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