We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Passing bad data to basic auth can result in a 500
https://sentry.changaco.oy.lc/share/issue/322e3532/

To reproduce: `curl --basic -u admin:admin 'http://localhost:8339/'`


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `liberapay/security/csrf.py`
Content:
```
1 """Cross Site Request Forgery middleware, borrowed from Django.
2 
3 See also:
4 
5     https://github.com/django/django/blob/master/django/middleware/csrf.py
6     https://docs.djangoproject.com/en/dev/ref/contrib/csrf/
7     https://github.com/gratipay/gratipay.com/issues/88
8 
9 """
10 from __future__ import absolute_import, division, print_function, unicode_literals
11 
12 from datetime import timedelta
13 import re
14 
15 from .crypto import constant_time_compare, get_random_string
16 
17 
18 TOKEN_LENGTH = 32
19 CSRF_TOKEN = str('csrf_token')  # bytes in python2, unicode in python3
20 CSRF_TIMEOUT = timedelta(days=7)
21 
22 _get_new_token = lambda: get_random_string(TOKEN_LENGTH)
23 _token_re = re.compile(r'^[a-zA-Z0-9]{%d}$' % TOKEN_LENGTH)
24 _sanitize_token = lambda t: t if _token_re.match(t) else None
25 
26 
27 def extract_token_from_cookie(request):
28     """Given a Request object, return a csrf_token.
29     """
30     try:
31         token = request.headers.cookie[CSRF_TOKEN].value
32     except KeyError:
33         token = None
34     else:
35         token = _sanitize_token(token)
36 
37     # Don't set a CSRF cookie on assets, to avoid busting the cache.
38     # Don't set it on callbacks, because we don't need it there.
39 
40     if request.path.raw.startswith('/assets/') or request.path.raw.startswith('/callbacks/'):
41         token = None
42     else:
43         token = token or _get_new_token()
44 
45     return {'csrf_token': token}
46 
47 
48 def reject_forgeries(request, response, csrf_token):
49     # Assume that anything not defined as 'safe' by RC2616 needs protection.
50     if request.line.method not in ('GET', 'HEAD', 'OPTIONS', 'TRACE'):
51 
52         # except webhooks
53         if request.line.uri.startswith('/callbacks/'):
54             return
55         # and requests using HTTP auth
56         if b'Authorization' in request.headers:
57             return
58 
59         # Check non-cookie token for match.
60         second_token = ""
61         if request.line.method == "POST":
62             if isinstance(request.body, dict):
63                 second_token = request.body.get('csrf_token', '')
64 
65         if second_token == "":
66             # Fall back to X-CSRF-TOKEN, to make things easier for AJAX,
67             # and possible for PUT/DELETE.
68             second_token = request.headers.get(b'X-CSRF-TOKEN', b'').decode('ascii', 'replace')
69 
70         if not constant_time_compare(second_token, csrf_token):
71             raise response.error(403, "Bad CSRF cookie")
72 
73 
74 def add_token_to_response(response, csrf_token=None):
75     """Store the latest CSRF token as a cookie.
76     """
77     if csrf_token:
78         # Don't set httponly so that we can POST using XHR.
79         # https://github.com/gratipay/gratipay.com/issues/3030
80         response.set_cookie(CSRF_TOKEN, csrf_token, expires=CSRF_TIMEOUT, httponly=False)
81
```

Path: `liberapay/security/authentication.py`
Content:
```
1 """Defines website authentication helpers.
2 """
3 import binascii
4 
5 from six.moves.urllib.parse import urlencode
6 
7 from pando import Response
8 
9 from liberapay.constants import SESSION, SESSION_TIMEOUT
10 from liberapay.exceptions import LoginRequired
11 from liberapay.models.participant import Participant
12 
13 
14 class _ANON(object):
15     ANON = True
16     is_admin = False
17     id = None
18     __bool__ = __nonzero__ = lambda *a: False
19     get_tip_to = lambda self, tippee: Participant._zero_tip_dict(tippee)
20     __repr__ = lambda self: '<ANON>'
21 
22 
23 ANON = _ANON()
24 
25 
26 def _get_body(request):
27     try:
28         body = request.body
29     except Response:
30         return
31     if not isinstance(body, dict):
32         return
33     return body
34 
35 
36 def sign_in_with_form_data(body, state):
37     p = None
38     _, website = state['_'], state['website']
39 
40     if body.get('log-in.id'):
41         id = body.pop('log-in.id')
42         password = body.pop('log-in.password', None)
43         k = 'email' if '@' in id else 'username'
44         if password:
45             p = Participant.authenticate(
46                 k, 'password',
47                 id, password,
48             )
49             if not p:
50                 state['log-in.error'] = _("Bad username or password.")
51         elif k == 'username':
52             state['log-in.error'] = _("\"{0}\" is not a valid email address.", id)
53             return
54         else:
55             email = id
56             p = Participant._from_thing('email', email)
57             if p:
58                 p.start_session()
59                 qs = {'log-in.id': p.id, 'log-in.token': p.session_token}
60                 p.send_email(
61                     'login_link',
62                     email=email,
63                     link=p.url('settings/', qs),
64                     link_validity=SESSION_TIMEOUT,
65                 )
66                 state['log-in.email-sent-to'] = email
67             else:
68                 state['log-in.error'] = _(
69                     "We didn't find any account whose primary email address is {0}.",
70                     email
71                 )
72             p = None
73 
74     elif 'sign-in.email' in body:
75         response = state['response']
76         kind = body.pop('sign-in.kind')
77         if kind not in ('individual', 'organization'):
78             raise response.error(400, 'bad kind')
79         email = body.pop('sign-in.email')
80         if not email:
81             raise response.error(400, 'email is required')
82         with website.db.get_cursor() as c:
83             p = Participant.make_active(
84                 kind, body.pop('sign-in.username', None),
85                 body.pop('sign-in.password', None), cursor=c,
86             )
87             p.set_email_lang(state['request'].headers.get(b'Accept-Language'), cursor=c)
88             p.add_email(email, cursor=c)
89         p.authenticated = True
90 
91     return p
92 
93 
94 def start_user_as_anon():
95     """Make sure we always have a user object, regardless of exceptions during authentication.
96     """
97     return {'user': ANON}
98 
99 
100 def authenticate_user_if_possible(request, response, state, user, _):
101     """This signs the user in.
102     """
103     if request.line.uri.startswith('/assets/'):
104         return
105 
106     # HTTP auth
107     if b'Authorization' in request.headers:
108         header = request.headers[b'Authorization']
109         if not header.startswith(b'Basic '):
110             raise response.error(401, 'Unsupported authentication method')
111         try:
112             creds = binascii.a2b_base64(header[len('Basic '):]).decode('utf8').split(':', 1)
113         except (binascii.Error, UnicodeDecodeError):
114             raise response.error(400, 'Malformed "Authorization" header')
115         participant = Participant.authenticate('id', 'password', *creds)
116         if not participant:
117             raise response.error(401, 'Invalid credentials')
118         return {'user': participant}
119 
120     # Cookie and form auth
121     # We want to try cookie auth first, but we want form auth to supersede it
122     p = None
123     if SESSION in request.headers.cookie:
124         creds = request.headers.cookie[SESSION].value.split(':', 1)
125         p = Participant.authenticate('id', 'session', *creds)
126         if p:
127             state['user'] = p
128     session_p, p = p, None
129     session_suffix = ''
130     redirect_url = request.line.uri
131     if request.method == 'POST':
132         body = _get_body(request)
133         if body:
134             p = sign_in_with_form_data(body, state)
135             carry_on = body.pop('log-in.carry-on', None)
136             if not p and carry_on:
137                 p_email = session_p and (
138                     session_p.email or session_p.get_emails()[0].address
139                 )
140                 if p_email != carry_on:
141                     state['log-in.carry-on'] = carry_on
142                     raise LoginRequired
143             redirect_url = body.get('sign-in.back-to') or redirect_url
144     elif request.method == 'GET' and request.qs.get('log-in.id'):
145         id, token = request.qs.pop('log-in.id'), request.qs.pop('log-in.token')
146         p = Participant.authenticate('id', 'session', id, token)
147         if not p and (not session_p or session_p.id != id):
148             raise response.error(400, _("This login link is expired or invalid."))
149         else:
150             qs = '?' + urlencode(request.qs, doseq=True) if request.qs else ''
151             redirect_url = request.path.raw + qs
152             session_p = p
153             session_suffix = '.em'
154     if p:
155         if session_p:
156             session_p.sign_out(response.headers.cookie)
157         if p.status == 'closed':
158             p.update_status('active')
159         p.sign_in(response.headers.cookie, session_suffix)
160         state['user'] = p
161         if request.body.pop('form.repost', None) != 'true':
162             response.redirect(redirect_url)
163 
164 
165 def add_auth_to_response(response, request=None, user=ANON):
166     if request is None:
167         return  # early parsing must've failed
168     if request.line.uri.startswith('/assets/'):
169         return  # assets never get auth headers
170 
171     if SESSION in request.headers.cookie:
172         if not user.ANON:
173             user.keep_signed_in(response.headers.cookie)
174
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/liberapay/security/authentication.py b/liberapay/security/authentication.py
--- a/liberapay/security/authentication.py
+++ b/liberapay/security/authentication.py
@@ -109,10 +109,12 @@
         if not header.startswith(b'Basic '):
             raise response.error(401, 'Unsupported authentication method')
         try:
-            creds = binascii.a2b_base64(header[len('Basic '):]).decode('utf8').split(':', 1)
-        except (binascii.Error, UnicodeDecodeError):
+            uid, pwd = binascii.a2b_base64(header[len('Basic '):]).decode('utf8').split(':', 1)
+        except (binascii.Error, UnicodeDecodeError, ValueError):
             raise response.error(400, 'Malformed "Authorization" header')
-        participant = Participant.authenticate('id', 'password', *creds)
+        if not uid.isdigit():
+            raise response.error(401, 'Invalid user id: expected an integer, got `%s`' % uid)
+        participant = Participant.authenticate('id', 'password', uid, pwd)
         if not participant:
             raise response.error(401, 'Invalid credentials')
         return {'user': participant}
diff --git a/liberapay/security/csrf.py b/liberapay/security/csrf.py
--- a/liberapay/security/csrf.py
+++ b/liberapay/security/csrf.py
@@ -1,4 +1,4 @@
-"""Cross Site Request Forgery middleware, borrowed from Django.
+"""Cross Site Request Forgery middleware, originally borrowed from Django.
 
 See also:
 
@@ -18,6 +18,7 @@
 TOKEN_LENGTH = 32
 CSRF_TOKEN = str('csrf_token')  # bytes in python2, unicode in python3
 CSRF_TIMEOUT = timedelta(days=7)
+SAFE_METHODS = {'GET', 'HEAD', 'OPTIONS', 'TRACE'}
 
 _get_new_token = lambda: get_random_string(TOKEN_LENGTH)
 _token_re = re.compile(r'^[a-zA-Z0-9]{%d}$' % TOKEN_LENGTH)
@@ -27,34 +28,36 @@
 def extract_token_from_cookie(request):
     """Given a Request object, return a csrf_token.
     """
-    try:
-        token = request.headers.cookie[CSRF_TOKEN].value
-    except KeyError:
-        token = None
-    else:
-        token = _sanitize_token(token)
 
-    # Don't set a CSRF cookie on assets, to avoid busting the cache.
-    # Don't set it on callbacks, because we don't need it there.
+    off = (
+        # Turn off CSRF protection on assets, to avoid busting the cache.
+        request.path.raw.startswith('/assets/') or
+        # Turn off CSRF protection on callbacks, so they can receive POST requests.
+        request.path.raw.startswith('/callbacks/') or
+        # Turn off CSRF when using HTTP auth, so API users can use POST and others.
+        b'Authorization' in request.headers
+    )
 
-    if request.path.raw.startswith('/assets/') or request.path.raw.startswith('/callbacks/'):
+    if off:
         token = None
     else:
-        token = token or _get_new_token()
+        try:
+            token = request.headers.cookie[CSRF_TOKEN].value
+        except KeyError:
+            token = _get_new_token()
+        else:
+            token = _sanitize_token(token) or _get_new_token()
 
     return {'csrf_token': token}
 
 
 def reject_forgeries(request, response, csrf_token):
-    # Assume that anything not defined as 'safe' by RC2616 needs protection.
-    if request.line.method not in ('GET', 'HEAD', 'OPTIONS', 'TRACE'):
-
-        # except webhooks
-        if request.line.uri.startswith('/callbacks/'):
-            return
-        # and requests using HTTP auth
-        if b'Authorization' in request.headers:
-            return
+    if csrf_token is None:
+        # CSRF protection is turned off for this request
+        return
+
+    # Assume that anything not defined as 'safe' by RFC7231 needs protection.
+    if request.line.method not in SAFE_METHODS:
 
         # Check non-cookie token for match.
         second_token = ""

{"golden_diff": "diff --git a/liberapay/security/authentication.py b/liberapay/security/authentication.py\n--- a/liberapay/security/authentication.py\n+++ b/liberapay/security/authentication.py\n@@ -109,10 +109,12 @@\n         if not header.startswith(b'Basic '):\n             raise response.error(401, 'Unsupported authentication method')\n         try:\n-            creds = binascii.a2b_base64(header[len('Basic '):]).decode('utf8').split(':', 1)\n-        except (binascii.Error, UnicodeDecodeError):\n+            uid, pwd = binascii.a2b_base64(header[len('Basic '):]).decode('utf8').split(':', 1)\n+        except (binascii.Error, UnicodeDecodeError, ValueError):\n             raise response.error(400, 'Malformed \"Authorization\" header')\n-        participant = Participant.authenticate('id', 'password', *creds)\n+        if not uid.isdigit():\n+            raise response.error(401, 'Invalid user id: expected an integer, got `%s`' % uid)\n+        participant = Participant.authenticate('id', 'password', uid, pwd)\n         if not participant:\n             raise response.error(401, 'Invalid credentials')\n         return {'user': participant}\ndiff --git a/liberapay/security/csrf.py b/liberapay/security/csrf.py\n--- a/liberapay/security/csrf.py\n+++ b/liberapay/security/csrf.py\n@@ -1,4 +1,4 @@\n-\"\"\"Cross Site Request Forgery middleware, borrowed from Django.\n+\"\"\"Cross Site Request Forgery middleware, originally borrowed from Django.\n \n See also:\n \n@@ -18,6 +18,7 @@\n TOKEN_LENGTH = 32\n CSRF_TOKEN = str('csrf_token')  # bytes in python2, unicode in python3\n CSRF_TIMEOUT = timedelta(days=7)\n+SAFE_METHODS = {'GET', 'HEAD', 'OPTIONS', 'TRACE'}\n \n _get_new_token = lambda: get_random_string(TOKEN_LENGTH)\n _token_re = re.compile(r'^[a-zA-Z0-9]{%d}$' % TOKEN_LENGTH)\n@@ -27,34 +28,36 @@\n def extract_token_from_cookie(request):\n     \"\"\"Given a Request object, return a csrf_token.\n     \"\"\"\n-    try:\n-        token = request.headers.cookie[CSRF_TOKEN].value\n-    except KeyError:\n-        token = None\n-    else:\n-        token = _sanitize_token(token)\n \n-    # Don't set a CSRF cookie on assets, to avoid busting the cache.\n-    # Don't set it on callbacks, because we don't need it there.\n+    off = (\n+        # Turn off CSRF protection on assets, to avoid busting the cache.\n+        request.path.raw.startswith('/assets/') or\n+        # Turn off CSRF protection on callbacks, so they can receive POST requests.\n+        request.path.raw.startswith('/callbacks/') or\n+        # Turn off CSRF when using HTTP auth, so API users can use POST and others.\n+        b'Authorization' in request.headers\n+    )\n \n-    if request.path.raw.startswith('/assets/') or request.path.raw.startswith('/callbacks/'):\n+    if off:\n         token = None\n     else:\n-        token = token or _get_new_token()\n+        try:\n+            token = request.headers.cookie[CSRF_TOKEN].value\n+        except KeyError:\n+            token = _get_new_token()\n+        else:\n+            token = _sanitize_token(token) or _get_new_token()\n \n     return {'csrf_token': token}\n \n \n def reject_forgeries(request, response, csrf_token):\n-    # Assume that anything not defined as 'safe' by RC2616 needs protection.\n-    if request.line.method not in ('GET', 'HEAD', 'OPTIONS', 'TRACE'):\n-\n-        # except webhooks\n-        if request.line.uri.startswith('/callbacks/'):\n-            return\n-        # and requests using HTTP auth\n-        if b'Authorization' in request.headers:\n-            return\n+    if csrf_token is None:\n+        # CSRF protection is turned off for this request\n+        return\n+\n+    # Assume that anything not defined as 'safe' by RFC7231 needs protection.\n+    if request.line.method not in SAFE_METHODS:\n \n         # Check non-cookie token for match.\n         second_token = \"\"\n", "issue": "Passing bad data to basic auth can result in a 500\nhttps://sentry.changaco.oy.lc/share/issue/322e3532/\n\nTo reproduce: `curl --basic -u admin:admin 'http://localhost:8339/'`\n\n", "before_files": [{"content": "\"\"\"Cross Site Request Forgery middleware, borrowed from Django.\n\nSee also:\n\n    https://github.com/django/django/blob/master/django/middleware/csrf.py\n    https://docs.djangoproject.com/en/dev/ref/contrib/csrf/\n    https://github.com/gratipay/gratipay.com/issues/88\n\n\"\"\"\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nfrom datetime import timedelta\nimport re\n\nfrom .crypto import constant_time_compare, get_random_string\n\n\nTOKEN_LENGTH = 32\nCSRF_TOKEN = str('csrf_token')  # bytes in python2, unicode in python3\nCSRF_TIMEOUT = timedelta(days=7)\n\n_get_new_token = lambda: get_random_string(TOKEN_LENGTH)\n_token_re = re.compile(r'^[a-zA-Z0-9]{%d}$' % TOKEN_LENGTH)\n_sanitize_token = lambda t: t if _token_re.match(t) else None\n\n\ndef extract_token_from_cookie(request):\n    \"\"\"Given a Request object, return a csrf_token.\n    \"\"\"\n    try:\n        token = request.headers.cookie[CSRF_TOKEN].value\n    except KeyError:\n        token = None\n    else:\n        token = _sanitize_token(token)\n\n    # Don't set a CSRF cookie on assets, to avoid busting the cache.\n    # Don't set it on callbacks, because we don't need it there.\n\n    if request.path.raw.startswith('/assets/') or request.path.raw.startswith('/callbacks/'):\n        token = None\n    else:\n        token = token or _get_new_token()\n\n    return {'csrf_token': token}\n\n\ndef reject_forgeries(request, response, csrf_token):\n    # Assume that anything not defined as 'safe' by RC2616 needs protection.\n    if request.line.method not in ('GET', 'HEAD', 'OPTIONS', 'TRACE'):\n\n        # except webhooks\n        if request.line.uri.startswith('/callbacks/'):\n            return\n        # and requests using HTTP auth\n        if b'Authorization' in request.headers:\n            return\n\n        # Check non-cookie token for match.\n        second_token = \"\"\n        if request.line.method == \"POST\":\n            if isinstance(request.body, dict):\n                second_token = request.body.get('csrf_token', '')\n\n        if second_token == \"\":\n            # Fall back to X-CSRF-TOKEN, to make things easier for AJAX,\n            # and possible for PUT/DELETE.\n            second_token = request.headers.get(b'X-CSRF-TOKEN', b'').decode('ascii', 'replace')\n\n        if not constant_time_compare(second_token, csrf_token):\n            raise response.error(403, \"Bad CSRF cookie\")\n\n\ndef add_token_to_response(response, csrf_token=None):\n    \"\"\"Store the latest CSRF token as a cookie.\n    \"\"\"\n    if csrf_token:\n        # Don't set httponly so that we can POST using XHR.\n        # https://github.com/gratipay/gratipay.com/issues/3030\n        response.set_cookie(CSRF_TOKEN, csrf_token, expires=CSRF_TIMEOUT, httponly=False)\n", "path": "liberapay/security/csrf.py"}, {"content": "\"\"\"Defines website authentication helpers.\n\"\"\"\nimport binascii\n\nfrom six.moves.urllib.parse import urlencode\n\nfrom pando import Response\n\nfrom liberapay.constants import SESSION, SESSION_TIMEOUT\nfrom liberapay.exceptions import LoginRequired\nfrom liberapay.models.participant import Participant\n\n\nclass _ANON(object):\n    ANON = True\n    is_admin = False\n    id = None\n    __bool__ = __nonzero__ = lambda *a: False\n    get_tip_to = lambda self, tippee: Participant._zero_tip_dict(tippee)\n    __repr__ = lambda self: '<ANON>'\n\n\nANON = _ANON()\n\n\ndef _get_body(request):\n    try:\n        body = request.body\n    except Response:\n        return\n    if not isinstance(body, dict):\n        return\n    return body\n\n\ndef sign_in_with_form_data(body, state):\n    p = None\n    _, website = state['_'], state['website']\n\n    if body.get('log-in.id'):\n        id = body.pop('log-in.id')\n        password = body.pop('log-in.password', None)\n        k = 'email' if '@' in id else 'username'\n        if password:\n            p = Participant.authenticate(\n                k, 'password',\n                id, password,\n            )\n            if not p:\n                state['log-in.error'] = _(\"Bad username or password.\")\n        elif k == 'username':\n            state['log-in.error'] = _(\"\\\"{0}\\\" is not a valid email address.\", id)\n            return\n        else:\n            email = id\n            p = Participant._from_thing('email', email)\n            if p:\n                p.start_session()\n                qs = {'log-in.id': p.id, 'log-in.token': p.session_token}\n                p.send_email(\n                    'login_link',\n                    email=email,\n                    link=p.url('settings/', qs),\n                    link_validity=SESSION_TIMEOUT,\n                )\n                state['log-in.email-sent-to'] = email\n            else:\n                state['log-in.error'] = _(\n                    \"We didn't find any account whose primary email address is {0}.\",\n                    email\n                )\n            p = None\n\n    elif 'sign-in.email' in body:\n        response = state['response']\n        kind = body.pop('sign-in.kind')\n        if kind not in ('individual', 'organization'):\n            raise response.error(400, 'bad kind')\n        email = body.pop('sign-in.email')\n        if not email:\n            raise response.error(400, 'email is required')\n        with website.db.get_cursor() as c:\n            p = Participant.make_active(\n                kind, body.pop('sign-in.username', None),\n                body.pop('sign-in.password', None), cursor=c,\n            )\n            p.set_email_lang(state['request'].headers.get(b'Accept-Language'), cursor=c)\n            p.add_email(email, cursor=c)\n        p.authenticated = True\n\n    return p\n\n\ndef start_user_as_anon():\n    \"\"\"Make sure we always have a user object, regardless of exceptions during authentication.\n    \"\"\"\n    return {'user': ANON}\n\n\ndef authenticate_user_if_possible(request, response, state, user, _):\n    \"\"\"This signs the user in.\n    \"\"\"\n    if request.line.uri.startswith('/assets/'):\n        return\n\n    # HTTP auth\n    if b'Authorization' in request.headers:\n        header = request.headers[b'Authorization']\n        if not header.startswith(b'Basic '):\n            raise response.error(401, 'Unsupported authentication method')\n        try:\n            creds = binascii.a2b_base64(header[len('Basic '):]).decode('utf8').split(':', 1)\n        except (binascii.Error, UnicodeDecodeError):\n            raise response.error(400, 'Malformed \"Authorization\" header')\n        participant = Participant.authenticate('id', 'password', *creds)\n        if not participant:\n            raise response.error(401, 'Invalid credentials')\n        return {'user': participant}\n\n    # Cookie and form auth\n    # We want to try cookie auth first, but we want form auth to supersede it\n    p = None\n    if SESSION in request.headers.cookie:\n        creds = request.headers.cookie[SESSION].value.split(':', 1)\n        p = Participant.authenticate('id', 'session', *creds)\n        if p:\n            state['user'] = p\n    session_p, p = p, None\n    session_suffix = ''\n    redirect_url = request.line.uri\n    if request.method == 'POST':\n        body = _get_body(request)\n        if body:\n            p = sign_in_with_form_data(body, state)\n            carry_on = body.pop('log-in.carry-on', None)\n            if not p and carry_on:\n                p_email = session_p and (\n                    session_p.email or session_p.get_emails()[0].address\n                )\n                if p_email != carry_on:\n                    state['log-in.carry-on'] = carry_on\n                    raise LoginRequired\n            redirect_url = body.get('sign-in.back-to') or redirect_url\n    elif request.method == 'GET' and request.qs.get('log-in.id'):\n        id, token = request.qs.pop('log-in.id'), request.qs.pop('log-in.token')\n        p = Participant.authenticate('id', 'session', id, token)\n        if not p and (not session_p or session_p.id != id):\n            raise response.error(400, _(\"This login link is expired or invalid.\"))\n        else:\n            qs = '?' + urlencode(request.qs, doseq=True) if request.qs else ''\n            redirect_url = request.path.raw + qs\n            session_p = p\n            session_suffix = '.em'\n    if p:\n        if session_p:\n            session_p.sign_out(response.headers.cookie)\n        if p.status == 'closed':\n            p.update_status('active')\n        p.sign_in(response.headers.cookie, session_suffix)\n        state['user'] = p\n        if request.body.pop('form.repost', None) != 'true':\n            response.redirect(redirect_url)\n\n\ndef add_auth_to_response(response, request=None, user=ANON):\n    if request is None:\n        return  # early parsing must've failed\n    if request.line.uri.startswith('/assets/'):\n        return  # assets never get auth headers\n\n    if SESSION in request.headers.cookie:\n        if not user.ANON:\n            user.keep_signed_in(response.headers.cookie)\n", "path": "liberapay/security/authentication.py"}], "after_files": [{"content": "\"\"\"Cross Site Request Forgery middleware, originally borrowed from Django.\n\nSee also:\n\n    https://github.com/django/django/blob/master/django/middleware/csrf.py\n    https://docs.djangoproject.com/en/dev/ref/contrib/csrf/\n    https://github.com/gratipay/gratipay.com/issues/88\n\n\"\"\"\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nfrom datetime import timedelta\nimport re\n\nfrom .crypto import constant_time_compare, get_random_string\n\n\nTOKEN_LENGTH = 32\nCSRF_TOKEN = str('csrf_token')  # bytes in python2, unicode in python3\nCSRF_TIMEOUT = timedelta(days=7)\nSAFE_METHODS = {'GET', 'HEAD', 'OPTIONS', 'TRACE'}\n\n_get_new_token = lambda: get_random_string(TOKEN_LENGTH)\n_token_re = re.compile(r'^[a-zA-Z0-9]{%d}$' % TOKEN_LENGTH)\n_sanitize_token = lambda t: t if _token_re.match(t) else None\n\n\ndef extract_token_from_cookie(request):\n    \"\"\"Given a Request object, return a csrf_token.\n    \"\"\"\n\n    off = (\n        # Turn off CSRF protection on assets, to avoid busting the cache.\n        request.path.raw.startswith('/assets/') or\n        # Turn off CSRF protection on callbacks, so they can receive POST requests.\n        request.path.raw.startswith('/callbacks/') or\n        # Turn off CSRF when using HTTP auth, so API users can use POST and others.\n        b'Authorization' in request.headers\n    )\n\n    if off:\n        token = None\n    else:\n        try:\n            token = request.headers.cookie[CSRF_TOKEN].value\n        except KeyError:\n            token = _get_new_token()\n        else:\n            token = _sanitize_token(token) or _get_new_token()\n\n    return {'csrf_token': token}\n\n\ndef reject_forgeries(request, response, csrf_token):\n    if csrf_token is None:\n        # CSRF protection is turned off for this request\n        return\n\n    # Assume that anything not defined as 'safe' by RFC7231 needs protection.\n    if request.line.method not in SAFE_METHODS:\n\n        # Check non-cookie token for match.\n        second_token = \"\"\n        if request.line.method == \"POST\":\n            if isinstance(request.body, dict):\n                second_token = request.body.get('csrf_token', '')\n\n        if second_token == \"\":\n            # Fall back to X-CSRF-TOKEN, to make things easier for AJAX,\n            # and possible for PUT/DELETE.\n            second_token = request.headers.get(b'X-CSRF-TOKEN', b'').decode('ascii', 'replace')\n\n        if not constant_time_compare(second_token, csrf_token):\n            raise response.error(403, \"Bad CSRF cookie\")\n\n\ndef add_token_to_response(response, csrf_token=None):\n    \"\"\"Store the latest CSRF token as a cookie.\n    \"\"\"\n    if csrf_token:\n        # Don't set httponly so that we can POST using XHR.\n        # https://github.com/gratipay/gratipay.com/issues/3030\n        response.set_cookie(CSRF_TOKEN, csrf_token, expires=CSRF_TIMEOUT, httponly=False)\n", "path": "liberapay/security/csrf.py"}, {"content": "\"\"\"Defines website authentication helpers.\n\"\"\"\nimport binascii\n\nfrom six.moves.urllib.parse import urlencode\n\nfrom pando import Response\n\nfrom liberapay.constants import SESSION, SESSION_TIMEOUT\nfrom liberapay.exceptions import LoginRequired\nfrom liberapay.models.participant import Participant\n\n\nclass _ANON(object):\n    ANON = True\n    is_admin = False\n    id = None\n    __bool__ = __nonzero__ = lambda *a: False\n    get_tip_to = lambda self, tippee: Participant._zero_tip_dict(tippee)\n    __repr__ = lambda self: '<ANON>'\n\n\nANON = _ANON()\n\n\ndef _get_body(request):\n    try:\n        body = request.body\n    except Response:\n        return\n    if not isinstance(body, dict):\n        return\n    return body\n\n\ndef sign_in_with_form_data(body, state):\n    p = None\n    _, website = state['_'], state['website']\n\n    if body.get('log-in.id'):\n        id = body.pop('log-in.id')\n        password = body.pop('log-in.password', None)\n        k = 'email' if '@' in id else 'username'\n        if password:\n            p = Participant.authenticate(\n                k, 'password',\n                id, password,\n            )\n            if not p:\n                state['log-in.error'] = _(\"Bad username or password.\")\n        elif k == 'username':\n            state['log-in.error'] = _(\"\\\"{0}\\\" is not a valid email address.\", id)\n            return\n        else:\n            email = id\n            p = Participant._from_thing('email', email)\n            if p:\n                p.start_session()\n                qs = {'log-in.id': p.id, 'log-in.token': p.session_token}\n                p.send_email(\n                    'login_link',\n                    email=email,\n                    link=p.url('settings/', qs),\n                    link_validity=SESSION_TIMEOUT,\n                )\n                state['log-in.email-sent-to'] = email\n            else:\n                state['log-in.error'] = _(\n                    \"We didn't find any account whose primary email address is {0}.\",\n                    email\n                )\n            p = None\n\n    elif 'sign-in.email' in body:\n        response = state['response']\n        kind = body.pop('sign-in.kind')\n        if kind not in ('individual', 'organization'):\n            raise response.error(400, 'bad kind')\n        email = body.pop('sign-in.email')\n        if not email:\n            raise response.error(400, 'email is required')\n        with website.db.get_cursor() as c:\n            p = Participant.make_active(\n                kind, body.pop('sign-in.username', None),\n                body.pop('sign-in.password', None), cursor=c,\n            )\n            p.set_email_lang(state['request'].headers.get(b'Accept-Language'), cursor=c)\n            p.add_email(email, cursor=c)\n        p.authenticated = True\n\n    return p\n\n\ndef start_user_as_anon():\n    \"\"\"Make sure we always have a user object, regardless of exceptions during authentication.\n    \"\"\"\n    return {'user': ANON}\n\n\ndef authenticate_user_if_possible(request, response, state, user, _):\n    \"\"\"This signs the user in.\n    \"\"\"\n    if request.line.uri.startswith('/assets/'):\n        return\n\n    # HTTP auth\n    if b'Authorization' in request.headers:\n        header = request.headers[b'Authorization']\n        if not header.startswith(b'Basic '):\n            raise response.error(401, 'Unsupported authentication method')\n        try:\n            uid, pwd = binascii.a2b_base64(header[len('Basic '):]).decode('utf8').split(':', 1)\n        except (binascii.Error, UnicodeDecodeError, ValueError):\n            raise response.error(400, 'Malformed \"Authorization\" header')\n        if not uid.isdigit():\n            raise response.error(401, 'Invalid user id: expected an integer, got `%s`' % uid)\n        participant = Participant.authenticate('id', 'password', uid, pwd)\n        if not participant:\n            raise response.error(401, 'Invalid credentials')\n        return {'user': participant}\n\n    # Cookie and form auth\n    # We want to try cookie auth first, but we want form auth to supersede it\n    p = None\n    if SESSION in request.headers.cookie:\n        creds = request.headers.cookie[SESSION].value.split(':', 1)\n        p = Participant.authenticate('id', 'session', *creds)\n        if p:\n            state['user'] = p\n    session_p, p = p, None\n    session_suffix = ''\n    redirect_url = request.line.uri\n    if request.method == 'POST':\n        body = _get_body(request)\n        if body:\n            p = sign_in_with_form_data(body, state)\n            carry_on = body.pop('log-in.carry-on', None)\n            if not p and carry_on:\n                p_email = session_p and (\n                    session_p.email or session_p.get_emails()[0].address\n                )\n                if p_email != carry_on:\n                    state['log-in.carry-on'] = carry_on\n                    raise LoginRequired\n            redirect_url = body.get('sign-in.back-to') or redirect_url\n    elif request.method == 'GET' and request.qs.get('log-in.id'):\n        id, token = request.qs.pop('log-in.id'), request.qs.pop('log-in.token')\n        p = Participant.authenticate('id', 'session', id, token)\n        if not p and (not session_p or session_p.id != id):\n            raise response.error(400, _(\"This login link is expired or invalid.\"))\n        else:\n            qs = '?' + urlencode(request.qs, doseq=True) if request.qs else ''\n            redirect_url = request.path.raw + qs\n            session_p = p\n            session_suffix = '.em'\n    if p:\n        if session_p:\n            session_p.sign_out(response.headers.cookie)\n        if p.status == 'closed':\n            p.update_status('active')\n        p.sign_in(response.headers.cookie, session_suffix)\n        state['user'] = p\n        if request.body.pop('form.repost', None) != 'true':\n            response.redirect(redirect_url)\n\n\ndef add_auth_to_response(response, request=None, user=ANON):\n    if request is None:\n        return  # early parsing must've failed\n    if request.line.uri.startswith('/assets/'):\n        return  # assets never get auth headers\n\n    if SESSION in request.headers.cookie:\n        if not user.ANON:\n            user.keep_signed_in(response.headers.cookie)\n", "path": "liberapay/security/authentication.py"}]}