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