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

fix: Ballot return to url via url params rather than session #7788

Merged
merged 9 commits into from
Aug 9, 2024

Conversation

holloway
Copy link
Contributor

@holloway holloway commented Aug 5, 2024

See #7287

This migrates the ballot_edit_return_point from session to a url param, with some additional safety checks to protect against phishing attacks.

Tasks

  • verify all incoming routes
  • [x] Playwright tests

ietf/doc/views_ballot.py Outdated Show resolved Hide resolved
@holloway holloway marked this pull request as ready for review August 7, 2024 05:39
Copy link

codecov bot commented Aug 7, 2024

Codecov Report

Attention: Patch coverage is 85.29412% with 5 lines in your changes missing coverage. Please review.

Project coverage is 88.79%. Comparing base (c7f6bde) to head (0a7605f).
Report is 36 commits behind head on main.

Files Patch % Lines
ietf/doc/views_ballot.py 77.27% 5 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##             main    #7788    +/-   ##
========================================
  Coverage   88.78%   88.79%            
========================================
  Files         296      303     +7     
  Lines       41320    41429   +109     
========================================
+ Hits        36687    36787   +100     
- Misses       4633     4642     +9     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Member

@jennifer-richards jennifer-richards left a comment

Choose a reason for hiding this comment

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

A trivial style nit, plus a worry about the impact of moving the redirect into the URL. You've guarded against phishing, but I wonder if we need a more aggressive guard there. One thought: we only have a few places to return to; would it be tractable to put parameter less general than the URL in the query parameter so that it can't be exploited?

Also, I think my inline comment was wrong and you are putting an absolute URL in the parameter - if that could be made into a relative one, that'd be somewhat safer.

Finally, I think the playwright tests are failing. Is that something to fix in the code or is that github struggling?

@@ -209,9 +211,14 @@ def edit_position(request, name, ballot_id):
save_position(form, doc, ballot, balloter, login, send_mail)

if send_mail:
qstr=""
query=dict()
Copy link
Member

Choose a reason for hiding this comment

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

syntax nit: query = dict() (or query = {} is perhaps trivially more efficient); either way, style is to have spaces around the =

# offsite links could be phishing attempts so let's reject them all, and require valid Datatracker
# routes
try:
urlresolve(return_to_url)
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we should instead (or maybe in addition) check that the URL is not absolute? I think that'd prevent the phishing issue.

I wonder if it'd be worth putting an even tighter check here and only redirect to the view that we want. E.g., someone could make a nuisance link that'd cause logout (at least, until we disable log out by GET). I worry that there might be more harmful games possible with this.

Copy link
Contributor Author

@holloway holloway Aug 7, 2024

Choose a reason for hiding this comment

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

check that the URL is not absolute?

Hmm... I thought about checking whether it was a relative vs absolute URL through checking if it started with a / but then there's that protocol-relative URL syntax of //example.com/phish (now considered an antipattern) so I'd have to use a real URL parser to safely detect relative links, and then if I were checking for valid routes that's a subset of relative URLs so it didn't feel necessary.

only redirect to the view that we want

there are currently 5 views that assign request.session['ballot_edit_return_point'] so I'm guessing there are 5 ways into the ballot editing and 5 ways we should redirect out. We could have an allow list of those 5 route patterns.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

^^ working on this now

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. Thoughts?

Copy link
Member

@jennifer-richards jennifer-richards left a comment

Choose a reason for hiding this comment

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

I like the approach. I think it is likely to be useful outside this spot so I have some slight reorg suggestions inline.

@@ -0,0 +1,34 @@
from django.urls import reverse as urlreverse, resolve as urlresolve, Resolver404

def _parse_return_to_path(return_to_path, default_return_to_path, allowed_path_handlers):
Copy link
Member

Choose a reason for hiding this comment

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

This seems like a generally useful method. Maybe rename to something starting with validate_ or similar and move to ietf.utils.http?


return return_to_path

def parse_ballot_edit_return_point(ballot_edit_return_point: str, doc_name: str, ballot_id: str):
Copy link
Member

Choose a reason for hiding this comment

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

Probably goes in views_ballot.py if you accept the suggestion to relocate the helper method. (I'm not opposed to moving away from mammoth files toward more, smaller files, but I think we should develop a strategy for organizing that)

from .return_to_url import parse_ballot_edit_return_point


class ReturnToUrlTests(TestCase):
Copy link
Member

Choose a reason for hiding this comment

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

Now assuming return_to_url.py is empty, make this a class in tests_ballot.py?

from django.views.decorators.csrf import csrf_exempt
from django.utils.html import escape
from urllib.parse import urlencode as urllib_urlencode

from .return_to_url import parse_ballot_edit_return_point
Copy link
Member

Choose a reason for hiding this comment

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

Minor, but organizationally we generally split imports into "basic packages", "django stuff", and "datatracker stuff" (i.e., ietf.*), roughly alphabetically in each section. This should then move down into the ietf.* section.

We're also slowly adopting the relative import syntax, so great to use it. If it's in a module that's not already using it, I've usually just followed whatever style is used there, but we should probably convert existing imports from ietf.thisapp.whatever to .whatever when doing so to keep it consistent.

(If you accept my reorg suggestions then that'll be moot here, though)

Copy link
Member

@jennifer-richards jennifer-richards left a comment

Choose a reason for hiding this comment

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

I probably should have caught this on the last round, but the ValueErrors in the validate method will bubble up and, because they aren't handled, cause 500 errors. They should be caught in the views and lead to a 4xx response instead (probably a 400 unless there's a more specific appropriate one).

I'd be inclined to keep the response message simple and just say that the url is not allowed, rather than throwing url path names at the user. If you want to preserve the details you could log them, but I don't know if that's needed.

@rjsparks rjsparks merged commit ff88981 into ietf-tools:main Aug 9, 2024
9 checks passed
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 13, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants