changeset 8455:d727e81e0097 stable

vcs: fix cloning remote repository with HTTP authentication (Issue #379) Using a remote clone URI of http://user:pass@host/... triggered an exception: ... E File ".../kallithea/lib/utils.py", line 256, in is_valid_repo_uri E GitRepository._check_url(url) E File ".../kallithea/lib/vcs/backends/git/repository.py", line 183, in _check_url E passmgr.add_password(*authinfo) E File "/usr/lib/python3.7/urllib/request.py", line 848, in add_password E self.reduce_uri(u, default_port) for u in uri) E File "/usr/lib/python3.7/urllib/request.py", line 848, in <genexpr> E self.reduce_uri(u, default_port) for u in uri) E File "/usr/lib/python3.7/urllib/request.py", line 875, in reduce_uri E host, port = splitport(authority) E File "/usr/lib/python3.7/urllib/parse.py", line 1022, in splitport E match = _portprog.fullmatch(host) E TypeError: cannot use a string pattern on a bytes-like object The authinfo tuple is obtained via mercurial.util.url, which unfortunately returns a tuple of bytes whereas urllib expects strings. It seems that mercurial internally has some more hacking around urllib as urllibcompat.py, which we don't use. Therefore, transform the bytes into strings before passing authinfo to urllib. As the realm can be None, we need to check it specifically otherwise safe_str would return a string 'None'. A basic test that catches the mentioned problem is added, even though it does not actually test that cloning with auth info will actually work (it only tests that it fails cleanly if the URI is not reachable). Additionally, one use of 'test_uri' in hg/repository.py still needed to be transformed from bytes to string. For git this was already ok.
author Thomas De Schampheleire <thomas.de_schampheleire@nokia.com>
date Wed, 22 Jul 2020 21:55:57 +0200
parents d1521d421610
children eca0cb56a822
files kallithea/lib/vcs/backends/hg/repository.py kallithea/lib/vcs/utils/helpers.py kallithea/tests/functional/test_admin_repos.py
diffstat 3 files changed, 28 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/kallithea/lib/vcs/backends/hg/repository.py	Wed Jul 22 21:48:53 2020 +0200
+++ b/kallithea/lib/vcs/backends/hg/repository.py	Wed Jul 22 21:55:57 2020 +0200
@@ -320,7 +320,7 @@
 
         req = urllib.request.Request(
             "%s?%s" % (
-                test_uri,
+                safe_str(test_uri),
                 urllib.parse.urlencode({
                     'cmd': 'between',
                     'pairs': "%s-%s" % ('0' * 40, '0' * 40),
--- a/kallithea/lib/vcs/utils/helpers.py	Wed Jul 22 21:48:53 2020 +0200
+++ b/kallithea/lib/vcs/utils/helpers.py	Wed Jul 22 21:55:57 2020 +0200
@@ -11,6 +11,7 @@
 import mercurial.url
 
 from kallithea.lib.vcs.exceptions import RepositoryError, VCSError
+from kallithea.lib.vcs.utils import safe_str
 from kallithea.lib.vcs.utils.paths import abspath
 
 
@@ -226,9 +227,21 @@
     test_uri, authinfo = url_obj.authinfo()
 
     if authinfo:
+        # authinfo is a tuple (realm, uris, user, password) where 'uris' itself
+        # is a tuple of URIs.
+        # If url_obj is obtained via mercurial.util.url, the obtained authinfo
+        # values will be bytes, e.g.
+        #    (None, (b'http://127.0.0.1/repo', b'127.0.0.1'), b'user', b'pass')
+        # However, urllib expects strings, not bytes, so we must convert them.
+
         # create a password manager
         passmgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
-        passmgr.add_password(*authinfo)
+        passmgr.add_password(
+            safe_str(authinfo[0]) if authinfo[0] else None, # realm
+            tuple(safe_str(x) for x in authinfo[1]),        # uris
+            safe_str(authinfo[2]),                          # user
+            safe_str(authinfo[3]),                          # password
+        )
 
         handlers.extend((mercurial.url.httpbasicauthhandler(passmgr),
                          mercurial.url.httpdigestauthhandler(passmgr)))
--- a/kallithea/tests/functional/test_admin_repos.py	Wed Jul 22 21:48:53 2020 +0200
+++ b/kallithea/tests/functional/test_admin_repos.py	Wed Jul 22 21:55:57 2020 +0200
@@ -344,6 +344,19 @@
                                                 _session_csrf_secret_token=self.session_csrf_secret_token()))
         response.mustcontain('Invalid repository URL')
 
+    def test_create_remote_repo_wrong_clone_uri_http_auth(self):
+        self.log_user()
+        repo_name = self.NEW_REPO
+        description = 'description for newly created repo'
+        response = self.app.post(base.url('repos'),
+                        fixture._get_repo_create_params(repo_private=False,
+                                                repo_name=repo_name,
+                                                repo_type=self.REPO_TYPE,
+                                                repo_description=description,
+                                                clone_uri='http://user:pass@127.0.0.1/repo',
+                                                _session_csrf_secret_token=self.session_csrf_secret_token()))
+        response.mustcontain('Invalid repository URL')
+
     def test_delete(self):
         self.log_user()
         repo_name = 'vcs_test_new_to_delete_%s' % self.REPO_TYPE