diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ab2ac42..d5440b7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,11 @@ Change Log ---------- +1.3.0 +~~~~~ +- Merge validation of multiple and single signature to single method +- Replace middleware classes to one single class HmacMiddleware + 1.2.0 ~~~~~ - Decorators diff --git a/VERSION.txt b/VERSION.txt index 6085e94..f0bb29e 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.2.1 +1.3.0 diff --git a/djangohmac/decorators.py b/djangohmac/decorators.py index 63884ef..a491ce4 100644 --- a/djangohmac/decorators.py +++ b/djangohmac/decorators.py @@ -28,7 +28,7 @@ def decorator(route): @wraps(route) def _wrapped_view(view, request, *args, **kwargs): try: - shmac.validate_multiple_signatures(request, only) + shmac.validate_signature(request, only) except HmacException: shmac.abort() return route(view, request, *args, **kwargs) diff --git a/djangohmac/middleware.py b/djangohmac/middleware.py index 2c43baf..d01d752 100644 --- a/djangohmac/middleware.py +++ b/djangohmac/middleware.py @@ -1,7 +1,7 @@ from .sign import HmacException, shmac -class GlobalHmacMiddleware(object): +class HmacMiddleware(object): """ Uses global signature `HMAC_SECRET` defined in settings """ def process_request(self, request): @@ -9,14 +9,3 @@ def process_request(self, request): shmac.validate_signature(request) except HmacException: shmac.abort() - - -class MultipleHmacMiddleware(object): - """ Uses multiple signatures defined in `HMAC_SECRETS` in settings - """ - - def process_request(self, request): - try: - shmac.validate_multiple_signatures(request) - except HmacException: - shmac.abort() diff --git a/djangohmac/sign.py b/djangohmac/sign.py index 8804d02..0b56724 100644 --- a/djangohmac/sign.py +++ b/djangohmac/sign.py @@ -52,6 +52,18 @@ def hmac_key(self): return six.b(settings.HMAC_SECRET) def get_signature(self, request): + """ Get signature from djagno requests + + Arguments: + request: Django request + + Returns: + string: HMAC signature + + Raises: + SecretKeyIsNotSet + """ + try: return request.META['HTTP_{}'.format(self.header.upper())] except KeyError: @@ -78,6 +90,9 @@ def make_hmac_for(self, name, data=''): Arguments: name (str): key name from HMAC_SECRETS dict data (str): HMAC message + + Raises: + UnknownKeyName """ try: key = self.hmac_keys[name] @@ -88,7 +103,48 @@ def make_hmac_for(self, name, data=''): )) return token - def validate_signature(self, request): + def _parse_signature(self, signature): + """ Split signature to user name and signature + + Arguments: + signature (string): Signature genrated by `make_hmac_for` + + Returns: + tuple of service name and signature + """ + try: + return decode_string( + base64.b64decode(decode_string(signature)) + ).split(':') + except (TypeError, binascii.Error): + raise InvalidSignature() + + def validate_signature(self, request, only=None): + """ Validate signate in given request. + + Arguments: + request: Django request + only: list of keys from HMAC_SECRETS to restrict signatures + + Returns: + boolean: True when signature is valid otherwice False + + Raises: + InvalidSignature + SecretKeyIsNotSet + """ + if self.hmac_disarm: + return True + try: + signature = self.get_signature(request) + key_name, hmac_token_client = self._parse_signature(signature) + if only and key_name not in only: + raise InvalidSignature('This view is only for {}'.format(only)) + return self.validate_multiple_signatures(key_name, signature, request) + except ValueError: + return self.validate_single_signature(request) + + def validate_single_signature(self, request): """ Validate signature from djagno request Arguments: @@ -96,9 +152,10 @@ def validate_signature(self, request): Returns: boolen + + Raises: + InvalidSignature """ - if self.hmac_disarm: - return True hmac_token_client = self.get_signature(request) hmac_token_server = self.make_hmac(request.body) if hmac_token_client != hmac_token_server: @@ -107,23 +164,7 @@ def validate_signature(self, request): )) return True - def _parse_multiple_signature(self, signature): - """ Split signature to user name and signature - - Arguments: - signature (string): Signature genrated by `make_hmac_for` - - Returns: - tuple of service name and signature - """ - try: - return decode_string( - base64.b64decode(decode_string(signature)) - ).split(':') - except (TypeError, binascii.Error): - raise InvalidSignature() - - def validate_multiple_signatures(self, request, only=None): + def validate_multiple_signatures(self, key_name, signature, request): """ Validate signature from djagno request. But it takes key from `HMAC_SECRETS` list @@ -133,13 +174,10 @@ def validate_multiple_signatures(self, request, only=None): Returns: boolen + + Raises: + InvalidSignature """ - if self.hmac_disarm: - return True - signature = self.get_signature(request) - key_name, hmac_token_client = self._parse_multiple_signature(signature) - if only and key_name not in only: - raise InvalidSignature('This view is only for {}'.format(only)) hmac_token_server = self.make_hmac_for(key_name, request.body) if signature != hmac_token_server: raise InvalidSignature('Signatures are different: {0} {1}'.format( diff --git a/docs/instalation.rst b/docs/instalation.rst index 15e6e15..d7c9434 100644 --- a/docs/instalation.rst +++ b/docs/instalation.rst @@ -17,12 +17,10 @@ To secure all your app with HMAC you can use a middleware. MIDDLEWARE_CLASSES = ( # ... - 'djangohmac.middleware.GlobalHmacMiddleware', + 'djangohmac.middleware.HmacMiddleware', ) -If your application is called by multiple services and each has different secret keys you should use `djangohmac.middleware.GlobalHmacMiddleware` instead. - .. note:: Middleware is applied on all views except the admin! diff --git a/tests/test_hmac.py b/tests/test_hmac.py index fe45899..7c5e21a 100644 --- a/tests/test_hmac.py +++ b/tests/test_hmac.py @@ -26,13 +26,13 @@ def test_custom_secret_key(self): def test_valid_multiple_signatures_should_pass(self): signature = self.hmac.make_hmac_for('serviceA') request = self.factory.get('/example', **{self.header: signature}) - assert self.hmac.validate_multiple_signatures(request) + assert self.hmac.validate_signature(request) def test_invalid_multiple_signatures_should_raice_exception(self): signature = self.hmac.make_hmac_for('serviceA', 'some data') request = self.factory.get('/example', **{self.header: signature}) with self.assertRaises(InvalidSignature): - self.hmac.validate_multiple_signatures(request) + self.hmac.validate_signature(request) def test_unknown_key_name_for_multiple_signatures_should_raice_exception(self): with self.assertRaises(UnknownKeyName): @@ -42,12 +42,7 @@ def test_valid_signature_for_services_are_restricted_view(self): signature = self.hmac.make_hmac_for('serviceB') request = self.factory.get('/example', **{self.header: signature}) with self.assertRaises(InvalidSignature): - self.hmac.validate_multiple_signatures( + self.hmac.validate_signature( request, only=['serviceA'] ) - - def test_valid_signature_for_restricted_view(self): - signature = self.hmac.make_hmac_for('serviceA') - request = self.factory.get('/example', **{self.header: signature}) - self.hmac.validate_multiple_signatures(request, only=['serviceA']) diff --git a/tests/test_middleware.py b/tests/test_middleware.py index 09a4c7e..bf47f36 100644 --- a/tests/test_middleware.py +++ b/tests/test_middleware.py @@ -3,14 +3,14 @@ from django.test import RequestFactory, TestCase # First Party Libs -from djangohmac.middleware import GlobalHmacMiddleware, MultipleHmacMiddleware +from djangohmac.middleware import HmacMiddleware from djangohmac.sign import Hmac -class GlobalHmacMiddlewareTestCase(TestCase): +class SingleHmacMiddlewareTestCase(TestCase): def setUp(self): - self.hmacmiddleware = GlobalHmacMiddleware() + self.hmacmiddleware = HmacMiddleware() self.factory = RequestFactory() self.hmac = Hmac() @@ -34,7 +34,7 @@ def test_raise_exception_when_invalid_hmac_is_send(self): class MultipleHmacMiddlewareTestCase(TestCase): def setUp(self): - self.hmacmiddleware = MultipleHmacMiddleware() + self.hmacmiddleware = HmacMiddleware() self.factory = RequestFactory() self.hmac = Hmac()