Skip to content

Commit

Permalink
Merge pull request #24 from qld-gov-au/develop
Browse files Browse the repository at this point in the history
Develop to main
  • Loading branch information
ThrawnCA authored Jun 17, 2022
2 parents 075d2ec + 64966ca commit d25531b
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 2 deletions.
21 changes: 19 additions & 2 deletions ckanext/csrf_filter/anti_csrf.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,12 @@
"""The format of the token HTML field.
"""
TOKEN_VALIDATION_PATTERN = re.compile(
r'^[0-9a-z]+![0-9]+/[0-9]+/[-_a-z0-9%+=/]+$',
re.IGNORECASE)
ENCODED_TOKEN_VALIDATION_PATTERN = re.compile(
r'^[0-9a-z]+![0-9]+/[0-9]+/[-_a-z0-9%+=]+$',
re.IGNORECASE)
API_URL = re.compile(r'^/api\b.*')
API_URL = re.compile(r'^/+api/.*')
LOGIN_URL = re.compile(r'^(/user)?/log(ged_)?in(_generic)?')
CONFIRM_MODULE_PATTERN = r'data-module=["\']confirm-action["\']'
CONFIRM_MODULE = re.compile(CONFIRM_MODULE_PATTERN)
Expand Down Expand Up @@ -103,16 +106,29 @@ def _read_token_values(token):
message = parts[1]
# limiting to 2 means that even if a username contains a slash, it won't cause an extra split
message_parts = message.split('/', 2)
username = message_parts[2]
encoded_username = quote(username, safe='%')
if username != encoded_username:
username = encoded_username
message_parts[2] = username
message = '/'.join(message_parts)

return {
"message": message,
"hash": parts[0],
"timestamp": int(message_parts[0]),
"nonce": int(message_parts[1]),
"username": message_parts[2]
"username": username
}


def _ensure_token_encoded(token):
if ENCODED_TOKEN_VALIDATION_PATTERN.match(token):
return token
token_parts = _read_token_values(token)
return token_parts['hash'] + '!' + token_parts['message']


def is_valid_token(token):
""" Verify the integrity of the provided token.
It must have the expected format (hash!timestamp/nonce/username),
Expand Down Expand Up @@ -237,6 +253,7 @@ def _get_submitted_form_token(request):
LOG.error("Invalid CSRF token format")
return None

token = _ensure_token_encoded(token)
request_helper.scoped_attrs()[TOKEN_FIELD_NAME] = token
request_helper.delete_param(TOKEN_FIELD_NAME)
return token
Expand Down
19 changes: 19 additions & 0 deletions ckanext/csrf_filter/test_anti_csrf.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import re
import unittest
from webob import Request

from ckanext.csrf_filter import anti_csrf
import six
Expand Down Expand Up @@ -152,6 +153,24 @@ def test_username_with_slash(self):
print("Testing wrong user token '{}'".format(bad_token))
self.assertFalse(anti_csrf.is_valid_token(bad_token))

# test with real Request object
token_expression = 'token=' + good_token
environ = {
'REQUEST_METHOD': 'POST',
'PATH_INFO': '/',
'CONTENT_TYPE': 'application/x-www-form-urlencoded',
'SERVER_NAME': 'localhost',
'SERVER_PORT': '80',
'SERVER_PROTOCOL': 'HTTP/1.1',
'HTTP_COOKIE': token_expression,
'wsgi.url_scheme': 'http',
'wsgi.input': six.BytesIO(six.ensure_binary(token_expression)),
'CONTENT_LENGTH': len(token_expression),
'wsgi.errors': six.BytesIO(),
}
request = Request(environ)
self.assertTrue(anti_csrf.check_csrf(request))

def test_inject_token(self):
""" Test that tokens are correctly injected into HTML when logged in.
"""
Expand Down

0 comments on commit d25531b

Please sign in to comment.