From f2b093ff834e381a5bf693eb4a3cb661dd4327c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20R=C3=B6yhy?= Date: Mon, 14 Mar 2016 21:57:36 +0200 Subject: [PATCH 1/7] RSA Signature authentication works, hmac untested --- .../authentication.py | 54 ++++++++++++------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/rest_framework_httpsignature/authentication.py b/rest_framework_httpsignature/authentication.py index 5e31600..fed2eba 100644 --- a/rest_framework_httpsignature/authentication.py +++ b/rest_framework_httpsignature/authentication.py @@ -1,6 +1,6 @@ from rest_framework import authentication from rest_framework import exceptions -from httpsig import HeaderSigner +from httpsig import HeaderSigner, HeaderVerifier import re @@ -11,6 +11,22 @@ class SignatureAuthentication(authentication.BaseAuthentication): API_KEY_HEADER = 'X-Api-Key' ALGORITHM = 'hmac-sha256' + REQUIRED_HEADERS = ['date',] + + def get_request_headers(self,META): + import re + regex_http_ = re.compile(r'^HTTP_.+$') + regex_content_type = re.compile(r'^CONTENT_TYPE$') + regex_content_length = re.compile(r'^CONTENT_LENGTH$') + + request_headers = {} + for header in META: + if regex_http_.match(header) \ + or regex_content_type.match(header) \ + or regex_content_length.match(header): + request_headers[header] = META[header] + + return request_headers def get_signature_from_signature_string(self, signature): """Return the signature from the signature header or None.""" @@ -55,20 +71,7 @@ def build_dict_to_sign(self, request, signature_headers): d[header] = request.META.get(self.header_canonical(header)) return d - def build_signature(self, user_api_key, user_secret, request): - """Return the signature for the request.""" - path = request.get_full_path() - sent_signature = request.META.get( - self.header_canonical('Authorization')) - signature_headers = self.get_headers_from_signature(sent_signature) - unsigned = self.build_dict_to_sign(request, signature_headers) - # Sign string and compare. - signer = HeaderSigner( - key_id=user_api_key, secret=user_secret, - headers=signature_headers, algorithm=self.ALGORITHM) - signed = signer.sign(unsigned, method=request.method, path=path) - return signed['authorization'] def fetch_user_data(self, api_key): """Retuns (User instance, API Secret) or None if api_key is bad.""" @@ -95,11 +98,26 @@ def authenticate(self, request): raise exceptions.AuthenticationFailed('Bad API key') # Build string to sign from "headers" part of Signature value. - computed_string = self.build_signature(api_key, secret, request) - computed_signature = self.get_signature_from_signature_string( - computed_string) + path = request.get_full_path() + sent_signature = request.META.get( + self.header_canonical('Authorization')) + host = request.META.get(self.header_canonical('Host')) + signature_headers = self.get_headers_from_signature(sent_signature) + unsigned = self.build_dict_to_sign(request, signature_headers) + + unsigned.update({'authorization': sent_string}) + + #unsigned['date'] = unsigned['date'] + 'd' + + hv = HeaderVerifier(headers=unsigned, + secret=secret, + required_headers=self.REQUIRED_HEADERS, + method=request.method, + path=path, + host=host) + res = hv.verify() - if computed_signature != sent_signature: + if not hv.verify(): raise exceptions.AuthenticationFailed('Bad signature') return (user, api_key) From 7367196267d697b5e137108081f6c578204fb662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20R=C3=B6yhy?= Date: Tue, 15 Mar 2016 11:20:07 +0200 Subject: [PATCH 2/7] All former tests passing, still needs test cases for RSA pub keys --- .../authentication.py | 32 ++++++++++++++----- rest_framework_httpsignature/tests.py | 1 - 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/rest_framework_httpsignature/authentication.py b/rest_framework_httpsignature/authentication.py index fed2eba..ef1d5ec 100644 --- a/rest_framework_httpsignature/authentication.py +++ b/rest_framework_httpsignature/authentication.py @@ -1,6 +1,8 @@ from rest_framework import authentication from rest_framework import exceptions from httpsig import HeaderSigner, HeaderVerifier +from httpsig.utils import HttpSigException + import re @@ -71,7 +73,20 @@ def build_dict_to_sign(self, request, signature_headers): d[header] = request.META.get(self.header_canonical(header)) return d + def build_signature(self, user_api_key, user_secret, request): + """Return the signature for the request.""" + path = request.get_full_path() + sent_signature = request.META.get( + self.header_canonical('Authorization')) + signature_headers = self.get_headers_from_signature(sent_signature) + unsigned = self.build_dict_to_sign(request, signature_headers) + # Sign string and compare. + signer = HeaderSigner( + key_id=user_api_key, secret=user_secret, + headers=signature_headers, algorithm=self.ALGORITHM) + signed = signer.sign(unsigned, method=request.method, path=path) + return signed['authorization'] def fetch_user_data(self, api_key): """Retuns (User instance, API Secret) or None if api_key is bad.""" @@ -108,14 +123,15 @@ def authenticate(self, request): unsigned.update({'authorization': sent_string}) #unsigned['date'] = unsigned['date'] + 'd' - - hv = HeaderVerifier(headers=unsigned, - secret=secret, - required_headers=self.REQUIRED_HEADERS, - method=request.method, - path=path, - host=host) - res = hv.verify() + try: + hv = HeaderVerifier(headers=unsigned, + secret=secret, + required_headers=self.REQUIRED_HEADERS, + method=request.method, + path=path, + host=host) + except (HttpSigException, KeyError) as e: + raise exceptions.AuthenticationFailed(str(e)) if not hv.verify(): raise exceptions.AuthenticationFailed('Bad signature') diff --git a/rest_framework_httpsignature/tests.py b/rest_framework_httpsignature/tests.py index e2eda6e..7844291 100644 --- a/rest_framework_httpsignature/tests.py +++ b/rest_framework_httpsignature/tests.py @@ -3,7 +3,6 @@ from rest_framework_httpsignature.authentication import SignatureAuthentication from rest_framework.exceptions import AuthenticationFailed import re - User = get_user_model() ENDPOINT = '/api' From 6985be8cb09a42432f5ffb05d3a57f9af3116a8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20R=C3=B6yhy?= Date: Tue, 15 Mar 2016 12:02:34 +0200 Subject: [PATCH 3/7] improved exception handling --- .../authentication.py | 58 ++++++++++--------- rest_framework_httpsignature/tests.py | 4 +- 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/rest_framework_httpsignature/authentication.py b/rest_framework_httpsignature/authentication.py index ef1d5ec..77526b5 100644 --- a/rest_framework_httpsignature/authentication.py +++ b/rest_framework_httpsignature/authentication.py @@ -10,25 +10,11 @@ class SignatureAuthentication(authentication.BaseAuthentication): SIGNATURE_RE = re.compile('signature="(.+?)"') SIGNATURE_HEADERS_RE = re.compile('headers="([\(\)\sa-z0-9-]+?)"') + KEYID_RE = re.compile('.*keyId="(.*?)".*') API_KEY_HEADER = 'X-Api-Key' ALGORITHM = 'hmac-sha256' - REQUIRED_HEADERS = ['date',] - - def get_request_headers(self,META): - import re - regex_http_ = re.compile(r'^HTTP_.+$') - regex_content_type = re.compile(r'^CONTENT_TYPE$') - regex_content_length = re.compile(r'^CONTENT_LENGTH$') - - request_headers = {} - for header in META: - if regex_http_.match(header) \ - or regex_content_type.match(header) \ - or regex_content_length.match(header): - request_headers[header] = META[header] - - return request_headers + REQUIRED_HEADERS = ['date'] def get_signature_from_signature_string(self, signature): """Return the signature from the signature header or None.""" @@ -37,6 +23,13 @@ def get_signature_from_signature_string(self, signature): return None return match.group(1) + def get_keyid_from_auth_string(self, auth_header): + """Return the signature from the signature header or None.""" + match = self.KEYID_RE.search(auth_header) + if not match: + return None + return match.group(1) + def get_headers_from_signature(self, signature): """Returns a list of headers fields to sign. @@ -93,18 +86,23 @@ def fetch_user_data(self, api_key): return None def authenticate(self, request): - # Check for API key header. - api_key_header = self.header_canonical(self.API_KEY_HEADER) - api_key = request.META.get(api_key_header) - if not api_key: - return None # Check if request has a "Signature" request header. authorization_header = self.header_canonical('Authorization') - sent_string = request.META.get(authorization_header) - if not sent_string: + auth_string = request.META.get(authorization_header) + if not auth_string: raise exceptions.AuthenticationFailed('No signature provided') - sent_signature = self.get_signature_from_signature_string(sent_string) + + # Check for API key header. + api_key = None + if self.ALGORITHM.lower().startswith('rsa'): + api_key = self.get_keyid_from_auth_string(auth_string) + else: + api_key_header = self.header_canonical(self.API_KEY_HEADER) + api_key = request.META.get(api_key_header) + + if not api_key: + raise exceptions.AuthenticationFailed('No api key provided') # Fetch credentials for API key from the data store. try: @@ -120,9 +118,10 @@ def authenticate(self, request): signature_headers = self.get_headers_from_signature(sent_signature) unsigned = self.build_dict_to_sign(request, signature_headers) - unsigned.update({'authorization': sent_string}) + unsigned.update({'authorization': auth_string}) #unsigned['date'] = unsigned['date'] + 'd' + try: hv = HeaderVerifier(headers=unsigned, secret=secret, @@ -130,10 +129,13 @@ def authenticate(self, request): method=request.method, path=path, host=host) - except (HttpSigException, KeyError) as e: + except (HttpSigException, KeyError, Exception) as e: raise exceptions.AuthenticationFailed(str(e)) - if not hv.verify(): - raise exceptions.AuthenticationFailed('Bad signature') + try: + if not hv.verify(): + raise exceptions.AuthenticationFailed('Bad signature') + except Exception as e: + raise exceptions.AuthenticationFailed(str(e)) return (user, api_key) diff --git a/rest_framework_httpsignature/tests.py b/rest_framework_httpsignature/tests.py index 7844291..9cb726a 100644 --- a/rest_framework_httpsignature/tests.py +++ b/rest_framework_httpsignature/tests.py @@ -166,8 +166,8 @@ def setUp(self): def test_no_credentials(self): request = RequestFactory().get(ENDPOINT) - res = self.auth.authenticate(request) - self.assertIsNone(res) + self.assertRaises(AuthenticationFailed, + self.auth.authenticate, request) def test_only_api_key(self): request = RequestFactory().get( From a56d3adfabc49d831585bee4dadcd83ee24fd60c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20R=C3=B6yhy?= Date: Tue, 15 Mar 2016 13:03:28 +0200 Subject: [PATCH 4/7] wrote test for passing and failing rsa-sha256 tests --- .../tests/__init__.py | 1 + .../tests/private_key.pem | 28 ++++ .../tests/private_key2.pem | 28 ++++ .../tests/public_key.pem | 9 ++ .../{tests.py => tests/signature.py} | 120 +++++++++++++++++- 5 files changed, 180 insertions(+), 6 deletions(-) create mode 100644 rest_framework_httpsignature/tests/__init__.py create mode 100644 rest_framework_httpsignature/tests/private_key.pem create mode 100644 rest_framework_httpsignature/tests/private_key2.pem create mode 100644 rest_framework_httpsignature/tests/public_key.pem rename rest_framework_httpsignature/{tests.py => tests/signature.py} (64%) diff --git a/rest_framework_httpsignature/tests/__init__.py b/rest_framework_httpsignature/tests/__init__.py new file mode 100644 index 0000000..afc8b4b --- /dev/null +++ b/rest_framework_httpsignature/tests/__init__.py @@ -0,0 +1 @@ +from .signature import * diff --git a/rest_framework_httpsignature/tests/private_key.pem b/rest_framework_httpsignature/tests/private_key.pem new file mode 100644 index 0000000..413f1f4 --- /dev/null +++ b/rest_framework_httpsignature/tests/private_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDHLvR6pSVDn90y +KmUsmq0W5wraCM0U8SdltKgrfmoVpcPFz555LiNy1yKQAZRUg8GAAdtPL1Wp/NvT +ddYohhK2Pg0Aux/Zwkhh44JVIYH8dfgFCQhvcr1GzVij57vxfaNQkL/0ZyjzfyfX +hi8T/mFz/C2D8GxMfoFPvgTAiG1kprgt7ZnEPt3efHDX6qffs+9Ke4/Glb01redP +bb0BUTwwx8TjlcZ6Tho0U2NGspqGLznuHC7rL7G/uYieIFJooi0mP6MX1W/aUnVD +pZcdIkoPR3PznVzjjBSt6mnAUkXxX0aZy5sw9zWAGJub/FYzytFudiZUXSDfgwBo +/6kxrW2tAgMBAAECggEAW63UH6NlzIOHl3CGEwq6wsDjcMn+QzZgcOK/SQ2tnHso +6iKPCa3f6Rr2sJvZfzEJ3nZ8UC00W8KkF+e0BAD6GeHjsENw/JT9JflG4xJCN0bB +OugWdt20GyOnOgIOsq+mfQ2zHLZi1fjgCMadYrGCf5VCCemen3LW6DJJE6l32IxI +QN0c0nWoBMzTraL2VzcbqNbJas4RoOqCwY5H+ApqaHiQ34bpLQQffH8mMmGW+Cms +jpp6vNY3td8j1fMI2IENZfikEN1m/R2AJxJXSIrEWbilUAZkYyGAFZkybwjA2qzm +oL0xAK9+/EXIDJczE7r96U/OIgICSALTHOHtrwUkQQKBgQDsWauNlDCct9mvsbNV +gK2hYehyvsaBVIEKxizu8ING9KdBfSQwoHj3tmTgBOcQ2cTirO3TamBraPxRrsrS +GRMGUHKGFIDxxpRjyd2zYs0oK5txuwOCWEsuQdyNyhBpbwnJtRdlrK+SnfLceNiV +f38ShR++GiRRuagG3Lds+Uj2MQKBgQDXvj+97WoVpPpZhfUGMYYbH/JBUH5cYjSH +F4gYzg2/kx+6xlptg7Lt4ui14BtiwXt4I1d47qT0pUAdghPvXv9NvwxUgu5zPmaV +5YpKV00jjHAGUqyscKPrqn8fMyOSzIJnuOOMwIoSljPMPM0bV6V+xEmFR2It3gX+ +G5iS2BIEPQKBgQCADvHRqypPr5mmBV1KhYcOOtNMYKuDZXrpkIjGCdDHQEXjSN+z +7S694Lh1XJKp4aQ4wUO22htV9zNHOrKv9WAGes4icbePyG2cR8L0sCLCkiYOECsN +k7NgY9URihssVTpzbMg5kcAra6Mr69pF3ifGrBSP1vA4y6QL28kSpVrv8QKBgE0S +7Yi3qX+ECeAzqB6HUMad+hj1Xb85YlSkxn0+F9FKCTrbo/Cd7S1pNAPNxVrZjneU +AKr2br3rz2T7VI3enUy0JP6ILBHFyDZi4629VJSPlnHb1U5hi14k8fc+eMX4A9p0 +Re7B1lHfkS+0xP2wqTIJg852ew+x0ug+CZrkUENtAoGATbP0QXNbiGuECq/D/yht +GfNcP7owme7zotiN714kOCy9Kf3yyOt+Sb4etbSxaQRYeRhsFc4ijUYWD31V6vG1 +yYES3T56fqLsT3Ia41AIEGneUAlzK/y0ZSSyOT8ftcfZdDTVHgHic1f0Tk/GlSAs +KEwzD83pe2R9r0wI6+5SWQk= +-----END PRIVATE KEY----- diff --git a/rest_framework_httpsignature/tests/private_key2.pem b/rest_framework_httpsignature/tests/private_key2.pem new file mode 100644 index 0000000..88ba2cf --- /dev/null +++ b/rest_framework_httpsignature/tests/private_key2.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDG26OoL5SAXGj7 +bOvf2WGSERXvf3qgRmFpeO65bI988r/xWv8odFs2iGZAFSDbZ6vMGx6HsLXJ731a +jIC3biSdIx55+zOx0puU3AxycLTLoSQjyzbNaY70df92qF62MYkPcPfC2gzj91uu +9W+9ZPZEVCIhrJD7oDEhnIKFKGITjQbSB3pNxTylAByRflYfkcD6uPcd7+hbOg2s +8L9Gm1ztixYAoOY0AGKY90PRKv4Duy0dTEbNuPCxLhO5pP/Vle7tovukZe/aoZu4 +FoRRO+TIsDhJEx93eWXgPuiybFhL02YhgXw6qxawzXs4Fn2c4wBL0t7QeO67HlPl +bEqioCtLAgMBAAECggEAUZU0jECQ9SR0cYobLygYznsx+6LaJT0ao9HYZrwyFfnl +Y1iIzAkIjtPg1zOT2k+q/L63hMWrnyAg1nBEMnz+inUpALRdXfvglm68sIqqscv3 +brPlVNqUqphqaTzkNm0WJP6ctxUMKs6Fj77jy9jK6/d0VUpd5M2wunBiX8zUh94f +osnJrsjA45KLBdUIP2Zk42wnHbursovIfxcnDOFQzQt4lNmIN/J3gfSk6pfJU0cX +HLH5SiNtIoWEQ92CzQLrmu2x7VQPocuHL06iC0LzOg/sbbIFBErY650U/xZmJf8N +no8vhyhzwqfYUKLXe2Xah/iUnPGPO2mq1opc2u5UCQKBgQDkteaS0V0aQmkZ46LK +mBgk9UUawf6Gm5SxYZfMviavMt31gKKrFWzawY8OwJOUibaKuE1Hq4umai7QHkik +rHGNxB7eeVOGz2GG6OdUMZtd+btnIlVhZMHO53JipbygmCzbVxvgzBFRzPPpDt1k +RX+H2o6ibXXyCF1i1BMfEEmmzwKBgQDeld+tu9COz5hxSOvkdE1v8HUo+ncuVNTB +3nvhO4KrIs74JUD0rRNUw9bbGuMH/LQ0NznqLQ2P/jW6Zp/O8dNp7skgO+COmPH6 +XsIZSGqfJ4NFPTAunL1ZHe6dG81w4Nsc8lwaojwqajpppKf0Z+U1c7jUGqq94uT8 +rosnRlPSxQKBgEdZS9YPhGj1wM33yshDDH0zGtzPGjUqAggYNwADbhQH3WCCQbz3 +kR7pdVSX1TJoh87c0hcCuC0xQOtiFy1wMniUb0DePqV2uqkYrVoBo8N8be8tsc8R +XLjMUU3fAGplLtE6apMFdn27X3gcUArA95kNIKQhW8MmwuNa36A4N5HXAoGAapcq +/m+qeDlBrz5UeJqZWrmz4WPQHwfQuuZoPHvbH0kUBBETAhi/4R/HjDVb8z84rKil +u1bH3+TEpfbvIJL9wwTum9kQuDjV6Cfom2LqbDznyAh9QlUc98g1tFbUEvIa+8m0 +Aa0fUtB8GIsZQxld0jMQl8INcdFuBvMvACfVjGECgYAbQi6Ia/dbUHO3MIbZNway +g9HSu8lshfTJ4xJCGKfzxdGlo5b2iGYFhgB1ynKxebnXCvz4IA1YfJT0LArCjuui +gkXy8MNhsXTAunR5LK3GFo1+GLJBw4dowp/Zc9ILHXGX5K6qFeh7eZQUiGfqVXo0 +fuK7XVI3YRbZM4YDuIhFQA== +-----END PRIVATE KEY----- diff --git a/rest_framework_httpsignature/tests/public_key.pem b/rest_framework_httpsignature/tests/public_key.pem new file mode 100644 index 0000000..9d3d24c --- /dev/null +++ b/rest_framework_httpsignature/tests/public_key.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxy70eqUlQ5/dMiplLJqt +FucK2gjNFPEnZbSoK35qFaXDxc+eeS4jctcikAGUVIPBgAHbTy9Vqfzb03XWKIYS +tj4NALsf2cJIYeOCVSGB/HX4BQkIb3K9Rs1Yo+e78X2jUJC/9Gco838n14YvE/5h +c/wtg/BsTH6BT74EwIhtZKa4Le2ZxD7d3nxw1+qn37PvSnuPxpW9Na3nT229AVE8 +MMfE45XGek4aNFNjRrKahi857hwu6y+xv7mIniBSaKItJj+jF9Vv2lJ1Q6WXHSJK +D0dz851c44wUreppwFJF8V9GmcubMPc1gBibm/xWM8rRbnYmVF0g34MAaP+pMa1t +rQIDAQAB +-----END PUBLIC KEY----- diff --git a/rest_framework_httpsignature/tests.py b/rest_framework_httpsignature/tests/signature.py similarity index 64% rename from rest_framework_httpsignature/tests.py rename to rest_framework_httpsignature/tests/signature.py index 9cb726a..15ea2dd 100644 --- a/rest_framework_httpsignature/tests.py +++ b/rest_framework_httpsignature/tests/signature.py @@ -2,12 +2,13 @@ from django.contrib.auth import get_user_model from rest_framework_httpsignature.authentication import SignatureAuthentication from rest_framework.exceptions import AuthenticationFailed -import re +import re, os + User = get_user_model() ENDPOINT = '/api' METHOD = 'GET' -KEYID = 'some-key' +KEYID = 'somekey' SECRET = 'my secret string' SIGNATURE = 'some.signature' @@ -24,7 +25,6 @@ def build_signature(headers, key_id=KEYID, signature=SIGNATURE): class HeadersUnitTestCase(SimpleTestCase): - request = RequestFactory() def setUp(self): @@ -68,7 +68,6 @@ def test_build_signature_for_request_line(self): class SignatureTestCase(SimpleTestCase): - def setUp(self): self.auth = SignatureAuthentication() @@ -104,7 +103,6 @@ def test_get_signature_without_headers(self): class BuildSignatureTestCase(SimpleTestCase): - request = RequestFactory() KEYID = 'su-key' @@ -141,7 +139,6 @@ def test_build_signature(self): class SignatureAuthenticationTestCase(TestCase): - class APISignatureAuthentication(SignatureAuthentication): """Extend the SignatureAuthentication to test it.""" @@ -203,3 +200,114 @@ def test_can_authenticate(self): self.assertIsNotNone(result) self.assertEqual(result[0], self.test_user) self.assertEqual(result[1], KEYID) + + +class SignatureAuthenticationRSATestCase(TestCase): + class APISignatureAuthentication(SignatureAuthentication): + """Extend the SignatureAuthentication to test it. + TODO: CLEANUP this test code + """ + ALGORITHM = 'rsa-sha256' + + def __init__(self, user): + self.user = user + + def fetch_user_data(self, api_key): + import os + + if api_key != KEYID: + return None + public_key_path = os.path.join(os.path.dirname(__file__), + 'public_key.pem') + with open(public_key_path, 'rb') as f: + public_key = f.read() + return (self.user, public_key) + + TEST_USERNAME = 'test-user' + TEST_PASSWORD = 'test-password' + + def setUp(self): + self.test_user = User(username=self.TEST_USERNAME) + self.test_user.set_password(self.TEST_PASSWORD) + self.auth = self.APISignatureAuthentication(self.test_user) + + def test_rsa_pubkey_pass(self): + + from httpsig.sign import HeaderSigner + + private_key_path = os.path.join(os.path.dirname(__file__), + 'private_key.pem') + with open(private_key_path, 'rb') as f: + private_key = f.read() + + HOST = "example.com" + METHOD = "GET" + PATH = '/foo?param=value&pet=dog' + hs = HeaderSigner(key_id=KEYID, secret=private_key, + algorithm=self.auth.ALGORITHM, + headers=[ + '(request-target)', + 'host', + 'date', + 'content-type', + 'content-md5', + 'content-length' + ]) + unsigned = { + 'Host': HOST, + 'Date': 'Thu, 05 Jan 2012 21:31:40 GMT', + 'Content-Type': 'application/json', + 'Content-MD5': 'Sd/dVLAcvNLSq16eXua5uQ==', + 'Content-Length': '18', + } + signed = hs.sign(unsigned, method=METHOD, path=PATH) + + # convert headers to DJANGO format and create request + DJ_HEADERS = {} + for key, value in signed.iteritems(): + DJ_HEADERS.update({self.auth.header_canonical(key): value}) + request = RequestFactory().get(PATH, {}, **DJ_HEADERS) + + result = self.auth.authenticate(request) + self.assertIsNotNone(result) + self.assertEqual(result[0], self.test_user) + self.assertEqual(result[1], KEYID) + + def test_rsa_pubkey_fail(self): + + from httpsig.sign import HeaderSigner + + private_key_path = os.path.join(os.path.dirname(__file__), + 'private_key2.pem') + with open(private_key_path, 'rb') as f: + private_key = f.read() + + HOST = "example.com" + METHOD = "GET" + PATH = '/foo?param=value&pet=dog' + hs = HeaderSigner(key_id=KEYID, secret=private_key, + algorithm=self.auth.ALGORITHM, + headers=[ + '(request-target)', + 'host', + 'date', + 'content-type', + 'content-md5', + 'content-length' + ]) + unsigned = { + 'Host': HOST, + 'Date': 'Thu, 05 Jan 2012 21:31:40 GMT', + 'Content-Type': 'application/json', + 'Content-MD5': 'Sd/dVLAcvNLSq16eXua5uQ==', + 'Content-Length': '18', + } + signed = hs.sign(unsigned, method=METHOD, path=PATH) + + # convert headers to DJANGO format and create request + DJ_HEADERS = {} + for key, value in signed.iteritems(): + DJ_HEADERS.update({self.auth.header_canonical(key): value}) + request = RequestFactory().get(PATH, {}, **DJ_HEADERS) + self.assertRaises(AuthenticationFailed, + self.auth.authenticate, request) From 4c68bd1d5e3de9cad93b1c2841d861348805b4da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20R=C3=B6yhy?= Date: Tue, 15 Mar 2016 13:19:55 +0200 Subject: [PATCH 5/7] Fixed tests --- rest_framework_httpsignature/tests/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework_httpsignature/tests/__init__.py b/rest_framework_httpsignature/tests/__init__.py index afc8b4b..aaad08b 100644 --- a/rest_framework_httpsignature/tests/__init__.py +++ b/rest_framework_httpsignature/tests/__init__.py @@ -1 +1 @@ -from .signature import * +from signature import * From 7eef61516272f22b8128e88a4c770344fedf6ae5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20R=C3=B6yhy?= Date: Tue, 15 Mar 2016 13:37:45 +0200 Subject: [PATCH 6/7] test is now findable for django >= 1.6 http://stackoverflow.com/questions/5160688/organizing-django-unit-tests?answertab=active#tab-top --- rest_framework_httpsignature/tests/__init__.py | 1 - .../tests/{signature.py => test_signatures.py} | 0 2 files changed, 1 deletion(-) rename rest_framework_httpsignature/tests/{signature.py => test_signatures.py} (100%) diff --git a/rest_framework_httpsignature/tests/__init__.py b/rest_framework_httpsignature/tests/__init__.py index aaad08b..e69de29 100644 --- a/rest_framework_httpsignature/tests/__init__.py +++ b/rest_framework_httpsignature/tests/__init__.py @@ -1 +0,0 @@ -from signature import * diff --git a/rest_framework_httpsignature/tests/signature.py b/rest_framework_httpsignature/tests/test_signatures.py similarity index 100% rename from rest_framework_httpsignature/tests/signature.py rename to rest_framework_httpsignature/tests/test_signatures.py From f5d74d548193ce79ea47ac6a6398d197c10ee89f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20R=C3=B6yhy?= Date: Tue, 24 Jul 2018 10:16:08 +0300 Subject: [PATCH 7/7] test are passing in python 3.6 --- rest_framework_httpsignature/tests/test_signatures.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rest_framework_httpsignature/tests/test_signatures.py b/rest_framework_httpsignature/tests/test_signatures.py index 15ea2dd..a03db8f 100644 --- a/rest_framework_httpsignature/tests/test_signatures.py +++ b/rest_framework_httpsignature/tests/test_signatures.py @@ -3,6 +3,7 @@ from rest_framework_httpsignature.authentication import SignatureAuthentication from rest_framework.exceptions import AuthenticationFailed import re, os +import six User = get_user_model() @@ -134,7 +135,7 @@ def test_build_signature(self): signature_string = self.auth.build_signature( self.KEYID, SECRET, req) signature = re.match( - '.*signature="(.+)",?.*', signature_string).group(1) + '.*signature="(.+?)"', signature_string).group(1) self.assertEqual(expected_signature, signature) @@ -264,7 +265,7 @@ def test_rsa_pubkey_pass(self): # convert headers to DJANGO format and create request DJ_HEADERS = {} - for key, value in signed.iteritems(): + for key, value in six.iteritems(signed): DJ_HEADERS.update({self.auth.header_canonical(key): value}) request = RequestFactory().get(PATH, {}, **DJ_HEADERS) @@ -306,7 +307,7 @@ def test_rsa_pubkey_fail(self): # convert headers to DJANGO format and create request DJ_HEADERS = {} - for key, value in signed.iteritems(): + for key, value in six.iteritems(signed): DJ_HEADERS.update({self.auth.header_canonical(key): value}) request = RequestFactory().get(PATH, {}, **DJ_HEADERS) self.assertRaises(AuthenticationFailed,