99from cryptography .hazmat .backends import default_backend
1010from cryptography .hazmat .primitives import serialization
1111import jwt
12+ from six import string_types , text_type
1213
1314from .oauth2 import OAuth2
15+ from ..object .user import User
1416from ..util .compat import total_seconds
1517
1618
@@ -28,14 +30,21 @@ def __init__(
2830 jwt_key_id ,
2931 rsa_private_key_file_sys_path ,
3032 rsa_private_key_passphrase = None ,
33+ user = None ,
3134 store_tokens = None ,
3235 box_device_id = '0' ,
3336 box_device_name = '' ,
3437 access_token = None ,
3538 network_layer = None ,
3639 jwt_algorithm = 'RS256' ,
3740 ):
38- """
41+ """Extends baseclass method.
42+
43+ At most one of `enterprise_id` and `user` may be non-`None`. If they
44+ are both `None`, then the caller is required to manually call either
45+ `authenticate_user()` or `authenticate_instance()` with the desired
46+ `sub` claim in order to begin using this auth object.
47+
3948 :param client_id:
4049 Box API key used for identifying the application the user is authenticating with.
4150 :type client_id:
@@ -46,8 +55,13 @@ def __init__(
4655 `unicode`
4756 :param enterprise_id:
4857 The ID of the Box Developer Edition enterprise.
58+
59+ May be `None`, if the caller knows that it will not be
60+ authenticating as an enterprise instance / service account.
61+
62+ Must be `None` if `user` is passed.
4963 :type enterprise_id:
50- `unicode`
64+ `unicode` or `None`
5165 :param jwt_key_id:
5266 Key ID for the JWT assertion.
5367 :type jwt_key_id:
@@ -60,6 +74,24 @@ def __init__(
6074 Passphrase used to unlock the private key. Do not pass a unicode string - this must be bytes.
6175 :type rsa_private_key_passphrase:
6276 `str` or None
77+ :param user:
78+ (optional) The user to authenticate, expressed as a Box User ID or
79+ as a :class:`User` instance.
80+
81+ This value is not required. But if it is provided, then the user
82+ will be auto-authenticated at the time of the first API call or
83+ when calling `authenticate_user()` without any arguments.
84+
85+ Must be `None` if `enterprise_id` is passed.
86+
87+ May be one of this application's created App User. Depending on the
88+ configured User Access Level, may also be any other App User or
89+ Managed User in the enterprise.
90+
91+ <https://docs.box.com/docs/configuring-box-platform#section-3-enabling-app-auth-and-app-users>
92+ <https://docs.box.com/docs/authentication#section-choosing-an-authentication-type>
93+ :type user:
94+ `unicode` or :class:`User` or `None`
6395 :param store_tokens:
6496 Optional callback for getting access to tokens for storing them.
6597 :type store_tokens:
@@ -85,6 +117,9 @@ def __init__(
85117 :type jwt_algorithm:
86118 `unicode`
87119 """
120+ if enterprise_id and user :
121+ raise TypeError ("Cannot pass both 'enterprise_id' and 'user'." )
122+ user_id = self ._normalize_user_id (user )
88123 super (JWTAuth , self ).__init__ (
89124 client_id ,
90125 client_secret ,
@@ -104,12 +139,12 @@ def __init__(
104139 self ._enterprise_id = enterprise_id
105140 self ._jwt_algorithm = jwt_algorithm
106141 self ._jwt_key_id = jwt_key_id
107- self ._user_id = None
142+ self ._user_id = user_id
108143
109144 def _auth_with_jwt (self , sub , sub_type ):
110145 """
111146 Get an access token for use with Box Developer Edition. Pass an enterprise ID to get an enterprise token
112- (which can be used to provision/deprovision users), or a user ID to get an app user token.
147+ (which can be used to provision/deprovision users), or a user ID to get a user token.
113148
114149 :param sub:
115150 The enterprise ID or user ID to auth.
@@ -157,31 +192,92 @@ def _auth_with_jwt(self, sub, sub_type):
157192 data ['box_device_name' ] = self ._box_device_name
158193 return self .send_token_request (data , access_token = None , expect_refresh_token = False )[0 ]
159194
160- def authenticate_app_user (self , user ):
195+ def authenticate_user (self , user = None ):
161196 """
162- Get an access token for an App User (part of Box Developer Edition).
197+ Get an access token for a User.
198+
199+ May be one of this application's created App User. Depending on the
200+ configured User Access Level, may also be any other App User or Managed
201+ User in the enterprise.
202+
203+ <https://docs.box.com/docs/configuring-box-platform#section-3-enabling-app-auth-and-app-users>
204+ <https://docs.box.com/docs/authentication#section-choosing-an-authentication-type>
163205
164206 :param user:
165- The user to authenticate.
207+ (optional) The user to authenticate, expressed as a Box User ID or
208+ as a :class:`User` instance.
209+
210+ If not given, then the most recently provided user ID, if
211+ available, will be used.
166212 :type user:
167- :class:`User`
213+ `unicode` or :class:`User`
214+ :raises:
215+ :exc:`ValueError` if no user ID was passed and the object is not
216+ currently configured with one.
168217 :return:
169- The access token for the app user.
218+ The access token for the user.
170219 :rtype:
171220 `unicode`
172221 """
173- sub = self ._user_id = user .object_id
222+ sub = self ._normalize_user_id (user ) or self ._user_id
223+ if not sub :
224+ raise ValueError ("authenticate_user: Requires the user ID, but it was not provided." )
225+ self ._user_id = sub
174226 return self ._auth_with_jwt (sub , 'user' )
175227
176- def authenticate_instance (self ):
228+ authenticate_app_user = authenticate_user
229+
230+ @classmethod
231+ def _normalize_user_id (cls , user ):
232+ """Get a Box user ID from a selection of supported param types.
233+
234+ :param user:
235+ An object representing the user or user ID.
236+
237+ Currently supported types are `unicode` (which represents the user
238+ ID) and :class:`User`.
239+
240+ If `None`, returns `None`.
241+ :raises: :exc:`TypeError` for unsupported types.
242+ :rtype: `unicode` or `None`
243+ """
244+ if user is None :
245+ return None
246+ if isinstance (user , User ):
247+ return user .object_id
248+ if isinstance (user , string_types ):
249+ return text_type (user )
250+ raise TypeError ("Got unsupported type {0!r} for user." .format (user .__class__ .__name__ ))
251+
252+ def authenticate_instance (self , enterprise = None ):
177253 """
178254 Get an access token for a Box Developer Edition enterprise.
179255
256+ :param enterprise:
257+ The ID of the Box Developer Edition enterprise.
258+
259+ Optional if the value was already given to `__init__`,
260+ otherwise required.
261+ :type enterprise: `unicode` or `None`
262+ :raises:
263+ :exc:`ValueError` if `None` was passed for the enterprise ID here
264+ and in `__init__`, or if the non-`None` value passed here does not
265+ match the non-`None` value passed to `__init__`.
180266 :return:
181267 The access token for the enterprise which can provision/deprovision app users.
182268 :rtype:
183269 `unicode`
184270 """
271+ enterprises = [enterprise , self ._enterprise_id ]
272+ if not any (enterprises ):
273+ raise ValueError ("authenticate_instance: Requires the enterprise ID, but it was not provided." )
274+ if all (enterprises ) and (enterprise != self ._enterprise_id ):
275+ raise ValueError (
276+ "authenticate_instance: Given enterprise ID {given_enterprise!r}, but {auth} already has ID {existing_enterprise!r}"
277+ .format (auth = self , given_enterprise = enterprise , existing_enterprise = self ._enterprise_id )
278+ )
279+ if not self ._enterprise_id :
280+ self ._enterprise_id = enterprise
185281 self ._user_id = None
186282 return self ._auth_with_jwt (self ._enterprise_id , 'enterprise' )
187283
@@ -195,4 +291,4 @@ def _refresh(self, access_token):
195291 if self ._user_id is None :
196292 return self .authenticate_instance ()
197293 else :
198- return self ._auth_with_jwt ( self . _user_id , 'user' )
294+ return self .authenticate_user ( )
0 commit comments