Repository: benoitc/gunicorn

Issue:
Username parsing for Basic auth fails
I noticed that few test cases have started to fail when running `python setup.py test` 

See logs in details


<details>

```
running test
WARNING: Testing via this command is deprecated and will be removed in a future version. Users looking for a generic test entry point independent of test runner are encouraged to use tox.
running egg_info
writing gunicorn.egg-info/PKG-INFO
writing dependency_links to gunicorn.egg-info/dependency_links.txt
writing entry points to gunicorn.egg-info/entry_points.txt
writing requirements to gunicorn.egg-info/requires.txt
writing top-level names to gunicorn.egg-info/top_level.txt
reading manifest file 'gunicorn.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
warning: no previously-included files matching '__pycache__' found under directory '*'
adding license file 'LICENSE'
writing manifest file 'gunicorn.egg-info/SOURCES.txt'
running build_ext
===================================================================== test session starts =====================================================================
platform linux -- Python 3.10.6, pytest-7.3.1, pluggy-1.0.0
rootdir: /home/tsaarni/work/gunicorn
configfile: setup.cfg
plugins: cov-4.0.0
collected 215 items                                                                                                                                           

tests/test_arbiter.py ............                                                                                                                      [  5%]
tests/test_config.py ..............................................                                                                                     [ 26%]
tests/test_http.py ....................                                                                                                                 [ 36%]
tests/test_invalid_requests.py .......................                                                                                                  [ 46%]
tests/test_logger.py ..FFF.                                                                                                                             [ 49%]
tests/test_pidfile.py .....                                                                                                                             [ 52%]
tests/test_reload.py ..                                                                                                                                 [ 53%]
tests/test_sock.py .....                                                                                                                                [ 55%]
tests/test_ssl.py ......                                                                                                                                [ 58%]
tests/test_statsd.py ........                                                                                                                           [ 61%]
tests/test_systemd.py ....                                                                                                                              [ 63%]
tests/test_util.py ..........................................                                                                                           [ 83%]
tests/test_valid_requests.py ..................................                                                                                         [ 99%]
tests/workers/test_geventlet.py .                                                                                                                       [ 99%]
tests/workers/test_ggevent.py .                                                                                                                         [100%]

========================================================================== FAILURES ===========================================================================
__________________________________________________ test_get_username_from_basic_auth_header[Basic YnJrMHY6] ___________________________________________________

auth = 'Basic YnJrMHY6'

    @pytest.mark.parametrize('auth', [
        # auth type is case in-sensitive
        'Basic YnJrMHY6',
        'basic YnJrMHY6',
        'BASIC YnJrMHY6',
    ])
    def test_get_username_from_basic_auth_header(auth):
        request = SimpleNamespace(headers=())
        response = SimpleNamespace(
            status='200', response_length=1024, sent=1024,
            headers=(('Content-Type', 'text/plain'),),
        )
        environ = {
            'REQUEST_METHOD': 'GET', 'RAW_URI': '/my/path?foo=bar',
            'PATH_INFO': '/my/path', 'QUERY_STRING': 'foo=bar',
            'SERVER_PROTOCOL': 'HTTP/1.1',
            'HTTP_AUTHORIZATION': auth,
        }
        logger = Logger(Config())
        atoms = logger.atoms(response, request, environ, datetime.timedelta(seconds=1))
>       assert atoms['u'] == 'brk0v'
E       AssertionError

tests/test_logger.py:72: AssertionError
__________________________________________________ test_get_username_from_basic_auth_header[basic YnJrMHY6] ___________________________________________________

auth = 'basic YnJrMHY6'

    @pytest.mark.parametrize('auth', [
        # auth type is case in-sensitive
        'Basic YnJrMHY6',
        'basic YnJrMHY6',
        'BASIC YnJrMHY6',
    ])
    def test_get_username_from_basic_auth_header(auth):
        request = SimpleNamespace(headers=())
        response = SimpleNamespace(
            status='200', response_length=1024, sent=1024,
            headers=(('Content-Type', 'text/plain'),),
        )
        environ = {
            'REQUEST_METHOD': 'GET', 'RAW_URI': '/my/path?foo=bar',
            'PATH_INFO': '/my/path', 'QUERY_STRING': 'foo=bar',
            'SERVER_PROTOCOL': 'HTTP/1.1',
            'HTTP_AUTHORIZATION': auth,
        }
        logger = Logger(Config())
        atoms = logger.atoms(response, request, environ, datetime.timedelta(seconds=1))
>       assert atoms['u'] == 'brk0v'
E       AssertionError

tests/test_logger.py:72: AssertionError
__________________________________________________ test_get_username_from_basic_auth_header[BASIC YnJrMHY6] ___________________________________________________

auth = 'BASIC YnJrMHY6'

    @pytest.mark.parametrize('auth', [
        # auth type is case in-sensitive
        'Basic YnJrMHY6',
        'basic YnJrMHY6',
        'BASIC YnJrMHY6',
    ])
    def test_get_username_from_basic_auth_header(auth):
        request = SimpleNamespace(headers=())
        response = SimpleNamespace(
            status='200', response_length=1024, sent=1024,
            headers=(('Content-Type', 'text/plain'),),
        )
        environ = {
            'REQUEST_METHOD': 'GET', 'RAW_URI': '/my/path?foo=bar',
            'PATH_INFO': '/my/path', 'QUERY_STRING': 'foo=bar',
            'SERVER_PROTOCOL': 'HTTP/1.1',
            'HTTP_AUTHORIZATION': auth,
        }
        logger = Logger(Config())
        atoms = logger.atoms(response, request, environ, datetime.timedelta(seconds=1))
>       assert atoms['u'] == 'brk0v'
E       AssertionError

tests/test_logger.py:72: AssertionError

---------- coverage: platform linux, python 3.10.6-final-0 -----------
Coverage XML written to file coverage.xml

=================================================================== short test summary info ===================================================================
FAILED tests/test_logger.py::test_get_username_from_basic_auth_header[Basic YnJrMHY6] - AssertionError
FAILED tests/test_logger.py::test_get_username_from_basic_auth_header[basic YnJrMHY6] - AssertionError
FAILED tests/test_logger.py::test_get_username_from_basic_auth_header[BASIC YnJrMHY6] - AssertionError
================================================================ 3 failed, 212 passed in 2.57s ================================================================
```

</details>


Discussion:

Resolving patch:
diff --git a/gunicorn/glogging.py b/gunicorn/glogging.py
--- a/gunicorn/glogging.py
+++ b/gunicorn/glogging.py
@@ -469,10 +469,7 @@ def _get_user(self, environ):
                     # so we need to convert it to a byte string
                     auth = base64.b64decode(auth[1].strip().encode('utf-8'))
                     # b64decode returns a byte string
-                    auth.split(b":", 1)[0].decode("UTF-8", "replace")
+                    user = auth.split(b":", 1)[0].decode("UTF-8")
                 except (TypeError, binascii.Error, UnicodeDecodeError) as exc:
                     self.debug("Couldn't get username: %s", exc)
-                    return user
-                if len(auth) == 2:
-                    user = auth[0]
         return user


Test patch: