Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Retry JWT auth when got error: required unique jti claim. #768

Merged
merged 1 commit into from Nov 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 8 additions & 6 deletions boxsdk/auth/server_auth.py
@@ -1,4 +1,3 @@

import time
from abc import ABC, abstractmethod
from datetime import datetime
Expand Down Expand Up @@ -132,7 +131,7 @@ def _authenticate(self, subject_id: str, subject_type: str) -> str:

if code == 429 or code >= 500:
date = None
elif box_datetime is not None and self._was_exp_claim_rejected_due_to_clock_skew(network_response):
elif box_datetime is not None and self._is_auth_error_retryable(network_response):
date = box_datetime
else:
raise ex
Expand Down Expand Up @@ -168,13 +167,15 @@ def _get_date_header(network_response: 'NetworkResponse') -> Optional[datetime]:
return None

@staticmethod
def _was_exp_claim_rejected_due_to_clock_skew(network_response: 'NetworkResponse') -> bool:
def _is_auth_error_retryable(network_response: 'NetworkResponse') -> bool:
"""
Determine whether the network response indicates that the authorization request was rejected because of
the exp claim. This can happen if the current system time is too different from the Box server time.
the exp or jti claim and can be retried. Exp claim error can happen if the current system time is too
different from the Box server time. If got an error: "A unique 'jti' value is required",
we also retry auth in order to use new 'jti' claim.

Returns True if the status code is 400, the error code is invalid_grant, and the error description indicates
a problem with the exp claim; False, otherwise.
a problem with the exp or jti claim; False, otherwise.

:param network_response:
The response from the Box API that should include a Date header.
Expand All @@ -186,7 +187,8 @@ def _was_exp_claim_rejected_due_to_clock_skew(network_response: 'NetworkResponse
return False
error_code = json_response.get('error', '')
error_description = json_response.get('error_description', '')
return status_code == 400 and error_code == 'invalid_grant' and 'exp' in error_description
return status_code == 400 and error_code == 'invalid_grant' \
and ('exp' in error_description or 'jti' in error_description)

@classmethod
def _normalize_user_id(cls, user: Any) -> Optional[str]:
Expand Down
5 changes: 3 additions & 2 deletions test/unit/auth/test_jwt_auth.py
Expand Up @@ -464,7 +464,7 @@ def test_from_settings_dictionary(

@pytest.fixture
def expect_auth_retry(status_code, error_description, include_date_header, error_code):
return status_code == 400 and 'exp' in error_description and include_date_header and error_code == 'invalid_grant'
return status_code == 400 and ('exp' in error_description or 'jti' in error_description) and include_date_header and error_code == 'invalid_grant'


@pytest.fixture
Expand All @@ -486,7 +486,8 @@ def unsuccessful_jwt_response(box_datetime, status_code, error_description, incl
@pytest.mark.parametrize('rsa_passphrase', (None,))
@pytest.mark.parametrize('pass_private_key_by_path', (False,))
@pytest.mark.parametrize('status_code', (400, 401))
@pytest.mark.parametrize('error_description', ('invalid box_sub_type claim', 'invalid kid', "check the 'exp' claim"))
@pytest.mark.parametrize('error_description', (
'invalid box_sub_type claim', 'invalid kid', "check the 'exp' claim", "check the 'jti' claim"))
@pytest.mark.parametrize('error_code', ('invalid_grant', 'bad_request'))
@pytest.mark.parametrize('include_date_header', (True, False))
def test_auth_retry_for_invalid_exp_claim(
Expand Down