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
6 changes: 6 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ Release History
Upcoming
++++++++

1.4.0 (2016-01-05)
++++++++++++++++++

- Added key id parameter to JWT Auth.


1.3.3 (2016-01-04)
++++++++++++++++++

Expand Down
9 changes: 9 additions & 0 deletions boxsdk/auth/jwt_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def __init__(
client_id,
client_secret,
enterprise_id,
jwt_key_id,
rsa_private_key_file_sys_path,
rsa_private_key_passphrase=None,
store_tokens=None,
Expand All @@ -47,6 +48,10 @@ def __init__(
The ID of the Box Developer Edition enterprise.
:type enterprise_id:
`unicode`
:param jwt_key_id:
Key ID for the JWT assertion.
:type jwt_key_id:
`unicode`
:param rsa_private_key_file_sys_path:
Path to an RSA private key file, used for signing the JWT assertion.
:type rsa_private_key_file_sys_path:
Expand Down Expand Up @@ -98,6 +103,7 @@ def __init__(
)
self._enterprise_id = enterprise_id
self._jwt_algorithm = jwt_algorithm
self._jwt_key_id = jwt_key_id
self._user_id = None

def _auth_with_jwt(self, sub, sub_type):
Expand Down Expand Up @@ -135,6 +141,9 @@ def _auth_with_jwt(self, sub, sub_type):
},
self._rsa_private_key,
algorithm=self._jwt_algorithm,
headers={
'kid': self._jwt_key_id,
},
)
data = {
'grant_type': self._GRANT_TYPE,
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ pyjwt>=1.3.0
requests>=2.4.3
requests-toolbelt>=0.4.0
six >= 1.4.0
.
-e .
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def main():
install_requires.append('ordereddict>=1.1')
setup(
name='boxsdk',
version='1.3.4',
version='1.4.0',
description='Official Box Python SDK',
long_description=open(join(base_dir, 'README.rst')).read(),
author='Box',
Expand Down
29 changes: 21 additions & 8 deletions test/unit/auth/test_jwt_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ def jwt_algorithm(request):
return request.param


@pytest.fixture(scope='module')
def jwt_key_id():
return 'jwt_key_id_1'


@pytest.fixture(params=(None, b'strong_password'))
def rsa_passphrase(request):
return request.param
Expand All @@ -50,6 +55,7 @@ def jwt_auth_init_mocks(
mock_network_layer,
successful_token_response,
jwt_algorithm,
jwt_key_id,
rsa_passphrase,
enterprise_id=None,
):
Expand Down Expand Up @@ -79,6 +85,7 @@ def jwt_auth_init_mocks(
network_layer=mock_network_layer,
box_device_name='my_awesome_device',
jwt_algorithm=jwt_algorithm,
jwt_key_id=jwt_key_id,
)

jwt_auth_open.assert_called_once_with(sentinel.rsa_path)
Expand All @@ -102,7 +109,7 @@ def jwt_auth_init_mocks(


@contextmanager
def jwt_auth_auth_mocks(jti_length, jwt_algorithm, sub, sub_type, oauth, assertion, client_id, secret):
def jwt_auth_auth_mocks(jti_length, jwt_algorithm, jwt_key_id, sub, sub_type, oauth, assertion, client_id, secret):
# pylint:disable=redefined-outer-name
with patch('jwt.encode') as jwt_encode:
with patch('boxsdk.auth.jwt_auth.datetime') as mock_datetime:
Expand Down Expand Up @@ -131,20 +138,21 @@ def jwt_auth_auth_mocks(jti_length, jwt_algorithm, sub, sub_type, oauth, asserti
'aud': 'https://api.box.com/oauth2/token',
'jti': jti,
'exp': exp,
}, secret, algorithm=jwt_algorithm)
}, secret, algorithm=jwt_algorithm, headers={'kid': jwt_key_id})


def test_authenticate_app_user_sends_post_request_with_correct_params(
mock_network_layer,
successful_token_response,
jti_length,
jwt_algorithm,
jwt_key_id,
rsa_passphrase,
):
# pylint:disable=redefined-outer-name
fake_user_id = 'fake_user_id'
with jwt_auth_init_mocks(mock_network_layer, successful_token_response, jwt_algorithm, rsa_passphrase) as params:
with jwt_auth_auth_mocks(jti_length, jwt_algorithm, fake_user_id, 'user', *params) as oauth:
with jwt_auth_init_mocks(mock_network_layer, successful_token_response, jwt_algorithm, jwt_key_id, rsa_passphrase) as params:
with jwt_auth_auth_mocks(jti_length, jwt_algorithm, jwt_key_id, fake_user_id, 'user', *params) as oauth:
oauth.authenticate_app_user(User(None, fake_user_id))


Expand All @@ -153,6 +161,7 @@ def test_authenticate_instance_sends_post_request_with_correct_params(
successful_token_response,
jti_length,
jwt_algorithm,
jwt_key_id,
rsa_passphrase,
):
# pylint:disable=redefined-outer-name
Expand All @@ -161,10 +170,11 @@ def test_authenticate_instance_sends_post_request_with_correct_params(
mock_network_layer,
successful_token_response,
jwt_algorithm,
jwt_key_id,
rsa_passphrase,
enterprise_id,
) as params:
with jwt_auth_auth_mocks(jti_length, jwt_algorithm, enterprise_id, 'enterprise', *params) as oauth:
with jwt_auth_auth_mocks(jti_length, jwt_algorithm, jwt_key_id, enterprise_id, 'enterprise', *params) as oauth:
oauth.authenticate_instance()


Expand All @@ -173,12 +183,13 @@ def test_refresh_app_user_sends_post_request_with_correct_params(
successful_token_response,
jti_length,
jwt_algorithm,
jwt_key_id,
rsa_passphrase,
):
# pylint:disable=redefined-outer-name
fake_user_id = 'fake_user_id'
with jwt_auth_init_mocks(mock_network_layer, successful_token_response, jwt_algorithm, rsa_passphrase) as params:
with jwt_auth_auth_mocks(jti_length, jwt_algorithm, fake_user_id, 'user', *params) as oauth:
with jwt_auth_init_mocks(mock_network_layer, successful_token_response, jwt_algorithm, jwt_key_id, rsa_passphrase) as params:
with jwt_auth_auth_mocks(jti_length, jwt_algorithm, jwt_key_id, fake_user_id, 'user', *params) as oauth:
oauth._user_id = fake_user_id # pylint:disable=protected-access
oauth.refresh(None)

Expand All @@ -188,6 +199,7 @@ def test_refresh_instance_sends_post_request_with_correct_params(
successful_token_response,
jti_length,
jwt_algorithm,
jwt_key_id,
rsa_passphrase,
):
# pylint:disable=redefined-outer-name
Expand All @@ -196,8 +208,9 @@ def test_refresh_instance_sends_post_request_with_correct_params(
mock_network_layer,
successful_token_response,
jwt_algorithm,
jwt_key_id,
rsa_passphrase,
enterprise_id,
) as params:
with jwt_auth_auth_mocks(jti_length, jwt_algorithm, enterprise_id, 'enterprise', *params) as oauth:
with jwt_auth_auth_mocks(jti_length, jwt_algorithm, jwt_key_id, enterprise_id, 'enterprise', *params) as oauth:
oauth.refresh(None)