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