Groups keyboard shortcuts have been updated
Dismiss
See shortcuts

How to handle unknown resource IDs with URL dispatch and a security policy?

13 views
Skip to first unread message

Sean Hammond

unread,
Nov 21, 2024, 11:43:26 AM11/21/24
to pylons-...@googlegroups.com
Hey,

I'm wondering what's the best way for a Pyramid app that uses URL dispatch rather than traversal to handle requests that reference resources that don't exist.

For example imagine a JSON API request like this, to edit the role of a member in a group:

HTTP PATCH /api/groups/{groupid}/members/{memberid}

(Where the JSON body of the request would be something like this: `{"role": "admin"}`.)

And imagine someone makes a request with a {groupid} and/or {memberid} that doesn't exist:

HTTP PATCH /api/groups/UNKNOWN_GROUPID/members/UNKNOWN_MEMBERID

And imagine that this request routes to a view callable with a config like this:

@view_config(
route_name="whatever",
request_method="PATCH",
permission="edit",
)
def edit_membership(context, request):
...

Because of the `permission` when Pyramid handles the request it's going to call the permits() method of the app's security policy with the request, context and "edit" for the permission. This is where I get confused: permits() must return either Allowed or Denied, and neither seems correct:

1. It doesn't seem quite right to me for permits() to return Denied in this situation. It's not that the request doesn't have permission to edit the membership, the problem is that the membership doesn't exist. Returning Denied would cause Pyramid to send a 401 response, when a 404 would be more appropriate.

2. permits() could return Allowed and then the view could raise HTTPNotFound. This would return the correct response but it seems wrong to me for permits() to say that the request is permitted to edit a membership that doesn't exist and I imagine there could even be race conditions resulting in security issues (if a membership with the matching IDs gets created by a concurrent request and it happens to be a membership that the API request should *not* have been permitted to edit).

(1) might be acceptable if you're happy for requests to unknown resources to 401 rather than 404ing. Is that the answer?

Otherwise I don't see an acceptable option. Am I missing something?

If using either traversal or hybrid traversal + URL dispatch (https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/hybrid.html) there is a solution: if the groupid and/or memberid doesn't exist then the app's resource lookup code can raise KeyError which will cause Pyramid to 404 without ever calling the security policy. The security policy code can be simpler because it can assume that if it is called then the groupid and memberid must have already been found by resource lookup. And the view code can be simpler because it can assume that if it is called then the groupid and memberid have been found and the permissions check has passed. Perfect!

But how should a URL dispatch-based app that doesn't use traversal handle this situation? Or do you just have to use traversal?

Thanks!

Ian Wilson

unread,
Nov 21, 2024, 1:08:28 PM11/21/24
to pylons-...@googlegroups.com
I use a route factory and inside that factory if I can't resolve the context then I raise HTTPNotFound.  The factories let you use context without using traversal.

Something like this:

def member_factory(request):
    member = lookup_member(request.matchdict['memberid'])
    if not member:
        raise HTTPNotFound()
    return member

config.add_route('members-api', '/api/groups/{groupid}/members/{memberid}', factory=member_factory)

Furthermore you can also check if other path parts don't make sense and 404, ie.  member.group.id != request.matchdict['groupid'].  Finally if you have an id and a slug, for something like, /articles/{article_id}--{article_slug}, you can redirect to the latest slug, raising HTTPMovedPermanently, but still keep url integrity by using a stable id. It works pretty well.



--
You received this message because you are subscribed to the Google Groups "pylons-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pylons-discus...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/pylons-discuss/d9337096-7d9d-461b-8972-391b0a62fe69%40app.fastmail.com.

Sean Hammond

unread,
Nov 21, 2024, 1:37:13 PM11/21/24
to pylons-...@googlegroups.com
On Thu, 21 Nov 2024, at 6:08 PM, Ian Wilson wrote:
I use a route factory and inside that factory if I can't resolve the context then I raise HTTPNotFound.  The factories let you use context without using traversal.

Aha, I hadn't realised that the factory can raise HTTPNotFound. That works. Thanks!
Reply all
Reply to author
Forward
0 new messages