Skip to content

Commit 2d2a585

Browse files
committed
Merge pull request #70 from box/jwt-key-rotation
Add 'kid' (jwt key id) to JWTAuth header
2 parents f26bb9c + a9fc97f commit 2d2a585

File tree

5 files changed

+38
-10
lines changed

5 files changed

+38
-10
lines changed

HISTORY.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ Release History
66
Upcoming
77
++++++++
88

9+
1.4.0 (2016-01-05)
10+
++++++++++++++++++
11+
12+
- Added key id parameter to JWT Auth.
13+
14+
915
1.3.3 (2016-01-04)
1016
++++++++++++++++++
1117

boxsdk/auth/jwt_auth.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def __init__(
2525
client_id,
2626
client_secret,
2727
enterprise_id,
28+
jwt_key_id,
2829
rsa_private_key_file_sys_path,
2930
rsa_private_key_passphrase=None,
3031
store_tokens=None,
@@ -47,6 +48,10 @@ def __init__(
4748
The ID of the Box Developer Edition enterprise.
4849
:type enterprise_id:
4950
`unicode`
51+
:param jwt_key_id:
52+
Key ID for the JWT assertion.
53+
:type jwt_key_id:
54+
`unicode`
5055
:param rsa_private_key_file_sys_path:
5156
Path to an RSA private key file, used for signing the JWT assertion.
5257
:type rsa_private_key_file_sys_path:
@@ -98,6 +103,7 @@ def __init__(
98103
)
99104
self._enterprise_id = enterprise_id
100105
self._jwt_algorithm = jwt_algorithm
106+
self._jwt_key_id = jwt_key_id
101107
self._user_id = None
102108

103109
def _auth_with_jwt(self, sub, sub_type):
@@ -135,6 +141,9 @@ def _auth_with_jwt(self, sub, sub_type):
135141
},
136142
self._rsa_private_key,
137143
algorithm=self._jwt_algorithm,
144+
headers={
145+
'kid': self._jwt_key_id,
146+
},
138147
)
139148
data = {
140149
'grant_type': self._GRANT_TYPE,

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ pyjwt>=1.3.0
44
requests>=2.4.3
55
requests-toolbelt>=0.4.0
66
six >= 1.4.0
7-
.
7+
-e .

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def main():
6161
install_requires.append('ordereddict>=1.1')
6262
setup(
6363
name='boxsdk',
64-
version='1.3.4',
64+
version='1.4.0',
6565
description='Official Box Python SDK',
6666
long_description=open(join(base_dir, 'README.rst')).read(),
6767
author='Box',

test/unit/auth/test_jwt_auth.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ def jwt_algorithm(request):
2828
return request.param
2929

3030

31+
@pytest.fixture(scope='module')
32+
def jwt_key_id():
33+
return 'jwt_key_id_1'
34+
35+
3136
@pytest.fixture(params=(None, b'strong_password'))
3237
def rsa_passphrase(request):
3338
return request.param
@@ -50,6 +55,7 @@ def jwt_auth_init_mocks(
5055
mock_network_layer,
5156
successful_token_response,
5257
jwt_algorithm,
58+
jwt_key_id,
5359
rsa_passphrase,
5460
enterprise_id=None,
5561
):
@@ -79,6 +85,7 @@ def jwt_auth_init_mocks(
7985
network_layer=mock_network_layer,
8086
box_device_name='my_awesome_device',
8187
jwt_algorithm=jwt_algorithm,
88+
jwt_key_id=jwt_key_id,
8289
)
8390

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

103110

104111
@contextmanager
105-
def jwt_auth_auth_mocks(jti_length, jwt_algorithm, sub, sub_type, oauth, assertion, client_id, secret):
112+
def jwt_auth_auth_mocks(jti_length, jwt_algorithm, jwt_key_id, sub, sub_type, oauth, assertion, client_id, secret):
106113
# pylint:disable=redefined-outer-name
107114
with patch('jwt.encode') as jwt_encode:
108115
with patch('boxsdk.auth.jwt_auth.datetime') as mock_datetime:
@@ -131,20 +138,21 @@ def jwt_auth_auth_mocks(jti_length, jwt_algorithm, sub, sub_type, oauth, asserti
131138
'aud': 'https://api.box.com/oauth2/token',
132139
'jti': jti,
133140
'exp': exp,
134-
}, secret, algorithm=jwt_algorithm)
141+
}, secret, algorithm=jwt_algorithm, headers={'kid': jwt_key_id})
135142

136143

137144
def test_authenticate_app_user_sends_post_request_with_correct_params(
138145
mock_network_layer,
139146
successful_token_response,
140147
jti_length,
141148
jwt_algorithm,
149+
jwt_key_id,
142150
rsa_passphrase,
143151
):
144152
# pylint:disable=redefined-outer-name
145153
fake_user_id = 'fake_user_id'
146-
with jwt_auth_init_mocks(mock_network_layer, successful_token_response, jwt_algorithm, rsa_passphrase) as params:
147-
with jwt_auth_auth_mocks(jti_length, jwt_algorithm, fake_user_id, 'user', *params) as oauth:
154+
with jwt_auth_init_mocks(mock_network_layer, successful_token_response, jwt_algorithm, jwt_key_id, rsa_passphrase) as params:
155+
with jwt_auth_auth_mocks(jti_length, jwt_algorithm, jwt_key_id, fake_user_id, 'user', *params) as oauth:
148156
oauth.authenticate_app_user(User(None, fake_user_id))
149157

150158

@@ -153,6 +161,7 @@ def test_authenticate_instance_sends_post_request_with_correct_params(
153161
successful_token_response,
154162
jti_length,
155163
jwt_algorithm,
164+
jwt_key_id,
156165
rsa_passphrase,
157166
):
158167
# pylint:disable=redefined-outer-name
@@ -161,10 +170,11 @@ def test_authenticate_instance_sends_post_request_with_correct_params(
161170
mock_network_layer,
162171
successful_token_response,
163172
jwt_algorithm,
173+
jwt_key_id,
164174
rsa_passphrase,
165175
enterprise_id,
166176
) as params:
167-
with jwt_auth_auth_mocks(jti_length, jwt_algorithm, enterprise_id, 'enterprise', *params) as oauth:
177+
with jwt_auth_auth_mocks(jti_length, jwt_algorithm, jwt_key_id, enterprise_id, 'enterprise', *params) as oauth:
168178
oauth.authenticate_instance()
169179

170180

@@ -173,12 +183,13 @@ def test_refresh_app_user_sends_post_request_with_correct_params(
173183
successful_token_response,
174184
jti_length,
175185
jwt_algorithm,
186+
jwt_key_id,
176187
rsa_passphrase,
177188
):
178189
# pylint:disable=redefined-outer-name
179190
fake_user_id = 'fake_user_id'
180-
with jwt_auth_init_mocks(mock_network_layer, successful_token_response, jwt_algorithm, rsa_passphrase) as params:
181-
with jwt_auth_auth_mocks(jti_length, jwt_algorithm, fake_user_id, 'user', *params) as oauth:
191+
with jwt_auth_init_mocks(mock_network_layer, successful_token_response, jwt_algorithm, jwt_key_id, rsa_passphrase) as params:
192+
with jwt_auth_auth_mocks(jti_length, jwt_algorithm, jwt_key_id, fake_user_id, 'user', *params) as oauth:
182193
oauth._user_id = fake_user_id # pylint:disable=protected-access
183194
oauth.refresh(None)
184195

@@ -188,6 +199,7 @@ def test_refresh_instance_sends_post_request_with_correct_params(
188199
successful_token_response,
189200
jti_length,
190201
jwt_algorithm,
202+
jwt_key_id,
191203
rsa_passphrase,
192204
):
193205
# pylint:disable=redefined-outer-name
@@ -196,8 +208,9 @@ def test_refresh_instance_sends_post_request_with_correct_params(
196208
mock_network_layer,
197209
successful_token_response,
198210
jwt_algorithm,
211+
jwt_key_id,
199212
rsa_passphrase,
200213
enterprise_id,
201214
) as params:
202-
with jwt_auth_auth_mocks(jti_length, jwt_algorithm, enterprise_id, 'enterprise', *params) as oauth:
215+
with jwt_auth_auth_mocks(jti_length, jwt_algorithm, jwt_key_id, enterprise_id, 'enterprise', *params) as oauth:
203216
oauth.refresh(None)

0 commit comments

Comments
 (0)