@@ -739,6 +739,7 @@ def get_oauth2_credentials(username, password, reload_remote_accounts=True):
739739 permission_url = AppConfig .get_option_with_catch_all_fallback (config , username , 'permission_url' )
740740 token_url = AppConfig .get_option_with_catch_all_fallback (config , username , 'token_url' )
741741 oauth2_scope = AppConfig .get_option_with_catch_all_fallback (config , username , 'oauth2_scope' )
742+ oauth2_resource = AppConfig .get_option_with_catch_all_fallback (config , username , 'oauth2_resource' )
742743 oauth2_flow = AppConfig .get_option_with_catch_all_fallback (config , username , 'oauth2_flow' )
743744 redirect_uri = AppConfig .get_option_with_catch_all_fallback (config , username , 'redirect_uri' )
744745 redirect_listen_address = AppConfig .get_option_with_catch_all_fallback (config , username ,
@@ -751,8 +752,9 @@ def get_oauth2_credentials(username, password, reload_remote_accounts=True):
751752 jwt_key_path = AppConfig .get_option_with_catch_all_fallback (config , username , 'jwt_key_path' )
752753
753754 # because the proxy supports a wide range of OAuth 2.0 flows, in addition to the token_url we only mandate the
754- # core parameters that are required by all methods: oauth2_scope and client_id
755- if not (token_url and oauth2_scope and client_id ):
755+ # core parameters that are required by all methods: client_id and oauth2_scope (or, for non-standard ROPCG,
756+ # currently only known to be used by 21Vianet, oauth2_resource instead of oauth2_scope - see GitHub #351)
757+ if not (token_url and client_id and ((oauth2_flow == 'password' and oauth2_resource ) or oauth2_scope )):
756758 Log .error ('Proxy config file entry incomplete for account' , username , '- aborting login' )
757759 return (False , '%s: Incomplete config file entry found for account %s - please make sure all required '
758760 'fields are added (at least token_url, oauth2_scope and client_id)' % (APP_NAME , username ))
@@ -863,7 +865,7 @@ def get_oauth2_credentials(username, password, reload_remote_accounts=True):
863865
864866 access_token = response ['access_token' ]
865867 config .set (username , 'access_token' , cryptographer .encrypt (access_token ))
866- config .set (username , 'access_token_expiry' , str (current_time + response ['expires_in' ]))
868+ config .set (username , 'access_token_expiry' , str (current_time + int ( response ['expires_in' ]) ))
867869 if 'refresh_token' in response :
868870 config .set (username , 'refresh_token' , cryptographer .encrypt (response ['refresh_token' ]))
869871 AppConfig .save ()
@@ -907,8 +909,8 @@ def get_oauth2_credentials(username, password, reload_remote_accounts=True):
907909 # note: get_oauth2_authorisation_tokens may be a blocking call (DAG flow retries until user code entry)
908910 response = OAuth2Helper .get_oauth2_authorisation_tokens (token_url , redirect_uri , client_id ,
909911 client_secret , jwt_client_assertion ,
910- auth_result , oauth2_scope , oauth2_flow ,
911- username , password )
912+ auth_result , oauth2_scope , oauth2_resource ,
913+ oauth2_flow , username , password )
912914
913915 if AppConfig .get_global ('encrypt_client_secret_on_first_use' , fallback = False ):
914916 if client_secret :
@@ -924,7 +926,7 @@ def get_oauth2_credentials(username, password, reload_remote_accounts=True):
924926 config .set (username , 'token_salt' , cryptographer .salt )
925927 config .set (username , 'token_iterations' , str (cryptographer .iterations ))
926928 config .set (username , 'access_token' , cryptographer .encrypt (access_token ))
927- config .set (username , 'access_token_expiry' , str (current_time + response ['expires_in' ]))
929+ config .set (username , 'access_token_expiry' , str (current_time + int ( response ['expires_in' ]) ))
928930
929931 if 'refresh_token' in response :
930932 config .set (username , 'refresh_token' , cryptographer .encrypt (response ['refresh_token' ]))
@@ -1169,11 +1171,14 @@ def get_oauth2_authorisation_code(permission_url, redirect_uri, redirect_listen_
11691171 @staticmethod
11701172 # pylint: disable-next=too-many-positional-arguments
11711173 def get_oauth2_authorisation_tokens (token_url , redirect_uri , client_id , client_secret , jwt_client_assertion ,
1172- authorisation_result , oauth2_scope , oauth2_flow , username , password ):
1174+ authorisation_result , oauth2_scope , oauth2_resource , oauth2_flow , username ,
1175+ password ):
11731176 """Requests OAuth 2.0 access and refresh tokens from token_url using the given client_id, client_secret,
11741177 authorisation_code and redirect_uri, returning a dict with 'access_token', 'expires_in', and 'refresh_token'
11751178 on success, or throwing an exception on failure (e.g., HTTP 400)"""
1176- if oauth2_flow == 'service_account' : # service accounts are slightly different, and are handled separately
1179+
1180+ # service accounts are slightly different, and are handled separately
1181+ if oauth2_flow == 'service_account' :
11771182 return OAuth2Helper .get_service_account_authorisation_token (client_id , client_secret , oauth2_scope ,
11781183 username )
11791184
@@ -1199,11 +1204,18 @@ def get_oauth2_authorisation_tokens(token_url, redirect_uri, client_id, client_s
11991204 if oauth2_flow == 'device' :
12001205 params ['grant_type' ] = 'urn:ietf:params:oauth:grant-type:device_code'
12011206 params ['device_code' ] = authorisation_result ['device_code' ]
1202- expires_in = authorisation_result ['expires_in' ]
1207+ expires_in = int ( authorisation_result ['expires_in' ])
12031208 authorisation_result ['interval' ] = authorisation_result .get ('interval' , 5 ) # see RFC 8628, Section 3.2
12041209 elif oauth2_flow == 'password' :
12051210 params ['username' ] = username
12061211 params ['password' ] = password
1212+ # there is at least one non-standard implementation of ROPCG that uses a resource parameter instead of a
1213+ # scope (e.g., 21Vianet's version of O365; see GitHub #351) - note that it is not known whether this is
1214+ # always instead of the scope value or potentially in addition to it, so we only remove if not specified
1215+ if oauth2_resource :
1216+ params ['resource' ] = oauth2_resource
1217+ if not oauth2_scope :
1218+ del params ['scope' ]
12071219 if not redirect_uri :
12081220 del params ['redirect_uri' ] # redirect_uri is not typically required in non-code flows; remove if empty
12091221
0 commit comments