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}