Skip to content

Fix up some of the logic around repo and submodule URLs #3860

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

Merged
merged 2 commits into from
Mar 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 50 additions & 22 deletions readthedocs/core/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ def __call__(self, value):
@deconstructible
class RepositoryURLValidator(object):

disallow_relative_url = True

# Pattern for ``[email protected]:user/repo`` pattern
re_git_user = re.compile(r'^[\w]+@.+')

def __call__(self, value):
allow_private_repos = getattr(settings, 'ALLOW_PRIVATE_REPOS', False)
public_schemes = ['https', 'http', 'git', 'ftps', 'ftp']
Expand All @@ -60,28 +65,51 @@ def __call__(self, value):
if allow_private_repos:
valid_schemes += private_schemes
url = urlparse(value)
if (
( # pylint: disable=too-many-boolean-expressions
url.scheme not in valid_schemes and
'@' not in value and
not value.startswith('lp:')
) or
(
value.startswith('/') or
value.startswith('file://') or
value.startswith('.')
)
):
# Avoid ``/path/to/local/file`` and ``file://`` scheme but allow
# ``[email protected]:user/project.git`` and ``lp:bazaar``
raise ValidationError(_('Invalid scheme for URL'))
elif '&&' in value or '|' in value:

# Malicious characters go first
if '&&' in value or '|' in value:
raise ValidationError(_('Invalid character in the URL'))
elif (
('@' in value or url.scheme in private_schemes) and
not allow_private_repos
):
raise ValidationError('Clonning via SSH is not supported')
return value
elif url.scheme in valid_schemes:
return value

# Repo URL is not a supported scheme at this point, but there are
# several cases where we might support it
# Launchpad
elif value.startswith('lp:'):
return value
# Relative paths are conditionally supported
elif value.startswith('.') and not self.disallow_relative_url:
return value
# SSH cloning and ``[email protected]:user/project.git``
elif self.re_git_user.search(value) or url.scheme in private_schemes:
if allow_private_repos:
return value
else:
# Throw a more helpful error message
raise ValidationError('Manual cloning via SSH is not supported')

# No more valid URLs without supported URL schemes
raise ValidationError(_('Invalid scheme for URL'))


class SubmoduleURLValidator(RepositoryURLValidator):

"""
A URL validator for repository submodules

If a repository has a relative submodule, the URL path is effectively the
supermodule's remote ``origin`` URL with the relative path applied.

From the git docs::

``<repository>`` is the URL of the new submodule's origin repository.
This may be either an absolute URL, or (if it begins with ``./`` or
``../``), the location relative to the superproject's default remote
repository
"""

disallow_relative_url = False


validate_repository_url = RepositoryURLValidator()
validate_submodule_url = SubmoduleURLValidator()
6 changes: 6 additions & 0 deletions readthedocs/rtd_tests/tests/test_backend.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import absolute_import
from os.path import exists

import pytest
from django.contrib.auth.models import User
import django_dynamic_fixture as fixture

Expand Down Expand Up @@ -106,11 +107,16 @@ def test_check_submodule_urls(self):
repo = self.project.vcs_repo()
repo.checkout('submodule')
self.assertTrue(repo.are_submodules_valid())
repo.checkout('relativesubmodule')
self.assertTrue(repo.are_submodules_valid())

@pytest.mark.xfail(strict=True, reason="Fixture is not working correctly")
def test_check_invalid_submodule_urls(self):
with self.assertRaises(RepositoryError) as e:
repo.checkout('invalidsubmodule')
self.assertEqual(e.msg, RepositoryError.INVALID_SUBMODULES)


class TestHgBackend(RTDTestCase):
def setUp(self):
hg_repo = make_test_hg()
Expand Down
9 changes: 9 additions & 0 deletions readthedocs/rtd_tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ def make_test_git():
log.info(check_output(['git', 'add', '.'], env=env))
log.info(check_output(['git', 'commit', '-m"Add submodule"'], env=env))

# Add a relative submodule URL in the relativesubmodule branch
log.info(check_output(['git', 'checkout', '-b', 'relativesubmodule', 'master'], env=env))
log.info(check_output(
['git', 'submodule', 'add', '-b', 'master', './', 'relativesubmodule'],
env=env
))
log.info(check_output(['git', 'add', '.'], env=env))
log.info(check_output(['git', 'commit', '-m"Add relative submodule"'], env=env))
# Add an invalid submodule URL in the invalidsubmodule branch
log.info(check_output(['git', 'checkout', '-b', 'invalidsubmodule', 'master'], env=env))
Copy link
Member

Choose a reason for hiding this comment

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

Now this invalidsubmodule is no more invalid.

We probably need to remove this.

log.info(check_output(
Expand All @@ -77,6 +85,7 @@ def make_test_git():
))
log.info(check_output(['git', 'add', '.'], env=env))
log.info(check_output(['git', 'commit', '-m"Add invalid submodule"'], env=env))

# Checkout to master branch again
log.info(check_output(['git', 'checkout', 'master'], env=env))
chdir(path)
Expand Down
4 changes: 2 additions & 2 deletions readthedocs/vcs_support/backends/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import git
from six import PY2, StringIO

from readthedocs.core.validators import validate_repository_url
from readthedocs.core.validators import validate_submodule_url
from readthedocs.projects.exceptions import RepositoryError
from readthedocs.vcs_support.base import BaseVCS, VCSVersion

Expand Down Expand Up @@ -79,7 +79,7 @@ def are_submodules_valid(self):
repo = git.Repo(self.working_dir)
for submodule in repo.submodules:
try:
validate_repository_url(submodule.url)
validate_submodule_url(submodule.url)
except ValidationError:
return False
return True
Expand Down