-
Notifications
You must be signed in to change notification settings - Fork 968
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
Validate Alternate Repository Location URLS #16817
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -14,6 +14,8 @@ | |||||
|
||||||
import wtforms | ||||||
|
||||||
from urllib3 import PoolManager, Timeout | ||||||
|
||||||
import warehouse.utils.otp as otp | ||||||
import warehouse.utils.webauthn as webauthn | ||||||
|
||||||
|
@@ -25,6 +27,7 @@ | |||||
TOTPValueMixin, | ||||||
WebAuthnCredentialMixin, | ||||||
) | ||||||
from warehouse.constants import MIME_PYPI_SIMPLE_V1_ALL | ||||||
from warehouse.i18n import localize as _ | ||||||
from warehouse.organizations.models import ( | ||||||
OrganizationRoleType, | ||||||
|
@@ -713,6 +716,23 @@ class CreateTeamForm(SaveTeamForm): | |||||
__params__ = SaveTeamForm.__params__ | ||||||
|
||||||
|
||||||
def validate_is_simple(form, field): | ||||||
try: | ||||||
timeout = Timeout(connect=1.0, read=1.0) | ||||||
http = PoolManager(timeout=timeout) | ||||||
response = http.request( | ||||||
"HEAD", field.data, headers={"Accept": ", ".join(MIME_PYPI_SIMPLE_V1_ALL)} | ||||||
) | ||||||
if response.headers.get("Content-Type") not in MIME_PYPI_SIMPLE_V1_ALL: | ||||||
raise wtforms.validators.ValidationError( | ||||||
_("Unable to parse simple index at given url") | ||||||
) | ||||||
except Exception as exc: | ||||||
raise wtforms.validators.ValidationError( | ||||||
_(f"Unable to parse simple index at given url: {exc}") | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
And maybe even tack on a URL? https://packaging.python.org/specifications/simple-repository-api/ |
||||||
) | ||||||
|
||||||
|
||||||
class AddAlternateRepositoryForm(forms.Form): | ||||||
"""Form to add an Alternate Repository Location for a Project.""" | ||||||
|
||||||
|
@@ -744,6 +764,7 @@ class AddAlternateRepositoryForm(forms.Form): | |||||
), | ||||||
), | ||||||
forms.URIValidator(), | ||||||
validate_is_simple, | ||||||
] | ||||||
) | ||||||
description = wtforms.TextAreaField( | ||||||
|
Original file line number | Diff line number | Diff line change | |||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -1129,7 +1129,7 @@ | ||||||||||||||||||||||||||||
self.add_alternate_repository_form_class = AddAlternateRepositoryForm | |||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||
@view_config(request_method="GET") | |||||||||||||||||||||||||||||
def manage_project_settings(self): | |||||||||||||||||||||||||||||
def manage_project_settings(self, add_alternate_repository_form=None): | |||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: is this because we're using two methods for GET vs POST, and how to make sure that the form values aren't reset on validation failure? I don't know if I've seen this pattern before, and wanted to understand it more. |
|||||||||||||||||||||||||||||
if not self.request.organization_access: | |||||||||||||||||||||||||||||
# Disable transfer of project to any organization. | |||||||||||||||||||||||||||||
organization_choices = set() | |||||||||||||||||||||||||||||
|
@@ -1153,19 +1153,23 @@ | ||||||||||||||||||||||||||||
active_organizations_owned | active_organizations_managed | |||||||||||||||||||||||||||||
) - current_organization | |||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||
add_alt_repo_form = self.add_alternate_repository_form_class() | |||||||||||||||||||||||||||||
add_alt_repo_form = ( | |||||||||||||||||||||||||||||
self.add_alternate_repository_form_class() | |||||||||||||||||||||||||||||
if add_alternate_repository_form is None | |||||||||||||||||||||||||||||
else add_alternate_repository_form | |||||||||||||||||||||||||||||
) | |||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||
return { | |||||||||||||||||||||||||||||
"project": self.project, | |||||||||||||||||||||||||||||
"MAX_FILESIZE": MAX_FILESIZE, | |||||||||||||||||||||||||||||
"MAX_PROJECT_SIZE": MAX_PROJECT_SIZE, | |||||||||||||||||||||||||||||
"transfer_organization_project_form": ( | |||||||||||||||||||||||||||||
self.transfer_organization_project_form_class( | |||||||||||||||||||||||||||||
organization_choices=organization_choices, | |||||||||||||||||||||||||||||
) | |||||||||||||||||||||||||||||
), | |||||||||||||||||||||||||||||
"add_alternate_repository_form_class": add_alt_repo_form, | |||||||||||||||||||||||||||||
} | |||||||||||||||||||||||||||||
Check warning Code scanning / CodeQL Reflected server-side cross-site scripting Medium
Cross-site scripting vulnerability due to a
user-provided value Error loading related location Loading |
|||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||
@view_config( | |||||||||||||||||||||||||||||
request_method="POST", | |||||||||||||||||||||||||||||
|
@@ -1182,12 +1186,7 @@ | ||||||||||||||||||||||||||||
self.request._("Invalid alternate repository location details"), | |||||||||||||||||||||||||||||
queue="error", | |||||||||||||||||||||||||||||
) | |||||||||||||||||||||||||||||
return HTTPSeeOther( | |||||||||||||||||||||||||||||
self.request.route_path( | |||||||||||||||||||||||||||||
"manage.project.settings", | |||||||||||||||||||||||||||||
project_name=self.project.name, | |||||||||||||||||||||||||||||
) | |||||||||||||||||||||||||||||
) | |||||||||||||||||||||||||||||
return self.manage_project_settings(add_alternate_repository_form=form) | |||||||||||||||||||||||||||||
Check warning Code scanning / CodeQL Reflected server-side cross-site scripting Medium
Cross-site scripting vulnerability due to a
user-provided value Error loading related location Loading
Copilot Autofix AI about 2 months ago To fix the reflected server-side cross-site scripting vulnerability, we need to ensure that any user-provided data is properly escaped before being rendered in the template. In this case, we can use the
Suggested changeset
1
warehouse/manage/views/__init__.py
Copilot is powered by AI and may make mistakes. Always verify output.
Refresh and try again.
|
|||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||
# add the alternate repository location entry | |||||||||||||||||||||||||||||
alt_repo = AlternateRepository( | |||||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: we use
requests
in other places in the code (example), and this appears to be the first time we're using urllib3 directly. Is that to get both connect and read timeouts? If so, then we can pass those as a tuple to requests like so:But that apparently has the same affect as only specifying it once, since th value will be applied to both, so we could satisfy with
https://requests.readthedocs.io/en/latest/user/advanced/#timeouts
I'm also wondering why we aren't using
request.http.head(...)
fromwarehouse/warehouse/http.py
Lines 48 to 53 in 1fbb4ac
request
object available to them?