Skip to content

Return different status codes based on resource existence #1161

@RubenVerborgh

Description

@RubenVerborgh

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:

  1. credentials = await this.credentialsExtractor.handleSafe(request)
  2. requiredModes = await this.modesExtractor.handleSafe(operation)
  3. agentPermissions = await this.permissionReader.handleSafe({ credentials, identifier: operation.target })
  4. await this.authorizer.handleSafe({ credentials, identifier: operation.target, requiredModes, agentPermissions })
  5. 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.)

Metadata

Metadata

Assignees

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions