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