44
55from contextlib import contextmanager
66from datetime import datetime , timedelta
7+ import io
78from itertools import product
89import json
910import random
1011import string
1112
1213from cryptography .hazmat .backends import default_backend
14+ from cryptography .hazmat .primitives .asymmetric .rsa import RSAPrivateKey , generate_private_key as generate_rsa_private_key
15+ from cryptography .hazmat .primitives import serialization
1316from mock import Mock , mock_open , patch , sentinel
1417import pytest
15- from six import string_types , text_type
18+ from six import binary_type , string_types , text_type
1619
1720from boxsdk .auth .jwt_auth import JWTAuth
1821from boxsdk .config import API
@@ -35,11 +38,26 @@ def jwt_key_id():
3538 return 'jwt_key_id_1'
3639
3740
41+ @pytest .fixture (scope = 'module' )
42+ def rsa_private_key_object ():
43+ return generate_rsa_private_key (public_exponent = 65537 , key_size = 4096 , backend = default_backend ())
44+
45+
3846@pytest .fixture (params = (None , b'strong_password' ))
3947def rsa_passphrase (request ):
4048 return request .param
4149
4250
51+ @pytest .fixture
52+ def rsa_private_key_bytes (rsa_private_key_object , rsa_passphrase ):
53+ encryption = serialization .BestAvailableEncryption (rsa_passphrase ) if rsa_passphrase else serialization .NoEncryption ()
54+ return rsa_private_key_object .private_bytes (
55+ encoding = serialization .Encoding .PEM ,
56+ format = serialization .PrivateFormat .PKCS8 ,
57+ encryption_algorithm = encryption ,
58+ )
59+
60+
4361@pytest .fixture (scope = 'function' )
4462def successful_token_response (successful_token_mock , successful_token_json_response ):
4563 # pylint:disable=redefined-outer-name
@@ -52,8 +70,76 @@ def successful_token_response(successful_token_mock, successful_token_json_respo
5270 return successful_token_mock
5371
5472
73+ @pytest .mark .parametrize (('key_file' , 'key_data' ), [(None , None ), ('fake sys path' , 'fake key data' )])
74+ @pytest .mark .parametrize ('rsa_passphrase' , [None ])
75+ def test_jwt_auth_init_raises_type_error_unless_exactly_one_of_rsa_private_key_file_or_data_is_given (key_file , key_data , rsa_private_key_bytes ):
76+ kwargs = dict (
77+ rsa_private_key_data = rsa_private_key_bytes ,
78+ client_id = None ,
79+ client_secret = None ,
80+ jwt_key_id = None ,
81+ enterprise_id = None ,
82+ )
83+ JWTAuth (** kwargs )
84+ kwargs .update (rsa_private_key_file_sys_path = key_file , rsa_private_key_data = key_data )
85+ with pytest .raises (TypeError ):
86+ JWTAuth (** kwargs )
87+
88+
89+ @pytest .mark .parametrize ('key_data' , [object (), u'ƒøø' ])
90+ @pytest .mark .parametrize ('rsa_passphrase' , [None ])
91+ def test_jwt_auth_init_raises_type_error_if_rsa_private_key_data_has_unexpected_type (key_data , rsa_private_key_bytes ):
92+ kwargs = dict (
93+ rsa_private_key_data = rsa_private_key_bytes ,
94+ client_id = None ,
95+ client_secret = None ,
96+ jwt_key_id = None ,
97+ enterprise_id = None ,
98+ )
99+ JWTAuth (** kwargs )
100+ kwargs .update (rsa_private_key_data = key_data )
101+ with pytest .raises (TypeError ):
102+ JWTAuth (** kwargs )
103+
104+
105+ @pytest .mark .parametrize ('rsa_private_key_data_type' , [io .BytesIO , text_type , binary_type , RSAPrivateKey ])
106+ def test_jwt_auth_init_accepts_rsa_private_key_data (rsa_private_key_bytes , rsa_passphrase , rsa_private_key_data_type ):
107+ if rsa_private_key_data_type is text_type :
108+ rsa_private_key_data = text_type (rsa_private_key_bytes .decode ('ascii' ))
109+ elif rsa_private_key_data_type is RSAPrivateKey :
110+ rsa_private_key_data = serialization .load_pem_private_key (
111+ rsa_private_key_bytes ,
112+ password = rsa_passphrase ,
113+ backend = default_backend (),
114+ )
115+ else :
116+ rsa_private_key_data = rsa_private_key_data_type (rsa_private_key_bytes )
117+ JWTAuth (
118+ rsa_private_key_data = rsa_private_key_data ,
119+ rsa_private_key_passphrase = rsa_passphrase ,
120+ client_id = None ,
121+ client_secret = None ,
122+ jwt_key_id = None ,
123+ enterprise_id = None ,
124+ )
125+
126+
127+ @pytest .fixture (params = [False , True ])
128+ def pass_private_key_by_path (request ):
129+ """For jwt_auth_init_mocks, whether to pass the private key via sys_path (True) or pass the data directly (False)."""
130+ return request .param
131+
132+
55133@pytest .fixture
56- def jwt_auth_init_mocks (mock_network_layer , successful_token_response , jwt_algorithm , jwt_key_id , rsa_passphrase ):
134+ def jwt_auth_init_mocks (
135+ mock_network_layer ,
136+ successful_token_response ,
137+ jwt_algorithm ,
138+ jwt_key_id ,
139+ rsa_passphrase ,
140+ rsa_private_key_bytes ,
141+ pass_private_key_by_path ,
142+ ):
57143 # pylint:disable=redefined-outer-name
58144
59145 @contextmanager
@@ -70,15 +156,14 @@ def _jwt_auth_init_mocks(**kwargs):
70156 'box_device_id' : '0' ,
71157 'box_device_name' : 'my_awesome_device' ,
72158 }
73-
74159 mock_network_layer .request .return_value = successful_token_response
75- key_file_read_data = b'key_file_read_data'
76- with patch ('boxsdk.auth.jwt_auth.open' , mock_open (read_data = key_file_read_data ), create = True ) as jwt_auth_open :
160+ with patch ('boxsdk.auth.jwt_auth.open' , mock_open (read_data = rsa_private_key_bytes ), create = True ) as jwt_auth_open :
77161 with patch ('cryptography.hazmat.primitives.serialization.load_pem_private_key' ) as load_pem_private_key :
78162 oauth = JWTAuth (
79163 client_id = fake_client_id ,
80164 client_secret = fake_client_secret ,
81- rsa_private_key_file_sys_path = sentinel .rsa_path ,
165+ rsa_private_key_file_sys_path = (sentinel .rsa_path if pass_private_key_by_path else None ),
166+ rsa_private_key_data = (None if pass_private_key_by_path else rsa_private_key_bytes ),
82167 rsa_private_key_passphrase = rsa_passphrase ,
83168 network_layer = mock_network_layer ,
84169 box_device_name = 'my_awesome_device' ,
@@ -87,11 +172,13 @@ def _jwt_auth_init_mocks(**kwargs):
87172 enterprise_id = kwargs .pop ('enterprise_id' , None ),
88173 ** kwargs
89174 )
90-
91- jwt_auth_open .assert_called_once_with (sentinel .rsa_path , 'rb' )
92- jwt_auth_open .return_value .read .assert_called_once_with () # pylint:disable=no-member
175+ if pass_private_key_by_path :
176+ jwt_auth_open .assert_called_once_with (sentinel .rsa_path , 'rb' )
177+ jwt_auth_open .return_value .read .assert_called_once_with () # pylint:disable=no-member
178+ else :
179+ jwt_auth_open .assert_not_called ()
93180 load_pem_private_key .assert_called_once_with (
94- key_file_read_data ,
181+ rsa_private_key_bytes ,
95182 password = rsa_passphrase ,
96183 backend = default_backend (),
97184 )
0 commit comments