Skip to content

Commit b002bcb

Browse files
committed
Bump botocore dependency specification
1 parent d6fce5e commit b002bcb

File tree

6 files changed

+64
-23
lines changed

6 files changed

+64
-23
lines changed

CHANGES.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Changes
33

44
2.24.1 (2025-08-09)
55
^^^^^^^^^^^^^^^^^^^
6-
* relax botocore dependency specification
6+
* bump botocore dependency specification
77

88
2.24.0 (2025-07-31)
99
^^^^^^^^^^^^^^^^^^^

aiobotocore/credentials.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
from copy import deepcopy
66

77
import botocore.compat
8+
import dateutil.parser
89
from botocore import UNSIGNED
9-
from botocore.compat import compat_shell_split
10+
from botocore.compat import compat_shell_split, total_seconds
1011
from botocore.config import Config
1112
from botocore.credentials import (
1213
_DEFAULT_ADVISORY_REFRESH_TIMEOUT,
@@ -1048,7 +1049,16 @@ async def _get_credentials(self):
10481049
initial_token_data = self._token_provider.load_token()
10491050
token = (await initial_token_data.get_frozen_token()).token
10501051
else:
1051-
token = self._token_loader(self._start_url)['accessToken']
1052+
token_dict = self._token_loader(self._start_url)
1053+
token = token_dict['accessToken']
1054+
1055+
# raise an UnauthorizedSSOTokenError if the loaded legacy token
1056+
# is expired to save a call to GetRoleCredentials with an
1057+
# expired token.
1058+
expiration = dateutil.parser.parse(token_dict['expiresAt'])
1059+
remaining = total_seconds(expiration - self._time_fetcher())
1060+
if remaining <= 0:
1061+
raise UnauthorizedSSOTokenError()
10521062

10531063
kwargs = {
10541064
'roleName': self._role_name,

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ dynamic = ["version", "readme"]
3232
dependencies = [
3333
"aiohttp >= 3.9.2, < 4.0.0",
3434
"aioitertools >= 0.5.1, < 1.0.0",
35-
"botocore >= 1.39.9, < 1.39.15", # NOTE: When updating, always keep `project.optional-dependencies` aligned
35+
"botocore >= 1.39.15, < 1.40.1", # NOTE: When updating, always keep `project.optional-dependencies` aligned
3636
"python-dateutil >= 2.1, < 3.0.0",
3737
"jmespath >= 0.7.1, < 2.0.0",
3838
"multidict >= 6.0.0, < 7.0.0",
@@ -41,10 +41,10 @@ dependencies = [
4141

4242
[project.optional-dependencies]
4343
awscli = [
44-
"awscli >= 1.41.9, < 1.41.15",
44+
"awscli >= 1.41.15, < 1.42.1",
4545
]
4646
boto3 = [
47-
"boto3 >= 1.39.9, < 1.39.15",
47+
"boto3 >= 1.39.15, < 1.40.1",
4848
]
4949
httpx = [
5050
"httpx >= 0.25.1, < 0.29"

tests/botocore_tests/unit/test_credentials.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,14 +1089,18 @@ async def ssl_credential_fetcher_setup():
10891089
self.start_url = 'https://d-92671207e4.awsapps.com/start'
10901090
self.role_name = 'test-role'
10911091
self.account_id = '1234567890'
1092-
self.access_token = 'some.sso.token'
1092+
self.access_token = {
1093+
'accessToken': 'some.sso.token',
1094+
'expiresAt': '2018-10-18T22:26:40Z',
1095+
}
10931096
# This is just an arbitrary point in time we can pin to
10941097
self.now = datetime(2008, 9, 23, 12, 26, 40, tzinfo=tzutc())
10951098
# The SSO endpoint uses ms whereas the OIDC endpoint uses seconds
10961099
self.now_timestamp = 1222172800000
1100+
self.mock_time_fetcher = mock.Mock(return_value=self.now)
10971101

10981102
self.loader = mock.Mock(spec=SSOTokenLoader)
1099-
self.loader.return_value = {'accessToken': self.access_token}
1103+
self.loader.return_value = self.access_token
11001104
self.fetcher = AioSSOCredentialFetcher(
11011105
self.start_url,
11021106
self.sso_region,
@@ -1105,11 +1109,13 @@ async def ssl_credential_fetcher_setup():
11051109
self.mock_session.create_client,
11061110
token_loader=self.loader,
11071111
cache=self.cache,
1112+
time_fetcher=self.mock_time_fetcher,
11081113
)
11091114

11101115
tc = TestCase()
11111116
self.assertEqual = tc.assertEqual
11121117
self.assertRaises = tc.assertRaises
1118+
self.assertFalse = tc.assertFalse
11131119
yield self
11141120

11151121

@@ -1292,7 +1298,7 @@ async def test_sso_credential_fetcher_can_fetch_credentials(
12921298
expected_params = {
12931299
'roleName': self.role_name,
12941300
'accountId': self.account_id,
1295-
'accessToken': self.access_token,
1301+
'accessToken': self.access_token['accessToken'],
12961302
}
12971303
expected_response = {
12981304
'roleCredentials': {
@@ -1334,7 +1340,7 @@ async def test_sso_cred_fetcher_raises_helpful_message_on_unauthorized_exception
13341340
expected_params = {
13351341
'roleName': self.role_name,
13361342
'accountId': self.account_id,
1337-
'accessToken': self.access_token,
1343+
'accessToken': self.access_token['accessToken'],
13381344
}
13391345
self.stubber.add_client_error(
13401346
'get_role_credentials',
@@ -1346,6 +1352,31 @@ async def test_sso_cred_fetcher_raises_helpful_message_on_unauthorized_exception
13461352
await self.fetcher.fetch_credentials()
13471353

13481354

1355+
async def test_sso_cred_fetcher_expired_legacy_token_has_expected_behavior(
1356+
ssl_credential_fetcher_setup,
1357+
):
1358+
self = ssl_credential_fetcher_setup
1359+
# Mock the current time to be in the future after the access token has expired
1360+
now = datetime(2018, 10, 19, 12, 26, 40, tzinfo=tzutc())
1361+
mock_client = mock.AsyncMock()
1362+
create_mock_client = mock.Mock(return_value=mock_client)
1363+
fetcher = AioSSOCredentialFetcher(
1364+
self.start_url,
1365+
self.sso_region,
1366+
self.role_name,
1367+
self.account_id,
1368+
create_mock_client,
1369+
token_loader=self.loader,
1370+
cache=self.cache,
1371+
time_fetcher=mock.Mock(return_value=now),
1372+
)
1373+
# since the cached token is expired, an UnauthorizedSSOTokenError should be
1374+
# raised and GetRoleCredentials should not be called.
1375+
with self.assertRaises(botocore.exceptions.UnauthorizedSSOTokenError):
1376+
await fetcher.fetch_credentials()
1377+
self.assertFalse(mock_client.get_role_credentials.called)
1378+
1379+
13491380
# from TestSSOProvider
13501381
@pytest.fixture
13511382
async def sso_provider_setup():

tests/test_patches.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ def test_protocol_parsers():
407407
(
408408
SSOCredentialFetcher._get_credentials,
409409
{
410-
'13ac3b73e0745dfeaa934a8873179ca6c22a164f',
410+
'd15db30acdb7295a055e89b6c3bc077847e9b37c',
411411
},
412412
),
413413
(

uv.lock

Lines changed: 12 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)