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