Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
*.env
config
reports
target
target
/certs/
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Solid Specification Conformance Tests

## Release 0.0.12
* Use harness API to test sending requests without a content type header.
* Ensure container created correctly on slash semantic tests.

## Release 0.0.11
* Moved repository to `solid-contrib` organization.

Expand Down
33 changes: 10 additions & 23 deletions protocol/writing-resource/content-type-reject.feature
Original file line number Diff line number Diff line change
@@ -1,32 +1,19 @@
Feature: Server MUST reject write requests without Content-Type

Background: Set up clients and paths
* def testContainer = rootTestContainer.reserveContainer()
* def resource = testContainer.reserveResource('.ttl')
* def testContainer = rootTestContainer.createContainer()

Scenario: Server rejects PUT requests without Content-Type
Given url resource.url
And headers clients.alice.getAuthHeaders('PUT', resource.url)
And header Content-Type = ''
And request "<> a <#Something> ."
When method PUT
Then status 400
* def resource = testContainer.reserveResource('.ttl')
* def response = clients.alice.sendAuthorized('PUT', resource.url, '<> a <#Something> .', null, null)
Then assert response.status == 400

Scenario: Server rejects POST requests without Content-Type
* def containerUrl = testContainer.url
Given url containerUrl
And headers clients.alice.getAuthHeaders('POST', containerUrl)
And header Content-Type = ''
And request "<> a <#Something> ."
When method POST
Then status 400
* def response = clients.alice.sendAuthorized('POST', testContainer.url, '<> a <#Something> .', null, null)
Then assert response.status == 400

Scenario: Server rejects PATCH requests without Content-Type
Given url resource.url
And headers clients.alice.getAuthHeaders('PATCH', resource.url)
And header Content-Type = ''
And request "INSERT DATA { <> a <#Something> . }"
When method PATCH
Then status 400


* def resource = testContainer.createResource('.ttl', karate.readAsString('../fixtures/example.ttl'), 'text/turtle')
* def patch = '@prefix solid: <http://www.w3.org/ns/solid/terms#>. _:insert a solid:InsertDeletePatch; solid:inserts { <> a <http://example.org#Foo> . }.'
* def response = clients.alice.sendAuthorized('PATCH', resource.url, patch, null, null)
Then assert response.status == 400
80 changes: 74 additions & 6 deletions protocol/writing-resource/slash-semantics-exclude.feature
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Feature: With and without trailing slash cannot co-exist

Background: Set up clients and paths
* def testContainer = rootTestContainer.reserveContainer()
* def testContainer = rootTestContainer.createContainer()
* configure followRedirects = false

Scenario: PUT container, then try resource with same name
Expand All @@ -13,47 +13,115 @@ Feature: With and without trailing slash cannot co-exist
When method PUT
Then assert responseStatus >= 200 && responseStatus < 300

# confirm there is no non-container resource with the same URI
* def resourceUrl = testContainer.url + 'foo'
Given url resourceUrl
And headers clients.alice.getAuthHeaders('GET', resourceUrl)
When method GET
Then match [301, 404, 410] contains responseStatus

# attempt to overwrite the container with a resource of the same name
Given url resourceUrl
And headers clients.alice.getAuthHeaders('PUT', resourceUrl)
And header Content-Type = 'text/plain'
And request "Hello"
And request 'Hello'
When method PUT
# See https://www.rfc-editor.org/rfc/rfc7231.html#page-27 for why 409 or 415
Then match [409, 415] contains responseStatus


Scenario: PUT resource, then try container with same name
* def resourceUrl = testContainer.url + 'foo'
Given url resourceUrl
And headers clients.alice.getAuthHeaders('PUT', resourceUrl)
And header Content-Type = 'text/plain'
And request "Hello"
And request 'Hello'
When method PUT
Then assert responseStatus >= 200 && responseStatus < 300

# confirm there is no container with the same URI
* def childContainerUrl = testContainer.url + 'foo/'
Given url childContainerUrl
And headers clients.alice.getAuthHeaders('GET', childContainerUrl)
When method GET
Then match [301, 404, 410] contains responseStatus

* def childContainerUrl = testContainer.url + 'foo/'
# attempt to overwrite the resource with a container of the same name
Given url childContainerUrl
And headers clients.alice.getAuthHeaders('PUT', childContainerUrl)
And header Content-Type = 'text/turtle'
When method PUT
# See https://www.rfc-editor.org/rfc/rfc7231.html#page-27 for why 409 or 415
Then match [409, 415] contains responseStatus

# TODO: Evil test to check various suffices.
Scenario: POST container, then try resource with same name
Given url testContainer.url
And headers clients.alice.getAuthHeaders('POST', testContainer.url)
And header Content-Type = 'text/turtle'
And header Link = '<http://www.w3.org/ns/ldp#BasicContainer>; rel="type"'
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hadn't spotted that PUT with "/" just doesn't need the link since it is implied. I will remove that but I'll add equivalent tests for POST.

When method POST
Then assert responseStatus >= 200 && responseStatus < 300
And def childContainerUrl = responseHeaders['Location'][0]
And assert childContainerUrl.endsWith('/')

# confirm there is no non-container resource with the same URI
* def resourceUrl = childContainerUrl.slice(0, -1)
Given url resourceUrl
And headers clients.alice.getAuthHeaders('GET', resourceUrl)
When method GET
Then match [301, 404, 410] contains responseStatus

# attempt to overwrite the container with a resource of the same name by PUT
Given url resourceUrl
And headers clients.alice.getAuthHeaders('PUT', resourceUrl)
And header Content-Type = 'text/plain'
And request 'Hello'
When method PUT
# See https://www.rfc-editor.org/rfc/rfc7231.html#page-27 for why 409 or 415
Then match [409, 415] contains responseStatus

# attempt to overwrite the container with a resource of the same name by POST with a slug
Given url testContainer.url
And headers clients.alice.getAuthHeaders('POST', testContainer.url)
And header Slug = resourceUrl.substring(resourceUrl.lastIndexOf('/') + 1)
And header Content-Type = 'text/plain'
And request 'Hello'
When method POST
# this should either succeed (without using the slug) or fail as a conflict
Then assert (responseStatus >= 200 && responseStatus < 300 && responseHeaders['Location'][0] != resourceUrl) || [409, 415].includes(responseStatus)

Scenario: POST resource, then try container with same name
Given url testContainer.url
And headers clients.alice.getAuthHeaders('POST', testContainer.url)
And header Content-Type = 'text/plain'
And request 'Hello'
When method POST
Then assert responseStatus >= 200 && responseStatus < 300
And def resourceUrl = responseHeaders['Location'][0]
And assert !resourceUrl.endsWith('/')

# confirm there is no container with the same URI
* def childContainerUrl = resourceUrl + '/'
Given url childContainerUrl
And headers clients.alice.getAuthHeaders('GET', childContainerUrl)
When method GET
Then match [301, 404, 410] contains responseStatus

# attempt to overwrite the resource with a container of the same name by PUT
Given url childContainerUrl
And headers clients.alice.getAuthHeaders('PUT', childContainerUrl)
And header Content-Type = 'text/turtle'
When method PUT
# See https://www.rfc-editor.org/rfc/rfc7231.html#page-27 for why 409 or 415
Then match [409, 415] contains responseStatus

# attempt to overwrite the resource with a container of the same name by POST with a slug
Given url testContainer.url
And headers clients.alice.getAuthHeaders('POST', testContainer.url)
And header Slug = resourceUrl.substring(resourceUrl.lastIndexOf('/') + 1)
And header Content-Type = 'text/turtle'
And header Link = '<http://www.w3.org/ns/ldp#BasicContainer>; rel="type"'
When method POST
# this should either succeed (without using the slug) or fail as a conflict
Then assert (responseStatus >= 200 && responseStatus < 300 && responseHeaders['Location'][0] != resourceUrl + '/') || [409, 415].includes(responseStatus)

# TODO: Evil test to check various suffices.
69 changes: 60 additions & 9 deletions run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ setup_css() {
mkdir -p config
cat > ./config/css-config.json <<EOF
{
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^3.0.0/components/context.jsonld",
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^4.0.0/components/context.jsonld",
"import": [
"files-scs:config/app/main/default.json",
"files-scs:config/app/init/initialize-prefilled-root.json",
Expand Down Expand Up @@ -64,12 +64,54 @@ setup_css() {
"@graph": [
{
"comment": [
"An example of what a config could look like if HTTPS is required.",
"Adds CLI options --httpsKey and --httpsCert and uses those to start an HTTPS server.",
"The http/server-factory import above has been omitted since that feature is set below."
]
},
{
"comment": "The key/cert values should be replaces with paths to the correct files. The 'options' block can be removed if not needed.",
"@id": "urn:solid-server-app-setup:default:CliExtractor",
"@type": "YargsCliExtractor",
"extendedParameters": {
"httpsKey": {
"demandOption": true,
"requiresArg": true,
"type": "string",
"describe": "File path to the HTTPS key."
},
"httpsCert": {
"demandOption": true,
"requiresArg": true,
"type": "string",
"describe": "File path to the HTTPS certificate."
}
}
},
{
"comment": "Adds resolvers to assign the CLI values to the Components.js variables.",
"@id": "urn:solid-server-app-setup:default:SettingsResolver",
"@type": "CombinedSettingsResolver",
"resolvers": [
{
"CombinedSettingsResolver:_resolvers_key": "urn:solid-server:custom:variable:httpsKey",
"CombinedSettingsResolver:_resolvers_value": {
"@type": "KeyExtractor",
"key": "httpsKey"
}
},
{
"CombinedSettingsResolver:_resolvers_key": "urn:solid-server:custom:variable:httpsCert",
"CombinedSettingsResolver:_resolvers_value": {
"@type": "KeyExtractor",
"key": "httpsCert"
}
}
]
},
{
"comment": [
"Creates an HTTPS server with the settings provided via the command line.",
"Replaces the example import from config/http/server-factory.https-example.json."
],
"@id": "urn:solid-server:default:ServerFactory",
"@type": "WebSocketServerFactory",
"baseServerFactory": {
Expand All @@ -78,8 +120,14 @@ setup_css() {
"handler": { "@id": "urn:solid-server:default:HttpHandler" },
"options_showStackTrace": { "@id": "urn:solid-server:default:variable:showStackTrace" },
"options_https": true,
"options_key": "/config/server.key",
"options_cert": "/config/server.cert"
"options_key": {
"@id": "urn:solid-server:custom:variable:httpsKey",
"@type": "Variable"
},
"options_cert": {
"@id": "urn:solid-server:custom:variable:httpsCert",
"@type": "Variable"
}
},
"webSocketHandler": {
"@type": "UnsecureWebSocketsProtocol",
Expand All @@ -91,16 +139,19 @@ setup_css() {
EOF

openssl req -new -x509 -days 365 -nodes \
-out config/server.cert \
-keyout config/server.key \
-out certs/server.cert \
-keyout certs/server.key \
-subj "/C=US/ST=California/L=Los Angeles/O=Security/OU=IT Department/CN=server"

# Assumption: You have added 'server' as a mapping of localhost in /etc/hosts

docker network create testnet
docker run -d --name=server --network=testnet --env NODE_TLS_REJECT_UNAUTHORIZED=0 \
-v "$(pwd)"/config:/config -p 443:443 -it solidproject/community-server:3 \
-c /config/css-config.json --port=443 --baseUrl=https://server/
-v "$(pwd)"/config:/config \
-v "$(pwd)"/certs:/certs -p 443:443 -it solidproject/community-server:4 \
-c /config/css-config.json \
--httpsKey=/certs/server.key --httpsCert=/certs/server.cert \
--port=443 --baseUrl=https://server/

until $(curl --output /dev/null --silent --head --fail -k https://server); do
printf '.'
Expand Down