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