diff hgext/lfs/blobstore.py @ 41607:698667eb7523

lfs: disable all authentication except Basic for HTTP(S) connections I ran into a problem pushing to an old Apache server- the normal outgoing traffic occurred, the Batch API request and response occurred, and then things suddenly halted. 5 minutes later, a 500 was returned, and the server log had a timeout reading 32K from `self._req.bodyfh` in hgweb.request.sendresponse(). Watching in WireShark, the Batch API got a 401, retried properly, then proceeded to PUT the blob (without authentication headers). This got a 401, but the client never retried with authentication. Worse, the blob was sent over the wire in the failed attempt. This kills digests for both the Batch API and the Transfer API. While in theory we could have the Batch API provide external URLs to a place that supports Basic Authentication, the LFS spec actually calls out using Basic Authentication[1]. It's not clear to me if they've been able to shoehorn in other methods. But let's keep it simple until somebody needs it. If we only had to support python2, we could just not add the handler for digest authentication. However in python3, AbstractBasicAuthHandler raises ValueError if it sees a scheme other than Basic. So we need to intercept all other schemes before it gets to that point. # no-check-commit because of urllib2.OpenerDirector foo_bar calling conventions [1] https://github.com/git-lfs/git-lfs/blob/master/docs/api/authentication.md
author Matt Harbison <matt_harbison@yahoo.com>
date Wed, 06 Feb 2019 22:30:49 -0500
parents 1bc01490178a
children 2372284d9457
line wrap: on
line diff
--- a/hgext/lfs/blobstore.py	Wed Jan 30 16:21:30 2019 -0800
+++ b/hgext/lfs/blobstore.py	Wed Feb 06 22:30:49 2019 -0500
@@ -264,6 +264,24 @@
     else:
         return stringutil.forcebytestr(urlerror)
 
+class lfsauthhandler(util.urlreq.basehandler):
+    handler_order = 480  # Before HTTPDigestAuthHandler (== 490)
+
+    def http_error_401(self, req, fp, code, msg, headers):
+        """Enforces that any authentication performed is HTTP Basic
+        Authentication.  No authentication is also acceptable.
+        """
+        authreq = headers.get(r'www-authenticate', None)
+        if authreq:
+            scheme = authreq.split()[0]
+
+            if scheme.lower() != r'basic':
+                msg = _(b'the server must support Basic Authentication')
+                raise util.urlerr.httperror(req.get_full_url(), code,
+                                            encoding.strfromlocal(msg), headers,
+                                            fp)
+        return None
+
 class _gitlfsremote(object):
 
     def __init__(self, repo, url):
@@ -275,6 +293,7 @@
         if not useragent:
             useragent = b'git-lfs/2.3.4 (Mercurial %s)' % util.version()
         self.urlopener = urlmod.opener(ui, authinfo, useragent)
+        self.urlopener.add_handler(lfsauthhandler())
         self.retry = ui.configint(b'lfs', b'retry')
 
     def writebatch(self, pointers, fromstore):