changeset 6926:32440c07a085

auth: consume request body before responding 401 or 403 during authentication In order to work correctly with reverse proxies like Apache, the application needs to consume the whole body before returning and closing the connection. Otherwise the reverse proxy may complain about a broken pipe. For example, if the client sends a lot of data and kallithea doesn't read all that data before sending 401, the connection will be closed before the reverse proxy has sent all the data. In this case an apache reverse proxy will fail with a broken pipe error. This is not necessary for all wsgi servers. Waitress automatically buffers (and therefore reads) all the data and uwsgi has a 'post-buffering' option to do the same. But AFAIK there is no way to push to a password protected hg repository when using gunicorn without this changeset.
author domruf <dominikruf@gmail.com>
date Mon, 11 Sep 2017 21:16:49 +0200
parents 58713c2ebfff
children 557fb1f85ca1
files kallithea/lib/base.py
diffstat 1 files changed, 10 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/kallithea/lib/base.py	Wed Sep 20 23:08:35 2017 +0200
+++ b/kallithea/lib/base.py	Mon Sep 11 21:16:49 2017 +0200
@@ -191,8 +191,14 @@
         self.authfunc = authfunc
         self._rc_auth_http_code = auth_http_code
 
-    def build_authentication(self):
+    def build_authentication(self, environ):
         head = paste.httpheaders.WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
+        # Consume the whole body before sending a response
+        try:
+            request_body_size = int(environ.get('CONTENT_LENGTH', 0))
+        except (ValueError):
+            request_body_size = 0
+        environ['wsgi.input'].read(request_body_size)
         if self._rc_auth_http_code and self._rc_auth_http_code == '403':
             # return 403 if alternative http return code is specified in
             # Kallithea config
@@ -202,17 +208,17 @@
     def authenticate(self, environ):
         authorization = paste.httpheaders.AUTHORIZATION(environ)
         if not authorization:
-            return self.build_authentication()
+            return self.build_authentication(environ)
         (authmeth, auth) = authorization.split(' ', 1)
         if 'basic' != authmeth.lower():
-            return self.build_authentication()
+            return self.build_authentication(environ)
         auth = auth.strip().decode('base64')
         _parts = auth.split(':', 1)
         if len(_parts) == 2:
             username, password = _parts
             if self.authfunc(username, password, environ) is not None:
                 return username
-        return self.build_authentication()
+        return self.build_authentication(environ)
 
     __call__ = authenticate