Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions ecommerce_api_client/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@
class JwtAuth(AuthBase):
"""Attaches JWT Authentication to the given Request object."""

def __init__(self, username, email, signing_key):
def __init__(self, username, email, signing_key, tracking_context=None):
self.username = username
self.email = email
self.signing_key = signing_key
self.tracking_context = tracking_context

def __call__(self, r):
data = {
'username': self.username,
'email': self.email
'email': self.email,
}

if self.tracking_context is not None:
data['tracking_context'] = self.tracking_context

r.headers['Authorization'] = 'JWT ' + jwt.encode(data, self.signing_key)
return r
8 changes: 6 additions & 2 deletions ecommerce_api_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
class EcommerceApiClient(slumber.API):
DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"

def __init__(self, url, signing_key, username, email, timeout=5):
def __init__(self, url, signing_key, username, email, timeout=5, tracking_context=None):
"""
Instantiate a new client.

Expand All @@ -28,4 +28,8 @@ def __init__(self, url, signing_key, username, email, timeout=5):

session = requests.Session()
session.timeout = timeout
super(EcommerceApiClient, self).__init__(url, session=session, auth=JwtAuth(username, email, signing_key))
super(EcommerceApiClient, self).__init__(
url,
session=session,
auth=JwtAuth(username, email, signing_key, tracking_context)
)
40 changes: 27 additions & 13 deletions ecommerce_api_client/tests/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,37 @@


class JwtAuthTests(TestCase):
@httpretty.activate
def test_headers(self):
""" Verify the class adds an Authorization header that includes the correct JWT. """

url = 'http://example.com/'
username = 'alice'
email = 'alice@example.com'
signing_key = 'edx'
def setUp(self):
super(JwtAuthTests, self).setUp()
self.url = 'http://example.com/'
self.username = 'alice'
self.email = 'alice@example.com'
self.signing_key = 'edx'
httpretty.register_uri(httpretty.GET, self.url)

def assert_expected_token_value(self, tracking_context=None):
""" DRY helper. """

# Mock the HTTP response and issue the request
httpretty.register_uri(httpretty.GET, url)
requests.get(url, auth=JwtAuth(username, email, signing_key))
auth_kwargs = {'tracking_context': tracking_context} if tracking_context else {}
requests.get(self.url, auth=JwtAuth(self.username, self.email, self.signing_key, **auth_kwargs))

# Verify the header was set on the request
# Verify the header was set as expected on the request
data = {
'username': username,
'email': email
'username': self.username,
'email': self.email
}
token = jwt.encode(data, signing_key)
data.update(auth_kwargs)
token = jwt.encode(data, self.signing_key)
self.assertEqual(httpretty.last_request().headers['Authorization'], 'JWT {0}'.format(token))

@httpretty.activate
def test_headers(self):
""" Verify the class adds an Authorization header that includes the correct JWT. """
self.assert_expected_token_value()

@httpretty.activate
def test_tracking_context(self):
""" Verify the tracking context is enclosed in the token payload, when specified. """
self.assert_expected_token_value({'foo': 'bar'})
8 changes: 8 additions & 0 deletions ecommerce_api_client/tests/test_client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from unittest import TestCase

import ddt
import mock

from ecommerce_api_client.client import EcommerceApiClient

Expand All @@ -9,6 +10,7 @@
SIGNING_KEY = 'edx'
USERNAME = 'edx'
EMAIL = 'edx@example.com'
TRACKING_CONTEXT = {'foo': 'bar'}


@ddt.ddt
Expand All @@ -28,3 +30,9 @@ def test_valid_configuration(self):
def test_invalid_configuration(self, args):
""" If the constructor arguments are invalid, an InvalidConfigurationError should be raised. """
self.assertRaises(ValueError, EcommerceApiClient, *args)

@mock.patch('ecommerce_api_client.auth.JwtAuth.__init__', return_value=None)
def test_tracking_context(self, mock_auth):
""" Ensure the tracking context is included with API requests if specified. """
EcommerceApiClient(URL, SIGNING_KEY, USERNAME, EMAIL, tracking_context=TRACKING_CONTEXT)
self.assertDictContainsSubset(mock_auth.call_args[1], TRACKING_CONTEXT)
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

setup(
name='ecommerce-api-client',
version='0.2.0',
version='0.3.0',
packages=['ecommerce_api_client'],
url='https://github.com/edx/ecommerce-api-client',
description='Client used to access edX E-Commerce Service',
Expand Down