diff --git a/HISTORY.rst b/HISTORY.rst index a3b721cf2..5c9f3737a 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -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) ++++++++++++++++++ diff --git a/boxsdk/auth/jwt_auth.py b/boxsdk/auth/jwt_auth.py index 306ee89a9..2d8169733 100644 --- a/boxsdk/auth/jwt_auth.py +++ b/boxsdk/auth/jwt_auth.py @@ -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, @@ -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: @@ -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): @@ -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, diff --git a/requirements.txt b/requirements.txt index f0a3af4a2..0e0ef6918 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,4 @@ pyjwt>=1.3.0 requests>=2.4.3 requests-toolbelt>=0.4.0 six >= 1.4.0 -. +-e . diff --git a/setup.py b/setup.py index abffff477..23b01bc80 100644 --- a/setup.py +++ b/setup.py @@ -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', diff --git a/test/unit/auth/test_jwt_auth.py b/test/unit/auth/test_jwt_auth.py index 62f0a2002..c4d5f11dd 100644 --- a/test/unit/auth/test_jwt_auth.py +++ b/test/unit/auth/test_jwt_auth.py @@ -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 @@ -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, ): @@ -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) @@ -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: @@ -131,7 +138,7 @@ 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( @@ -139,12 +146,13 @@ def test_authenticate_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.authenticate_app_user(User(None, fake_user_id)) @@ -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 @@ -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() @@ -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) @@ -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 @@ -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)