diff --git a/lws10-test-suite/.gitignore b/lws10-test-suite/.gitignore
new file mode 100644
index 0000000..40b878d
--- /dev/null
+++ b/lws10-test-suite/.gitignore
@@ -0,0 +1 @@
+node_modules/
\ No newline at end of file
diff --git a/lws10-test-suite/README.md b/lws10-test-suite/README.md
new file mode 100644
index 0000000..45eb0dd
--- /dev/null
+++ b/lws10-test-suite/README.md
@@ -0,0 +1,98 @@
+# LWS Protocol 1.0 Test Suite
+
+Test suite for the [Linked Web Storage Protocol 1.0](https://www.w3.org/TR/lws10-core/)
+and its authentication suite specifications:
+
+- [LWS 1.0 Authentication Suite: Self-signed Identity using did:key](https://www.w3.org/TR/lws10-authn-ssi-did-key/)
+- [LWS 1.0 Authentication Suite: OpenID Connect](https://www.w3.org/TR/lws10-authn-openid/)
+- [LWS 1.0 Authentication Suite: SAML 2.0](https://www.w3.org/TR/lws10-authn-saml/)
+
+## Directory Structure
+
+```
+tests/
+├── context.jsonld # Shared JSON-LD context for all manifests
+├── manifest.jsonld # Root manifest (core protocol tests)
+├── auth/
+│ ├── did-key/manifest.jsonld # did:key authentication suite tests
+│ ├── oidc/manifest.jsonld # OpenID Connect authentication suite tests
+│ └── saml/manifest.jsonld # SAML 2.0 authentication suite tests
+├── containers/
+│ └── alice-notes.json # Example container representation body
+├── linksets/
+│ ├── alice-notes.meta.json # Linkset for /alice/notes/
+│ └── shoppinglist.meta.json # Linkset for /alice/notes/shoppinglist.txt
+└── resources/
+ ├── storage-description.json # Storage description resource body
+ ├── authorization-server-metadata.json # AS metadata body
+ ├── shoppinglist.txt # Data resource body
+ ├── token-response-200.json # Successful token exchange response
+ └── token-response-400.json # Failed token exchange response
+```
+
+## Conventions
+
+### Inline vs. by-reference properties
+
+Properties that carry their value directly use a plain JSON name:
+
+```json
+"body": "some content"
+```
+
+Properties that reference an external resource by URL append `URL` to the name:
+
+```json
+"bodyURL": "../containers/alice-notes.json"
+```
+
+The same convention applies to `href` / `hrefTemplate` for Link header values, and
+`url` / `urlTemplate` for request targets.
+
+### Test types
+
+| Type | Meaning |
+|------|---------|
+| `lwst:ValidationTest` | The server MUST respond as described for conformance |
+| `lwst:NegativeTest` | The server MUST reject the request as described |
+
+### Traits (applied to tests for filtering)
+
+`Get`, `Post`, `Put`, `Delete`, `Patch` — HTTP method used
+`Public` — no authentication required
+`Private` — resource requires authentication/authorization
+`Authn` — exercises authentication behaviour
+`Authz` — exercises authorization behaviour
+`Container` — targets a Container resource
+`DataResource` — targets a DataResource
+`Discovery` — exercises service/storage description discovery
+
+### Authorization roles (ODRL-based)
+
+| Compact name | Meaning |
+|-------------|---------|
+| `Role-Public` | Any agent, authenticated or not |
+| `Role-Authenticated` | Any agent that presents a valid access token |
+| `Role-Owner` | The resource manager (owner) of the storage |
+
+### Prerequisite hierarchy
+
+Each test's `prereqs.hierarchy` array describes the resources (and their access policies)
+that a test harness MUST establish before issuing the test request. An empty array means
+no prior state is required.
+
+### Authentication
+
+`prereqs.authentication` and `request.authentication` hold the WebID/subject URI of the
+agent to authenticate as, or `null` for anonymous requests.
+
+## Test certificate authority
+
+Tests that require authentication may use a test CA whose certificate is distributed
+alongside this test suite. Implementations SHOULD accept this CA only in test/non-production
+environments.
+
+## Status values
+
+All tests start with `mf:Proposed`. They advance to `mf:Approved` once an independent
+implementation demonstrates conformance.
diff --git a/lws10-test-suite/auth/did-key b/lws10-test-suite/auth/did-key
new file mode 120000
index 0000000..40cfeee
--- /dev/null
+++ b/lws10-test-suite/auth/did-key
@@ -0,0 +1 @@
+../mnt/user-data/outputs/lws-tests/auth/did-key
\ No newline at end of file
diff --git a/lws10-test-suite/auth/oidc b/lws10-test-suite/auth/oidc
new file mode 120000
index 0000000..a9c4dbd
--- /dev/null
+++ b/lws10-test-suite/auth/oidc
@@ -0,0 +1 @@
+../mnt/user-data/outputs/lws-tests/auth/oidc
\ No newline at end of file
diff --git a/lws10-test-suite/auth/saml b/lws10-test-suite/auth/saml
new file mode 120000
index 0000000..f830e38
--- /dev/null
+++ b/lws10-test-suite/auth/saml
@@ -0,0 +1 @@
+../mnt/user-data/outputs/lws-tests/auth/saml
\ No newline at end of file
diff --git a/lws10-test-suite/containers/alice-notes.json b/lws10-test-suite/containers/alice-notes.json
new file mode 100644
index 0000000..d32ad7e
--- /dev/null
+++ b/lws10-test-suite/containers/alice-notes.json
@@ -0,0 +1,17 @@
+{
+ "@context": "https://www.w3.org/ns/lws/v1",
+ "id": "https://storage.example/alice/notes/",
+ "type": "Container",
+ "items": [
+ {
+ "id": "https://storage.example/alice/notes/shoppinglist.txt",
+ "type": "DataResource",
+ "contentType": "text/plain",
+ "size": 47
+ },
+ {
+ "id": "https://storage.example/alice/notes/ideas/",
+ "type": "Container"
+ }
+ ]
+}
diff --git a/lws10-test-suite/context.jsonld b/lws10-test-suite/context.jsonld
new file mode 100644
index 0000000..17931a0
--- /dev/null
+++ b/lws10-test-suite/context.jsonld
@@ -0,0 +1,94 @@
+{
+ "@context": {
+ "@vocab": "https://www.w3.org/ns/lws-tests/v1#",
+
+ "lws": "https://www.w3.org/ns/lws/v1#",
+ "lwst": "https://www.w3.org/ns/lws-tests/v1#",
+ "mf": "https://www.w3.org/ns/test-manifest#",
+ "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
+ "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
+ "xsd": "http://www.w3.org/2001/XMLSchema#",
+ "odrl": "http://www.w3.org/ns/odrl/2/",
+
+ "id": "@id",
+ "type": "@type",
+ "comment": "rdfs:comment",
+ "label": "rdfs:label",
+
+ "name": {"@id": "mf:name", "@type": "xsd:string"},
+ "entries": {"@id": "mf:entries","@container": "@list", "@type": "@id"},
+ "status": {"@id": "mf:status", "@type": "@vocab"},
+ "include": {"@id": "mf:include","@type": "@id", "@container": "@list"},
+
+ "traits": {"@id": "lwst:traits", "@type": "@vocab", "@container": "@set"},
+
+ "prereqs": {"@id": "lwst:prereqs"},
+ "hierarchy": {"@id": "lwst:hierarchy", "@container": "@list"},
+ "authentication": {"@id": "lwst:authentication"},
+ "authorization": {"@id": "lwst:authorization"},
+
+ "url": {"@id": "lwst:url", "@type": "@id"},
+ "urlTemplate": {"@id": "lwst:url", "@type": "xsd:string"},
+
+ "read": {"@id": "odrl:read", "@type": "@vocab"},
+ "write": {"@id": "odrl:write", "@type": "@vocab"},
+ "append": {"@id": "odrl:use", "@type": "@vocab"},
+ "control": {"@id": "odrl:modify","@type": "@vocab"},
+
+ "request": {"@id": "lwst:request"},
+ "method": {"@id": "lwst:method", "@type": "xsd:string"},
+ "slug": {"@id": "lwst:slug", "@type": "xsd:string"},
+ "contentType": {"@id": "lwst:contentType", "@type": "xsd:string"},
+
+ "linkHeaders": {"@id": "lwst:linkHeaders", "@container": "@list"},
+ "otherHeaders": {"@id": "lwst:otherHeaders", "@container": "@list"},
+ "rel": {"@id": "lwst:rel", "@type": "xsd:string"},
+ "href": {"@id": "lwst:href", "@type": "@id"},
+ "hrefTemplate": {"@id": "lwst:href", "@type": "xsd:string"},
+ "mediaType": {"@id": "lwst:mediaType", "@type": "xsd:string"},
+ "headerName": {"@id": "lwst:headerName", "@type": "xsd:string"},
+ "headerValue": {"@id": "lwst:headerValue", "@type": "xsd:string"},
+
+ "body": {"@id": "lwst:body", "@type": "xsd:string"},
+ "bodyURL": {"@id": "lwst:body", "@type": "@id"},
+
+ "response": {"@id": "lwst:response"},
+ "statusCode": {"@id": "lwst:statusCode", "@type": "xsd:integer"},
+ "authenticationChallenge": {"@id": "lwst:authenticationChallenge"},
+ "authorizationHint": {"@id": "lwst:authorizationHint"},
+
+ "wwwAuthenticate": {"@id": "lwst:wwwAuthenticate", "@type": "xsd:string"},
+ "asUri": {"@id": "lwst:asUri", "@type": "@id"},
+ "realm": {"@id": "lwst:realm", "@type": "@id"},
+
+ "locationURL": {"@id": "lwst:location", "@type": "@id"},
+
+ "authSuites": {"@id": "lwst:authSuites", "@type": "@id", "@container": "@set"},
+ "facets": {"@id": "lwst:facets", "@type": "@id", "@container": "@set"},
+
+ "Role-Public": "lwst:Role-Public",
+ "Role-Owner": "lwst:Role-Owner",
+ "Role-Authenticated": "lwst:Role-Authenticated",
+
+ "Proposed": "mf:Proposed",
+ "Approved": "mf:Approved",
+ "Pending": "mf:Pending",
+
+ "ValidationTest": "lwst:ValidationTest",
+ "NegativeTest": "lwst:NegativeTest",
+ "Manifest": "mf:Manifest",
+
+ "Get": "lwst:Get",
+ "Post": "lwst:Post",
+ "Put": "lwst:Put",
+ "Delete": "lwst:Delete",
+ "Patch": "lwst:Patch",
+ "Public": "lwst:Public",
+ "Private": "lwst:Private",
+ "Authn": "lwst:Authn",
+ "Authz": "lwst:Authz",
+ "Container": "lwst:Container",
+ "DataResource": "lwst:DataResource",
+ "Discovery": "lwst:Discovery"
+ }
+}
diff --git a/lws10-test-suite/linksets/alice-notes.meta.json b/lws10-test-suite/linksets/alice-notes.meta.json
new file mode 100644
index 0000000..871e2f8
--- /dev/null
+++ b/lws10-test-suite/linksets/alice-notes.meta.json
@@ -0,0 +1,22 @@
+{
+ "linkset": [
+ {
+ "anchor": "https://storage.example/alice/notes/",
+ "https://www.w3.org/ns/lws#storageDescription": [
+ { "href": "https://storage.example/alice/description" }
+ ],
+ "up": [
+ { "href": "https://storage.example/alice/" }
+ ],
+ "type": [
+ { "href": "https://www.w3.org/ns/lws#Container" }
+ ],
+ "linkset": [
+ {
+ "href": "https://storage.example/alice/notes/.meta",
+ "type": "application/linkset+json"
+ }
+ ]
+ }
+ ]
+}
diff --git a/lws10-test-suite/linksets/shoppinglist.meta.json b/lws10-test-suite/linksets/shoppinglist.meta.json
new file mode 100644
index 0000000..36e9447
--- /dev/null
+++ b/lws10-test-suite/linksets/shoppinglist.meta.json
@@ -0,0 +1,22 @@
+{
+ "linkset": [
+ {
+ "anchor": "https://storage.example/alice/notes/shoppinglist.txt",
+ "https://www.w3.org/ns/lws#storageDescription": [
+ { "href": "https://storage.example/alice/description" }
+ ],
+ "up": [
+ { "href": "https://storage.example/alice/notes/" }
+ ],
+ "type": [
+ { "href": "https://www.w3.org/ns/lws#DataResource" }
+ ],
+ "linkset": [
+ {
+ "href": "https://storage.example/alice/notes/shoppinglist.txt.meta",
+ "type": "application/linkset+json"
+ }
+ ]
+ }
+ ]
+}
diff --git a/lws10-test-suite/manifest.html b/lws10-test-suite/manifest.html
new file mode 100644
index 0000000..f7320d1
--- /dev/null
+++ b/lws10-test-suite/manifest.html
@@ -0,0 +1,779 @@
+
+
+
+
+
+ LWS Protocol Test Suite
+
+
+
+
+
+
+
+
+
+
+ Loading manifest…
+
+
+
+
+
+
diff --git a/lws10-test-suite/manifest.jsonld b/lws10-test-suite/manifest.jsonld
new file mode 100644
index 0000000..e8d75ce
--- /dev/null
+++ b/lws10-test-suite/manifest.jsonld
@@ -0,0 +1,810 @@
+{
+ "@context": [
+ { "@base": "https://raw.githubusercontent.com/w3c/lws-protocol/main/lws10-test-suite/" },
+ "./context.jsonld"
+ ],
+ "@graph": [
+ {
+ "@type": "Manifest",
+ "@id": "",
+ "label": "LWS Protocol 1.0 Test Suite",
+ "comment": "Tests for Linked Web Storage Protocol 1.0 (https://www.w3.org/TR/lws10-core/). Tests cover container operations, data resource operations, discovery, authentication, and authorization.",
+ "authSuites": [
+ "https://www.w3.org/TR/lws10-authn-ssi-did-key/",
+ "https://www.w3.org/TR/lws10-authn-openid/",
+ "https://www.w3.org/TR/lws10-authn-saml/"
+ ],
+ "include": [
+ "auth/did-key/manifest.jsonld",
+ "auth/oidc/manifest.jsonld",
+ "auth/saml/manifest.jsonld"
+ ],
+ "entries": [
+
+ {
+ "@type": "ValidationTest",
+ "@id": "#discovery-unauthorized-response-headers",
+ "name": "discovery-unauthorized-response-headers",
+ "traits": ["Get", "Discovery", "Private"],
+ "status": "Proposed",
+ "comment": "unauthorized GET -> 401 { WWW-Authenticate: [Bearer, as_uri, realm], Link }",
+ "source": "https://w3c.github.io/lws-protocol/lws10-core/#authorization-server-discovery",
+ "prereqs": {
+ "hierarchy": [
+ {
+ "url": "/storage_1/metadata/",
+ "authorization": {
+ "read": "Role-Owner",
+ "write": "Role-Owner"
+ }
+ }
+ ],
+ "authentication": null
+ },
+ "request": {
+ "method": "GET",
+ "url": "/storage_1/metadata/",
+ "authentication": null,
+ "authorization": null,
+ "linkHeaders": [],
+ "otherHeaders": []
+ },
+ "response": {
+ "statusCode": 401,
+ "authenticationChallenge": {
+ "wwwAuthenticate": "Bearer",
+ "asUri": "https://authorization.example",
+ "realm": "https://storage.example/storage_1"
+ },
+ "linkHeaders": [
+ {
+ "rel": "storageDescription",
+ "hrefTemplate": "https://storage.example/storage_1/metadata"
+ }
+ ],
+ "otherHeaders": []
+ }
+ },
+
+ {
+ "@type": "ValidationTest",
+ "@id": "#discovery-storage-description",
+ "name": "discovery-storage-description",
+ "traits": ["Get", "Discovery", "Public"],
+ "status": "Proposed",
+ "comment": "public GET [Accept: lws+json] -> 200 lws+json { Link: [storageDescription], body: {@type: Storage, service: storageDescription} }",
+ "source": "https://w3c.github.io/lws-protocol/lws10-core/#discovery",
+ "prereqs": {
+ "hierarchy": [
+ {
+ "url": "/alice/",
+ "authorization": {
+ "read": "Role-Public",
+ "write": "Role-Owner"
+ }
+ }
+ ],
+ "authentication": null
+ },
+ "request": {
+ "method": "GET",
+ "url": "/alice/description",
+ "authentication": null,
+ "authorization": null,
+ "linkHeaders": [],
+ "otherHeaders": [
+ { "headerName": "Accept", "headerValue": "application/lws+json" }
+ ]
+ },
+ "response": {
+ "statusCode": 200,
+ "contentType": "application/lws+json",
+ "linkHeaders": [
+ {
+ "rel": "storageDescription",
+ "hrefTemplate": "/alice/description"
+ }
+ ],
+ "bodyURL": "resources/storage-description.json"
+ }
+ },
+
+ {
+ "@type": "ValidationTest",
+ "@id": "#discovery-get-links-storageDescription",
+ "name": "discovery-get-links-storageDescription",
+ "traits": ["Get", "Discovery", "Public"],
+ "status": "Proposed",
+ "comment": "public GET container -> 200 { Link: [storageDescription] }",
+ "source": "https://w3c.github.io/lws-protocol/lws10-core/#discovery",
+ "prereqs": {
+ "hierarchy": [
+ {
+ "url": "/alice/notes/",
+ "authorization": {
+ "read": "Role-Public",
+ "write": "Role-Owner"
+ }
+ }
+ ],
+ "authentication": null
+ },
+ "request": {
+ "method": "GET",
+ "url": "/alice/notes/",
+ "authentication": null,
+ "authorization": null,
+ "linkHeaders": [],
+ "otherHeaders": []
+ },
+ "response": {
+ "statusCode": 200,
+ "linkHeaders": [
+ {
+ "rel": "storageDescription",
+ "hrefTemplate": "/alice/description"
+ }
+ ]
+ }
+ },
+
+ {
+ "@type": "ValidationTest",
+ "@id": "#getContainer",
+ "name": "getContainer",
+ "traits": ["Get", "Public", "Container"],
+ "status": "Proposed",
+ "comment": "public GET container [Accept: lws+json] -> 200 lws+json { Link: [linkset, up, type=Container, storageDescription] }",
+ "source": "https://w3c.github.io/lws-protocol/lws10-core/#read-resource",
+ "prereqs": {
+ "hierarchy": [
+ {
+ "url": "/alice/notes/",
+ "authorization": {
+ "read": "Role-Public",
+ "write": "Role-Owner"
+ }
+ }
+ ],
+ "authentication": null
+ },
+ "request": {
+ "method": "GET",
+ "url": "/alice/notes/",
+ "authentication": null,
+ "authorization": null,
+ "linkHeaders": [],
+ "otherHeaders": [
+ { "headerName": "Accept", "headerValue": "application/lws+json" }
+ ]
+ },
+ "response": {
+ "statusCode": 200,
+ "contentType": "application/lws+json",
+ "authenticationChallenge": null,
+ "authorizationHint": null,
+ "linkHeaders": [
+ {
+ "rel": "linkset",
+ "hrefTemplate": "/alice/notes/.meta",
+ "mediaType": "application/linkset+json"
+ },
+ {
+ "rel": "up",
+ "hrefTemplate": "/alice/"
+ },
+ {
+ "rel": "type",
+ "href": "https://www.w3.org/ns/lws#Container"
+ },
+ {
+ "rel": "storageDescription",
+ "hrefTemplate": "/alice/description"
+ }
+ ],
+ "otherHeaders": [],
+ "bodyURL": "containers/alice-notes.json"
+ }
+ },
+
+ {
+ "@type": "NegativeTest",
+ "@id": "#getContainer-private-unauthorized",
+ "name": "getContainer-private-unauthorized",
+ "traits": ["Get", "Private", "Container", "Authz"],
+ "status": "Proposed",
+ "comment": "unauthn GET private-container -> 401 { WWW-Authenticate: [Bearer, as_uri, realm] }",
+ "source": "https://w3c.github.io/lws-protocol/lws10-core/#authorization",
+ "prereqs": {
+ "hierarchy": [
+ {
+ "url": "/alice/notes/",
+ "authorization": {
+ "read": "Role-Owner",
+ "write": "Role-Owner"
+ }
+ }
+ ],
+ "authentication": null
+ },
+ "request": {
+ "method": "GET",
+ "url": "/alice/notes/",
+ "authentication": null,
+ "authorization": null,
+ "linkHeaders": [],
+ "otherHeaders": []
+ },
+ "response": {
+ "statusCode": 401,
+ "authenticationChallenge": {
+ "wwwAuthenticate": "Bearer",
+ "asUri": "https://authorization.example",
+ "realm": "https://storage.example/alice/"
+ }
+ }
+ },
+
+ {
+ "@type": "ValidationTest",
+ "@id": "#getContainer-authenticated-owner",
+ "name": "getContainer-authenticated-owner",
+ "traits": ["Get", "Private", "Container", "Authn", "Authz"],
+ "status": "Proposed",
+ "comment": "owner GET private-container [Accept: lws+json] -> 200 lws+json { Link: [linkset, up, type=Container] }",
+ "source": "https://w3c.github.io/lws-protocol/lws10-core/#read-resource",
+ "prereqs": {
+ "hierarchy": [
+ {
+ "url": "/alice/notes/",
+ "authorization": {
+ "read": "Role-Owner",
+ "write": "Role-Owner"
+ }
+ }
+ ],
+ "authentication": "https://id.example/alice"
+ },
+ "request": {
+ "method": "GET",
+ "url": "/alice/notes/",
+ "authentication": "https://id.example/alice",
+ "authorization": "Role-Owner",
+ "linkHeaders": [],
+ "otherHeaders": [
+ { "headerName": "Accept", "headerValue": "application/lws+json" }
+ ]
+ },
+ "response": {
+ "statusCode": 200,
+ "contentType": "application/lws+json",
+ "linkHeaders": [
+ {
+ "rel": "linkset",
+ "hrefTemplate": "/alice/notes/.meta",
+ "mediaType": "application/linkset+json"
+ },
+ { "rel": "up", "hrefTemplate": "/alice/" },
+ { "rel": "type", "href": "https://www.w3.org/ns/lws#Container" }
+ ],
+ "bodyURL": "containers/alice-notes.json"
+ }
+ },
+
+ {
+ "@type": "ValidationTest",
+ "@id": "#createDataResource",
+ "name": "createDataResource",
+ "traits": ["Post", "Authn", "Authz", "Container", "DataResource"],
+ "status": "Proposed",
+ "comment": "owner POST container [Content-Type: text/plain, Slug: shoppinglist.txt] -> 201 { Location, Link: [linkset, up, type=DataResource] }",
+ "source": "https://w3c.github.io/lws-protocol/lws10-core/#create-resource",
+ "prereqs": {
+ "hierarchy": [
+ {
+ "url": "/alice/notes/",
+ "authorization": {
+ "read": "Role-Public",
+ "write": "Role-Owner"
+ }
+ }
+ ],
+ "authentication": "https://id.example/alice"
+ },
+ "request": {
+ "method": "POST",
+ "url": "/alice/notes/",
+ "authentication": "https://id.example/alice",
+ "authorization": "Role-Owner",
+ "contentType": "text/plain",
+ "slug": "shoppinglist.txt",
+ "linkHeaders": [],
+ "otherHeaders": [
+ { "headerName": "Content-Length", "headerValue": "47" }
+ ],
+ "bodyURL": "resources/shoppinglist.txt"
+ },
+ "response": {
+ "statusCode": 201,
+ "locationURL": "/alice/notes/shoppinglist.txt",
+ "linkHeaders": [
+ {
+ "rel": "linkset",
+ "hrefTemplate": "/alice/notes/shoppinglist.txt.meta",
+ "mediaType": "application/linkset+json"
+ },
+ { "rel": "up", "hrefTemplate": "/alice/notes/" },
+ { "rel": "type", "href": "https://www.w3.org/ns/lws#DataResource" }
+ ],
+ "otherHeaders": [
+ { "headerName": "Content-Length", "headerValue": "0" }
+ ]
+ }
+ },
+
+ {
+ "@type": "NegativeTest",
+ "@id": "#createDataResource-unauthorized",
+ "name": "createDataResource-unauthorized",
+ "traits": ["Post", "Authz", "Container"],
+ "status": "Proposed",
+ "comment": "unauthn POST container -> 401 { WWW-Authenticate: [Bearer, as_uri, realm] }",
+ "source": "https://w3c.github.io/lws-protocol/lws10-core/#authorization",
+ "prereqs": {
+ "hierarchy": [
+ {
+ "url": "/alice/notes/",
+ "authorization": {
+ "read": "Role-Public",
+ "write": "Role-Owner"
+ }
+ }
+ ],
+ "authentication": null
+ },
+ "request": {
+ "method": "POST",
+ "url": "/alice/notes/",
+ "authentication": null,
+ "authorization": null,
+ "contentType": "text/plain",
+ "linkHeaders": [],
+ "otherHeaders": [],
+ "body": "some content"
+ },
+ "response": {
+ "statusCode": 401,
+ "authenticationChallenge": {
+ "wwwAuthenticate": "Bearer",
+ "asUri": "https://authorization.example",
+ "realm": "https://storage.example/alice/"
+ }
+ }
+ },
+
+ {
+ "@type": "ValidationTest",
+ "@id": "#readDataResource",
+ "name": "readDataResource",
+ "traits": ["Get", "Public", "DataResource"],
+ "status": "Proposed",
+ "comment": "public GET resource -> 200 text/plain { Link: [linkset, up, type=DataResource, storageDescription] }",
+ "source": "https://w3c.github.io/lws-protocol/lws10-core/#read-resource",
+ "prereqs": {
+ "hierarchy": [
+ {
+ "url": "/alice/notes/shoppinglist.txt",
+ "authorization": {
+ "read": "Role-Public",
+ "write": "Role-Owner"
+ }
+ }
+ ],
+ "authentication": null
+ },
+ "request": {
+ "method": "GET",
+ "url": "/alice/notes/shoppinglist.txt",
+ "authentication": null,
+ "authorization": null,
+ "linkHeaders": [],
+ "otherHeaders": []
+ },
+ "response": {
+ "statusCode": 200,
+ "contentType": "text/plain",
+ "linkHeaders": [
+ {
+ "rel": "linkset",
+ "hrefTemplate": "/alice/notes/shoppinglist.txt.meta",
+ "mediaType": "application/linkset+json"
+ },
+ { "rel": "up", "hrefTemplate": "/alice/notes/" },
+ { "rel": "type", "href": "https://www.w3.org/ns/lws#DataResource" },
+ {
+ "rel": "storageDescription",
+ "hrefTemplate": "/alice/description"
+ }
+ ],
+ "bodyURL": "resources/shoppinglist.txt"
+ }
+ },
+
+ {
+ "@type": "ValidationTest",
+ "@id": "#updateDataResource",
+ "name": "updateDataResource",
+ "traits": ["Put", "Authn", "Authz", "DataResource"],
+ "status": "Proposed",
+ "comment": "owner PUT resource [Content-Type: text/plain] -> 200 { Link: [up] }",
+ "source": "https://w3c.github.io/lws-protocol/lws10-core/#update-resource",
+ "prereqs": {
+ "hierarchy": [
+ {
+ "url": "/alice/notes/shoppinglist.txt",
+ "authorization": {
+ "read": "Role-Public",
+ "write": "Role-Owner"
+ }
+ }
+ ],
+ "authentication": "https://id.example/alice"
+ },
+ "request": {
+ "method": "PUT",
+ "url": "/alice/notes/shoppinglist.txt",
+ "authentication": "https://id.example/alice",
+ "authorization": "Role-Owner",
+ "contentType": "text/plain",
+ "linkHeaders": [],
+ "otherHeaders": [],
+ "body": "milk\neggs\nbread\n"
+ },
+ "response": {
+ "statusCode": 200,
+ "linkHeaders": [
+ { "rel": "up", "hrefTemplate": "/alice/notes/" }
+ ],
+ "otherHeaders": []
+ }
+ },
+
+ {
+ "@type": "ValidationTest",
+ "@id": "#deleteDataResource",
+ "name": "deleteDataResource",
+ "traits": ["Delete", "Authn", "Authz", "DataResource"],
+ "status": "Proposed",
+ "comment": "owner DELETE resource -> 200",
+ "source": "https://w3c.github.io/lws-protocol/lws10-core/#delete-resource",
+ "prereqs": {
+ "hierarchy": [
+ {
+ "url": "/alice/notes/shoppinglist.txt",
+ "authorization": {
+ "read": "Role-Public",
+ "write": "Role-Owner"
+ }
+ }
+ ],
+ "authentication": "https://id.example/alice"
+ },
+ "request": {
+ "method": "DELETE",
+ "url": "/alice/notes/shoppinglist.txt",
+ "authentication": "https://id.example/alice",
+ "authorization": "Role-Owner",
+ "linkHeaders": [],
+ "otherHeaders": []
+ },
+ "response": {
+ "statusCode": 200,
+ "otherHeaders": []
+ }
+ },
+
+ {
+ "@type": "NegativeTest",
+ "@id": "#deleteDataResource-unauthorized",
+ "name": "deleteDataResource-unauthorized",
+ "traits": ["Delete", "Authz", "DataResource"],
+ "status": "Proposed",
+ "comment": "unauthn DELETE resource -> 401 { WWW-Authenticate: [Bearer, as_uri, realm] }",
+ "source": "https://w3c.github.io/lws-protocol/lws10-core/#authorization",
+ "prereqs": {
+ "hierarchy": [
+ {
+ "url": "/alice/notes/shoppinglist.txt",
+ "authorization": {
+ "read": "Role-Public",
+ "write": "Role-Owner"
+ }
+ }
+ ],
+ "authentication": null
+ },
+ "request": {
+ "method": "DELETE",
+ "url": "/alice/notes/shoppinglist.txt",
+ "authentication": null,
+ "authorization": null,
+ "linkHeaders": [],
+ "otherHeaders": []
+ },
+ "response": {
+ "statusCode": 401,
+ "authenticationChallenge": {
+ "wwwAuthenticate": "Bearer",
+ "asUri": "https://authorization.example",
+ "realm": "https://storage.example/alice/"
+ }
+ }
+ },
+
+ {
+ "@type": "ValidationTest",
+ "@id": "#getLinkset",
+ "name": "getLinkset",
+ "traits": ["Get", "Public", "Container"],
+ "status": "Proposed",
+ "comment": "public GET /.meta [Accept: linkset+json] -> 200 linkset+json",
+ "source": "https://w3c.github.io/lws-protocol/lws10-core/#logical-resource-organization",
+ "prereqs": {
+ "hierarchy": [
+ {
+ "url": "/alice/notes/",
+ "authorization": {
+ "read": "Role-Public",
+ "write": "Role-Owner"
+ }
+ }
+ ],
+ "authentication": null
+ },
+ "request": {
+ "method": "GET",
+ "url": "/alice/notes/.meta",
+ "authentication": null,
+ "authorization": null,
+ "linkHeaders": [],
+ "otherHeaders": [
+ { "headerName": "Accept", "headerValue": "application/linkset+json" }
+ ]
+ },
+ "response": {
+ "statusCode": 200,
+ "contentType": "application/linkset+json",
+ "bodyURL": "linksets/alice-notes.meta.json"
+ }
+ },
+
+ {
+ "@type": "ValidationTest",
+ "@id": "#getContainer-containmentIntegrity",
+ "name": "getContainer-containmentIntegrity",
+ "traits": ["Get", "Public", "Container"],
+ "status": "Proposed",
+ "comment": "public GET container [Accept: lws+json] -> 200 lws+json { body.items ∋ member-resource }",
+ "source": "https://w3c.github.io/lws-protocol/lws10-core/#container-representation",
+ "prereqs": {
+ "hierarchy": [
+ {
+ "url": "/alice/notes/",
+ "authorization": {
+ "read": "Role-Public",
+ "write": "Role-Owner"
+ }
+ },
+ {
+ "url": "/alice/notes/shoppinglist.txt",
+ "authorization": {
+ "read": "Role-Public",
+ "write": "Role-Owner"
+ }
+ }
+ ],
+ "authentication": null
+ },
+ "request": {
+ "method": "GET",
+ "url": "/alice/notes/",
+ "authentication": null,
+ "authorization": null,
+ "linkHeaders": [],
+ "otherHeaders": [
+ { "headerName": "Accept", "headerValue": "application/lws+json" }
+ ]
+ },
+ "response": {
+ "statusCode": 200,
+ "contentType": "application/lws+json",
+ "comment": "The response body MUST include shoppinglist.txt in items.",
+ "bodyURL": "containers/alice-notes.json"
+ }
+ },
+
+ {
+ "@type": "ValidationTest",
+ "@id": "#authz-server-metadata-well-known",
+ "name": "authz-server-metadata-well-known",
+ "traits": ["Get", "Discovery", "Public"],
+ "status": "Proposed",
+ "comment": "public GET AS:/.well-known/lws-configuration [Accept: json] -> 200 json",
+ "source": "https://w3c.github.io/lws-protocol/lws10-core/#authorization-server-discovery",
+ "prereqs": {
+ "hierarchy": [],
+ "authentication": null
+ },
+ "request": {
+ "method": "GET",
+ "url": "https://authorization.example/.well-known/lws-configuration",
+ "authentication": null,
+ "authorization": null,
+ "linkHeaders": [],
+ "otherHeaders": [
+ { "headerName": "Accept", "headerValue": "application/json" }
+ ]
+ },
+ "response": {
+ "statusCode": 200,
+ "contentType": "application/json",
+ "bodyURL": "resources/authorization-server-metadata.json"
+ }
+ },
+
+ {
+ "@type": "ValidationTest",
+ "@id": "#authz-token-exchange-valid",
+ "name": "authz-token-exchange-valid",
+ "traits": ["Post", "Authn", "Authz"],
+ "status": "Proposed",
+ "comment": "POST AS:/token [grant_type=token-exchange, subject_token_type=id_token, valid subject_token] -> 200 json { access_token }",
+ "source": "https://w3c.github.io/lws-protocol/lws10-core/#authorization",
+ "prereqs": {
+ "hierarchy": [],
+ "authentication": "https://id.example/alice"
+ },
+ "request": {
+ "method": "POST",
+ "url": "https://authorization.example/token",
+ "contentType": "application/x-www-form-urlencoded",
+ "authentication": null,
+ "authorization": null,
+ "linkHeaders": [],
+ "otherHeaders": [],
+ "body": "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange&resource=https%3A%2F%2Fstorage.example%2Falice%2F&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aid_token&subject_token="
+ },
+ "response": {
+ "statusCode": 200,
+ "contentType": "application/json",
+ "bodyURL": "resources/token-response-200.json"
+ }
+ },
+
+ {
+ "@type": "NegativeTest",
+ "@id": "#authz-token-exchange-invalid-resource",
+ "name": "authz-token-exchange-invalid-resource",
+ "traits": ["Post", "Authz"],
+ "status": "Proposed",
+ "comment": "POST AS:/token [grant_type=token-exchange, resource=unknown] -> 400 json { error }",
+ "source": "https://w3c.github.io/lws-protocol/lws10-core/#authorization",
+ "prereqs": {
+ "hierarchy": [],
+ "authentication": null
+ },
+ "request": {
+ "method": "POST",
+ "url": "https://authorization.example/token",
+ "contentType": "application/x-www-form-urlencoded",
+ "linkHeaders": [],
+ "otherHeaders": [],
+ "body": "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange&resource=https%3A%2F%2Funknown.example%2F&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aid_token&subject_token="
+ },
+ "response": {
+ "statusCode": 400,
+ "bodyURL": "resources/token-response-400.json"
+ }
+ },
+
+ {
+ "@type": "NegativeTest",
+ "@id": "#authz-expired-token-rejected",
+ "name": "authz-expired-token-rejected",
+ "traits": ["Get", "Authz", "Private"],
+ "status": "Proposed",
+ "comment": "GET [Authorization: Bearer ] -> 401 { WWW-Authenticate: [Bearer, as_uri, realm, error=invalid_token] }",
+ "source": "https://w3c.github.io/lws-protocol/lws10-core/#authorization",
+ "prereqs": {
+ "hierarchy": [
+ {
+ "url": "/alice/notes/",
+ "authorization": {
+ "read": "Role-Owner",
+ "write": "Role-Owner"
+ }
+ }
+ ],
+ "authentication": "https://id.example/alice"
+ },
+ "request": {
+ "method": "GET",
+ "url": "/alice/notes/",
+ "authentication": "https://id.example/alice",
+ "authorization": "Role-Owner",
+ "comment": "The Bearer token in the Authorization header is expired.",
+ "linkHeaders": [],
+ "otherHeaders": [
+ {
+ "headerName": "Authorization",
+ "headerValue": "Bearer "
+ }
+ ]
+ },
+ "response": {
+ "statusCode": 401,
+ "authenticationChallenge": {
+ "wwwAuthenticate": "Bearer",
+ "asUri": "https://authorization.example",
+ "realm": "https://storage.example/alice/",
+ "error": "invalid_token"
+ }
+ }
+ },
+
+ {
+ "@type": "ValidationTest",
+ "@id": "#createContainer",
+ "name": "createContainer",
+ "traits": ["Post", "Authn", "Authz", "Container"],
+ "status": "Proposed",
+ "comment": "owner POST container [Content-Type: lws+json, Link: type=Container, Slug: notes] -> 201 { Location, Link: [linkset, up, type=Container] }",
+ "source": "https://w3c.github.io/lws-protocol/lws10-core/#create-resource",
+ "prereqs": {
+ "hierarchy": [
+ {
+ "url": "/alice/",
+ "authorization": {
+ "read": "Role-Public",
+ "write": "Role-Owner"
+ }
+ }
+ ],
+ "authentication": "https://id.example/alice"
+ },
+ "request": {
+ "method": "POST",
+ "url": "/alice/",
+ "authentication": "https://id.example/alice",
+ "authorization": "Role-Owner",
+ "contentType": "application/lws+json",
+ "slug": "notes",
+ "linkHeaders": [
+ {
+ "rel": "type",
+ "href": "https://www.w3.org/ns/lws#Container"
+ }
+ ],
+ "otherHeaders": [],
+ "body": ""
+ },
+ "response": {
+ "statusCode": 201,
+ "locationURL": "/alice/notes/",
+ "linkHeaders": [
+ {
+ "rel": "linkset",
+ "hrefTemplate": "/alice/notes/.meta",
+ "mediaType": "application/linkset+json"
+ },
+ { "rel": "up", "hrefTemplate": "/alice/" },
+ { "rel": "type", "href": "https://www.w3.org/ns/lws#Container" }
+ ],
+ "otherHeaders": []
+ }
+ }
+
+ ]
+ }
+ ]
+}
diff --git a/lws10-test-suite/mnt/user-data/outputs/lws-tests/auth/did-key/manifest.jsonld b/lws10-test-suite/mnt/user-data/outputs/lws-tests/auth/did-key/manifest.jsonld
new file mode 100644
index 0000000..caa1ced
--- /dev/null
+++ b/lws10-test-suite/mnt/user-data/outputs/lws-tests/auth/did-key/manifest.jsonld
@@ -0,0 +1,89 @@
+{
+ "@context": [
+ { "@base": "https://raw.githubusercontent.com/w3c/lws-protocol/main/tests/auth/did-key/" },
+ "../../context.jsonld"
+ ],
+ "@graph": [
+ {
+ "@id": "",
+ "@type": "Manifest",
+ "label": "LWS Authentication: Self-signed Identity using did:key",
+ "comment": "Tests for the LWS 1.0 Authentication Suite: Self-signed Identity using did:key (https://www.w3.org/TR/lws10-authn-ssi-did-key/)",
+ "entries": [
+ {
+ "@id": "#authn-didkey-valid-credential",
+ "@type": "ValidationTest",
+ "name": "authn-didkey-valid-credential",
+ "traits": ["Authn"],
+ "status": "Proposed",
+ "comment": "A valid did:key authentication credential is accepted by the authorization server.",
+ "prereqs": {
+ "authentication": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
+ "hierarchy": []
+ },
+ "request": {
+ "method": "POST",
+ "url": "https://authorization.example/token",
+ "contentType": "application/x-www-form-urlencoded",
+ "body": "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange&resource=https%3A%2F%2Fstorage.example%2Falice%2F&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aid_token&subject_token=",
+ "linkHeaders": [],
+ "otherHeaders": []
+ },
+ "response": {
+ "statusCode": 200,
+ "contentType": "application/json",
+ "bodyURL": "../../resources/token-response-200.json"
+ }
+ },
+ {
+ "@id": "#authn-didkey-invalid-signature",
+ "@type": "NegativeTest",
+ "name": "authn-didkey-invalid-signature",
+ "traits": ["Authn"],
+ "status": "Proposed",
+ "comment": "A did:key credential with an invalid signature is rejected.",
+ "prereqs": {
+ "authentication": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
+ "hierarchy": []
+ },
+ "request": {
+ "method": "POST",
+ "url": "https://authorization.example/token",
+ "contentType": "application/x-www-form-urlencoded",
+ "body": "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange&resource=https%3A%2F%2Fstorage.example%2Falice%2F&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aid_token&subject_token=",
+ "linkHeaders": [],
+ "otherHeaders": []
+ },
+ "response": {
+ "statusCode": 400,
+ "bodyURL": "../../resources/token-response-400.json"
+ }
+ },
+ {
+ "@id": "#authn-didkey-missing-credential",
+ "@type": "NegativeTest",
+ "name": "authn-didkey-missing-credential",
+ "traits": ["Authn"],
+ "status": "Proposed",
+ "comment": "A token exchange request with no subject_token is rejected.",
+ "prereqs": {
+ "authentication": null,
+ "hierarchy": []
+ },
+ "request": {
+ "method": "POST",
+ "url": "https://authorization.example/token",
+ "contentType": "application/x-www-form-urlencoded",
+ "body": "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange&resource=https%3A%2F%2Fstorage.example%2Falice%2F",
+ "linkHeaders": [],
+ "otherHeaders": []
+ },
+ "response": {
+ "statusCode": 400,
+ "bodyURL": "../../resources/token-response-400.json"
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/lws10-test-suite/mnt/user-data/outputs/lws-tests/auth/oidc/manifest.jsonld b/lws10-test-suite/mnt/user-data/outputs/lws-tests/auth/oidc/manifest.jsonld
new file mode 100644
index 0000000..a8bcd93
--- /dev/null
+++ b/lws10-test-suite/mnt/user-data/outputs/lws-tests/auth/oidc/manifest.jsonld
@@ -0,0 +1,116 @@
+{
+ "@context": [
+ { "@base": "https://raw.githubusercontent.com/w3c/lws-protocol/main/tests/auth/oidc/" },
+ "../../context.jsonld"
+ ],
+ "@graph": [
+ {
+ "@id": "",
+ "@type": "Manifest",
+ "label": "LWS Authentication: OpenID Connect",
+ "comment": "Tests for the LWS 1.0 Authentication Suite: OpenID Connect (https://www.w3.org/TR/lws10-authn-openid/)",
+ "entries": [
+ {
+ "@id": "#authn-oidc-valid-id-token",
+ "@type": "ValidationTest",
+ "name": "authn-oidc-valid-id-token",
+ "traits": ["Authn"],
+ "status": "Proposed",
+ "comment": "A valid OIDC ID token is accepted by the authorization server at the token exchange endpoint.",
+ "prereqs": {
+ "authentication": {
+ "type": "OIDCIdToken",
+ "issuer": "https://identity.example",
+ "subject": "https://identity.example/users/alice"
+ },
+ "hierarchy": []
+ },
+ "request": {
+ "method": "POST",
+ "url": "https://authorization.example/token",
+ "contentType": "application/x-www-form-urlencoded",
+ "body": "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange&resource=https%3A%2F%2Fstorage.example%2Falice%2F&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aid_token&subject_token=",
+ "linkHeaders": [],
+ "otherHeaders": []
+ },
+ "response": {
+ "statusCode": 200,
+ "contentType": "application/json",
+ "bodyURL": "../../resources/token-response-200.json"
+ }
+ },
+ {
+ "@id": "#authn-oidc-expired-id-token",
+ "@type": "NegativeTest",
+ "name": "authn-oidc-expired-id-token",
+ "traits": ["Authn"],
+ "status": "Proposed",
+ "comment": "An expired OIDC ID token is rejected by the authorization server.",
+ "prereqs": {
+ "authentication": {
+ "type": "OIDCIdToken",
+ "issuer": "https://identity.example",
+ "subject": "https://identity.example/users/alice",
+ "expired": true
+ },
+ "hierarchy": []
+ },
+ "request": {
+ "method": "POST",
+ "url": "https://authorization.example/token",
+ "contentType": "application/x-www-form-urlencoded",
+ "body": "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange&resource=https%3A%2F%2Fstorage.example%2Falice%2F&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aid_token&subject_token=",
+ "linkHeaders": [],
+ "otherHeaders": []
+ },
+ "response": {
+ "statusCode": 400,
+ "bodyURL": "../../resources/token-response-400.json"
+ }
+ },
+ {
+ "@id": "#authn-oidc-server-metadata-discovery",
+ "@type": "ValidationTest",
+ "name": "authn-oidc-server-metadata-discovery",
+ "traits": ["Authn", "Discovery"],
+ "status": "Proposed",
+ "comment": "A 401 response from a storage server includes WWW-Authenticate with as_uri and realm, enabling OIDC-based auth server discovery.",
+ "prereqs": {
+ "authentication": null,
+ "hierarchy": [
+ {
+ "url": "/alice/notes/",
+ "authorization": {
+ "read": "Role-Owner",
+ "write": "Role-Owner"
+ }
+ }
+ ]
+ },
+ "request": {
+ "method": "GET",
+ "url": "/alice/notes/",
+ "authentication": null,
+ "authorization": null,
+ "linkHeaders": [],
+ "otherHeaders": []
+ },
+ "response": {
+ "statusCode": 401,
+ "authenticationChallenge": {
+ "wwwAuthenticate": "Bearer",
+ "asUri": "https://authorization.example",
+ "realm": "https://storage.example/alice/"
+ },
+ "linkHeaders": [
+ {
+ "rel": "https://www.w3.org/ns/lws#storageDescription",
+ "href": "/alice/description"
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/lws10-test-suite/mnt/user-data/outputs/lws-tests/auth/saml/manifest.jsonld b/lws10-test-suite/mnt/user-data/outputs/lws-tests/auth/saml/manifest.jsonld
new file mode 100644
index 0000000..3dc43e9
--- /dev/null
+++ b/lws10-test-suite/mnt/user-data/outputs/lws-tests/auth/saml/manifest.jsonld
@@ -0,0 +1,74 @@
+{
+ "@context": [
+ { "@base": "https://raw.githubusercontent.com/w3c/lws-protocol/main/tests/auth/saml/" },
+ "../../context.jsonld"
+ ],
+ "@graph": [
+ {
+ "@id": "",
+ "@type": "Manifest",
+ "label": "LWS Authentication: SAML 2.0",
+ "comment": "Tests for the LWS 1.0 Authentication Suite: SAML 2.0 (https://www.w3.org/TR/lws10-authn-saml/)",
+ "entries": [
+ {
+ "@id": "#authn-saml-valid-assertion",
+ "@type": "ValidationTest",
+ "name": "authn-saml-valid-assertion",
+ "traits": ["Authn"],
+ "status": "Proposed",
+ "comment": "A valid SAML 2.0 assertion is accepted at the token exchange endpoint.",
+ "prereqs": {
+ "authentication": {
+ "type": "SAML2Assertion",
+ "issuer": "https://idp.example",
+ "subject": "https://idp.example/users/alice"
+ },
+ "hierarchy": []
+ },
+ "request": {
+ "method": "POST",
+ "url": "https://authorization.example/token",
+ "contentType": "application/x-www-form-urlencoded",
+ "body": "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange&resource=https%3A%2F%2Fstorage.example%2Falice%2F&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Asaml2&subject_token=",
+ "linkHeaders": [],
+ "otherHeaders": []
+ },
+ "response": {
+ "statusCode": 200,
+ "contentType": "application/json",
+ "bodyURL": "../../resources/token-response-200.json"
+ }
+ },
+ {
+ "@id": "#authn-saml-invalid-signature",
+ "@type": "NegativeTest",
+ "name": "authn-saml-invalid-signature",
+ "traits": ["Authn"],
+ "status": "Proposed",
+ "comment": "A SAML 2.0 assertion with a tampered signature is rejected.",
+ "prereqs": {
+ "authentication": {
+ "type": "SAML2Assertion",
+ "issuer": "https://idp.example",
+ "subject": "https://idp.example/users/alice",
+ "tampered": true
+ },
+ "hierarchy": []
+ },
+ "request": {
+ "method": "POST",
+ "url": "https://authorization.example/token",
+ "contentType": "application/x-www-form-urlencoded",
+ "body": "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange&resource=https%3A%2F%2Fstorage.example%2Falice%2F&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Asaml2&subject_token=",
+ "linkHeaders": [],
+ "otherHeaders": []
+ },
+ "response": {
+ "statusCode": 400,
+ "bodyURL": "../../resources/token-response-400.json"
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/lws10-test-suite/resources/authorization-server-metadata.json b/lws10-test-suite/resources/authorization-server-metadata.json
new file mode 100644
index 0000000..31aa2f6
--- /dev/null
+++ b/lws10-test-suite/resources/authorization-server-metadata.json
@@ -0,0 +1,14 @@
+{
+ "issuer": "https://authorization.example",
+ "grant_types_supported": [
+ "urn:ietf:params:oauth:grant-type:token-exchange"
+ ],
+ "token_endpoint": "https://authorization.example/token",
+ "jwks_uri": "https://authorization.example/jwks",
+ "claims_supported": ["sub", "iss", "client_id", "aud"],
+ "response_types_supported": ["token"],
+ "subject_token_types_supported": [
+ "urn:ietf:params:oauth:token-type:jwt",
+ "urn:ietf:params:oauth:token-type:id-token"
+ ]
+}
diff --git a/lws10-test-suite/resources/shoppinglist.txt b/lws10-test-suite/resources/shoppinglist.txt
new file mode 100644
index 0000000..b188fd3
--- /dev/null
+++ b/lws10-test-suite/resources/shoppinglist.txt
@@ -0,0 +1,6 @@
+milk
+eggs
+bread
+butter
+apples
+orange juice
diff --git a/lws10-test-suite/resources/storage-description.json b/lws10-test-suite/resources/storage-description.json
new file mode 100644
index 0000000..9f09e86
--- /dev/null
+++ b/lws10-test-suite/resources/storage-description.json
@@ -0,0 +1,19 @@
+{
+ "@context": "https://www.w3.org/ns/lws/v1",
+ "id": "https://storage.example/alice/",
+ "type": "Storage",
+ "service": [
+ {
+ "type": "StorageDescription",
+ "serviceEndpoint": "https://storage.example/alice/description"
+ },
+ {
+ "type": "AuthorizationServer",
+ "serviceEndpoint": "https://authorization.example"
+ }
+ ],
+ "capability": [
+ { "type": "Notifications" },
+ { "type": "ProfileNegotiation" }
+ ]
+}
diff --git a/lws10-test-suite/resources/token-response-200.json b/lws10-test-suite/resources/token-response-200.json
new file mode 100644
index 0000000..8c643fb
--- /dev/null
+++ b/lws10-test-suite/resources/token-response-200.json
@@ -0,0 +1,5 @@
+{
+ "access_token": "",
+ "token_type": "Bearer",
+ "expires_in": 300
+}
diff --git a/lws10-test-suite/resources/token-response-400.json b/lws10-test-suite/resources/token-response-400.json
new file mode 100644
index 0000000..648dd2a
--- /dev/null
+++ b/lws10-test-suite/resources/token-response-400.json
@@ -0,0 +1,3 @@
+{
+ "error": "invalid_request"
+}