Background
The issue solid/specification#379 (and originally flagged as https://github.com/solid/community-server/issues/618) details cases the decision between an authorization response (401/403) and a resource response (2xx/3xx/404) depend on resource state. The server can only decide whether a response is 401/403 or something else by going to storage for an existence check, whereas previously we had hoped/assumed that the request and ACL state would provide all elements necessary for such a decision.
While solid/specification#379 tracks consequences and looks into a possible mitigation, we need to implement these cases.
Cases
|
Operation |
C/ |
C/R |
C/R exists |
no C/R exists |
Remarks |
| 1 |
GET|HEAD|OPTIONS C/R |
Read |
Write |
403 |
404 |
|
| 2 |
DELETE C/R |
– |
Read |
403 |
404 |
|
| 3 |
DELETE C/R |
Read |
|
403 |
404 |
|
| 4 |
DELETE C/ |
Read |
- |
403 |
404 |
(for C/ exists; same as 3) |
| 5 |
DELETE C/R |
Append |
Read |
403 |
404 |
|
| 6 |
DELETE C/R |
Write |
Read |
403 |
404 |
|
| 7 |
PATCH C/R (Read) |
– |
Write |
403 |
404 |
|
| 8 |
PATCH C/R (Insert) |
– |
Write |
200 |
403 |
|
| 9 |
PATCH C/R (Write) |
– |
Write |
200 |
403 |
|
| 10 |
PUT C/R |
– |
Write |
200 |
403 |
|
Analysis:
a. Cases 1–7 require 404 when a resource does not exist (rather than 401/403)
b. Cases 8–10 require 200 when a resource exists (rather than 401/403`)
Preliminary solution ideas
1. Exposing existence
ResourceStore currently has a method called resourceExists (which we should rename to hasResource or containsResource for consistency).
I propose to extract a sub-interface called ResourceSet (or so) which contains only this method, such that ResourceStore extends ResourceSet.
2. Incorporating existence
If we look at the current AuthorizingHttpHandler logic, we have:
credentials = await this.credentialsExtractor.handleSafe(request)
requiredModes = await this.modesExtractor.handleSafe(operation)
agentPermissions = await this.permissionReader.handleSafe({ credentials, identifier: operation.target })
await this.authorizer.handleSafe({ credentials, identifier: operation.target, requiredModes, agentPermissions })
this.operationHandler.handleSafe(input)
- For cases 8–10, it seems that
Create should be part of the requiredModes. We could, for instance, have a modesExtractor that has access to the ResourceSet. However, there are cases we don't need to know about that mode at all. (We could make that extractor conditional.)
Create seems to mean Append permission to the container above. (At least for now, in WAC.)
- So we could also extract
requiredModes per resource, and as such directly require Append on the container.
- But it's probably cleaner if
authorizer knows how to handle the Create case for WAC; then the modes are always on the resource.
- For cases 1–7, the authorizer needs to allow the request if the resource does not exist, only for the operation to report that it does not exist.
- That seems to be harder to express in terms of required modes and permissions.
- One way around this (and describe elsewhere as the "proxy" idea) is that there is some logic preceding the above, that throws an early
404 before even looking at permissions. That way, the permissions system only has to deny access to such requests. The downside is then that the permissions system by itself is not correct; the upside is that we have a separate component that can be turned on or off regarding whether we want to disclose 404s as such, or anonymize them as 401/403. (The other way round is also an option, but that would need to be decided elsewhere.)
Background
The issue solid/specification#379 (and originally flagged as https://github.com/solid/community-server/issues/618) details cases the decision between an authorization response (
401/403) and a resource response (2xx/3xx/404) depend on resource state. The server can only decide whether a response is401/403or something else by going to storage for an existence check, whereas previously we had hoped/assumed that the request and ACL state would provide all elements necessary for such a decision.While solid/specification#379 tracks consequences and looks into a possible mitigation, we need to implement these cases.
Cases
GET|HEAD|OPTIONS C/RDELETE C/RDELETE C/RDELETE C/DELETE C/RDELETE C/RPATCH C/R(Read)PATCH C/R(Insert)PATCH C/R(Write)PUT C/RAnalysis:
a. Cases 1–7 require
404when a resource does not exist (rather than401/403)b. Cases 8–10 require
200when a resource exists (rather than401/403`)Preliminary solution ideas
1. Exposing existence
ResourceStorecurrently has a method calledresourceExists(which we should rename tohasResourceorcontainsResourcefor consistency).I propose to extract a sub-interface called
ResourceSet(or so) which contains only this method, such thatResourceStore extends ResourceSet.2. Incorporating existence
If we look at the current
AuthorizingHttpHandlerlogic, we have:credentials = await this.credentialsExtractor.handleSafe(request)requiredModes = await this.modesExtractor.handleSafe(operation)agentPermissions = await this.permissionReader.handleSafe({ credentials, identifier: operation.target })await this.authorizer.handleSafe({ credentials, identifier: operation.target, requiredModes, agentPermissions })this.operationHandler.handleSafe(input)Createshould be part of therequiredModes. We could, for instance, have amodesExtractorthat has access to theResourceSet. However, there are cases we don't need to know about that mode at all. (We could make that extractor conditional.)Createseems to meanAppendpermission to the container above. (At least for now, in WAC.)requiredModesper resource, and as such directly requireAppendon the container.authorizerknows how to handle theCreatecase for WAC; then the modes are always on the resource.404before even looking at permissions. That way, the permissions system only has to deny access to such requests. The downside is then that the permissions system by itself is not correct; the upside is that we have a separate component that can be turned on or off regarding whether we want to disclose404s as such, or anonymize them as401/403. (The other way round is also an option, but that would need to be decided elsewhere.)