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