From dba805bcfddf26647031e1a1678e1b9eb770d4a8 Mon Sep 17 00:00:00 2001 From: Pete Edwards Date: Fri, 14 Oct 2022 09:53:54 +0100 Subject: [PATCH] Test for non-existing resources (#95) * Added initial tests for non-existing resources * Aadded CSS-inspired test tables * Moved test changes to existing files, merged inherited and not inherited permission tests * Added write feature files * For read tests reuse same resources and move shared code to use common utils * Allow 200 responses, restrict to 201 for PUT to create, remove redundant test Co-authored-by: surilindur --- run.sh | 2 + .../protected-operation/common.feature | 119 ++++++++++ .../read-access-agent.feature | 221 +++++++++-------- .../read-access-bob.feature | 221 +++++++++-------- .../read-access-public.feature | 223 ++++++++++-------- .../read-inherited-access-agent.feature | 117 --------- .../read-inherited-access-bob.feature | 117 --------- .../read-inherited-access-public.feature | 121 ---------- .../write-access-agent.feature | 157 ++++++++++++ .../write-access-bob.feature | 157 ++++++++++++ .../write-access-public.feature | 123 ++++++++++ .../write-without-read.feature | 41 ---- .../web-access-control-test-manifest.ttl | 19 +- 13 files changed, 952 insertions(+), 686 deletions(-) create mode 100644 web-access-control/protected-operation/common.feature delete mode 100644 web-access-control/protected-operation/read-inherited-access-agent.feature delete mode 100644 web-access-control/protected-operation/read-inherited-access-bob.feature delete mode 100644 web-access-control/protected-operation/read-inherited-access-public.feature create mode 100644 web-access-control/protected-operation/write-access-agent.feature create mode 100644 web-access-control/protected-operation/write-access-bob.feature create mode 100644 web-access-control/protected-operation/write-access-public.feature delete mode 100644 web-access-control/protected-operation/write-without-read.feature diff --git a/run.sh b/run.sh index 64ec363..0e2184c 100755 --- a/run.sh +++ b/run.sh @@ -99,7 +99,9 @@ EOF stop_css() { echo 'Stopping CSS' docker stop server + echo 'Stopped CSS' docker rm server + echo 'Removed CSS' docker network rm testnet } diff --git a/web-access-control/protected-operation/common.feature b/web-access-control/protected-operation/common.feature new file mode 100644 index 0000000..240cd76 --- /dev/null +++ b/web-access-control/protected-operation/common.feature @@ -0,0 +1,119 @@ +@ignore +Feature: + +Scenario: + * def authHeaders = + """ + function (method, url, agent) { + const agentLowerCase = agent.toLowerCase() + return agentLowerCase !== 'public' ? clients[agentLowerCase].getAuthHeaders(method, url) : {} + } + """ + * def resourcePermissions = + """ + function (modes) { + if (modes && modes !== 'inherited' && modes !== 'no') { + return Object.entries({ R: 'read', W: 'write', A: 'append', C: 'control' }) + .filter(([mode, permission]) => modes.includes(mode)) + .map(([mode, permission]) => permission) + } + return undefined + } + """ + * def getRequestData = + """ + function (type) { + switch(type) { + case 'rdf': + return { + contentType: 'text/turtle', + requestBody: '<> "Bob replaced it." .', + responseShouldNotContain: "Bob replaced it" + } + case 'text/n3': + return { + contentType: 'text/n3', + requestBody: '@prefix solid: . _:insert a solid:InsertDeletePatch; solid:inserts { <> a . }.', + responseShouldNotContain: "http://example.org#Foo" + } + default: + return { + contentType: 'text/plain', + requestBody: "Bob's text", + responseShouldNotContain: "Bob's text" + } + } + } + """ + * def resourceEntry = + """ + function (container, type) { + switch (type) { + case 'plain': + return container.createResource('.txt', 'Hello', 'text/plain') + case 'fictive': + return container.reserveResource('.txt') + case 'rdf': + return container.createResource('.ttl', karate.readAsString('../fixtures/example.ttl'), 'text/turtle') + case 'container': + return container.createContainer() + default: + return undefined + } + } + """ + * def createResource = + """ + function (containerModes, resourceModes, resourceType, subject, agent) { + const testContainerPermissions = resourcePermissions(containerModes) + const testResourcePermissions = resourcePermissions(resourceModes) + const testContainerInheritablePermissions = resourceModes === 'inherited' + ? testContainerPermissions + : undefined + + const testContainer = rootTestContainer.createContainer() + const testResource = resourceEntry(testContainer, resourceType) + + const testContainerAccess = testContainer.accessDatasetBuilder + if (subject === 'agent') { + if (testContainerPermissions) { + testContainerAccess.setAgentAccess(testContainer.url, agent, testContainerPermissions) + } + if (testContainerInheritablePermissions) { + testContainerAccess.setInheritableAgentAccess(testContainer.url, agent, testContainerInheritablePermissions) + } + } else if (subject === 'authenticated') { + if (testContainerPermissions) { + testContainerAccess.setAuthenticatedAccess(testContainer.url, testContainerPermissions) + } + if (testContainerInheritablePermissions) { + testContainerAccess.setInheritableAuthenticatedAccess(testContainer.url, testContainerInheritablePermissions) + } + } else if (subject === 'public') { + if (testContainerPermissions) { + testContainerAccess.setPublicAccess(testContainer.url, testContainerPermissions) + } + if (testContainerInheritablePermissions) { + testContainerAccess.setInheritablePublicAccess(testContainer.url, testContainerInheritablePermissions) + } + } + testContainer.accessDataset = testContainerAccess.build() + + if (resourceType !== 'fictive' && resourceModes !== 'inherited') { + const testResourceAccess = testResource.accessDatasetBuilder + if (testResourcePermissions) { + if (subject === 'agent') { + testResourceAccess.setAgentAccess(testResource.url, agent, testResourcePermissions) + } else if (subject === 'authenticated') { + testResourceAccess.setAuthenticatedAccess(testResource.url, testResourcePermissions) + } else if (subject === 'public') { + testResourceAccess.setPublicAccess(testResource.url, testResourcePermissions) + } + } + testResource.accessDataset = testResourceAccess.build() + } + return testResource + } + """ + * def getResource = (container, resource, type) => testResources[`${container}:${resource}:${type}`] + * def testResources = resources.reduce((map, t) => { map[`${t.container}:${t.resource}:${t.type}`] = createResource(t.container, t.resource, t.type, subject, agent); return map;}, {}) diff --git a/web-access-control/protected-operation/read-access-agent.feature b/web-access-control/protected-operation/read-access-agent.feature index 0e90ccd..b9d4dd8 100644 --- a/web-access-control/protected-operation/read-access-agent.feature +++ b/web-access-control/protected-operation/read-access-agent.feature @@ -1,119 +1,156 @@ Feature: Only authenticated agents can read (and only that) a resource when granted read access # Grant authenticated agents (setAuthenticatedAccess): - # - full access to the parent container (to ensure the tests are specific to the resource) - # - restricted access to the test resources + # - restricted access or no access to the parent container + # - restricted access to the test resources, or inherited access for fictive resources Background: Create test resources with correct access modes - * def authHeaders = (method, url, public) => !public ? clients.bob.getAuthHeaders(method, url) : {} - * def createResources = - """ - function (modes) { - const testContainer = rootTestContainer.createContainer() - testContainer.accessDataset = testContainer.accessDatasetBuilder - .setAuthenticatedAccess(testContainer.url, ['read', 'write', 'append', 'control']).build() - const plainResource = testContainer.createResource('.txt', 'Hello', 'text/plain') - plainResource.accessDataset = plainResource.accessDatasetBuilder.setAuthenticatedAccess(plainResource.url, modes).build() - const rdfResource = testContainer.createResource('.ttl', karate.readAsString('../fixtures/example.ttl'), 'text/turtle') - rdfResource.accessDataset = rdfResource.accessDatasetBuilder.setAuthenticatedAccess(rdfResource.url, modes).build() - const container = testContainer.createContainer() - container.accessDataset = container.accessDatasetBuilder.setAuthenticatedAccess(container.url, modes).build() - return { plain: plainResource, rdf: rdfResource, container: container } - } - """ - # Create 3 test resources with read access for authenticated agents - * def testsR = callonce createResources ['read'] - # Create 3 test resources with append, write, control access for authenticated agents - * def testsAWC = callonce createResources ['append', 'write', 'control'] + * table resources + | type | container | resource | + | 'plain' | 'no' | 'R' | + | 'plain' | 'R' | 'inherited' | + | 'fictive' | 'R' | 'inherited' | + | 'rdf' | 'no' | 'R' | + | 'rdf' | 'R' | 'inherited' | + | 'container' | 'no' | 'R' | + | 'container' | 'R' | 'inherited' | + * def utils = callonce read('common.feature') ({resources, subject: 'authenticated'}) - Scenario Outline: read a resource () to which an authenticated agent has access - Given url tests[type].url - And headers authHeaders(method, tests[type].url, public) + Scenario Outline: read a resource (), when an authenticated agent has access to the container and access to the resource + * def testResource = utils.getResource(container, resource, type) + Given url testResource.url + And headers utils.authHeaders(method, testResource.url, agent) When method Then status Examples: - | agent | type | mode | method | public! | status | - | Bob can | plain | R | GET | false | 200 | - | Bob can | rdf | R | GET | false | 200 | - | Bob can | container | R | GET | false | 200 | - | Bob can | plain | R | HEAD | false | 200 | - | Bob can | rdf | R | HEAD | false | 200 | - | Bob can | container | R | HEAD | false | 200 | - | Public cannot | plain | R | GET | true | 401 | - | Public cannot | rdf | R | GET | true | 401 | - | Public cannot | container | R | GET | true | 401 | - | Public cannot | plain | R | HEAD | true | 401 | - | Public cannot | rdf | R | HEAD | true | 401 | - | Public cannot | container | R | HEAD | true | 401 | - | Bob cannot | plain | AWC | GET | false | 403 | - | Bob cannot | rdf | AWC | GET | false | 403 | - | Bob cannot | container | AWC | GET | false | 403 | - | Bob cannot | plain | AWC | HEAD | false | 403 | - | Bob cannot | rdf | AWC | HEAD | false | 403 | - | Bob cannot | container | AWC | HEAD | false | 403 | - | Public cannot | plain | AWC | GET | true | 401 | - | Public cannot | rdf | AWC | GET | true | 401 | - | Public cannot | container | AWC | GET | true | 401 | - | Public cannot | plain | AWC | HEAD | true | 401 | - | Public cannot | rdf | AWC | HEAD | true | 401 | - | Public cannot | container | AWC | HEAD | true | 401 | + | agent | result | method | type | container | resource | status | + | Bob | can | GET | plain | no | R | 200 | + | Bob | can | GET | plain | R | inherited | 200 | + | Bob | can | GET | fictive | R | inherited | 404 | + | Bob | can | GET | rdf | no | R | 200 | + | Bob | can | GET | rdf | R | inherited | 200 | + | Bob | can | GET | container | no | R | 200 | + | Bob | can | GET | container | R | inherited | 200 | + | Bob | can | HEAD | plain | no | R | 200 | + | Bob | can | HEAD | plain | R | inherited | 200 | + | Bob | can | HEAD | fictive | R | inherited | 404 | + | Bob | can | HEAD | rdf | no | R | 200 | + | Bob | can | HEAD | rdf | R | inherited | 200 | + | Bob | can | HEAD | container | no | R | 200 | + | Bob | can | HEAD | container | R | inherited | 200 | + | Public | cannot | GET | plain | no | R | 401 | + | Public | cannot | GET | plain | R | inherited | 401 | + | Public | cannot | GET | fictive | R | inherited | 401 | + | Public | cannot | GET | rdf | no | R | 401 | + | Public | cannot | GET | rdf | R | inherited | 401 | + | Public | cannot | GET | container | no | R | 401 | + | Public | cannot | GET | container | R | inherited | 401 | + | Public | cannot | HEAD | plain | no | R | 401 | + | Public | cannot | HEAD | plain | R | inherited | 401 | + | Public | cannot | HEAD | fictive | R | inherited | 401 | + | Public | cannot | HEAD | rdf | no | R | 401 | + | Public | cannot | HEAD | rdf | R | inherited | 401 | + | Public | cannot | HEAD | container | no | R | 401 | + | Public | cannot | HEAD | container | R | inherited | 401 | - Scenario Outline: cannot to a resource to which an authenticated agent has access - Given url tests[type].url - And headers authHeaders(method, tests[type].url, public) + Scenario Outline: to a resource, when an authenticated agent has access to the container and access to the resource + * def testResource = utils.getResource(container, resource, type) + Given url testResource.url + And headers utils.authHeaders(method, testResource.url, agent) And header Content-Type = 'text/turtle' And request '@prefix rdfs: . <> rdfs:comment "Bob added this.".' When method - Then status + Then match contains responseStatus Examples: - | agent | type | mode | method | public! | status | - | Bob | rdf | R | PUT | false | 403 | - | Bob | container | R | PUT | false | 403 | - | Bob | rdf | R | POST | false | 403 | - | Bob | container | R | POST | false | 403 | - | Public | rdf | R | PUT | true | 401 | - | Public | container | R | PUT | true | 401 | - | Public | rdf | R | POST | true | 401 | - | Public | container | R | POST | true | 401 | + | agent | result | method | type | container | resource | status | + | Bob | cannot | PUT | rdf | no | R | [403] | + | Bob | cannot | PUT | rdf | R | inherited | [403] | + | Bob | cannot | PUT | fictive | R | inherited | [403] | + | Bob | cannot | PUT | container | no | R | [403] | + | Bob | cannot | PUT | container | R | inherited | [403] | + | Bob | cannot | POST | rdf | no | R | [403] | + | Bob | cannot | POST | rdf | R | inherited | [403] | + | Bob | cannot | POST | fictive | R | inherited | [403, 404] | + | Bob | cannot | POST | container | no | R | [403] | + | Bob | cannot | POST | container | R | inherited | [403] | + | Public | cannot | PUT | rdf | no | R | [401] | + | Public | cannot | PUT | rdf | R | inherited | [401] | + | Public | cannot | PUT | fictive | R | inherited | [401] | + | Public | cannot | PUT | container | no | R | [401] | + | Public | cannot | PUT | container | R | inherited | [401] | + | Public | cannot | POST | rdf | no | R | [401] | + | Public | cannot | POST | rdf | R | inherited | [401] | + | Public | cannot | POST | fictive | R | inherited | [401] | + | Public | cannot | POST | container | no | R | [401] | + | Public | cannot | POST | container | R | inherited | [401] | - Scenario Outline: cannot to a resource to which an authenticated agent has access - Given url tests[type].url - And headers authHeaders(method, tests[type].url, public) + Scenario Outline: to a resource, when an authenticated agent has access to the container and access to the resource + * def testResource = utils.getResource(container, resource, type) + Given url testResource.url + And headers utils.authHeaders(method, testResource.url, agent) And header Content-Type = 'text/n3' And request '@prefix solid: . _:insert a solid:InsertDeletePatch; solid:inserts { <> a . }.' When method Then status Examples: - | agent | type | mode | method | public! | status | - | Bob | rdf | R | PATCH | false | 403 | - | Bob | container | R | PATCH | false | 403 | - | Public | rdf | R | PATCH | true | 401 | - | Public | container | R | PATCH | true | 401 | + | agent | result | method | type | container | resource | status | + | Bob | cannot | PATCH | rdf | no | R | 403 | + | Bob | cannot | PATCH | rdf | R | inherited | 403 | + | Bob | cannot | PATCH | fictive | R | inherited | 403 | + | Bob | cannot | PATCH | container | no | R | 403 | + | Bob | cannot | PATCH | container | R | inherited | 403 | + | Public | cannot | PATCH | rdf | no | R | 401 | + | Public | cannot | PATCH | rdf | R | inherited | 401 | + | Public | cannot | PATCH | fictive | R | inherited | 401 | + | Public | cannot | PATCH | container | no | R | 401 | + | Public | cannot | PATCH | container | R | inherited | 401 | - Scenario Outline: cannot to a resource to which an authenticated agent has access - Given url tests[type].url - And headers authHeaders(method, tests[type].url, public) + Scenario Outline: to a resource, when an authenticated agent has access to the container and access to the resource + * def testResource = utils.getResource(container, resource, type) + Given url testResource.url + And headers utils.authHeaders(method, testResource.url, agent) And header Content-Type = 'text/plain' And request "Bob's text" When method Then match contains responseStatus Examples: - | agent | type | mode | method | public! | status | - | Bob | plain | R | PUT | false | [403] | - | Bob | plain | R | POST | false | [403] | - | Bob | plain | R | PATCH | false | [403, 405, 415] | - | Public | plain | R | PUT | true | [401] | - | Public | plain | R | POST | true | [401] | - | Public | plain | R | PATCH | true | [401, 405, 415] | + | agent | result | method | type | container | resource | status | + | Bob | cannot | PUT | plain | no | R | [403] | + | Bob | cannot | PUT | plain | R | inherited | [403] | + | Bob | cannot | PUT | fictive | R | inherited | [403] | + | Bob | cannot | POST | plain | no | R | [403] | + | Bob | cannot | POST | plain | R | inherited | [403] | + | Bob | cannot | POST | fictive | R | inherited | [403, 404] | + | Bob | cannot | PATCH | plain | no | R | [403, 405, 415] | + | Bob | cannot | PATCH | plain | R | inherited | [403, 405, 415] | + | Bob | cannot | PATCH | fictive | R | inherited | [403, 405, 415] | + | Public | cannot | PUT | plain | no | R | [401] | + | Public | cannot | PUT | plain | R | inherited | [401] | + | Public | cannot | PUT | fictive | R | inherited | [401] | + | Public | cannot | POST | plain | no | R | [401] | + | Public | cannot | POST | plain | R | inherited | [401] | + | Public | cannot | POST | fictive | R | inherited | [401] | + | Public | cannot | PATCH | plain | no | R | [401, 405, 415] | + | Public | cannot | PATCH | plain | R | inherited | [401, 405, 415] | + | Public | cannot | PATCH | fictive | R | inherited | [401, 405, 415] | - Scenario Outline: cannot a resource to which an authenticated agent has access - Given url tests[type].url - And headers authHeaders(method, tests[type].url, public) + Scenario Outline: a resource, when an authenticated agent has access to the container and access to the resource + * def testResource = utils.getResource(container, resource, type) + Given url testResource.url + And headers utils.authHeaders(method, testResource.url, agent) When method - Then status + Then match contains responseStatus Examples: - | agent | type | mode | method | public! | status | - | Bob | plain | R | DELETE | false | 403 | - | Bob | rdf | R | DELETE | false | 403 | - | Bob | container | R | DELETE | false | 403 | - | Public | plain | R | DELETE | true | 401 | - | Public | rdf | R | DELETE | true | 401 | - | Public | container | R | DELETE | true | 401 | + | agent | result | method | type | container | resource | status | + | Bob | cannot | DELETE | plain | no | R | [403] | + | Bob | cannot | DELETE | plain | R | inherited | [403] | + | Bob | cannot | DELETE | fictive | R | inherited | [403, 404] | + | Bob | cannot | DELETE | rdf | no | R | [403] | + | Bob | cannot | DELETE | rdf | R | inherited | [403] | + | Bob | cannot | DELETE | container | no | R | [403] | + | Bob | cannot | DELETE | container | R | inherited | [403] | + | Public | cannot | DELETE | plain | no | R | [401] | + | Public | cannot | DELETE | plain | R | inherited | [401] | + | Public | cannot | DELETE | fictive | R | inherited | [401] | + | Public | cannot | DELETE | rdf | no | R | [401] | + | Public | cannot | DELETE | rdf | R | inherited | [401] | + | Public | cannot | DELETE | container | no | R | [401] | + | Public | cannot | DELETE | container | R | inherited | [401] | diff --git a/web-access-control/protected-operation/read-access-bob.feature b/web-access-control/protected-operation/read-access-bob.feature index b0f75a2..86e6f26 100644 --- a/web-access-control/protected-operation/read-access-bob.feature +++ b/web-access-control/protected-operation/read-access-bob.feature @@ -1,119 +1,156 @@ Feature: Only Bob can read (and only that) a resource when granted read access # Grant a specific agent (setAgentAccess): - # - full access to the parent container (to ensure the tests are specific to the resource) - # - restricted access to the test resources + # - restricted access or no access to the parent container + # - restricted access to the test resources, or inherited access for fictive resources Background: Create test resources with correct access modes - * def authHeaders = (method, url, public) => !public ? clients.bob.getAuthHeaders(method, url) : {} - * def createResources = - """ - function (modes) { - const testContainer = rootTestContainer.createContainer() - testContainer.accessDataset = testContainer.accessDatasetBuilder - .setAgentAccess(testContainer.url, webIds.bob, ['read', 'write', 'append', 'control']).build() - const plainResource = testContainer.createResource('.txt', 'Hello', 'text/plain') - plainResource.accessDataset = plainResource.accessDatasetBuilder.setAgentAccess(plainResource.url, webIds.bob, modes).build() - const rdfResource = testContainer.createResource('.ttl', karate.readAsString('../fixtures/example.ttl'), 'text/turtle') - rdfResource.accessDataset = rdfResource.accessDatasetBuilder.setAgentAccess(rdfResource.url, webIds.bob, modes).build() - const container = testContainer.createContainer() - container.accessDataset = container.accessDatasetBuilder.setAgentAccess(container.url, webIds.bob, modes).build() - return { plain: plainResource, rdf: rdfResource, container: container } - } - """ - # Create 3 test resources with read access for Bob - * def testsR = callonce createResources ['read'] - # Create 3 test resources with append, write, control access for Bob - * def testsAWC = callonce createResources ['append', 'write', 'control'] + * table resources + | type | container | resource | + | 'plain' | 'no' | 'R' | + | 'plain' | 'R' | 'inherited' | + | 'fictive' | 'R' | 'inherited' | + | 'rdf' | 'no' | 'R' | + | 'rdf' | 'R' | 'inherited' | + | 'container' | 'no' | 'R' | + | 'container' | 'R' | 'inherited' | + * def utils = callonce read('common.feature') ({resources, subject: 'agent', agent: webIds.bob}) - Scenario Outline: read a resource () to which Bob has access - Given url tests[type].url - And headers authHeaders(method, tests[type].url, public) + Scenario Outline: read a resource (), when Bob has access to the container and access to the resource + * def testResource = utils.getResource(container, resource, type) + Given url testResource.url + And headers utils.authHeaders(method, testResource.url, agent) When method Then status Examples: - | agent | type | mode | method | public! | status | - | Bob can | plain | R | GET | false | 200 | - | Bob can | rdf | R | GET | false | 200 | - | Bob can | container | R | GET | false | 200 | - | Bob can | plain | R | HEAD | false | 200 | - | Bob can | rdf | R | HEAD | false | 200 | - | Bob can | container | R | HEAD | false | 200 | - | Public cannot | plain | R | GET | true | 401 | - | Public cannot | rdf | R | GET | true | 401 | - | Public cannot | container | R | GET | true | 401 | - | Public cannot | plain | R | HEAD | true | 401 | - | Public cannot | rdf | R | HEAD | true | 401 | - | Public cannot | container | R | HEAD | true | 401 | - | Bob cannot | plain | AWC | GET | false | 403 | - | Bob cannot | rdf | AWC | GET | false | 403 | - | Bob cannot | container | AWC | GET | false | 403 | - | Bob cannot | plain | AWC | HEAD | false | 403 | - | Bob cannot | rdf | AWC | HEAD | false | 403 | - | Bob cannot | container | AWC | HEAD | false | 403 | - | Public cannot | plain | AWC | GET | true | 401 | - | Public cannot | rdf | AWC | GET | true | 401 | - | Public cannot | container | AWC | GET | true | 401 | - | Public cannot | plain | AWC | HEAD | true | 401 | - | Public cannot | rdf | AWC | HEAD | true | 401 | - | Public cannot | container | AWC | HEAD | true | 401 | + | agent | result | method | type | container | resource | status | + | Bob | can | GET | plain | no | R | 200 | + | Bob | can | GET | plain | R | inherited | 200 | + | Bob | can | GET | fictive | R | inherited | 404 | + | Bob | can | GET | rdf | no | R | 200 | + | Bob | can | GET | rdf | R | inherited | 200 | + | Bob | can | GET | container | no | R | 200 | + | Bob | can | GET | container | R | inherited | 200 | + | Bob | can | HEAD | plain | no | R | 200 | + | Bob | can | HEAD | plain | R | inherited | 200 | + | Bob | can | HEAD | fictive | R | inherited | 404 | + | Bob | can | HEAD | rdf | no | R | 200 | + | Bob | can | HEAD | rdf | R | inherited | 200 | + | Bob | can | HEAD | container | no | R | 200 | + | Bob | can | HEAD | container | R | inherited | 200 | + | Public | cannot | GET | plain | no | R | 401 | + | Public | cannot | GET | plain | R | inherited | 401 | + | Public | cannot | GET | fictive | R | inherited | 401 | + | Public | cannot | GET | rdf | no | R | 401 | + | Public | cannot | GET | rdf | R | inherited | 401 | + | Public | cannot | GET | container | no | R | 401 | + | Public | cannot | GET | container | R | inherited | 401 | + | Public | cannot | HEAD | plain | no | R | 401 | + | Public | cannot | HEAD | plain | R | inherited | 401 | + | Public | cannot | HEAD | fictive | R | inherited | 401 | + | Public | cannot | HEAD | rdf | no | R | 401 | + | Public | cannot | HEAD | rdf | R | inherited | 401 | + | Public | cannot | HEAD | container | no | R | 401 | + | Public | cannot | HEAD | container | R | inherited | 401 | - Scenario Outline: cannot to a resource to which Bob has access - Given url tests[type].url - And headers authHeaders(method, tests[type].url, public) + Scenario Outline: to a resource, when Bob has access to the container and access to the resource + * def testResource = utils.getResource(container, resource, type) + Given url testResource.url + And headers utils.authHeaders(method, testResource.url, agent) And header Content-Type = 'text/turtle' And request '@prefix rdfs: . <> rdfs:comment "Bob added this.".' When method - Then status + Then match contains responseStatus Examples: - | agent | type | mode | method | public! | status | - | Bob | rdf | R | PUT | false | 403 | - | Bob | container | R | PUT | false | 403 | - | Bob | rdf | R | POST | false | 403 | - | Bob | container | R | POST | false | 403 | - | Public | rdf | R | PUT | true | 401 | - | Public | container | R | PUT | true | 401 | - | Public | rdf | R | POST | true | 401 | - | Public | container | R | POST | true | 401 | + | agent | result | method | type | container | resource | status | + | Bob | cannot | PUT | rdf | no | R | [403] | + | Bob | cannot | PUT | rdf | R | inherited | [403] | + | Bob | cannot | PUT | fictive | R | inherited | [403] | + | Bob | cannot | PUT | container | no | R | [403] | + | Bob | cannot | PUT | container | R | inherited | [403] | + | Bob | cannot | POST | rdf | no | R | [403] | + | Bob | cannot | POST | rdf | R | inherited | [403] | + | Bob | cannot | POST | fictive | R | inherited | [403, 404] | + | Bob | cannot | POST | container | no | R | [403] | + | Bob | cannot | POST | container | R | inherited | [403] | + | Public | cannot | PUT | rdf | no | R | [401] | + | Public | cannot | PUT | rdf | R | inherited | [401] | + | Public | cannot | PUT | fictive | R | inherited | [401] | + | Public | cannot | PUT | container | no | R | [401] | + | Public | cannot | PUT | container | R | inherited | [401] | + | Public | cannot | POST | rdf | no | R | [401] | + | Public | cannot | POST | rdf | R | inherited | [401] | + | Public | cannot | POST | fictive | R | inherited | [401] | + | Public | cannot | POST | container | no | R | [401] | + | Public | cannot | POST | container | R | inherited | [401] | - Scenario Outline: cannot to a resource to which Bob has access - Given url tests[type].url - And headers authHeaders(method, tests[type].url, public) + Scenario Outline: to a resource, when Bob has access to the container and access to the resource + * def testResource = utils.getResource(container, resource, type) + Given url testResource.url + And headers utils.authHeaders(method, testResource.url, agent) And header Content-Type = 'text/n3' And request '@prefix solid: . _:insert a solid:InsertDeletePatch; solid:inserts { <> a . }.' When method Then status Examples: - | agent | type | mode | method | public! | status | - | Bob | rdf | R | PATCH | false | 403 | - | Bob | container | R | PATCH | false | 403 | - | Public | rdf | R | PATCH | true | 401 | - | Public | container | R | PATCH | true | 401 | + | agent | result | method | type | container | resource | status | + | Bob | cannot | PATCH | rdf | no | R | 403 | + | Bob | cannot | PATCH | rdf | R | inherited | 403 | + | Bob | cannot | PATCH | fictive | R | inherited | 403 | + | Bob | cannot | PATCH | container | no | R | 403 | + | Bob | cannot | PATCH | container | R | inherited | 403 | + | Public | cannot | PATCH | rdf | no | R | 401 | + | Public | cannot | PATCH | rdf | R | inherited | 401 | + | Public | cannot | PATCH | fictive | R | inherited | 401 | + | Public | cannot | PATCH | container | no | R | 401 | + | Public | cannot | PATCH | container | R | inherited | 401 | - Scenario Outline: cannot to a resource to which Bob has access - Given url tests[type].url - And headers authHeaders(method, tests[type].url, public) + Scenario Outline: to a resource, when Bob has access to the container and access to the resource + * def testResource = utils.getResource(container, resource, type) + Given url testResource.url + And headers utils.authHeaders(method, testResource.url, agent) And header Content-Type = 'text/plain' And request "Bob's text" When method Then match contains responseStatus Examples: - | agent | type | mode | method | public! | status | - | Bob | plain | R | PUT | false | [403] | - | Bob | plain | R | POST | false | [403] | - | Bob | plain | R | PATCH | false | [403, 405, 415] | - | Public | plain | R | PUT | true | [401] | - | Public | plain | R | POST | true | [401] | - | Public | plain | R | PATCH | true | [401, 405, 415] | + | agent | result | method | type | container | resource | status | + | Bob | cannot | PUT | plain | no | R | [403] | + | Bob | cannot | PUT | plain | R | inherited | [403] | + | Bob | cannot | PUT | fictive | R | inherited | [403] | + | Bob | cannot | POST | plain | no | R | [403] | + | Bob | cannot | POST | plain | R | inherited | [403] | + | Bob | cannot | POST | fictive | R | inherited | [403, 404] | + | Bob | cannot | PATCH | plain | no | R | [403, 405, 415] | + | Bob | cannot | PATCH | plain | R | inherited | [403, 405, 415] | + | Bob | cannot | PATCH | fictive | R | inherited | [403, 405, 415] | + | Public | cannot | PUT | plain | no | R | [401] | + | Public | cannot | PUT | plain | R | inherited | [401] | + | Public | cannot | PUT | fictive | R | inherited | [401] | + | Public | cannot | POST | plain | no | R | [401] | + | Public | cannot | POST | plain | R | inherited | [401] | + | Public | cannot | POST | fictive | R | inherited | [401] | + | Public | cannot | PATCH | plain | no | R | [401, 405, 415] | + | Public | cannot | PATCH | plain | R | inherited | [401, 405, 415] | + | Public | cannot | PATCH | fictive | R | inherited | [401, 405, 415] | - Scenario Outline: cannot a resource to which Bob has access - Given url tests[type].url - And headers authHeaders(method, tests[type].url, public) + Scenario Outline: a resource, when Bob has access to the container and access to the resource + * def testResource = utils.getResource(container, resource, type) + Given url testResource.url + And headers utils.authHeaders(method, testResource.url, agent) When method - Then status + Then match contains responseStatus Examples: - | agent | type | mode | method | public! | status | - | Bob | plain | R | DELETE | false | 403 | - | Bob | rdf | R | DELETE | false | 403 | - | Bob | container | R | DELETE | false | 403 | - | Public | plain | R | DELETE | true | 401 | - | Public | rdf | R | DELETE | true | 401 | - | Public | container | R | DELETE | true | 401 | + | agent | result | method | type | container | resource | status | + | Bob | cannot | DELETE | plain | no | R | [403] | + | Bob | cannot | DELETE | plain | R | inherited | [403] | + | Bob | cannot | DELETE | fictive | R | inherited | [403, 404] | + | Bob | cannot | DELETE | rdf | no | R | [403] | + | Bob | cannot | DELETE | rdf | R | inherited | [403] | + | Bob | cannot | DELETE | container | no | R | [403] | + | Bob | cannot | DELETE | container | R | inherited | [403] | + | Public | cannot | DELETE | plain | no | R | [401] | + | Public | cannot | DELETE | plain | R | inherited | [401] | + | Public | cannot | DELETE | fictive | R | inherited | [401] | + | Public | cannot | DELETE | rdf | no | R | [401] | + | Public | cannot | DELETE | rdf | R | inherited | [401] | + | Public | cannot | DELETE | container | no | R | [401] | + | Public | cannot | DELETE | container | R | inherited | [401] | diff --git a/web-access-control/protected-operation/read-access-public.feature b/web-access-control/protected-operation/read-access-public.feature index 31eda78..291a1d0 100644 --- a/web-access-control/protected-operation/read-access-public.feature +++ b/web-access-control/protected-operation/read-access-public.feature @@ -1,123 +1,160 @@ Feature: Public agents can read (and only that) a resource when granted read access # Grant public agents (setPublicAccess): - # - full access to the parent container (to ensure the tests are specific to the resource) - # - restricted access to the test resources + # - restricted access or no access to the parent container + # - restricted access to the test resources, or inherited access for fictive resources Background: Create test resources with correct access modes - * def authHeaders = (method, url, public) => !public ? clients.bob.getAuthHeaders(method, url) : {} - * def createResources = - """ - function (modes) { - const testContainer = rootTestContainer.createContainer() - testContainer.accessDataset = testContainer.accessDatasetBuilder - .setPublicAccess(testContainer.url, ['read', 'write', 'append', 'control']).build() - const plainResource = testContainer.createResource('.txt', 'Hello', 'text/plain') - plainResource.accessDataset = plainResource.accessDatasetBuilder.setPublicAccess(plainResource.url, modes).build() - const rdfResource = testContainer.createResource('.ttl', karate.readAsString('../fixtures/example.ttl'), 'text/turtle') - rdfResource.accessDataset = rdfResource.accessDatasetBuilder.setPublicAccess(rdfResource.url, modes).build() - const container = testContainer.createContainer() - container.accessDataset = container.accessDatasetBuilder.setPublicAccess(container.url, modes).build() - return { plain: plainResource, rdf: rdfResource, container: container } - } - """ - # Create 3 test resources with read access for public agents - * def testsR = callonce createResources ['read'] - # Create 3 test resources with append, write, control access for public agents - * def testsAWC = callonce createResources ['append', 'write', 'control'] + * table resources + | type | container | resource | + | 'plain' | 'no' | 'R' | + | 'plain' | 'R' | 'inherited' | + | 'fictive' | 'R' | 'inherited' | + | 'rdf' | 'no' | 'R' | + | 'rdf' | 'R' | 'inherited' | + | 'container' | 'no' | 'R' | + | 'container' | 'R' | 'inherited' | + * def utils = callonce read('common.feature') ({resources, subject: 'public'}) - Scenario Outline: read a resource () to which a public agent has access - Given url tests[type].url - And headers authHeaders(method, tests[type].url, public) + Scenario Outline: read a resource (), when a public agent has access to the container and access to the resource + * def testResource = utils.getResource(container, resource, type) + Given url testResource.url + And headers utils.authHeaders(method, testResource.url, agent) When method Then status Examples: - | agent | type | mode | method | public! | status | - | Bob can | plain | R | GET | false | 200 | - | Bob can | rdf | R | GET | false | 200 | - | Bob can | container | R | GET | false | 200 | - | Bob can | plain | R | HEAD | false | 200 | - | Bob can | rdf | R | HEAD | false | 200 | - | Bob can | container | R | HEAD | false | 200 | - | Bob cannot | plain | AWC | GET | false | 403 | - | Bob cannot | rdf | AWC | GET | false | 403 | - | Bob cannot | container | AWC | GET | false | 403 | - | Bob cannot | plain | AWC | HEAD | false | 403 | - | Bob cannot | rdf | AWC | HEAD | false | 403 | - | Bob cannot | container | AWC | HEAD | false | 403 | - | Public cannot | plain | AWC | GET | true | 401 | - | Public cannot | rdf | AWC | GET | true | 401 | - | Public cannot | container | AWC | GET | true | 401 | - | Public cannot | plain | AWC | HEAD | true | 401 | - | Public cannot | rdf | AWC | HEAD | true | 401 | - | Public cannot | container | AWC | HEAD | true | 401 | + | agent | result | method | type | container | resource | status | + | Bob | can | GET | plain | no | R | 200 | + | Bob | can | GET | plain | R | inherited | 200 | + | Bob | can | GET | fictive | R | inherited | 404 | + | Bob | can | GET | rdf | no | R | 200 | + | Bob | can | GET | rdf | R | inherited | 200 | + | Bob | can | GET | container | no | R | 200 | + | Bob | can | GET | container | R | inherited | 200 | + | Bob | can | HEAD | plain | no | R | 200 | + | Bob | can | HEAD | plain | R | inherited | 200 | + | Bob | can | HEAD | fictive | R | inherited | 404 | + | Bob | can | HEAD | rdf | no | R | 200 | + | Bob | can | HEAD | rdf | R | inherited | 200 | + | Bob | can | HEAD | container | no | R | 200 | + | Bob | can | HEAD | container | R | inherited | 200 | @publicagent Examples: - | agent | type | mode | method | public! | status | - | Public can | plain | R | GET | true | 200 | - | Public can | rdf | R | GET | true | 200 | - | Public can | container | R | GET | true | 200 | - | Public can | plain | R | HEAD | true | 200 | - | Public can | rdf | R | HEAD | true | 200 | - | Public can | container | R | HEAD | true | 200 | + | agent | result | method | type | container | resource | status | + | Public | can | GET | plain | no | R | 200 | + | Public | can | GET | plain | R | inherited | 200 | + | Public | can | GET | fictive | R | inherited | 404 | + | Public | can | GET | rdf | no | R | 200 | + | Public | can | GET | rdf | R | inherited | 200 | + | Public | can | GET | container | no | R | 200 | + | Public | can | GET | container | R | inherited | 200 | + | Public | can | HEAD | plain | no | R | 200 | + | Public | can | HEAD | plain | R | inherited | 200 | + | Public | can | HEAD | fictive | R | inherited | 404 | + | Public | can | HEAD | rdf | no | R | 200 | + | Public | can | HEAD | rdf | R | inherited | 200 | + | Public | can | HEAD | container | no | R | 200 | + | Public | can | HEAD | container | R | inherited | 200 | - Scenario Outline: cannot to a resource to which a public agent has access - Given url tests[type].url - And headers authHeaders(method, tests[type].url, public) + Scenario Outline: to a resource, when a public agent has access to the container and access to the resource + * def testResource = utils.getResource(container, resource, type) + Given url testResource.url + And headers utils.authHeaders(method, testResource.url, agent) And header Content-Type = 'text/turtle' And request '@prefix rdfs: . <> rdfs:comment "Bob added this.".' When method - Then status + Then match contains responseStatus Examples: - | agent | type | mode | method | public! | status | - | Bob | rdf | R | PUT | false | 403 | - | Bob | container | R | PUT | false | 403 | - | Bob | rdf | R | POST | false | 403 | - | Bob | container | R | POST | false | 403 | - | Public | rdf | R | PUT | true | 401 | - | Public | container | R | PUT | true | 401 | - | Public | rdf | R | POST | true | 401 | - | Public | container | R | POST | true | 401 | + | agent | result | method | type | container | resource | status | + | Bob | cannot | PUT | rdf | no | R | [403] | + | Bob | cannot | PUT | rdf | R | inherited | [403] | + | Bob | cannot | PUT | fictive | R | inherited | [403] | + | Bob | cannot | PUT | container | no | R | [403] | + | Bob | cannot | PUT | container | R | inherited | [403] | + | Bob | cannot | POST | rdf | no | R | [403] | + | Bob | cannot | POST | rdf | R | inherited | [403] | + | Bob | cannot | POST | fictive | R | inherited | [403, 404] | + | Bob | cannot | POST | container | no | R | [403] | + | Bob | cannot | POST | container | R | inherited | [403] | + | Public | cannot | PUT | rdf | no | R | [401] | + | Public | cannot | PUT | rdf | R | inherited | [401] | + | Public | cannot | PUT | fictive | R | inherited | [401] | + | Public | cannot | PUT | container | no | R | [401] | + | Public | cannot | PUT | container | R | inherited | [401] | + | Public | cannot | POST | rdf | no | R | [401] | + | Public | cannot | POST | rdf | R | inherited | [401] | + | Public | cannot | POST | fictive | R | inherited | [401, 404] | + | Public | cannot | POST | container | no | R | [401] | + | Public | cannot | POST | container | R | inherited | [401] | - Scenario Outline: cannot to a resource to which a public agent has access - Given url tests[type].url - And headers authHeaders(method, tests[type].url, public) + Scenario Outline: to a resource, when a public agent has access to the container and access to the resource + * def testResource = utils.getResource(container, resource, type) + Given url testResource.url + And headers utils.authHeaders(method, testResource.url, agent) And header Content-Type = 'text/n3' And request '@prefix solid: . _:insert a solid:InsertDeletePatch; solid:inserts { <> a . }.' When method Then status Examples: - | agent | type | mode | method | public! | status | - | Bob | rdf | R | PATCH | false | 403 | - | Bob | container | R | PATCH | false | 403 | - | Public | rdf | R | PATCH | true | 401 | - | Public | container | R | PATCH | true | 401 | + | agent | result | method | type | container | resource | status | + | Bob | cannot | PATCH | rdf | no | R | 403 | + | Bob | cannot | PATCH | rdf | R | inherited | 403 | + | Bob | cannot | PATCH | fictive | R | inherited | 403 | + | Bob | cannot | PATCH | container | no | R | 403 | + | Bob | cannot | PATCH | container | R | inherited | 403 | + | Public | cannot | PATCH | rdf | no | R | 401 | + | Public | cannot | PATCH | rdf | R | inherited | 401 | + | Public | cannot | PATCH | fictive | R | inherited | 401 | + | Public | cannot | PATCH | container | no | R | 401 | + | Public | cannot | PATCH | container | R | inherited | 401 | - Scenario Outline: cannot to a resource to which a public agent has access - Given url tests[type].url - And headers authHeaders(method, tests[type].url, public) + Scenario Outline: to a resource, when a public agent has access to the container and access to the resource + * def testResource = utils.getResource(container, resource, type) + Given url testResource.url + And headers utils.authHeaders(method, testResource.url, agent) And header Content-Type = 'text/plain' And request "Bob's text" When method Then match contains responseStatus Examples: - | agent | type | mode | method | public! | status | - | Bob | plain | R | PUT | false | [403] | - | Bob | plain | R | POST | false | [403] | - | Bob | plain | R | PATCH | false | [403, 405, 415] | - | Public | plain | R | PUT | true | [401] | - | Public | plain | R | POST | true | [401] | - | Public | plain | R | PATCH | true | [401, 405, 415] | + | agent | result | method | type | container | resource | status | + | Bob | cannot | PUT | plain | no | R | [403] | + | Bob | cannot | PUT | plain | R | inherited | [403] | + | Bob | cannot | PUT | fictive | R | inherited | [403] | + | Bob | cannot | POST | plain | no | R | [403] | + | Bob | cannot | POST | plain | R | inherited | [403] | + | Bob | cannot | POST | fictive | R | inherited | [403, 404] | + | Bob | cannot | PATCH | plain | no | R | [403, 405, 415] | + | Bob | cannot | PATCH | plain | R | inherited | [403, 405, 415] | + | Bob | cannot | PATCH | fictive | R | inherited | [403, 405, 415] | + | Public | cannot | PUT | plain | no | R | [401] | + | Public | cannot | PUT | plain | R | inherited | [401] | + | Public | cannot | PUT | fictive | R | inherited | [401] | + | Public | cannot | POST | plain | no | R | [401] | + | Public | cannot | POST | plain | R | inherited | [401] | + | Public | cannot | POST | fictive | R | inherited | [401, 404] | + | Public | cannot | PATCH | plain | no | R | [401, 405, 415] | + | Public | cannot | PATCH | plain | R | inherited | [401, 405, 415] | + | Public | cannot | PATCH | fictive | R | inherited | [401, 405, 415] | - Scenario Outline: cannot a resource to which a public agent has access - Given url tests[type].url - And headers authHeaders(method, tests[type].url, public) + Scenario Outline: a resource, when a public agent has access to the container and access to the resource + * def testResource = utils.getResource(container, resource, type) + Given url testResource.url + And headers utils.authHeaders(method, testResource.url, agent) When method - Then status + Then match contains responseStatus Examples: - | agent | type | mode | method | public! | status | - | Bob | plain | R | DELETE | false | 403 | - | Bob | rdf | R | DELETE | false | 403 | - | Bob | container | R | DELETE | false | 403 | - | Public | plain | R | DELETE | true | 401 | - | Public | rdf | R | DELETE | true | 401 | - | Public | container | R | DELETE | true | 401 | + | agent | result | method | type | container | resource | status | + | Bob | cannot | DELETE | plain | no | R | [403] | + | Bob | cannot | DELETE | plain | R | inherited | [403] | + | Bob | cannot | DELETE | fictive | R | inherited | [403, 404] | + | Bob | cannot | DELETE | rdf | no | R | [403] | + | Bob | cannot | DELETE | rdf | R | inherited | [403] | + | Bob | cannot | DELETE | container | no | R | [403] | + | Bob | cannot | DELETE | container | R | inherited | [403] | + | Public | cannot | DELETE | plain | no | R | [401] | + | Public | cannot | DELETE | plain | R | inherited | [401] | + | Public | cannot | DELETE | fictive | R | inherited | [401, 404] | + | Public | cannot | DELETE | rdf | no | R | [401] | + | Public | cannot | DELETE | rdf | R | inherited | [401] | + | Public | cannot | DELETE | container | no | R | [401] | + | Public | cannot | DELETE | container | R | inherited | [401] | diff --git a/web-access-control/protected-operation/read-inherited-access-agent.feature b/web-access-control/protected-operation/read-inherited-access-agent.feature deleted file mode 100644 index a6f85e0..0000000 --- a/web-access-control/protected-operation/read-inherited-access-agent.feature +++ /dev/null @@ -1,117 +0,0 @@ -Feature: Only authenticated agents can read (and only that) a resource when granted inherited read access - # Grant authenticated agents (setAuthenticatedAccess/setInheritableAuthenticatedAccess): - # - full access to the parent container (to ensure the tests are specific to the resource) - # - restricted access to the any contained resources via the parent - Background: Create test resources with correct access modes - * def authHeaders = (method, url, public) => !public ? clients.bob.getAuthHeaders(method, url) : {} - * def createResources = - """ - function (modes) { - const testContainer = rootTestContainer.createContainer() - testContainer.accessDataset = testContainer.accessDatasetBuilder - .setAuthenticatedAccess(testContainer.url, ['read', 'write', 'append', 'control']) - .setInheritableAuthenticatedAccess(testContainer.url, modes).build() - const plainResource = testContainer.createResource('.txt', 'Hello', 'text/plain') - const rdfResource = testContainer.createResource('.ttl', karate.readAsString('../fixtures/example.ttl'), 'text/turtle') - const container = testContainer.createContainer() - return { plain: plainResource, rdf: rdfResource, container: container } - } - """ - # Create 3 test resources with read access for authenticated agents - * def testsR = callonce createResources ['read'] - # Create 3 test resources with append, write, control access for authenticated agents - * def testsAWC = callonce createResources ['append', 'write', 'control'] - - Scenario Outline: read a resource () to which an authenticated agent has inherited access - Given url tests[type].url - And headers authHeaders(method, tests[type].url, public) - When method - Then status - Examples: - | agent | type | mode | method | public! | status | - | Bob can | plain | R | GET | false | 200 | - | Bob can | rdf | R | GET | false | 200 | - | Bob can | container | R | GET | false | 200 | - | Bob can | plain | R | HEAD | false | 200 | - | Bob can | rdf | R | HEAD | false | 200 | - | Bob can | container | R | HEAD | false | 200 | - | Public cannot | plain | R | GET | true | 401 | - | Public cannot | rdf | R | GET | true | 401 | - | Public cannot | container | R | GET | true | 401 | - | Public cannot | plain | R | HEAD | true | 401 | - | Public cannot | rdf | R | HEAD | true | 401 | - | Public cannot | container | R | HEAD | true | 401 | - | Bob cannot | plain | AWC | GET | false | 403 | - | Bob cannot | rdf | AWC | GET | false | 403 | - | Bob cannot | container | AWC | GET | false | 403 | - | Bob cannot | plain | AWC | HEAD | false | 403 | - | Bob cannot | rdf | AWC | HEAD | false | 403 | - | Bob cannot | container | AWC | HEAD | false | 403 | - | Public cannot | plain | AWC | GET | true | 401 | - | Public cannot | rdf | AWC | GET | true | 401 | - | Public cannot | container | AWC | GET | true | 401 | - | Public cannot | plain | AWC | HEAD | true | 401 | - | Public cannot | rdf | AWC | HEAD | true | 401 | - | Public cannot | container | AWC | HEAD | true | 401 | - - Scenario Outline: cannot to a resource to which an authenticated agent has inherited access - Given url tests[type].url - And headers authHeaders(method, tests[type].url, public) - And header Content-Type = 'text/turtle' - And request '@prefix rdfs: . <> rdfs:comment "Bob added this.".' - When method - Then status - Examples: - | agent | type | mode | method | public! | status | - | Bob | rdf | R | PUT | false | 403 | - | Bob | container | R | PUT | false | 403 | - | Bob | rdf | R | POST | false | 403 | - | Bob | container | R | POST | false | 403 | - | Public | rdf | R | PUT | true | 401 | - | Public | container | R | PUT | true | 401 | - | Public | rdf | R | POST | true | 401 | - | Public | container | R | POST | true | 401 | - - Scenario Outline: cannot to a resource to which an authenticated agent has inherited access - Given url tests[type].url - And headers authHeaders(method, tests[type].url, public) - And header Content-Type = 'text/n3' - And request '@prefix solid: . _:insert a solid:InsertDeletePatch; solid:inserts { <> a . }.' - When method - Then status - Examples: - | agent | type | mode | method | public! | status | - | Bob | rdf | R | PATCH | false | 403 | - | Bob | container | R | PATCH | false | 403 | - | Public | rdf | R | PATCH | true | 401 | - | Public | container | R | PATCH | true | 401 | - - Scenario Outline: cannot to a resource to which an authenticated agent has inherited access - Given url tests[type].url - And headers authHeaders(method, tests[type].url, public) - And header Content-Type = 'text/plain' - And request "Bob's text" - When method - Then match contains responseStatus - Examples: - | agent | type | mode | method | public! | status | - | Bob | plain | R | PUT | false | [403] | - | Bob | plain | R | POST | false | [403] | - | Bob | plain | R | PATCH | false | [403, 405, 415] | - | Public | plain | R | PUT | true | [401] | - | Public | plain | R | POST | true | [401] | - | Public | plain | R | PATCH | true | [401, 405, 415] | - - Scenario Outline: cannot a resource to which an authenticated agent has inherited access - Given url tests[type].url - And headers authHeaders(method, tests[type].url, public) - When method - Then status - Examples: - | agent | type | mode | method | public! | status | - | Bob | plain | R | DELETE | false | 403 | - | Bob | rdf | R | DELETE | false | 403 | - | Bob | container | R | DELETE | false | 403 | - | Public | plain | R | DELETE | true | 401 | - | Public | rdf | R | DELETE | true | 401 | - | Public | container | R | DELETE | true | 401 | diff --git a/web-access-control/protected-operation/read-inherited-access-bob.feature b/web-access-control/protected-operation/read-inherited-access-bob.feature deleted file mode 100644 index 1298968..0000000 --- a/web-access-control/protected-operation/read-inherited-access-bob.feature +++ /dev/null @@ -1,117 +0,0 @@ -Feature: Only Bob can read (and only that) a resource when granted inherited read access - # Grant a specific agent (setAgentAccess/setInheritableAgentAccess): - # - full access to the parent container (to ensure the tests are specific to the resource) - # - restricted access to the any contained resources via the parent - Background: Create test resources with correct access modes - * def authHeaders = (method, url, public) => !public ? clients.bob.getAuthHeaders(method, url) : {} - * def createResources = - """ - function (modes) { - const testContainer = rootTestContainer.createContainer() - testContainer.accessDataset = testContainer.accessDatasetBuilder - .setAgentAccess(testContainer.url, webIds.bob, ['read', 'write', 'append', 'control']) - .setInheritableAgentAccess(testContainer.url, webIds.bob, modes).build() - const plainResource = testContainer.createResource('.txt', 'Hello', 'text/plain') - const rdfResource = testContainer.createResource('.ttl', karate.readAsString('../fixtures/example.ttl'), 'text/turtle') - const container = testContainer.createContainer() - return { plain: plainResource, rdf: rdfResource, container: container } - } - """ - # Create 3 test resources with read access for Bob - * def testsR = callonce createResources ['read'] - # Create 3 test resources with append, write, control access for Bob - * def testsAWC = callonce createResources ['append', 'write', 'control'] - - Scenario Outline: read a resource () to which Bob has inherited access - Given url tests[type].url - And headers authHeaders(method, tests[type].url, public) - When method - Then status - Examples: - | agent | type | mode | method | public! | status | - | Bob can | plain | R | GET | false | 200 | - | Bob can | rdf | R | GET | false | 200 | - | Bob can | container | R | GET | false | 200 | - | Bob can | plain | R | HEAD | false | 200 | - | Bob can | rdf | R | HEAD | false | 200 | - | Bob can | container | R | HEAD | false | 200 | - | Public cannot | plain | R | GET | true | 401 | - | Public cannot | rdf | R | GET | true | 401 | - | Public cannot | container | R | GET | true | 401 | - | Public cannot | plain | R | HEAD | true | 401 | - | Public cannot | rdf | R | HEAD | true | 401 | - | Public cannot | container | R | HEAD | true | 401 | - | Bob cannot | plain | AWC | GET | false | 403 | - | Bob cannot | rdf | AWC | GET | false | 403 | - | Bob cannot | container | AWC | GET | false | 403 | - | Bob cannot | plain | AWC | HEAD | false | 403 | - | Bob cannot | rdf | AWC | HEAD | false | 403 | - | Bob cannot | container | AWC | HEAD | false | 403 | - | Public cannot | plain | AWC | GET | true | 401 | - | Public cannot | rdf | AWC | GET | true | 401 | - | Public cannot | container | AWC | GET | true | 401 | - | Public cannot | plain | AWC | HEAD | true | 401 | - | Public cannot | rdf | AWC | HEAD | true | 401 | - | Public cannot | container | AWC | HEAD | true | 401 | - - Scenario Outline: cannot to a resource to which Bob has inherited access - Given url tests[type].url - And headers authHeaders(method, tests[type].url, public) - And header Content-Type = 'text/turtle' - And request '@prefix rdfs: . <> rdfs:comment "Bob added this.".' - When method - Then status - Examples: - | agent | type | mode | method | public! | status | - | Bob | rdf | R | PUT | false | 403 | - | Bob | container | R | PUT | false | 403 | - | Bob | rdf | R | POST | false | 403 | - | Bob | container | R | POST | false | 403 | - | Public | rdf | R | PUT | true | 401 | - | Public | container | R | PUT | true | 401 | - | Public | rdf | R | POST | true | 401 | - | Public | container | R | POST | true | 401 | - - Scenario Outline: cannot to a resource to which Bob has inherited access - Given url tests[type].url - And headers authHeaders(method, tests[type].url, public) - And header Content-Type = 'text/n3' - And request '@prefix solid: . _:insert a solid:InsertDeletePatch; solid:inserts { <> a . }.' - When method - Then status - Examples: - | agent | type | mode | method | public! | status | - | Bob | rdf | R | PATCH | false | 403 | - | Bob | container | R | PATCH | false | 403 | - | Public | rdf | R | PATCH | true | 401 | - | Public | container | R | PATCH | true | 401 | - - Scenario Outline: cannot to a resource to which Bob has inherited access - Given url tests[type].url - And headers authHeaders(method, tests[type].url, public) - And header Content-Type = 'text/plain' - And request "Bob's text" - When method - Then match contains responseStatus - Examples: - | agent | type | mode | method | public! | status | - | Bob | plain | R | PUT | false | [403] | - | Bob | plain | R | POST | false | [403] | - | Bob | plain | R | PATCH | false | [403, 405, 415] | - | Public | plain | R | PUT | true | [401] | - | Public | plain | R | POST | true | [401] | - | Public | plain | R | PATCH | true | [401, 405, 415] | - - Scenario Outline: cannot a resource to which Bob has inherited access - Given url tests[type].url - And headers authHeaders(method, tests[type].url, public) - When method - Then status - Examples: - | agent | type | mode | method | public! | status | - | Bob | plain | R | DELETE | false | 403 | - | Bob | rdf | R | DELETE | false | 403 | - | Bob | container | R | DELETE | false | 403 | - | Public | plain | R | DELETE | true | 401 | - | Public | rdf | R | DELETE | true | 401 | - | Public | container | R | DELETE | true | 401 | diff --git a/web-access-control/protected-operation/read-inherited-access-public.feature b/web-access-control/protected-operation/read-inherited-access-public.feature deleted file mode 100644 index 1ca5f0d..0000000 --- a/web-access-control/protected-operation/read-inherited-access-public.feature +++ /dev/null @@ -1,121 +0,0 @@ -Feature: Public agents can read (and only that) a resource when granted inherited read access - # Grant public agents (setPublicAccess/setInheritablePublicAccess): - # - full access to the parent container (to ensure the tests are specific to the resource) - # - restricted access to the any contained resources via the parent - Background: Create test resources with correct access modes - * def authHeaders = (method, url, public) => !public ? clients.bob.getAuthHeaders(method, url) : {} - * def createResources = - """ - function (modes) { - const testContainer = rootTestContainer.createContainer() - testContainer.accessDataset = testContainer.accessDatasetBuilder - .setPublicAccess(testContainer.url, ['read', 'write', 'append', 'control']) - .setInheritablePublicAccess(testContainer.url, modes).build() - const plainResource = testContainer.createResource('.txt', 'Hello', 'text/plain') - const rdfResource = testContainer.createResource('.ttl', karate.readAsString('../fixtures/example.ttl'), 'text/turtle') - const container = testContainer.createContainer() - return { plain: plainResource, rdf: rdfResource, container: container } - } - """ - # Create 3 test resources with read access for public agents - * def testsR = callonce createResources ['read'] - # Create 3 test resources with append, write, control access for public agents - * def testsAWC = callonce createResources ['append', 'write', 'control'] - - Scenario Outline: read a resource () to which a public agent has inherited access - Given url tests[type].url - And headers authHeaders(method, tests[type].url, public) - When method - Then status - Examples: - | agent | type | mode | method | public! | status | - | Bob can | plain | R | GET | false | 200 | - | Bob can | rdf | R | GET | false | 200 | - | Bob can | container | R | GET | false | 200 | - | Bob can | plain | R | HEAD | false | 200 | - | Bob can | rdf | R | HEAD | false | 200 | - | Bob can | container | R | HEAD | false | 200 | - | Bob cannot | plain | AWC | GET | false | 403 | - | Bob cannot | rdf | AWC | GET | false | 403 | - | Bob cannot | container | AWC | GET | false | 403 | - | Bob cannot | plain | AWC | HEAD | false | 403 | - | Bob cannot | rdf | AWC | HEAD | false | 403 | - | Bob cannot | container | AWC | HEAD | false | 403 | - | Public cannot | plain | AWC | GET | true | 401 | - | Public cannot | rdf | AWC | GET | true | 401 | - | Public cannot | container | AWC | GET | true | 401 | - | Public cannot | plain | AWC | HEAD | true | 401 | - | Public cannot | rdf | AWC | HEAD | true | 401 | - | Public cannot | container | AWC | HEAD | true | 401 | - - @publicagent - Examples: - | agent | type | mode | method | public! | status | - | Public can | plain | R | GET | true | 200 | - | Public can | rdf | R | GET | true | 200 | - | Public can | container | R | GET | true | 200 | - | Public can | plain | R | HEAD | true | 200 | - | Public can | rdf | R | HEAD | true | 200 | - | Public can | container | R | HEAD | true | 200 | - - Scenario Outline: cannot to a resource to which a public agent has inherited access - Given url tests[type].url - And headers authHeaders(method, tests[type].url, public) - And header Content-Type = 'text/turtle' - And request '@prefix rdfs: . <> rdfs:comment "Bob added this.".' - When method - Then status - Examples: - | agent | type | mode | method | public! | status | - | Bob | rdf | R | PUT | false | 403 | - | Bob | container | R | PUT | false | 403 | - | Bob | rdf | R | POST | false | 403 | - | Bob | container | R | POST | false | 403 | - | Public | rdf | R | PUT | true | 401 | - | Public | container | R | PUT | true | 401 | - | Public | rdf | R | POST | true | 401 | - | Public | container | R | POST | true | 401 | - - Scenario Outline: cannot to a resource to which a public agent has inherited access - Given url tests[type].url - And headers authHeaders(method, tests[type].url, public) - And header Content-Type = 'text/n3' - And request '@prefix solid: . _:insert a solid:InsertDeletePatch; solid:inserts { <> a . }.' - When method - Then status - Examples: - | agent | type | mode | method | public! | status | - | Bob | rdf | R | PATCH | false | 403 | - | Bob | container | R | PATCH | false | 403 | - | Public | rdf | R | PATCH | true | 401 | - | Public | container | R | PATCH | true | 401 | - - Scenario Outline: cannot to a resource to which a public agent has inherited access - Given url tests[type].url - And headers authHeaders(method, tests[type].url, public) - And header Content-Type = 'text/plain' - And request "Bob's text" - When method - Then match contains responseStatus - Examples: - | agent | type | mode | method | public! | status | - | Bob | plain | R | PUT | false | [403] | - | Bob | plain | R | POST | false | [403] | - | Bob | plain | R | PATCH | false | [403, 405, 415] | - | Public | plain | R | PUT | true | [401] | - | Public | plain | R | POST | true | [401] | - | Public | plain | R | PATCH | true | [401, 405, 415] | - - Scenario Outline: cannot a resource to which a public agent has inherited access - Given url tests[type].url - And headers authHeaders(method, tests[type].url, public) - When method - Then status - Examples: - | agent | type | mode | method | public! | status | - | Bob | plain | R | DELETE | false | 403 | - | Bob | rdf | R | DELETE | false | 403 | - | Bob | container | R | DELETE | false | 403 | - | Public | plain | R | DELETE | true | 401 | - | Public | rdf | R | DELETE | true | 401 | - | Public | container | R | DELETE | true | 401 | diff --git a/web-access-control/protected-operation/write-access-agent.feature b/web-access-control/protected-operation/write-access-agent.feature new file mode 100644 index 0000000..0d78bdd --- /dev/null +++ b/web-access-control/protected-operation/write-access-agent.feature @@ -0,0 +1,157 @@ +Feature: Only authenticated agents can write (and only that) a resource when granted write access + # Grant authenticated agents (setAuthenticatedAccess): + # - restricted access or no access to the parent container + # - restricted access to the test resources, or inherited access for fictive resources + Background: Create test resources with correct access modes + * table resources + | type | container | resource | + | 'plain' | 'no' | 'WAC' | + | 'plain' | 'WAC' | 'inherited' | + | 'fictive' | 'WAC' | 'inherited' | + | 'rdf' | 'no' | 'WAC' | + | 'rdf' | 'WAC' | 'inherited' | + | 'container' | 'no' | 'WAC' | + | 'container' | 'WAC' | 'inherited' | + * def utils = callonce read('common.feature') ({resources, subject: 'authenticated'}) + + Scenario Outline: read a resource (), when an authenticated agent has access to the container and access to the resource + * def testResource = utils.getResource(container, resource, type) + Given url testResource.url + And headers utils.authHeaders(method, testResource.url, agent) + When method + Then status + Examples: + | agent | result | method | type | container | resource | status | + | Bob | cannot | GET | plain | no | WAC | 403 | + | Bob | cannot | GET | plain | WAC | inherited | 403 | + | Bob | cannot | GET | fictive | WAC | inherited | 403 | + | Bob | cannot | GET | rdf | no | WAC | 403 | + | Bob | cannot | GET | rdf | WAC | inherited | 403 | + | Bob | cannot | GET | container | no | WAC | 403 | + | Bob | cannot | GET | container | WAC | inherited | 403 | + | Bob | cannot | HEAD | plain | no | WAC | 403 | + | Bob | cannot | HEAD | plain | WAC | inherited | 403 | + | Bob | cannot | HEAD | fictive | WAC | inherited | 403 | + | Bob | cannot | HEAD | rdf | no | WAC | 403 | + | Bob | cannot | HEAD | rdf | WAC | inherited | 403 | + | Bob | cannot | HEAD | container | no | WAC | 403 | + | Bob | cannot | HEAD | container | WAC | inherited | 403 | + | Public | cannot | GET | plain | no | WAC | 401 | + | Public | cannot | GET | plain | WAC | inherited | 401 | + | Public | cannot | GET | fictive | WAC | inherited | 401 | + | Public | cannot | GET | rdf | no | WAC | 401 | + | Public | cannot | GET | rdf | WAC | inherited | 401 | + | Public | cannot | GET | container | no | WAC | 401 | + | Public | cannot | GET | container | WAC | inherited | 401 | + | Public | cannot | HEAD | plain | no | WAC | 401 | + | Public | cannot | HEAD | plain | WAC | inherited | 401 | + | Public | cannot | HEAD | fictive | WAC | inherited | 401 | + | Public | cannot | HEAD | rdf | no | WAC | 401 | + | Public | cannot | HEAD | rdf | WAC | inherited | 401 | + | Public | cannot | HEAD | container | no | WAC | 401 | + | Public | cannot | HEAD | container | WAC | inherited | 401 | + + Scenario Outline: write a resource () and cannot read it, when an authenticated agent has access to the container and access to the resource + * def testResource = utils.createResource(container, resource, type, 'authenticated') + * def requestData = utils.getRequestData(type) + Given url testResource.url + And headers utils.authHeaders(method, testResource.url, agent) + And header Content-Type = requestData.contentType + And request requestData.requestBody + When method + Then match contains responseStatus + # Server may return payload with information about the operation e.g. "Created" so check it hasn't leaked the data which was PUT + And string responseString = response + And match responseString !contains requestData.responseShouldNotContain + + Given headers utils.authHeaders('GET', testResource.url, agent) + When method GET + Then status + + Examples: + | agent | result | method | type | container | resource | writeStatus | readStatus | + | Bob | can | PUT | rdf | no | W | [200, 201, 204, 205] | 403 | + | Bob | can | PUT | rdf | W | inherited | [200, 201, 204, 205] | 403 | + | Bob | can | PUT | plain | no | W | [200, 201, 204, 205] | 403 | + | Bob | can | PUT | plain | W | inherited | [200, 201, 204, 205] | 403 | + | Bob | can | PUT | fictive | W | inherited | [201] | 403 | + | Bob | can | POST | container | no | W | [200, 201, 204, 205] | 403 | + | Bob | can | POST | container | W | inherited | [200, 201, 204, 205] | 403 | + | Bob | can | POST | container | no | A | [200, 201, 204, 205] | 403 | + | Bob | can | POST | container | A | inherited | [200, 201, 204, 205] | 403 | + | Public | cannot | PUT | rdf | no | WAC | [401] | 401 | + | Public | cannot | PUT | rdf | WAC | inherited | [401] | 401 | + | Public | cannot | PUT | plain | no | WAC | [401] | 401 | + | Public | cannot | PUT | plain | WAC | inherited | [401] | 401 | + | Public | cannot | PUT | fictive | WAC | inherited | [401] | 401 | + | Public | cannot | POST | container | no | WAC | [401] | 401 | + | Public | cannot | POST | container | WAC | inherited | [401] | 401 | + + Scenario Outline: to a resource, when an authenticated agent has access to the container and access to the resource + * def testResource = utils.createResource(container, resource, type, 'authenticated') + * def requestData = utils.getRequestData('text/n3') + Given url testResource.url + And headers utils.authHeaders(method, testResource.url, agent) + And header Content-Type = requestData.contentType + And request requestData.requestBody + When method + Then match contains responseStatus + # Server may return payload with information about the operation e.g. "Created" so check it hasn't leaked the data which was PUT + And string responseString = response + And match responseString !contains requestData.responseShouldNotContain + + Given headers utils.authHeaders('GET', testResource.url, agent) + When method GET + Then status + + Examples: + | agent | result | method | type | container | resource | writeStatus | readStatus | + | Bob | can | PATCH | rdf | no | W | [200, 201, 204, 205] | 403 | + | Bob | can | PATCH | rdf | W | inherited | [200, 201, 204, 205] | 403 | + | Bob | can | PATCH | fictive | W | inherited | [200, 201, 204, 205] | 403 | + | Bob | can | PATCH | rdf | no | A | [200, 201, 204, 205] | 403 | + | Bob | can | PATCH | rdf | A | inherited | [200, 201, 204, 205] | 403 | + | Bob | can | PATCH | fictive | A | inherited | [200, 201, 204, 205] | 403 | + | Bob | cannot | PATCH | rdf | no | C | [403] | 403 | + | Bob | cannot | PATCH | rdf | C | inherited | [403] | 403 | + | Bob | cannot | PATCH | fictive | C | inherited | [403] | 403 | + | Public | cannot | PATCH | rdf | no | WAC | [401] | 401 | + | Public | cannot | PATCH | rdf | WAC | inherited | [401] | 401 | + | Public | cannot | PATCH | fictive | WAC | inherited | [401] | 401 | + + Scenario Outline: a resource, when an authenticated agent has access to the container and access to the resource + * def testResource = utils.createResource(container, resource, type, 'authenticated') + Given url testResource.url + And headers utils.authHeaders(method, testResource.url, agent) + When method + Then match contains responseStatus + Examples: + | agent | result | method | type | container | resource | status | + | Bob | cannot | DELETE | plain | no | C | [403] | + | Bob | cannot | DELETE | plain | C | inherited | [403] | + | Bob | cannot | DELETE | fictive | C | inherited | [403] | + | Bob | cannot | DELETE | rdf | no | C | [403] | + | Bob | cannot | DELETE | rdf | C | inherited | [403] | + | Bob | cannot | DELETE | container | no | C | [403] | + | Bob | cannot | DELETE | container | C | inherited | [403] | + | Bob | cannot | DELETE | plain | no | A | [403] | + | Bob | cannot | DELETE | plain | A | inherited | [403] | + | Bob | cannot | DELETE | fictive | A | inherited | [403] | + | Bob | cannot | DELETE | rdf | no | A | [403] | + | Bob | cannot | DELETE | rdf | A | inherited | [403] | + | Bob | cannot | DELETE | container | no | A | [403] | + | Bob | cannot | DELETE | container | A | inherited | [403] | + | Bob | cannot | DELETE | plain | no | W | [403] | + | Bob | can | DELETE | plain | W | inherited | [200, 202, 204, 205] | + | Bob | cannot | DELETE | fictive | W | inherited | [403] | + | Bob | cannot | DELETE | rdf | no | W | [403] | + | Bob | can | DELETE | rdf | W | inherited | [200, 202, 204, 205] | + | Bob | cannot | DELETE | container | no | W | [403] | + | Bob | cannot | DELETE | container | W | inherited | [403] | + | Public | cannot | DELETE | plain | no | WAC | [401] | + | Public | cannot | DELETE | plain | WAC | inherited | [401] | + | Public | cannot | DELETE | fictive | WAC | inherited | [401] | + | Public | cannot | DELETE | rdf | no | WAC | [401] | + | Public | cannot | DELETE | rdf | WAC | inherited | [401] | + | Public | cannot | DELETE | container | no | WAC | [401] | + | Public | cannot | DELETE | container | WAC | inherited | [401] | diff --git a/web-access-control/protected-operation/write-access-bob.feature b/web-access-control/protected-operation/write-access-bob.feature new file mode 100644 index 0000000..acab04f --- /dev/null +++ b/web-access-control/protected-operation/write-access-bob.feature @@ -0,0 +1,157 @@ +Feature: Only Bob can write (and only that) a resource when granted write access + # Grant a specific agent (setAgentAccess): + # - restricted access or no access to the parent container + # - restricted access to the test resources, or inherited access for fictive resources + Background: Create test resources with correct access modes + * table resources + | type | container | resource | + | 'plain' | 'no' | 'WAC' | + | 'plain' | 'WAC' | 'inherited' | + | 'fictive' | 'WAC' | 'inherited' | + | 'rdf' | 'no' | 'WAC' | + | 'rdf' | 'WAC' | 'inherited' | + | 'container' | 'no' | 'WAC' | + | 'container' | 'WAC' | 'inherited' | + * def utils = callonce read('common.feature') ({resources, subject: 'agent', agent: webIds.bob}) + + Scenario Outline: read a resource (), when Bob has access to the container and access to the resource + * def testResource = utils.getResource(container, resource, type) + Given url testResource.url + And headers utils.authHeaders(method, testResource.url, agent) + When method + Then status + Examples: + | agent | result | method | type | container | resource | status | + | Bob | cannot | GET | plain | no | WAC | 403 | + | Bob | cannot | GET | plain | WAC | inherited | 403 | + | Bob | cannot | GET | fictive | WAC | inherited | 403 | + | Bob | cannot | GET | rdf | no | WAC | 403 | + | Bob | cannot | GET | rdf | WAC | inherited | 403 | + | Bob | cannot | GET | container | no | WAC | 403 | + | Bob | cannot | GET | container | WAC | inherited | 403 | + | Bob | cannot | HEAD | plain | no | WAC | 403 | + | Bob | cannot | HEAD | plain | WAC | inherited | 403 | + | Bob | cannot | HEAD | fictive | WAC | inherited | 403 | + | Bob | cannot | HEAD | rdf | no | WAC | 403 | + | Bob | cannot | HEAD | rdf | WAC | inherited | 403 | + | Bob | cannot | HEAD | container | no | WAC | 403 | + | Bob | cannot | HEAD | container | WAC | inherited | 403 | + | Public | cannot | GET | plain | no | WAC | 401 | + | Public | cannot | GET | plain | WAC | inherited | 401 | + | Public | cannot | GET | fictive | WAC | inherited | 401 | + | Public | cannot | GET | rdf | no | WAC | 401 | + | Public | cannot | GET | rdf | WAC | inherited | 401 | + | Public | cannot | GET | container | no | WAC | 401 | + | Public | cannot | GET | container | WAC | inherited | 401 | + | Public | cannot | HEAD | plain | no | WAC | 401 | + | Public | cannot | HEAD | plain | WAC | inherited | 401 | + | Public | cannot | HEAD | fictive | WAC | inherited | 401 | + | Public | cannot | HEAD | rdf | no | WAC | 401 | + | Public | cannot | HEAD | rdf | WAC | inherited | 401 | + | Public | cannot | HEAD | container | no | WAC | 401 | + | Public | cannot | HEAD | container | WAC | inherited | 401 | + + Scenario Outline: write a resource () and cannot read it, when Bob has access to the container and access to the resource + * def testResource = utils.createResource(container, resource, type, 'agent', webIds.bob) + * def requestData = utils.getRequestData(type) + Given url testResource.url + And headers utils.authHeaders(method, testResource.url, agent) + And header Content-Type = requestData.contentType + And request requestData.requestBody + When method + Then match contains responseStatus + # Server may return payload with information about the operation e.g. "Created" so check it hasn't leaked the data which was PUT + And string responseString = response + And match responseString !contains requestData.responseShouldNotContain + + Given headers utils.authHeaders('GET', testResource.url, agent) + When method GET + Then status + + Examples: + | agent | result | method | type | container | resource | writeStatus | readStatus | + | Bob | can | PUT | rdf | no | W | [200, 201, 204, 205] | 403 | + | Bob | can | PUT | rdf | W | inherited | [200, 201, 204, 205] | 403 | + | Bob | can | PUT | plain | no | W | [200, 201, 204, 205] | 403 | + | Bob | can | PUT | plain | W | inherited | [200, 201, 204, 205] | 403 | + | Bob | can | PUT | fictive | W | inherited | [201] | 403 | + | Bob | can | POST | container | no | W | [200, 201, 204, 205] | 403 | + | Bob | can | POST | container | W | inherited | [200, 201, 204, 205] | 403 | + | Bob | can | POST | container | no | A | [200, 201, 204, 205] | 403 | + | Bob | can | POST | container | A | inherited | [200, 201, 204, 205] | 403 | + | Public | cannot | PUT | rdf | no | WAC | [401] | 401 | + | Public | cannot | PUT | rdf | WAC | inherited | [401] | 401 | + | Public | cannot | PUT | plain | no | WAC | [401] | 401 | + | Public | cannot | PUT | plain | WAC | inherited | [401] | 401 | + | Public | cannot | PUT | fictive | WAC | inherited | [401] | 401 | + | Public | cannot | POST | container | no | WAC | [401] | 401 | + | Public | cannot | POST | container | WAC | inherited | [401] | 401 | + + Scenario Outline: to a resource, when Bob has access to the container and access to the resource + * def testResource = utils.createResource(container, resource, type, 'agent', webIds.bob) + * def requestData = utils.getRequestData('text/n3') + Given url testResource.url + And headers utils.authHeaders(method, testResource.url, agent) + And header Content-Type = requestData.contentType + And request requestData.requestBody + When method + Then match contains responseStatus + # Server may return payload with information about the operation e.g. "Created" so check it hasn't leaked the data which was PUT + And string responseString = response + And match responseString !contains requestData.responseShouldNotContain + + Given headers utils.authHeaders('GET', testResource.url, agent) + When method GET + Then status + + Examples: + | agent | result | method | type | container | resource | writeStatus | readStatus | + | Bob | can | PATCH | rdf | no | W | [200, 201, 204, 205] | 403 | + | Bob | can | PATCH | rdf | W | inherited | [200, 201, 204, 205] | 403 | + | Bob | can | PATCH | fictive | W | inherited | [200, 201, 204, 205] | 403 | + | Bob | can | PATCH | rdf | no | A | [200, 201, 204, 205] | 403 | + | Bob | can | PATCH | rdf | A | inherited | [200, 201, 204, 205] | 403 | + | Bob | can | PATCH | fictive | A | inherited | [200, 201, 204, 205] | 403 | + | Bob | cannot | PATCH | rdf | no | C | [403] | 403 | + | Bob | cannot | PATCH | rdf | C | inherited | [403] | 403 | + | Bob | cannot | PATCH | fictive | C | inherited | [403] | 403 | + | Public | cannot | PATCH | rdf | no | WAC | [401] | 401 | + | Public | cannot | PATCH | rdf | WAC | inherited | [401] | 401 | + | Public | cannot | PATCH | fictive | WAC | inherited | [401] | 401 | + + Scenario Outline: a resource, when Bob has access to the container and access to the resource + * def testResource = utils.createResource(container, resource, type, 'agent', webIds.bob) + Given url testResource.url + And headers utils.authHeaders(method, testResource.url, agent) + When method + Then match contains responseStatus + Examples: + | agent | result | method | type | container | resource | status | + | Bob | cannot | DELETE | plain | no | C | [403] | + | Bob | cannot | DELETE | plain | C | inherited | [403] | + | Bob | cannot | DELETE | fictive | C | inherited | [403] | + | Bob | cannot | DELETE | rdf | no | C | [403] | + | Bob | cannot | DELETE | rdf | C | inherited | [403] | + | Bob | cannot | DELETE | container | no | C | [403] | + | Bob | cannot | DELETE | container | C | inherited | [403] | + | Bob | cannot | DELETE | plain | no | A | [403] | + | Bob | cannot | DELETE | plain | A | inherited | [403] | + | Bob | cannot | DELETE | fictive | A | inherited | [403] | + | Bob | cannot | DELETE | rdf | no | A | [403] | + | Bob | cannot | DELETE | rdf | A | inherited | [403] | + | Bob | cannot | DELETE | container | no | A | [403] | + | Bob | cannot | DELETE | container | A | inherited | [403] | + | Bob | cannot | DELETE | plain | no | W | [403] | + | Bob | can | DELETE | plain | W | inherited | [200, 202, 204, 205] | + | Bob | cannot | DELETE | fictive | W | inherited | [403] | + | Bob | cannot | DELETE | rdf | no | W | [403] | + | Bob | can | DELETE | rdf | W | inherited | [200, 202, 204, 205] | + | Bob | cannot | DELETE | container | no | W | [403] | + | Bob | cannot | DELETE | container | W | inherited | [403] | + | Public | cannot | DELETE | plain | no | WAC | [401] | + | Public | cannot | DELETE | plain | WAC | inherited | [401] | + | Public | cannot | DELETE | fictive | WAC | inherited | [401] | + | Public | cannot | DELETE | rdf | no | WAC | [401] | + | Public | cannot | DELETE | rdf | WAC | inherited | [401] | + | Public | cannot | DELETE | container | no | WAC | [401] | + | Public | cannot | DELETE | container | WAC | inherited | [401] | diff --git a/web-access-control/protected-operation/write-access-public.feature b/web-access-control/protected-operation/write-access-public.feature new file mode 100644 index 0000000..0ed4308 --- /dev/null +++ b/web-access-control/protected-operation/write-access-public.feature @@ -0,0 +1,123 @@ +Feature: Only authenticated agents can write (and only that) a resource when granted write access + # Grant public agents (setPublicAccess): + # - restricted access or no access to the parent container + # - restricted access to the test resources, or inherited access for fictive resources + Background: Create test resources with correct access modes + * table resources + | type | container | resource | + | 'plain' | 'no' | 'WAC' | + | 'plain' | 'WAC' | 'inherited' | + | 'fictive' | 'WAC' | 'inherited' | + | 'rdf' | 'no' | 'WAC' | + | 'rdf' | 'WAC' | 'inherited' | + | 'container' | 'no' | 'WAC' | + | 'container' | 'WAC' | 'inherited' | + * def utils = callonce read('common.feature') ({resources, subject: 'public'}) + + Scenario Outline: read a resource (), when a public agent has access to the container and access to the resource + * def testResource = utils.getResource(container, resource, type) + Given url testResource.url + When method + Then status + Examples: + | agent | result | method | type | container | resource | status | + | Public | cannot | GET | plain | no | WAC | 401 | + | Public | cannot | GET | plain | WAC | inherited | 401 | + | Public | cannot | GET | fictive | WAC | inherited | 401 | + | Public | cannot | GET | rdf | no | WAC | 401 | + | Public | cannot | GET | rdf | WAC | inherited | 401 | + | Public | cannot | GET | container | no | WAC | 401 | + | Public | cannot | GET | container | WAC | inherited | 401 | + | Public | cannot | HEAD | plain | no | WAC | 401 | + | Public | cannot | HEAD | plain | WAC | inherited | 401 | + | Public | cannot | HEAD | fictive | WAC | inherited | 401 | + | Public | cannot | HEAD | rdf | no | WAC | 401 | + | Public | cannot | HEAD | rdf | WAC | inherited | 401 | + | Public | cannot | HEAD | container | no | WAC | 401 | + | Public | cannot | HEAD | container | WAC | inherited | 401 | + + @publicagent + Scenario Outline: write a resource () and cannot read it, when a public agent has access to the container and access to the resource + * def testResource = utils.createResource(container, resource, type, 'public') + * def requestData = utils.getRequestData(type) + Given url testResource.url + And header Content-Type = requestData.contentType + And request requestData.requestBody + When method + Then match contains responseStatus + # Server may return payload with information about the operation e.g. "Created" so check it hasn't leaked the data which was PUT + And string responseString = response + And match responseString !contains requestData.responseShouldNotContain + + When method GET + Then status + + Examples: + | agent | result | method | type | container | resource | writeStatus | readStatus | + | Public | can | PUT | rdf | no | W | [200, 201, 204, 205] | 401 | + | Public | can | PUT | rdf | W | inherited | [200, 201, 204, 205] | 401 | + | Public | can | PUT | plain | no | W | [200, 201, 204, 205] | 401 | + | Public | can | PUT | plain | W | inherited | [200, 201, 204, 205] | 401 | + | Public | can | PUT | fictive | W | inherited | [201] | 401 | + | Public | can | POST | container | no | W | [200, 201, 204, 205] | 401 | + | Public | can | POST | container | W | inherited | [200, 201, 204, 205] | 401 | + | Public | can | POST | container | no | A | [200, 201, 204, 205] | 401 | + | Public | can | POST | container | A | inherited | [200, 201, 204, 205] | 401 | + + @publicagent + Scenario Outline: to a resource, when a public agent has access to the container and access to the resource + * def testResource = utils.createResource(container, resource, type, 'public') + * def requestData = utils.getRequestData('text/n3') + Given url testResource.url + And header Content-Type = requestData.contentType + And request requestData.requestBody + When method + Then match contains responseStatus + # Server may return payload with information about the operation e.g. "Created" so check it hasn't leaked the data which was PUT + And string responseString = response + And match responseString !contains requestData.responseShouldNotContain + + When method GET + Then status + + Examples: + | agent | result | method | type | container | resource | writeStatus | readStatus | + | Public | can | PATCH | rdf | no | W | [200, 201, 204, 205] | 401 | + | Public | can | PATCH | rdf | W | inherited | [200, 201, 204, 205] | 401 | + | Public | can | PATCH | fictive | W | inherited | [200, 201, 204, 205] | 401 | + | Public | can | PATCH | rdf | no | A | [200, 201, 204, 205] | 401 | + | Public | can | PATCH | rdf | A | inherited | [200, 201, 204, 205] | 401 | + | Public | can | PATCH | fictive | A | inherited | [200, 201, 204, 205] | 401 | + | Public | cannot | PATCH | rdf | no | C | [401] | 401 | + | Public | cannot | PATCH | rdf | C | inherited | [401] | 401 | + | Public | cannot | PATCH | fictive | C | inherited | [401] | 401 | + + @publicagent + Scenario Outline: a resource, when a public agent has access to the container and access to the resource + * def testResource = utils.createResource(container, resource, type, 'public') + Given url testResource.url + When method + Then match contains responseStatus + Examples: + | agent | result | method | type | container | resource | status | + | Public | cannot | DELETE | plain | no | C | [401] | + | Public | cannot | DELETE | plain | C | inherited | [401] | + | Public | cannot | DELETE | fictive | C | inherited | [401] | + | Public | cannot | DELETE | rdf | no | C | [401] | + | Public | cannot | DELETE | rdf | C | inherited | [401] | + | Public | cannot | DELETE | container | no | C | [401] | + | Public | cannot | DELETE | container | C | inherited | [401] | + | Public | cannot | DELETE | plain | no | A | [401] | + | Public | cannot | DELETE | plain | A | inherited | [401] | + | Public | cannot | DELETE | fictive | A | inherited | [401] | + | Public | cannot | DELETE | rdf | no | A | [401] | + | Public | cannot | DELETE | rdf | A | inherited | [401] | + | Public | cannot | DELETE | container | no | A | [401] | + | Public | cannot | DELETE | container | A | inherited | [401] | + | Public | cannot | DELETE | plain | no | W | [401] | + | Public | can | DELETE | plain | W | inherited | [200, 202, 204, 205] | + | Public | cannot | DELETE | fictive | W | inherited | [401] | + | Public | cannot | DELETE | rdf | no | W | [401] | + | Public | can | DELETE | rdf | W | inherited | [200, 202, 204, 205] | + | Public | cannot | DELETE | container | no | W | [401] | + | Public | cannot | DELETE | container | W | inherited | [401] | diff --git a/web-access-control/protected-operation/write-without-read.feature b/web-access-control/protected-operation/write-without-read.feature deleted file mode 100644 index 2353b70..0000000 --- a/web-access-control/protected-operation/write-without-read.feature +++ /dev/null @@ -1,41 +0,0 @@ -# This test will move to the write operations group when added but represents a useful test to keep until then. -Feature: Bob cannot read an RDF resource even if he can write to it - - Background: Create test resource with all default access except read for Bob - * def setup = - """ - function() { - const testContainer = rootTestContainer.createContainer(); - const access = testContainer.accessDatasetBuilder - .setAgentAccess(testContainer.url, webIds.bob, ['write']) - .setInheritableAgentAccess(testContainer.url, webIds.bob, ['append', 'write', 'control']) - .build(); - testContainer.accessDataset = access; - return testContainer.createResource('.ttl', karate.readAsString('../fixtures/example.ttl'), 'text/turtle'); - } - """ - * def resource = callonce setup - * url resource.url - - Scenario: Bob cannot read the resource with GET - Given headers clients.bob.getAuthHeaders('GET', resource.url) - When method GET - Then status 403 - - Scenario: Bob cannot read the resource with HEAD - Given headers clients.bob.getAuthHeaders('HEAD', resource.url) - When method HEAD - Then status 403 - - Scenario: Bob can PUT to the resource but doesn't get it back since he cannot read - Given request '<> "Bob replaced it." .' - And headers clients.bob.getAuthHeaders('PUT', resource.url) - And header Content-Type = 'text/turtle' - When method PUT - Then match [201, 204, 205] contains responseStatus - # Server may return payload with information about the operation e.g. "Created" so check it hasn't leaked the data which was PUT - And match response !contains "Bob replaced it" - - Given headers clients.bob.getAuthHeaders('GET', resource.url) - When method GET - Then status 403 diff --git a/web-access-control/web-access-control-test-manifest.ttl b/web-access-control/web-access-control-test-manifest.ttl index 99d12c1..92176b6 100644 --- a/web-access-control/web-access-control-test-manifest.ttl +++ b/web-access-control/web-access-control-test-manifest.ttl @@ -48,13 +48,6 @@ manifest:protected-operation-acl-propagation spec:testScript . -manifest:protected-operation-write-without-read - a td:TestCase ; - spec:requirementReference wac:access-modes ; - td:reviewStatus td:unreviewed ; - spec:testScript - . - manifest:server-wac-allow-header-exists a td:TestCase ; spec:requirementReference wac:server-wac-allow ; @@ -111,23 +104,23 @@ manifest:read-access-public spec:testScript . -manifest:read-inherited-access-bob +manifest:write-access-agent a td:TestCase ; spec:requirementReference wac:server-read-operation ; td:reviewStatus td:unreviewed ; spec:testScript - . + . -manifest:read-inherited-access-agent +manifest:write-access-bob a td:TestCase ; spec:requirementReference wac:server-read-operation ; td:reviewStatus td:unreviewed ; spec:testScript - . + . -manifest:read-inherited-access-public +manifest:write-access-public a td:TestCase ; spec:requirementReference wac:server-read-operation ; td:reviewStatus td:unreviewed ; spec:testScript - . + .