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