Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed #32124 -- Added per-view opt-out for APPEND_SLASH behavior. #13575

Merged
merged 1 commit into from
Oct 22, 2020

Conversation

carltongibson
Copy link
Member

No description provided.

@carltongibson
Copy link
Member Author

Hey @jdufresne — This is ref your #13134 for ticket-31747.

It's just the first part but the idea is to allow individual views to opt-out of APPEND_SLASH. This is for people who want the URL normalisation (so they don't want to just set APPEND_SLASH = False) but also have a sensitive_view that they want to exclude.

Phase 2 would then be to combine it with your patch in #13134, to allow an sensitive_admin_view to equally be hidden from a staff-member that wasn't meant to be allow to discover the URL, via enumerating admin URLs. (Non-staff members already handled by your patch.) For situations where that's not sufficient, I'd say folks just have to set APPEND_SLASH = False.

Hopefully this kind of thing would satisfy all the considerations. What do you think? Thanks!

@carltongibson
Copy link
Member Author

With _should_append_slash I had in mind do_not_call_in_templates but I realize that's not got an underscore. So we can rename (or use a decorator, or...)

@carltongibson
Copy link
Member Author

cc. @adamchainz this was the adjustment I was thinking about that we were talking about last week.

The return value for is_valid_path() would be -> Union[ResolverMatch, Literal[False]]. It's crying out to be an Optional[ResolverMatch], but that would be a bigger refactoring.

Anyhoo...

docs/ref/middleware.txt Outdated Show resolved Hide resolved
@carltongibson
Copy link
Member Author

Updated with a new decorator based approach.

Copy link
Sponsor Member

@adamchainz adamchainz left a comment

Choose a reason for hiding this comment

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

LGTM

"""
# view_func.should_append_slash = False would also work, but decorators are
# nicer if they don't have side effects, so return a new function.
def wrapped_view(*args, **kwargs):
Copy link
Member

Choose a reason for hiding this comment

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

Most examples I've seen would decorate this function with @wraps(view_func) then return wrapped_view?

Copy link
Member Author

Choose a reason for hiding this comment

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

🤷‍♀️ You say potato, I say potato (Please supply accents.) 😀

Comment on lines +9 to +10
# view_func.should_append_slash = False would also work, but decorators are
# nicer if they don't have side effects, so return a new function.
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure I really agree with this. The whole point of decorators is that they change the behaviour of the existing function. Wrapping the function here with another doesn't have the "side effect" of assigning a simple attribute to that original function, but then the original function is (strictly speaking) no longer in scope having been replaced by the wrapped function. Therefore it is unlikely anyone would use it anyway.

This is really just adding an extra level of indirection by wrapping in an additional function which then forces us to use functools.wraps() to fix things up. IMO the following is straightforward and achieves the same goal:

def no_append_slash(func):
    func.should_append_slash = False
    return func

Copy link
Member

Choose a reason for hiding this comment

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

Note that there are similar decorators that add attributes to functions in the works here and here that are not using this approach.

Copy link
Member

Choose a reason for hiding this comment

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

Note that there are decorators that are using the same approach, e.g. csrf_exempt() 😉

Copy link
Member

Choose a reason for hiding this comment

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

Yes, but I have to ask why? I get that there is a side effect, but how much of a problem is this? I can potentially see the problem if you do the following:

def view(request):
    ...

wrapped_view = no_append_slash(wrapped_view)

# Now both ``view`` and ``wrapped_view`` have ``should_append_slash`` set which
# means using ``view`` might not do what you expect.

But typically you're going to do the following:

@no_append_slash
def view(request):
    ...

Which would be no different to assigning the attribute directly after the function is created if not using a wrapper function:

def view(request):
    ...
view.should_append_slash = False

I was concerned that there may be an issue if you wanted to do something like the following, but it turns out that functools.wraps() also copies attributes other than __name__ and __doc__ so this works fine:

@csrf_exempt
@no_append_slash
def view(request):
    ...

But it also effectively translates to having to call three functions, the outer two which have no benefit other than to have copies of all of the attributes of the functions within them. This has some call overhead and, if there are many decorated functions, excess memory use to store all these additional function objects and extra attributes as they bubble up.

Copy link
Member

Choose a reason for hiding this comment

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

IMO supporting a typical usage is not enough in this case, because no_append_slash should be used to avoid enumerations, so it's security-related, like csrf_exempt. It must be bulletproof, without any side effects.

Copy link
Sponsor Member

Choose a reason for hiding this comment

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

I had a squint at this too, but didn't comment. But I agree with Nick - there isn't much of a reason to have an intermediate function, especially since decorators in Django aren't consistent. Mostly I see this as adding overhead and complicating debugging with an extra layer in stack traces.

Copy link
Member Author

Choose a reason for hiding this comment

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

So here's the question, would you remove the same usage from csrf_exempt? (Would you take that risk?)

Copy link
Member Author

Choose a reason for hiding this comment

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

I think the decorator strategy is orthogonal to this issue. Maybe there's a case for adjusting styles, or being consistent, but if so, we should address separately, where we can give it due consideration.

2¢: Sometimes, sure, you don't use wraps, but I don't see it's addition as an issue. (TBH don't really buy any of the call overhead, memory use, or stack trace points…)

Copy link
Member

@felixxm felixxm left a comment

Choose a reason for hiding this comment

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

@carltongibson Thanks 👍

docs/topics/http/decorators.txt Show resolved Hide resolved
docs/ref/middleware.txt Show resolved Hide resolved
Comment on lines +9 to +10
# view_func.should_append_slash = False would also work, but decorators are
# nicer if they don't have side effects, so return a new function.
Copy link
Member

Choose a reason for hiding this comment

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

Note that there are decorators that are using the same approach, e.g. csrf_exempt() 😉

django/urls/base.py Outdated Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
5 participants