diff --git a/openedx/core/djangoapps/credit/signature.py b/openedx/core/djangoapps/credit/signature.py index 799d56d27f..27f0c7cc97 100644 --- a/openedx/core/djangoapps/credit/signature.py +++ b/openedx/core/djangoapps/credit/signature.py @@ -16,17 +16,30 @@ we receive from the credit provider. """ +import logging import hashlib import hmac from django.conf import settings +log = logging.getLogger(__name__) + + def get_shared_secret_key(provider_id): """ Retrieve the shared secret key for a particular credit provider. """ - return getattr(settings, "CREDIT_PROVIDER_SECRET_KEYS", {}).get(provider_id) + secret = getattr(settings, "CREDIT_PROVIDER_SECRET_KEYS", {}).get(provider_id) + + if isinstance(secret, unicode): + try: + secret = str(secret) + except UnicodeEncodeError: + secret = None + log.error(u'Shared secret key for credit provider "%s" contains non-ASCII unicode.', provider_id) + + return secret def signature(params, shared_secret): @@ -35,6 +48,7 @@ def signature(params, shared_secret): Arguments: params (dict): Parameters to sign. Ignores the "signature" key if present. + shared_secret (str): The shared secret string. Returns: str: The 32-character signature. diff --git a/openedx/core/djangoapps/credit/tests/test_signature.py b/openedx/core/djangoapps/credit/tests/test_signature.py new file mode 100644 index 0000000000..3b40014703 --- /dev/null +++ b/openedx/core/djangoapps/credit/tests/test_signature.py @@ -0,0 +1,37 @@ +""" +Tests for digital signatures used to validate messages to/from credit providers. +""" + +from django.test import TestCase +from django.test.utils import override_settings + + +from openedx.core.djangoapps.credit import signature + + +class SignatureTest(TestCase): + """ + Tests for digital signatures. + """ + + @override_settings(CREDIT_PROVIDER_SECRET_KEYS={ + "asu": u'abcd1234' + }) + def test_unicode_secret_key(self): + # Test a key that has type `unicode` but consists of ASCII characters + # (This can happen, for example, when loading the key from a JSON configuration file) + # When retrieving the shared secret, the type should be converted to `str` + key = signature.get_shared_secret_key("asu") + sig = signature.signature({}, key) + self.assertEqual(sig, "7d70a26b834d9881cc14466eceac8d39188fc5ef5ffad9ab281a8327c2c0d093") + + @override_settings(CREDIT_PROVIDER_SECRET_KEYS={ + "asu": u'\u4567' + }) + def test_non_ascii_unicode_secret_key(self): + # Test a key that contains non-ASCII unicode characters + # This should return `None` and log an error; the caller + # is then responsible for logging the appropriate errors + # so we can fix the misconfiguration. + key = signature.get_shared_secret_key("asu") + self.assertIs(key, None)