001/*
002 * Licensed to DuraSpace under one or more contributor license agreements.
003 * See the NOTICE file distributed with this work for additional information
004 * regarding copyright ownership.
005 *
006 * DuraSpace licenses this file to you under the Apache License,
007 * Version 2.0 (the "License"); you may not use this file except in
008 * compliance with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.fcrepo.integration.auth.webac;
019
020import static java.util.Arrays.stream;
021import static javax.ws.rs.core.Response.Status.CREATED;
022import static org.apache.http.HttpStatus.SC_FORBIDDEN;
023import static org.apache.http.HttpStatus.SC_NOT_FOUND;
024import static org.apache.http.HttpStatus.SC_NO_CONTENT;
025import static org.apache.jena.vocabulary.DC_11.title;
026import static org.fcrepo.auth.webac.WebACRolesProvider.GROUP_AGENT_BASE_URI_PROPERTY;
027import static org.fcrepo.http.api.FedoraAcl.ROOT_AUTHORIZATION_PROPERTY;
028import static org.fcrepo.kernel.modeshape.utils.FedoraSessionUserUtil.USER_AGENT_BASE_URI_PROPERTY;
029import static org.junit.Assert.assertEquals;
030import static org.junit.Assert.assertTrue;
031
032import java.io.IOException;
033import java.io.InputStream;
034import java.util.Arrays;
035import java.util.Optional;
036import javax.ws.rs.core.Link;
037
038import org.apache.commons.codec.binary.Base64;
039import org.apache.commons.io.IOUtils;
040import org.apache.http.Header;
041import org.apache.http.HeaderElement;
042import org.apache.http.HttpEntity;
043import org.apache.http.HttpResponse;
044import org.apache.http.HttpStatus;
045import org.apache.http.NameValuePair;
046import org.apache.http.client.methods.CloseableHttpResponse;
047import org.apache.http.client.methods.HttpDelete;
048import org.apache.http.client.methods.HttpGet;
049import org.apache.http.client.methods.HttpHead;
050import org.apache.http.client.methods.HttpOptions;
051import org.apache.http.client.methods.HttpPatch;
052import org.apache.http.client.methods.HttpPost;
053import org.apache.http.client.methods.HttpPut;
054import org.apache.http.entity.ContentType;
055import org.apache.http.entity.InputStreamEntity;
056import org.apache.http.entity.StringEntity;
057import org.apache.http.message.AbstractHttpMessage;
058import org.fcrepo.integration.http.api.AbstractResourceIT;
059import org.junit.Ignore;
060import org.junit.Rule;
061import org.junit.Test;
062import org.junit.contrib.java.lang.system.RestoreSystemProperties;
063import org.slf4j.Logger;
064import org.slf4j.LoggerFactory;
065
066/**
067 * @author Peter Eichman
068 * @author whikloj
069 * @since September 4, 2015
070 */
071public class WebACRecipesIT extends AbstractResourceIT {
072
073    private static final Logger logger = LoggerFactory.getLogger(WebACRecipesIT.class);
074
075    @Rule
076    public final RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();
077
078    /**
079     * Convenience method to create an ACL with 0 or more authorization resources in the respository.
080     */
081    private String ingestAcl(final String username,
082            final String aclFilePath, final String aclResourcePath) throws IOException {
083
084        // create the ACL
085        final HttpResponse aclResponse = ingestTurtleResource(username, aclFilePath, aclResourcePath);
086
087        // return the URI to the newly created resource
088        return aclResponse.getFirstHeader("Location").getValue();
089    }
090
091    /**
092     * Convenience method to POST the contents of a Turtle file to the repository to create a new resource. Returns
093     * the HTTP response from that request. Throws an IOException if the server responds with anything other than a
094     * 201 Created response code.
095     */
096    private HttpResponse ingestTurtleResource(final String username, final String path, final String requestURI)
097            throws IOException {
098        final HttpPut request = new HttpPut(requestURI);
099
100        logger.debug("PUT to {} to create {}", requestURI, path);
101
102        setAuth(request, username);
103
104        final InputStream file = this.getClass().getResourceAsStream(path);
105        final InputStreamEntity fileEntity = new InputStreamEntity(file);
106        request.setEntity(fileEntity);
107        request.setHeader("Content-Type", "text/turtle");
108
109        try (final CloseableHttpResponse response = execute(request)) {
110            assertEquals(
111                "Didn't get a CREATED response!: " + IOUtils.toString(response.getEntity().getContent(), "UTF-8"),
112                CREATED.getStatusCode(), getStatus(response));
113            return response;
114        }
115
116    }
117
118    /**
119     * Convenience method to set up a regular FedoraResource
120     *
121     * @param path Path to put the resource under
122     * @return the Location of the newly created resource
123     * @throws IOException on error
124     */
125    private String ingestObj(final String path) throws IOException {
126        final HttpPut request = putObjMethod(path.replace(serverAddress, ""));
127        setAuth(request, "fedoraAdmin");
128        try (final CloseableHttpResponse response = execute(request)) {
129            assertEquals(HttpStatus.SC_CREATED, response.getStatusLine().getStatusCode());
130            return response.getFirstHeader("Location").getValue();
131        }
132    }
133
134    private String ingestBinary(final String path, final HttpEntity body) throws IOException {
135        logger.info("Ingesting {} binary to {}", body.getContentType().getValue(), path);
136        final HttpPut request = new HttpPut(serverAddress + path);
137        setAuth(request, "fedoraAdmin");
138        request.setEntity(body);
139        request.setHeader(body.getContentType());
140        final CloseableHttpResponse response = execute(request);
141        assertEquals(HttpStatus.SC_CREATED, response.getStatusLine().getStatusCode());
142        final String location = response.getFirstHeader("Location").getValue();
143        logger.info("Created binary at {}", location);
144        return location;
145
146    }
147
148    private String ingestDatastream(final String path, final String ds) throws IOException {
149        final HttpPut request = putDSMethod(path, ds, "some not so random content");
150        setAuth(request, "fedoraAdmin");
151        try (final CloseableHttpResponse response = execute(request)) {
152            assertEquals(HttpStatus.SC_CREATED, response.getStatusLine().getStatusCode());
153            return response.getFirstHeader("Location").getValue();
154        }
155    }
156
157    /**
158     * Convenience method for applying credentials to a request
159     *
160     * @param method the request to add the credentials to
161     * @param username the username to add
162     */
163    private static void setAuth(final AbstractHttpMessage method, final String username) {
164        final String creds = username + ":password";
165        final String encCreds = new String(Base64.encodeBase64(creds.getBytes()));
166        final String basic = "Basic " + encCreds;
167        method.setHeader("Authorization", basic);
168    }
169
170    @Test
171    public void scenario1() throws IOException {
172        final String testObj = ingestObj("/rest/webacl_box1");
173        final String acl1 = ingestAcl("fedoraAdmin", "/acls/01/acl.ttl",
174                                      testObj + "/fcr:acl");
175        final String aclLink = Link.fromUri(acl1).rel("acl").build().toString();
176
177        final HttpGet request = getObjMethod(testObj.replace(serverAddress, ""));
178        assertEquals("Anonymous can read " + testObj, HttpStatus.SC_FORBIDDEN, getStatus(request));
179
180        setAuth(request, "user01");
181        try (final CloseableHttpResponse response = execute(request)) {
182            assertEquals("User 'user01' can't read" + testObj, HttpStatus.SC_OK, getStatus(response));
183            // This gets the Link headers and filters for the correct one (aclLink::equals) defined above.
184            final Optional<String> header = stream(response.getHeaders("Link")).map(Header::getValue)
185                    .filter(aclLink::equals).findFirst();
186            // So you either have the correct Link header or you get nothing.
187            assertTrue("Missing Link header", header.isPresent());
188        }
189
190        final String childObj = ingestObj("/rest/webacl_box1/child");
191        final HttpGet getReq = getObjMethod(childObj.replace(serverAddress, ""));
192        setAuth(getReq, "user01");
193        try (final CloseableHttpResponse response = execute(getReq)) {
194            assertEquals("User 'user01' can't read child of " + testObj, HttpStatus.SC_OK, getStatus(response));
195        }
196    }
197
198    @Test
199    public void scenario2() throws IOException {
200        final String id = "/rest/box/bag/collection";
201        final String testObj = ingestObj(id);
202        ingestAcl("fedoraAdmin", "/acls/02/acl.ttl", testObj + "/fcr:acl");
203
204        logger.debug("Anonymous can not read " + testObj);
205        final HttpGet requestGet = getObjMethod(id);
206        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestGet));
207
208        logger.debug("GroupId 'Editors' can read " + testObj);
209        final HttpGet requestGet2 = getObjMethod(id);
210        setAuth(requestGet2, "jones");
211        requestGet2.setHeader("some-header", "Editors");
212        assertEquals(HttpStatus.SC_OK, getStatus(requestGet2));
213
214        logger.debug("Anonymous cannot write " + testObj);
215        final HttpPatch requestPatch = patchObjMethod(id);
216        requestPatch.setEntity(new StringEntity("INSERT { <> <" + title.getURI() + "> \"Test title\" . } WHERE {}"));
217        requestPatch.setHeader("Content-type", "application/sparql-update");
218        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestPatch));
219
220        logger.debug("Editors can write " + testObj);
221        final HttpPatch requestPatch2 = patchObjMethod(id);
222        setAuth(requestPatch2, "jones");
223        requestPatch2.setHeader("some-header", "Editors");
224        requestPatch2.setEntity(
225                new StringEntity("INSERT { <> <" + title.getURI() + "> \"Different title\" . } WHERE {}"));
226        requestPatch2.setHeader("Content-type", "application/sparql-update");
227        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(requestPatch2));
228    }
229
230    @Test
231    public void scenario3() throws IOException {
232        final String idDark = "/rest/dark/archive";
233        final String idLight = "/rest/dark/archive/sunshine";
234        final String testObj = ingestObj(idDark);
235        final String testObj2 = ingestObjWithACL(idLight, "/acls/03/acl.ttl");
236        ingestAcl("fedoraAdmin", "/acls/03/acl.ttl", testObj + "/fcr:acl");
237
238        logger.debug("Anonymous can't read " + testObj);
239        final HttpGet requestGet = getObjMethod(idDark);
240        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestGet));
241
242        logger.debug("Restricted can read " + testObj);
243        final HttpGet requestGet2 = getObjMethod(idDark);
244        setAuth(requestGet2, "jones");
245        requestGet2.setHeader("some-header", "Restricted");
246        assertEquals(HttpStatus.SC_OK, getStatus(requestGet2));
247
248        logger.debug("Anonymous can read " + testObj2);
249        final HttpGet requestGet3 = getObjMethod(idLight);
250        assertEquals(HttpStatus.SC_OK, getStatus(requestGet3));
251
252        logger.debug("Restricted can read " + testObj2);
253        final HttpGet requestGet4 = getObjMethod(idLight);
254        setAuth(requestGet4, "jones");
255        requestGet4.setHeader("some-header", "Restricted");
256        assertEquals(HttpStatus.SC_OK, getStatus(requestGet4));
257    }
258
259    @Test
260    public void scenario4() throws IOException {
261        final String id = "/rest/public_collection";
262        final String testObj = ingestObjWithACL(id, "/acls/04/acl.ttl");
263
264        logger.debug("Anonymous can read " + testObj);
265        final HttpGet requestGet = getObjMethod(id);
266        assertEquals(HttpStatus.SC_OK, getStatus(requestGet));
267
268        logger.debug("Editors can read " + testObj);
269        final HttpGet requestGet2 = getObjMethod(id);
270        setAuth(requestGet2, "jones");
271        requestGet2.setHeader("some-header", "Editors");
272        assertEquals(HttpStatus.SC_OK, getStatus(requestGet2));
273
274        logger.debug("Smith can access " + testObj);
275        final HttpGet requestGet3 = getObjMethod(id);
276        setAuth(requestGet3, "smith");
277        assertEquals(HttpStatus.SC_OK, getStatus(requestGet3));
278
279        logger.debug("Anonymous can't write " + testObj);
280        final HttpPatch requestPatch = patchObjMethod(id);
281        requestPatch.setHeader("Content-type", "application/sparql-update");
282        requestPatch.setEntity(new StringEntity("INSERT { <> <" + title.getURI() + "> \"Change title\" . } WHERE {}"));
283        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestPatch));
284
285        logger.debug("Editors can write " + testObj);
286        final HttpPatch requestPatch2 = patchObjMethod(id);
287        requestPatch2.setHeader("Content-type", "application/sparql-update");
288        requestPatch2.setEntity(new StringEntity("INSERT { <> <" + title.getURI() + "> \"New title\" . } WHERE {}"));
289        setAuth(requestPatch2, "jones");
290        requestPatch2.setHeader("some-header", "Editors");
291        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(requestPatch2));
292
293        logger.debug("Editors can create (PUT) child objects of " + testObj);
294        final HttpPut requestPut1 = putObjMethod(id + "/child1");
295        setAuth(requestPut1, "jones");
296        requestPut1.setHeader("some-header", "Editors");
297        assertEquals(HttpStatus.SC_CREATED, getStatus(requestPut1));
298
299        final HttpGet requestGet4 = getObjMethod(id + "/child1");
300        setAuth(requestGet4, "jones");
301        requestGet4.setHeader("some-header", "Editors");
302        assertEquals(HttpStatus.SC_OK, getStatus(requestGet4));
303
304        logger.debug("Editors can create (POST) child objects of " + testObj);
305        final HttpPost requestPost1 = postObjMethod(id);
306        requestPost1.addHeader("Slug", "child2");
307        setAuth(requestPost1, "jones");
308        requestPost1.setHeader("some-header", "Editors");
309        assertEquals(HttpStatus.SC_CREATED, getStatus(requestPost1));
310
311        final HttpGet requestGet5 = getObjMethod(id + "/child2");
312        setAuth(requestGet5, "jones");
313        requestGet5.setHeader("some-header", "Editors");
314        assertEquals(HttpStatus.SC_OK, getStatus(requestGet5));
315
316        logger.debug("Editors can create nested child objects of " + testObj);
317        final HttpPut requestPut2 = putObjMethod(id + "/a/b/c/child");
318        setAuth(requestPut2, "jones");
319        requestPut2.setHeader("some-header", "Editors");
320        assertEquals(HttpStatus.SC_CREATED, getStatus(requestPut2));
321
322        final HttpGet requestGet6 = getObjMethod(id + "/a/b/c/child");
323        setAuth(requestGet6, "jones");
324        requestGet6.setHeader("some-header", "Editors");
325        assertEquals(HttpStatus.SC_OK, getStatus(requestGet6));
326
327        logger.debug("Smith can't write " + testObj);
328        final HttpPatch requestPatch3 = patchObjMethod(id);
329        requestPatch3.setHeader("Content-type", "application/sparql-update");
330        requestPatch3.setEntity(
331                new StringEntity("INSERT { <> <" + title.getURI() + "> \"Different title\" . } WHERE {}"));
332        setAuth(requestPatch3, "smith");
333        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestPatch3));
334    }
335
336    @Test
337    public void scenario5() throws IOException {
338        final String idPublic = "/rest/mixedCollection/publicObj";
339        final String idPrivate = "/rest/mixedCollection/privateObj";
340        ingestObjWithACL("/rest/mixedCollection", "/acls/05/acl.ttl");
341        final String publicObj = ingestObj(idPublic);
342        final String privateObj = ingestObj(idPrivate);
343        final HttpPatch patch = patchObjMethod(idPublic);
344
345        setAuth(patch, "fedoraAdmin");
346        patch.setHeader("Content-type", "application/sparql-update");
347        patch.setEntity(new StringEntity("INSERT { <> a <http://example.com/terms#publicImage> . } WHERE {}"));
348        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(patch));
349
350
351        logger.debug("Anonymous can see eg:publicImage " + publicObj);
352        final HttpGet requestGet = getObjMethod(idPublic);
353        assertEquals(HttpStatus.SC_OK, getStatus(requestGet));
354
355        logger.debug("Anonymous can't see other resource " + privateObj);
356        final HttpGet requestGet2 = getObjMethod(idPrivate);
357        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestGet2));
358
359        logger.debug("Admins can see eg:publicImage " + publicObj);
360        final HttpGet requestGet3 = getObjMethod(idPublic);
361        setAuth(requestGet3, "jones");
362        requestGet3.setHeader("some-header", "Admins");
363        assertEquals(HttpStatus.SC_OK, getStatus(requestGet3));
364
365        logger.debug("Admins can see others" + privateObj);
366        final HttpGet requestGet4 = getObjMethod(idPrivate);
367        setAuth(requestGet4, "jones");
368        requestGet4.setHeader("some-header", "Admins");
369        assertEquals(HttpStatus.SC_OK, getStatus(requestGet4));
370    }
371
372    @Test
373    public void scenario9() throws IOException {
374        final String idPublic = "/rest/anotherCollection/publicObj";
375        final String groups = "/rest/group";
376        final String fooGroup = groups + "/foo";
377        final String testObj = ingestObj("/rest/anotherCollection");
378        final String publicObj = ingestObj(idPublic);
379
380        final HttpPut request = putObjMethod(fooGroup);
381        setAuth(request, "fedoraAdmin");
382
383        final InputStream file = this.getClass().getResourceAsStream("/acls/09/group.ttl");
384        final InputStreamEntity fileEntity = new InputStreamEntity(file);
385        request.setEntity(fileEntity);
386        request.setHeader("Content-Type", "text/turtle;charset=UTF-8");
387
388        assertEquals("Didn't get a CREATED response!", CREATED.getStatusCode(), getStatus(request));
389
390        ingestAcl("fedoraAdmin", "/acls/09/acl.ttl", testObj + "/fcr:acl");
391
392        logger.debug("Person1 can see object " + publicObj);
393        final HttpGet requestGet1 = getObjMethod(idPublic);
394        setAuth(requestGet1, "person1");
395        assertEquals(HttpStatus.SC_OK, getStatus(requestGet1));
396
397        logger.debug("Person2 can see object " + publicObj);
398        final HttpGet requestGet2 = getObjMethod(idPublic);
399        setAuth(requestGet2, "person2");
400        assertEquals(HttpStatus.SC_OK, getStatus(requestGet2));
401
402        logger.debug("Person3 user cannot see object " + publicObj);
403        final HttpGet requestGet3 = getObjMethod(idPublic);
404        setAuth(requestGet3, "person3");
405        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestGet3));
406    }
407
408    /**
409     * Test cases to verify authorization with only acl:Append mode configured
410     * in the acl authorization of an resource.
411     * Tests:
412     *  1. Deny(403) on GET.
413     *  2. Allow(204) on PATCH.
414     *  3. Deny(403) on DELETE.
415     *  4. Deny(403) on PATCH with SPARQL DELETE statements.
416     *  5. Allow(400) on PATCH with empty SPARQL content.
417     *  6. Deny(403) on PATCH with non-SPARQL content.
418     */
419    @Test
420    public void scenario18Test1() throws IOException {
421        final String testObj = ingestObj("/rest/append_only_resource");
422        final String id = "/rest/append_only_resource/" + getRandomUniqueId();
423        ingestObj(id);
424
425        logger.debug("user18 can read (has ACL:READ): {}", id);
426        final HttpGet requestGet = getObjMethod(id);
427        setAuth(requestGet, "user18");
428        assertEquals(HttpStatus.SC_OK, getStatus(requestGet));
429
430        logger.debug("user18 can't append (no ACL): {}", id);
431        final HttpPatch requestPatch = patchObjMethod(id);
432        setAuth(requestPatch, "user18");
433        requestPatch.setHeader("Content-type", "application/sparql-update");
434        requestPatch.setEntity(new StringEntity("INSERT { <> <" + title.getURI() + "> \"Test title\" . } WHERE {}"));
435
436        logger.debug("user18 can't delete (no ACL): {}", id);
437        final HttpDelete requestDelete = deleteObjMethod(id);
438        setAuth(requestDelete, "user18");
439        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestDelete));
440
441        ingestAcl("fedoraAdmin", "/acls/18/append-only-acl.ttl", testObj + "/fcr:acl");
442
443        logger.debug("user18 still can't read (ACL append): {}", id);
444        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestGet));
445
446        logger.debug("user18 can patch - SPARQL INSERTs (ACL append): {}", id);
447        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(requestPatch));
448
449        // Alter the Content-type to include a character set, to ensure correct matching.
450        requestPatch.setHeader("Content-type", "application/sparql-update; charset=UTF-8");
451        logger.debug("user18 can patch - SPARQL INSERTs (ACL append with charset): {}", id);
452        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(requestPatch));
453
454        logger.debug("user18 still can't delete (ACL append): {}", id);
455        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestDelete));
456
457        requestPatch.setEntity(new StringEntity("DELETE { <> <" + title.getURI() + "> \"Test title\" . } WHERE {}"));
458
459        logger.debug("user18 can not patch - SPARQL DELETEs (ACL append): {}", id);
460        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestPatch));
461
462        requestPatch.setEntity(null);
463
464        logger.debug("user18 can patch (is authorized, but bad request) - Empty SPARQL (ACL append): {}", id);
465        assertEquals(HttpStatus.SC_BAD_REQUEST, getStatus(requestPatch));
466
467        requestPatch.setHeader("Content-type", null);
468
469        logger.debug("user18 can not patch - Non SPARQL (ACL append): {}", id);
470        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestPatch));
471
472    }
473
474    /**
475     * Test cases to verify authorization with acl:Read and acl:Append modes
476     * configured in the acl authorization of an resource.
477     * Tests:
478     *  1. Allow(200) on GET.
479     *  2. Allow(204) on PATCH.
480     *  3. Deny(403) on DELETE.
481     */
482    @Test
483    public void scenario18Test2() throws IOException {
484        final String testObj = ingestObj("/rest/read_append_resource");
485
486        final String id = "/rest/read_append_resource/" + getRandomUniqueId();
487        ingestObj(id);
488
489        logger.debug("user18 can read (has ACL:READ): {}", id);
490        final HttpGet requestGet = getObjMethod(id);
491        setAuth(requestGet, "user18");
492        assertEquals(HttpStatus.SC_OK, getStatus(requestGet));
493
494        logger.debug("user18 can't append (no ACL): {}", id);
495        final HttpPatch requestPatch = patchObjMethod(id);
496        setAuth(requestPatch, "user18");
497        requestPatch.setHeader("Content-type", "application/sparql-update");
498        requestPatch.setEntity(new StringEntity(
499                "INSERT { <> <" + title.getURI() + "> \"some title\" . } WHERE {}"));
500        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestPatch));
501
502        ingestAcl("fedoraAdmin", "/acls/18/read-append-acl.ttl", testObj + "/fcr:acl");
503
504        logger.debug("user18 can't delete (no ACL): {}", id);
505        final HttpDelete requestDelete = deleteObjMethod(id);
506        setAuth(requestDelete, "user18");
507        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestDelete));
508
509        logger.debug("user18 can read (ACL read, append): {}", id);
510        assertEquals(HttpStatus.SC_OK, getStatus(requestGet));
511
512        logger.debug("user18 can append (ACL read, append): {}", id);
513        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(requestPatch));
514
515        logger.debug("user18 still can't delete (ACL read, append): {}", id);
516        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestDelete));
517    }
518
519    /**
520     * Test cases to verify authorization with acl:Read, acl:Append and
521     * acl:Write modes configured in the acl authorization of an resource.
522     * Tests:
523     *  1. Allow(200) on GET.
524     *  2. Allow(204) on PATCH.
525     *  3. Allow(204) on DELETE.
526     */
527    @Test
528    public void scenario18Test3() throws IOException {
529        final String testObj = ingestObj("/rest/read_append_write_resource");
530
531        final String id = "/rest/read_append_write_resource/" + getRandomUniqueId();
532        ingestObj(id);
533
534        logger.debug("user18 can read (has ACL:READ): {}", id);
535        final HttpGet requestGet = getObjMethod(id);
536        setAuth(requestGet, "user18");
537        assertEquals(HttpStatus.SC_OK, getStatus(requestGet));
538
539        logger.debug("user18 can't append (no ACL): {}", id);
540        final HttpPatch requestPatch = patchObjMethod(id);
541        setAuth(requestPatch, "user18");
542        requestPatch.setHeader("Content-type", "application/sparql-update");
543        requestPatch.setEntity(new StringEntity(
544                "INSERT { <> <http://purl.org/dc/elements/1.1/title> \"some title\" . } WHERE {}"));
545        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestPatch));
546
547        logger.debug("user18 can't delete (no ACL): {}", id);
548        final HttpDelete requestDelete = deleteObjMethod(id);
549        setAuth(requestDelete, "user18");
550        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestDelete));
551
552        ingestAcl("fedoraAdmin", "/acls/18/read-append-write-acl.ttl", testObj + "/fcr:acl");
553
554        logger.debug("user18 can read (ACL read, append, write): {}", id);
555        assertEquals(HttpStatus.SC_OK, getStatus(requestGet));
556
557        logger.debug("user18 can append (ACL read, append, write): {}", id);
558        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(requestPatch));
559
560        logger.debug("user18 can delete (ACL read, append, write): {}", id);
561        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(requestDelete));
562    }
563
564    @Test
565    public void testAccessToRoot() throws IOException {
566        final String id = "/rest/" + getRandomUniqueId();
567        final String testObj = ingestObj(id);
568
569        logger.debug("Anonymous can read (has ACL:READ): {}", id);
570        final HttpGet requestGet1 = getObjMethod(id);
571        assertEquals(HttpStatus.SC_OK, getStatus(requestGet1));
572
573        logger.debug("Can username 'user06a' read {} (has ACL:READ)", id);
574        final HttpGet requestGet2 = getObjMethod(id);
575        setAuth(requestGet2, "user06a");
576        assertEquals(HttpStatus.SC_OK, getStatus(requestGet2));
577
578        logger.debug("Can username 'notuser06b' read {} (has ACL:READ)", id);
579        final HttpGet requestGet3 = getObjMethod(id);
580        setAuth(requestGet3, "user06b");
581        assertEquals(HttpStatus.SC_OK, getStatus(requestGet3));
582
583        System.setProperty(ROOT_AUTHORIZATION_PROPERTY, "./target/test-classes/test-root-authorization2.ttl");
584        logger.debug("Can username 'user06a' read {} (overridden system ACL)", id);
585        final HttpGet requestGet4 = getObjMethod(id);
586        setAuth(requestGet4, "user06a");
587        assertEquals(HttpStatus.SC_OK, getStatus(requestGet4));
588        System.clearProperty(ROOT_AUTHORIZATION_PROPERTY);
589
590        // Add ACL to root
591        final String rootURI = getObjMethod("/rest").getURI().toString();
592        ingestAcl("fedoraAdmin", "/acls/06/acl.ttl", rootURI + "/fcr:acl");
593
594        logger.debug("Anonymous still can't read (ACL present)");
595        final HttpGet requestGet5 = getObjMethod(id);
596        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestGet5));
597
598        logger.debug("Can username 'user06a' read {} (ACL present)", testObj);
599        final HttpGet requestGet6 = getObjMethod(id);
600        setAuth(requestGet6, "user06a");
601        assertEquals(HttpStatus.SC_OK, getStatus(requestGet6));
602
603        logger.debug("Can username 'user06b' read {} (ACL present)", testObj);
604        final HttpGet requestGet7 = getObjMethod(id);
605        setAuth(requestGet7, "user06b");
606        assertEquals(HttpStatus.SC_OK, getStatus(requestGet7));
607    }
608
609    @Test
610    public void scenario21TestACLNotForInheritance() throws IOException {
611        final String parentPath = "/rest/resource_acl_no_inheritance";
612        // Ingest ACL with no acl:default statement to the parent resource
613        ingestObjWithACL(parentPath, "/acls/21/acl.ttl");
614
615        final String id = parentPath + "/" + getRandomUniqueId();
616        final String testObj = ingestObj(id);
617
618
619        // Test the parent ACL with no acl:default is applied for the parent resource authorization.
620        final HttpGet requestGet1 = getObjMethod(parentPath);
621        setAuth(requestGet1, "user21");
622        assertEquals("Agent user21 can't read resource " + parentPath + " with its own ACL!",
623                HttpStatus.SC_OK, getStatus(requestGet1));
624
625        final HttpGet requestGet2 = getObjMethod(id);
626        assertEquals("Agent user21 inherits read permission from parent ACL to read resource " + testObj + "!",
627                HttpStatus.SC_OK, getStatus(requestGet2));
628
629        // Test the default root ACL is inherited for authorization while the parent ACL with no acl:default is ignored
630        System.setProperty(ROOT_AUTHORIZATION_PROPERTY, "./target/test-classes/test-root-authorization2.ttl");
631        final HttpGet requestGet3 = getObjMethod(id);
632        setAuth(requestGet3, "user06a");
633        assertEquals("Agent user06a can't inherit read persmssion from root ACL to read resource " + testObj + "!",
634                HttpStatus.SC_OK, getStatus(requestGet3));
635    }
636
637    @Test
638    public void scenario22TestACLAuthorizationNotForInheritance() throws IOException {
639        final String parentPath = "/rest/resource_mix_acl_default";
640        final String parentObj = ingestObj(parentPath);
641
642        final String id = parentPath + "/" + getRandomUniqueId();
643        final String testObj = ingestObj(id);
644
645        // Ingest ACL with mix acl:default authorization to the parent resource
646        ingestAcl("fedoraAdmin", "/acls/22/acl.ttl", parentObj + "/fcr:acl");
647
648        // Test the parent ACL is applied for the parent resource authorization.
649        final HttpGet requestGet1 = getObjMethod(parentPath);
650        setAuth(requestGet1, "user22a");
651        assertEquals("Agent user22a can't read resource " + parentPath + " with its own ACL!",
652                HttpStatus.SC_OK, getStatus(requestGet1));
653
654        final HttpGet requestGet2 = getObjMethod(parentPath);
655        setAuth(requestGet2, "user22b");
656        assertEquals("Agent user22b can't read resource " + parentPath + " with its own ACL!",
657                HttpStatus.SC_OK, getStatus(requestGet1));
658
659        // Test the parent ACL is applied for the parent resource authorization.
660        final HttpGet requestGet3 = getObjMethod(id);
661        setAuth(requestGet3, "user22a");
662        assertEquals("Agent user22a inherits read permission from parent ACL to read resource " + testObj + "!",
663                HttpStatus.SC_FORBIDDEN, getStatus(requestGet3));
664
665        final HttpGet requestGet4 = getObjMethod(id);
666        setAuth(requestGet4, "user22b");
667        assertEquals("Agent user22b can't inherits read permission from parent ACL to read resource " + testObj + "!",
668                HttpStatus.SC_OK, getStatus(requestGet4));
669    }
670
671    @Test
672    public void testAccessToBinary() throws IOException {
673        // Block access to "book"
674        final String idBook = "/rest/book";
675        final String bookURI = ingestObj(idBook);
676
677        // Open access datastream, "file"
678        final String id = idBook + "/file";
679        final String testObj = ingestDatastream(idBook, "file");
680        ingestAcl("fedoraAdmin", "/acls/07/acl.ttl", bookURI + "/fcr:acl");
681
682        logger.debug("Anonymous can't read");
683        final HttpGet requestGet1 = getObjMethod(id);
684        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestGet1));
685
686        logger.debug("Can username 'user07' read {}", testObj);
687        final HttpGet requestGet2 = getObjMethod(id);
688
689        setAuth(requestGet2, "user07");
690        assertEquals(HttpStatus.SC_OK, getStatus(requestGet2));
691    }
692
693    @Test
694    @Ignore("FAILING")
695    public void testAccessToHashResource() throws IOException {
696        final String id = "/rest/some/parent#hash-resource";
697        final String testObj = ingestObj(id);
698        ingestAcl("fedoraAdmin", "/acls/08/acl.ttl", testObj + "/fcr:acl");
699
700        logger.debug("Anonymous can't read");
701        final HttpGet requestGet1 = getObjMethod(id);
702        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestGet1));
703
704        logger.debug("Can username 'user08' read {}", testObj);
705        final HttpGet requestGet2 = getObjMethod(id);
706        setAuth(requestGet2, "user08");
707        assertEquals(HttpStatus.SC_OK, getStatus(requestGet2));
708    }
709
710    @Test
711    @Ignore ("Until implemented with Memento")
712    public void testAccessToVersionedResources() throws IOException {
713        final String idVersion = "/rest/versionResource";
714        ingestObj(idVersion);
715
716        final HttpPatch requestPatch1 = patchObjMethod(idVersion);
717        setAuth(requestPatch1, "fedoraAdmin");
718        requestPatch1.addHeader("Content-type", "application/sparql-update");
719        requestPatch1.setEntity(
720                new StringEntity("PREFIX pcdm: <http://pcdm.org/models#> INSERT { <> a pcdm:Object } WHERE {}"));
721        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(requestPatch1));
722
723        ingestAcl("fedoraAdmin", "/acls/10/acl.ttl", idVersion + "/fcr:acl");
724
725        final HttpGet requestGet1 = getObjMethod(idVersion);
726        setAuth(requestGet1, "user10");
727        assertEquals("user10 can't read object", HttpStatus.SC_OK, getStatus(requestGet1));
728
729        final HttpPost requestPost1 = postObjMethod(idVersion + "/fcr:versions");
730        requestPost1.addHeader("Slug", "v0");
731        setAuth(requestPost1, "fedoraAdmin");
732        assertEquals("Unable to create a new version", HttpStatus.SC_CREATED, getStatus(requestPost1));
733
734        final HttpGet requestGet2 = getObjMethod(idVersion);
735        setAuth(requestGet2, "user10");
736        assertEquals("user10 can't read versioned object", HttpStatus.SC_OK, getStatus(requestGet2));
737    }
738
739    @Test
740    public void testDelegatedUserAccess() throws IOException {
741        logger.debug("testing delegated authentication");
742        final String targetPath = "/rest/foo";
743        final String targetResource = ingestObj(targetPath);
744
745        ingestAcl("fedoraAdmin", "/acls/11/acl.ttl", targetResource + "/fcr:acl");
746
747        final HttpGet adminGet = getObjMethod(targetPath);
748        setAuth(adminGet, "fedoraAdmin");
749        assertEquals("admin can read object", HttpStatus.SC_OK, getStatus(adminGet));
750
751        final HttpGet adminDelegatedGet = getObjMethod(targetPath);
752        setAuth(adminDelegatedGet, "fedoraAdmin");
753        adminDelegatedGet.addHeader("On-Behalf-Of", "user11");
754        assertEquals("delegated user can read object", HttpStatus.SC_OK, getStatus(adminDelegatedGet));
755
756        final HttpGet adminUnauthorizedDelegatedGet = getObjMethod(targetPath);
757        setAuth(adminUnauthorizedDelegatedGet, "fedoraAdmin");
758        adminUnauthorizedDelegatedGet.addHeader("On-Behalf-Of", "fakeuser");
759        assertEquals("delegated fakeuser cannot read object", HttpStatus.SC_FORBIDDEN,
760                getStatus(adminUnauthorizedDelegatedGet));
761
762        final HttpGet adminDelegatedGet2 = getObjMethod(targetPath);
763        setAuth(adminDelegatedGet2, "fedoraAdmin");
764        adminDelegatedGet2.addHeader("On-Behalf-Of", "info:user/user2");
765        assertEquals("delegated user can read object", HttpStatus.SC_OK, getStatus(adminDelegatedGet2));
766
767        final HttpGet adminUnauthorizedDelegatedGet2 = getObjMethod(targetPath);
768        setAuth(adminUnauthorizedDelegatedGet2, "fedoraAdmin");
769        adminUnauthorizedDelegatedGet2.addHeader("On-Behalf-Of", "info:user/fakeuser");
770        assertEquals("delegated fakeuser cannot read object", HttpStatus.SC_FORBIDDEN,
771                getStatus(adminUnauthorizedDelegatedGet2));
772
773        // Now test with the system property in effect
774        System.setProperty(USER_AGENT_BASE_URI_PROPERTY, "info:user/");
775        System.setProperty(GROUP_AGENT_BASE_URI_PROPERTY, "info:group/");
776
777        final HttpGet adminDelegatedGet3 = getObjMethod(targetPath);
778        setAuth(adminDelegatedGet3, "fedoraAdmin");
779        adminDelegatedGet3.addHeader("On-Behalf-Of", "info:user/user2");
780        assertEquals("delegated user can read object", HttpStatus.SC_OK, getStatus(adminDelegatedGet3));
781
782        final HttpGet adminUnauthorizedDelegatedGet3 = getObjMethod(targetPath);
783        setAuth(adminUnauthorizedDelegatedGet3, "fedoraAdmin");
784        adminUnauthorizedDelegatedGet3.addHeader("On-Behalf-Of", "info:user/fakeuser");
785        assertEquals("delegated fakeuser cannot read object", HttpStatus.SC_FORBIDDEN,
786                getStatus(adminUnauthorizedDelegatedGet3));
787
788        System.clearProperty(USER_AGENT_BASE_URI_PROPERTY);
789        System.clearProperty(GROUP_AGENT_BASE_URI_PROPERTY);
790    }
791
792    @Test
793    @Ignore ("Until implemented with Memento")
794
795    public void testAccessByUriToVersionedResources() throws IOException {
796        final String idVersion = "/rest/versionResourceUri";
797        ingestObj(idVersion);
798
799        ingestAcl("fedoraAdmin", "/acls/12/acl.ttl", idVersion + "/fcr:acl");
800
801        final HttpGet requestGet1 = getObjMethod(idVersion);
802        setAuth(requestGet1, "user12");
803        assertEquals("testuser can't read object", HttpStatus.SC_OK, getStatus(requestGet1));
804
805        final HttpPost requestPost1 = postObjMethod(idVersion + "/fcr:versions");
806        requestPost1.addHeader("Slug", "v0");
807        setAuth(requestPost1, "user12");
808        assertEquals("Unable to create a new version", HttpStatus.SC_CREATED, getStatus(requestPost1));
809
810        final HttpGet requestGet2 = getObjMethod(idVersion);
811        setAuth(requestGet2, "user12");
812        assertEquals("testuser can't read versioned object", HttpStatus.SC_OK, getStatus(requestGet2));
813    }
814
815    @Test
816    public void testAgentAsUri() throws IOException {
817        final String id = "/rest/" + getRandomUniqueId();
818        final String testObj = ingestObj(id);
819
820        logger.debug("Anonymous can read (has ACL:READ): {}", id);
821        final HttpGet requestGet1 = getObjMethod(id);
822        assertEquals(HttpStatus.SC_OK, getStatus(requestGet1));
823
824        logger.debug("Can username 'smith123' read {} (no ACL)", id);
825        final HttpGet requestGet2 = getObjMethod(id);
826        setAuth(requestGet2, "smith123");
827        assertEquals(HttpStatus.SC_OK, getStatus(requestGet2));
828
829        System.setProperty(USER_AGENT_BASE_URI_PROPERTY, "info:user/");
830        System.setProperty(GROUP_AGENT_BASE_URI_PROPERTY, "info:group/");
831
832        logger.debug("Can username 'smith123' read {} (overridden system ACL)", id);
833        final HttpGet requestGet3 = getObjMethod(id);
834        setAuth(requestGet3, "smith123");
835        assertEquals(HttpStatus.SC_OK, getStatus(requestGet3));
836
837        logger.debug("Can username 'group123' read {} (overridden system ACL)", id);
838        final HttpGet requestGet4 = getObjMethod(id);
839        setAuth(requestGet4, "group123");
840        assertEquals(HttpStatus.SC_OK, getStatus(requestGet4));
841
842        System.clearProperty(USER_AGENT_BASE_URI_PROPERTY);
843        System.clearProperty(GROUP_AGENT_BASE_URI_PROPERTY);
844
845        // Add ACL to object
846        ingestAcl("fedoraAdmin", "/acls/16/acl.ttl", testObj + "/fcr:acl");
847
848        logger.debug("Anonymous still can't read (ACL present)");
849        final HttpGet requestGet5 = getObjMethod(id);
850        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestGet5));
851
852        logger.debug("Can username 'smith123' read {} (ACL present, no system properties)", testObj);
853        final HttpGet requestGet6 = getObjMethod(id);
854        setAuth(requestGet6, "smith123");
855        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(requestGet6));
856
857        System.setProperty(USER_AGENT_BASE_URI_PROPERTY, "info:user/");
858        System.setProperty(GROUP_AGENT_BASE_URI_PROPERTY, "info:group/");
859
860        logger.debug("Can username 'smith123' read {} (ACL, system properties present)", id);
861        final HttpGet requestGet7 = getObjMethod(id);
862        setAuth(requestGet7, "smith123");
863        assertEquals(HttpStatus.SC_OK, getStatus(requestGet7));
864
865        logger.debug("Can groupname 'group123' read {} (ACL, system properties present)", id);
866        final HttpGet requestGet8 = getObjMethod(id);
867        setAuth(requestGet8, "group123");
868        assertEquals(HttpStatus.SC_OK, getStatus(requestGet8));
869
870        System.clearProperty(USER_AGENT_BASE_URI_PROPERTY);
871        System.clearProperty(GROUP_AGENT_BASE_URI_PROPERTY);
872    }
873
874    @Test
875    public void testRegisterNamespace() throws IOException {
876        final String testObj = ingestObj("/rest/test_namespace");
877        ingestAcl("fedoraAdmin", "/acls/13/acl.ttl", testObj + "/fcr:acl");
878
879        final String id = "/rest/test_namespace/" + getRandomUniqueId();
880        ingestObj(id);
881
882        final HttpPatch patchReq = patchObjMethod(id);
883        setAuth(patchReq, "user13");
884        patchReq.addHeader("Content-type", "application/sparql-update");
885        patchReq.setEntity(new StringEntity("PREFIX novel: <info://" + getRandomUniqueId() + ">\n"
886                + "INSERT DATA { <> novel:value 'test' }"));
887        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(patchReq));
888    }
889
890    @Test
891    public void testRegisterNodeType() throws IOException {
892        final String testObj = ingestObj("/rest/test_nodetype");
893        ingestAcl("fedoraAdmin", "/acls/14/acl.ttl", testObj + "/fcr:acl");
894
895        final String id = "/rest/test_nodetype/" + getRandomUniqueId();
896        ingestObj(id);
897
898        final HttpPatch patchReq = patchObjMethod(id);
899        setAuth(patchReq, "user14");
900        patchReq.addHeader("Content-type", "application/sparql-update");
901        patchReq.setEntity(new StringEntity("PREFIX dc: <http://purl.org/dc/elements/1.1/>\n"
902                + "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n"
903                + "INSERT DATA { <> rdf:type dc:type }"));
904        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(patchReq));
905    }
906
907
908    @Test
909    public void testDeletePropertyAsUser() throws IOException {
910        final String testObj = ingestObj("/rest/test_delete");
911        ingestAcl("fedoraAdmin", "/acls/15/acl.ttl", testObj + "/fcr:acl");
912
913        final String id = "/rest/test_delete/" + getRandomUniqueId();
914        ingestObj(id);
915
916        HttpPatch patchReq = patchObjMethod(id);
917        setAuth(patchReq, "user15");
918        patchReq.addHeader("Content-type", "application/sparql-update");
919        patchReq.setEntity(new StringEntity("PREFIX dc: <http://purl.org/dc/elements/1.1/>\n"
920                + "INSERT DATA { <> dc:title 'title' . " +
921                "                <> dc:rights 'rights' . }"));
922        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(patchReq));
923
924        patchReq = patchObjMethod(id);
925        setAuth(patchReq, "user15");
926        patchReq.addHeader("Content-type", "application/sparql-update");
927        patchReq.setEntity(new StringEntity("PREFIX dc: <http://purl.org/dc/elements/1.1/>\n"
928                + "DELETE { <> dc:title ?any . } WHERE { <> dc:title ?any . }"));
929        assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(patchReq));
930
931        patchReq = patchObjMethod(id);
932        setAuth(patchReq, "notUser15");
933        patchReq.addHeader("Content-type", "application/sparql-update");
934        patchReq.setEntity(new StringEntity("PREFIX dc: <http://purl.org/dc/elements/1.1/>\n"
935                + "DELETE { <> dc:rights ?any . } WHERE { <> dc:rights ?any . }"));
936        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(patchReq));
937    }
938
939    @Test
940    public void testHeadWithReadOnlyUser() throws IOException {
941        final String testObj = ingestObj("/rest/test_head");
942        ingestAcl("fedoraAdmin", "/acls/19/acl.ttl", testObj + "/fcr:acl");
943
944        final HttpHead headReq = new HttpHead(testObj);
945        setAuth(headReq, "user19");
946        assertEquals(HttpStatus.SC_OK, getStatus(headReq));
947    }
948
949    @Test
950    public void testOptionsWithReadOnlyUser() throws IOException {
951        final String testObj = ingestObj("/rest/test_options");
952        ingestAcl("fedoraAdmin", "/acls/20/acl.ttl", testObj + "/fcr:acl");
953
954        final HttpOptions optionsReq = new HttpOptions(testObj);
955        setAuth(optionsReq, "user20");
956        assertEquals(HttpStatus.SC_OK, getStatus(optionsReq));
957    }
958
959    private static HttpResponse HEAD(final String requestURI) throws IOException {
960        return HEAD(requestURI, "fedoraAdmin");
961    }
962
963    private static HttpResponse HEAD(final String requestURI, final String username) throws IOException {
964        final HttpHead req = new HttpHead(requestURI);
965        setAuth(req, username);
966        return execute(req);
967    }
968
969    private static HttpResponse PUT(final String requestURI) throws IOException {
970        return PUT(requestURI, "fedoraAdmin");
971    }
972
973    private static HttpResponse PUT(final String requestURI, final String username) throws IOException {
974        final HttpPut req = new HttpPut(requestURI);
975        setAuth(req, username);
976        return execute(req);
977    }
978
979    private static HttpResponse DELETE(final String requestURI, final String username) throws IOException {
980        final HttpDelete req = new HttpDelete(requestURI);
981        setAuth(req, username);
982        return execute(req);
983    }
984
985    private static HttpResponse GET(final String requestURI, final String username) throws IOException {
986        final HttpGet req = new HttpGet(requestURI);
987        setAuth(req, username);
988        return execute(req);
989    }
990
991    private static HttpResponse PATCH(final String requestURI, final HttpEntity body, final String username)
992            throws IOException {
993        final HttpPatch req = new HttpPatch(requestURI);
994        setAuth(req, username);
995        if (body != null) {
996            req.setEntity(body);
997        }
998        return execute(req);
999    }
1000
1001    private static String getLink(final HttpResponse res) {
1002        for (final Header h : res.getHeaders("Link")) {
1003            final HeaderElement link = h.getElements()[0];
1004            for (final NameValuePair param : link.getParameters()) {
1005                if (param.getName().equals("rel") && param.getValue().equals("acl")) {
1006                    return link.getName().replaceAll("^<|>$", "");
1007                }
1008            }
1009        }
1010        return null;
1011    }
1012
1013    private String ingestObjWithACL(final String path, final String aclResourcePath) throws IOException {
1014        final String newURI = ingestObj(path);
1015        final HttpResponse res = HEAD(newURI);
1016        final String aclURI = getLink(res);
1017
1018        logger.debug("Creating ACL at {}", aclURI);
1019        ingestAcl("fedoraAdmin", aclResourcePath, aclURI);
1020
1021        return newURI;
1022    }
1023
1024    @Test
1025    public void testControl() throws IOException {
1026        final String controlObj = ingestObjWithACL("/rest/control", "/acls/25/control.ttl");
1027        final String readwriteObj = ingestObjWithACL("/rest/readwrite", "/acls/25/readwrite.ttl");
1028
1029        final String rwChildACL = getLink(PUT(readwriteObj + "/child"));
1030        assertEquals(SC_FORBIDDEN, getStatus(HEAD(rwChildACL, "testuser")));
1031        assertEquals(SC_FORBIDDEN, getStatus(GET(rwChildACL, "testuser")));
1032        assertEquals(SC_FORBIDDEN, getStatus(PUT(rwChildACL, "testuser")));
1033        assertEquals(SC_FORBIDDEN, getStatus(DELETE(rwChildACL, "testuser")));
1034
1035        final String controlChildACL = getLink(PUT(controlObj + "/child"));
1036        assertEquals(SC_NOT_FOUND, getStatus(HEAD(controlChildACL, "testuser")));
1037        assertEquals(SC_NOT_FOUND, getStatus(GET(controlChildACL, "testuser")));
1038
1039        ingestAcl("testuser", "/acls/25/child-control.ttl", controlChildACL);
1040        final StringEntity sparqlUpdate = new StringEntity(
1041                "PREFIX acl: <http://www.w3.org/ns/auth/acl#>  INSERT { <#restricted> acl:mode acl:Read } WHERE { }",
1042                ContentType.create("application/sparql-update"));
1043        assertEquals(SC_NO_CONTENT, getStatus(PATCH(controlChildACL, sparqlUpdate, "testuser")));
1044
1045        assertEquals(SC_NO_CONTENT, getStatus(DELETE(controlChildACL, "testuser")));
1046    }
1047
1048    @Test
1049    public void testAppendOnlyToContainer() throws IOException {
1050        final String testObj = ingestObj("/rest/test_append");
1051        ingestAcl("fedoraAdmin", "/acls/23/acl.ttl", testObj + "/fcr:acl");
1052        final String username = "user23";
1053
1054        final HttpOptions optionsReq = new HttpOptions(testObj);
1055        setAuth(optionsReq, username);
1056        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(optionsReq));
1057
1058        final HttpHead headReq = new HttpHead(testObj);
1059        setAuth(headReq, username);
1060        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(headReq));
1061
1062        final HttpGet getReq = new HttpGet(testObj);
1063        setAuth(getReq, username);
1064        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(getReq));
1065
1066        final HttpPut putReq = new HttpPut(testObj);
1067        setAuth(putReq, username);
1068        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(putReq));
1069
1070        final HttpDelete deleteReq = new HttpDelete(testObj);
1071        setAuth(deleteReq, username);
1072        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(deleteReq));
1073
1074        final HttpPost postReq = new HttpPost(testObj);
1075        setAuth(postReq, username);
1076        assertEquals(HttpStatus.SC_CREATED, getStatus(postReq));
1077
1078        final String[] legalSPARQLQueries = new String[] {
1079            "INSERT DATA { <> <http://purl.org/dc/terms/title> \"Test23\" . }",
1080            "INSERT { <> <http://purl.org/dc/terms/alternative> \"Test XXIII\" . } WHERE {}",
1081            "DELETE {} INSERT { <> <http://purl.org/dc/terms/description> \"Test append only\" . } WHERE {}"
1082        };
1083        for (final String query : legalSPARQLQueries) {
1084            final HttpPatch patchReq = new HttpPatch(testObj);
1085            setAuth(patchReq, username);
1086            patchReq.setEntity(new StringEntity(query));
1087            patchReq.setHeader("Content-Type", "application/sparql-update");
1088            logger.debug("Testing SPARQL update: {}", query);
1089            assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(patchReq));
1090        }
1091
1092        final String[] illegalSPARQLQueries = new String[] {
1093            "DELETE DATA { <> <http://purl.org/dc/terms/title> \"Test23\" . }",
1094            "DELETE { <> <http://purl.org/dc/terms/alternative> \"Test XXIII\" . } WHERE {}",
1095            "DELETE { <> <http://purl.org/dc/terms/description> \"Test append only\" . } INSERT {} WHERE {}"
1096        };
1097        for (final String query : illegalSPARQLQueries) {
1098            final HttpPatch patchReq = new HttpPatch(testObj);
1099            setAuth(patchReq, username);
1100            patchReq.setEntity(new StringEntity(query));
1101            patchReq.setHeader("Content-Type", "application/sparql-update");
1102            logger.debug("Testing SPARQL update: {}", query);
1103            assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(patchReq));
1104        }
1105        final String[] allowedDeleteSPARQLQueries = new String[] {
1106            "DELETE DATA {}",
1107            "DELETE { } WHERE {}",
1108            "DELETE { } INSERT {} WHERE {}"
1109        };
1110        for (final String query : allowedDeleteSPARQLQueries) {
1111            final HttpPatch patchReq = new HttpPatch(testObj);
1112            setAuth(patchReq, username);
1113            patchReq.setEntity(new StringEntity(query));
1114            patchReq.setHeader("Content-Type", "application/sparql-update");
1115            logger.debug("Testing SPARQL update: {}", query);
1116            assertEquals(HttpStatus.SC_NO_CONTENT, getStatus(patchReq));
1117        }
1118
1119    }
1120
1121    @Test
1122    public void testAppendOnlyToBinary() throws IOException {
1123        final String testObj = ingestBinary("/rest/test_append_binary", new StringEntity("foo"));
1124        ingestAcl("fedoraAdmin", "/acls/24/acl.ttl", testObj + "/fcr:acl");
1125        final String username = "user24";
1126
1127        final HttpOptions optionsReq = new HttpOptions(testObj);
1128        setAuth(optionsReq, username);
1129        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(optionsReq));
1130
1131        final HttpHead headReq = new HttpHead(testObj);
1132        setAuth(headReq, username);
1133        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(headReq));
1134
1135        final HttpGet getReq = new HttpGet(testObj);
1136        setAuth(getReq, username);
1137        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(getReq));
1138
1139        final HttpPut putReq = new HttpPut(testObj);
1140        setAuth(putReq, username);
1141        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(putReq));
1142
1143        final HttpDelete deleteReq = new HttpDelete(testObj);
1144        setAuth(deleteReq, username);
1145        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(deleteReq));
1146
1147        final HttpPost postReq = new HttpPost(testObj);
1148        setAuth(postReq, username);
1149        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(postReq));
1150    }
1151
1152    @Test
1153    public void testFoafAgent() throws IOException {
1154        final String path = ingestObj("/rest/foaf-agent");
1155        ingestAcl("fedoraAdmin", "/acls/26/foaf-agent.ttl", path + "/fcr:acl");
1156        final String username = "user1";
1157
1158        final HttpGet req = new HttpGet(path);
1159
1160        //NB: Actually no authentication headers should be set for this test
1161        //since the point of foaf:Agent is to allow unauthenticated access for everyone.
1162        //However at this time the test integration test server requires callers to
1163        //authenticate.
1164        setAuth(req, username);
1165
1166        assertEquals(HttpStatus.SC_OK, getStatus(req));
1167    }
1168
1169    @Test
1170    public void testAuthenticatedAgent() throws IOException {
1171        final String path = ingestObj("/rest/authenticated-agent");
1172        ingestAcl("fedoraAdmin", "/acls/26/authenticated-agent.ttl", path + "/fcr:acl");
1173        final String username = "user1";
1174
1175        final HttpGet darkReq = new HttpGet(path);
1176        setAuth(darkReq, username);
1177        assertEquals(HttpStatus.SC_OK, getStatus(darkReq));
1178    }
1179
1180    @Test
1181    public void testAgentGroupWithHashUris() throws Exception {
1182        ingestTurtleResource("fedoraAdmin", "/acls/agent-group-list.ttl",
1183                             serverAddress + "/rest/agent-group-list");
1184        //check that the authorized are authorized.
1185        final String authorized = ingestObj("/rest/agent-group-with-hash-uri-authorized");
1186        ingestAcl("fedoraAdmin", "/acls/agent-group-with-hash-uri-authorized.ttl", authorized + "/fcr:acl");
1187
1188        final HttpGet getAuthorized = new HttpGet(authorized);
1189        setAuth(getAuthorized, "testuser");
1190        assertEquals(HttpStatus.SC_OK, getStatus(getAuthorized));
1191
1192        //check that the unauthorized are unauthorized.
1193        final String unauthorized = ingestObj("/rest/agent-group-with-hash-uri-unauthorized");
1194        ingestAcl("fedoraAdmin", "/acls/agent-group-with-hash-uri-unauthorized.ttl", unauthorized + "/fcr:acl");
1195
1196        final HttpGet getUnauthorized = new HttpGet(unauthorized);
1197        setAuth(getUnauthorized, "testuser");
1198        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(getUnauthorized));
1199    }
1200
1201    @Test
1202    public void testAgentGroupWithMembersAsURIs() throws Exception {
1203        System.setProperty(USER_AGENT_BASE_URI_PROPERTY, "http://example.com/");
1204        ingestTurtleResource("fedoraAdmin", "/acls/agent-group-list-with-member-uris.ttl",
1205                             serverAddress + "/rest/agent-group-list-with-member-uris");
1206        final String authorized = ingestObj("/rest/agent-group-with-vcard-member-as-uri");
1207        ingestAcl("fedoraAdmin", "/acls/agent-group-with-vcard-member-as-uri.ttl", authorized + "/fcr:acl");
1208        //check that test user is authorized to write
1209        final HttpPut childPut = new HttpPut(authorized + "/child");
1210        setAuth(childPut, "testuser");
1211        assertEquals(HttpStatus.SC_CREATED, getStatus(childPut));
1212    }
1213
1214    @Test
1215    public void testAgentGroup() throws Exception {
1216        ingestTurtleResource("fedoraAdmin", "/acls/agent-group-list-flat.ttl",
1217                             serverAddress + "/rest/agent-group-list-flat");
1218        //check that the authorized are authorized.
1219        final String flat = ingestObj("/rest/agent-group-flat");
1220        ingestAcl("fedoraAdmin", "/acls/agent-group-flat.ttl", flat + "/fcr:acl");
1221
1222        final HttpGet getFlat = new HttpGet(flat);
1223        setAuth(getFlat, "testuser");
1224        assertEquals(HttpStatus.SC_OK, getStatus(getFlat));
1225    }
1226
1227    @Test
1228    public void testAclAppendPermissions() throws Exception {
1229        final String testObj = ingestBinary("/rest/test-read-append", new StringEntity("foo"));
1230        ingestAcl("fedoraAdmin", "/acls/27/read-append.ttl", testObj + "/fcr:acl");
1231        final String username = "user27";
1232
1233        final HttpOptions optionsReq = new HttpOptions(testObj);
1234        setAuth(optionsReq, username);
1235        assertEquals(HttpStatus.SC_OK, getStatus(optionsReq));
1236
1237        final HttpHead headReq = new HttpHead(testObj);
1238        setAuth(headReq, username);
1239        assertEquals(HttpStatus.SC_OK, getStatus(headReq));
1240
1241        final HttpGet getReq = new HttpGet(testObj);
1242        setAuth(getReq, username);
1243        final String descriptionUri;
1244        try (final CloseableHttpResponse response = execute(getReq)) {
1245            assertEquals(HttpStatus.SC_OK, getStatus(response));
1246            descriptionUri = Arrays.stream(response.getHeaders("Link"))
1247                    .flatMap(header -> Arrays.stream(header.getValue().split(","))).map(linkStr -> Link.valueOf(
1248                            linkStr))
1249                    .filter(link -> link.getRels().contains("describedby")).map(link -> link.getUri().toString())
1250                    .findFirst().orElse(null);
1251        }
1252
1253
1254        final HttpPut putReq = new HttpPut(testObj);
1255        setAuth(putReq, username);
1256        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(putReq));
1257
1258        final HttpDelete deleteReq = new HttpDelete(testObj);
1259        setAuth(deleteReq, username);
1260        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(deleteReq));
1261
1262        final HttpPost postReq = new HttpPost(testObj);
1263        setAuth(postReq, username);
1264        assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(postReq));
1265
1266        if (descriptionUri != null) {
1267            final HttpOptions optionsDescReq = new HttpOptions(descriptionUri);
1268            setAuth(optionsDescReq, username);
1269            assertEquals(HttpStatus.SC_OK, getStatus(optionsDescReq));
1270
1271            final HttpHead headDescReq = new HttpHead(descriptionUri);
1272            setAuth(headDescReq, username);
1273            assertEquals(HttpStatus.SC_OK, getStatus(headDescReq));
1274
1275            final HttpGet getDescReq = new HttpGet(descriptionUri);
1276            setAuth(getDescReq, username);
1277            assertEquals(HttpStatus.SC_OK, getStatus(getDescReq));
1278
1279            final HttpPut putDescReq = new HttpPut(descriptionUri);
1280            setAuth(putDescReq, username);
1281            assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(putDescReq));
1282
1283            final HttpDelete deleteDescReq = new HttpDelete(descriptionUri);
1284            setAuth(deleteDescReq, username);
1285            assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(deleteDescReq));
1286
1287            final HttpPost postDescReq = new HttpPost(descriptionUri);
1288            setAuth(postDescReq, username);
1289            assertEquals(HttpStatus.SC_FORBIDDEN, getStatus(postDescReq));
1290        }
1291    }
1292}