Skip to content

Commit

Permalink
bump botocore dependency specification
Browse files Browse the repository at this point in the history
  • Loading branch information
jakob-keller committed Dec 16, 2024
1 parent 38973fe commit a73f8c2
Show file tree
Hide file tree
Showing 7 changed files with 267 additions and 4 deletions.
5 changes: 5 additions & 0 deletions aiobotocore/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
parse_get_bucket_location as boto_parse_get_bucket_location,
)
from botocore.hooks import HierarchicalEmitter, logger
from botocore.signers import (
add_dsql_generate_db_auth_token_methods as boto_add_dsql_generate_db_auth_token_methods,
)
from botocore.signers import (
add_generate_db_auth_token as boto_add_generate_db_auth_token,
)
Expand All @@ -25,6 +28,7 @@
parse_get_bucket_location,
)
from .signers import (
add_dsql_generate_db_auth_token_methods,
add_generate_db_auth_token,
add_generate_presigned_post,
add_generate_presigned_url,
Expand All @@ -37,6 +41,7 @@
boto_add_generate_presigned_post: add_generate_presigned_post,
boto_add_generate_db_auth_token: add_generate_db_auth_token,
boto_parse_get_bucket_location: parse_get_bucket_location,
boto_add_dsql_generate_db_auth_token_methods: add_dsql_generate_db_auth_token_methods,
}


Expand Down
91 changes: 90 additions & 1 deletion aiobotocore/signers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import botocore
import botocore.auth
from botocore.exceptions import UnknownClientMethodError
from botocore.exceptions import ParamValidationError, UnknownClientMethodError
from botocore.signers import (
RequestSigner,
S3PostPresigner,
Expand Down Expand Up @@ -200,6 +200,15 @@ def add_generate_db_auth_token(class_attributes, **kwargs):
class_attributes['generate_db_auth_token'] = generate_db_auth_token


def add_dsql_generate_db_auth_token_methods(class_attributes, **kwargs):
class_attributes['generate_db_connect_auth_token'] = (
dsql_generate_db_connect_auth_token
)
class_attributes['generate_db_connect_admin_auth_token'] = (
dsql_generate_db_connect_admin_auth_token
)


async def generate_db_auth_token(
self, DBHostname, Port, DBUsername, Region=None
):
Expand Down Expand Up @@ -256,6 +265,86 @@ async def generate_db_auth_token(
return presigned_url[len(scheme) :]


async def _dsql_generate_db_auth_token(
self, Hostname, Action, Region=None, ExpiresIn=900
):
"""Generate a DSQL database token for an arbitrary action.
:type Hostname: str
:param Hostname: The DSQL endpoint host name.
:type Action: str
:param Action: Action to perform on the cluster (DbConnectAdmin or DbConnect).
:type Region: str
:param Region: The AWS region where the DSQL Cluster is hosted. If None, the client region will be used.
:type ExpiresIn: int
:param ExpiresIn: The token expiry duration in seconds (default is 900 seconds).
:return: A presigned url which can be used as an auth token.
"""
possible_actions = ("DbConnect", "DbConnectAdmin")

if Action not in possible_actions:
raise ParamValidationError(
report=f"Received {Action} for action but expected one of: {', '.join(possible_actions)}"
)

if Region is None:
Region = self.meta.region_name

request_dict = {
'url_path': '/',
'query_string': '',
'headers': {},
'body': {
'Action': Action,
},
'method': 'GET',
}
scheme = 'https://'
endpoint_url = f'{scheme}{Hostname}'
prepare_request_dict(request_dict, endpoint_url)
presigned_url = await self._request_signer.generate_presigned_url(
operation_name=Action,
request_dict=request_dict,
region_name=Region,
expires_in=ExpiresIn,
signing_name='dsql',
)
return presigned_url[len(scheme) :]


async def dsql_generate_db_connect_auth_token(
self, Hostname, Region=None, ExpiresIn=900
):
"""Generate a DSQL database token for the "DbConnect" action.
:type Hostname: str
:param Hostname: The DSQL endpoint host name.
:type Region: str
:param Region: The AWS region where the DSQL Cluster is hosted. If None, the client region will be used.
:type ExpiresIn: int
:param ExpiresIn: The token expiry duration in seconds (default is 900 seconds).
:return: A presigned url which can be used as an auth token.
"""
return await _dsql_generate_db_auth_token(
self, Hostname, "DbConnect", Region, ExpiresIn
)


async def dsql_generate_db_connect_admin_auth_token(
self, Hostname, Region=None, ExpiresIn=900
):
"""Generate a DSQL database token for the "DbConnectAdmin" action.
:type Hostname: str
:param Hostname: The DSQL endpoint host name.
:type Region: str
:param Region: The AWS region where the DSQL Cluster is hosted. If None, the client region will be used.
:type ExpiresIn: int
:param ExpiresIn: The token expiry duration in seconds (default is 900 seconds).
:return: A presigned url which can be used as an auth token.
"""
return await _dsql_generate_db_auth_token(
self, Hostname, "DbConnectAdmin", Region, ExpiresIn
)


class AioS3PostPresigner(S3PostPresigner):
async def generate_presigned_post(
self,
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,18 @@ classifiers = [
dynamic = ["version", "readme"]

dependencies = [
"botocore >=1.35.67, <1.35.74", # NOTE: When updating, always keep `project.optional-dependencies` aligned
"botocore >=1.35.74, <1.35.82", # NOTE: When updating, always keep `project.optional-dependencies` aligned
"aiohttp >=3.9.2, <4.0.0",
"wrapt >=1.10.10, <2.0.0",
"aioitertools >=0.5.1, <1.0.0",
]

[project.optional-dependencies]
awscli = [
"awscli >=1.36.8, <1.36.15",
"awscli >=1.36.15, <1.36.23",
]
boto3 = [
"boto3 >=1.35.67, <1.35.74",
"boto3 >=1.35.74, <1.35.82",
]

[project.urls]
Expand Down
1 change: 1 addition & 0 deletions requirements-dev.in
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ docker~=7.1
moto[server,s3,sqs,awslambda,dynamodb,cloudformation,sns,batch,ec2,rds]~=4.2.9
pre-commit~=3.5.0
pytest-asyncio~=0.23.8
time-machine~=2.15.0
tomli; python_version < "3.11" # Requirement for tests/test_version.py
39 changes: 39 additions & 0 deletions tests/boto_tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Copyright 2012-2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.

from botocore.compat import parse_qs, urlparse


def _urlparse(url):
if isinstance(url, bytes):
# Not really necessary, but it helps to reduce noise on Python 2.x
url = url.decode('utf8')

Check warning on line 20 in tests/boto_tests/__init__.py

View check run for this annotation

Codecov / codecov/patch

tests/boto_tests/__init__.py#L20

Added line #L20 was not covered by tests
return urlparse(url)


def assert_url_equal(url1, url2):
parts1 = _urlparse(url1)
parts2 = _urlparse(url2)

# Because the query string ordering isn't relevant, we have to parse
# every single part manually and then handle the query string.
assert parts1.scheme == parts2.scheme
assert parts1.netloc == parts2.netloc
assert parts1.path == parts2.path
assert parts1.params == parts2.params
assert parts1.fragment == parts2.fragment
assert parts1.username == parts2.username
assert parts1.password == parts2.password
assert parts1.hostname == parts2.hostname
assert parts1.port == parts2.port
assert parse_qs(parts1.query) == parse_qs(parts2.query)
113 changes: 113 additions & 0 deletions tests/boto_tests/unit/test_signers.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
import datetime
from datetime import timezone
from unittest import mock

import pytest
from botocore.exceptions import ParamValidationError

import aiobotocore.credentials
import aiobotocore.session
import aiobotocore.signers
from tests.boto_tests import assert_url_equal

DATE = datetime.datetime(2024, 11, 7, 17, 39, 33, tzinfo=timezone.utc)


async def test_signers_generate_db_auth_token(rds_client):
Expand All @@ -31,3 +49,98 @@ async def test_signers_generate_db_auth_token(rds_client):
assert result2.startswith(
'prod-instance.us-east-1.rds.amazonaws.com:3306/?AWSAccessKeyId=xxx&'
)


class TestDSQLGenerateDBAuthToken:
@pytest.fixture(scope="session")
def hostname(self):
return 'test.dsql.us-east-1.on.aws'

@pytest.fixture(scope="session")
def action(self):
return 'DbConnect'

@pytest.fixture
async def client(self, session):
async with session.create_client(
'dsql',
region_name='us-east-1',
aws_access_key_id='ACCESS_KEY',
aws_secret_access_key='SECRET_KEY',
aws_session_token="SESSION_TOKEN",
) as client:
yield client

async def test_dsql_generate_db_auth_token(
self, client, hostname, action, time_machine
):
time_machine.move_to(DATE, tick=False)

result = await aiobotocore.signers._dsql_generate_db_auth_token(
client, hostname, action
)

expected_result = (
'test.dsql.us-east-1.on.aws/?Action=DbConnect'
'&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential='
'ACCESS_KEY%2F20241107%2Fus-east-1%2Fdsql%2Faws4_request'
'&X-Amz-Date=20241107T173933Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host'
'&X-Amz-Security-Token=SESSION_TOKEN&X-Amz-Signature='
'57fe03e060348aaa21405c239bf02572bbc911076e94dcd65c12ae569dd8fcf4'
)

# A scheme needs to be appended to the beginning or urlsplit may fail
# on certain systems.
assert_url_equal('https://' + result, 'https://' + expected_result)

async def test_dsql_generate_db_connect_auth_token(
self, client, hostname, time_machine
):
time_machine.move_to(DATE, tick=False)

result = await aiobotocore.signers.dsql_generate_db_connect_auth_token(
client, hostname
)

expected_result = (
'test.dsql.us-east-1.on.aws/?Action=DbConnect'
'&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential='
'ACCESS_KEY%2F20241107%2Fus-east-1%2Fdsql%2Faws4_request'
'&X-Amz-Date=20241107T173933Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host'
'&X-Amz-Security-Token=SESSION_TOKEN&X-Amz-Signature='
'57fe03e060348aaa21405c239bf02572bbc911076e94dcd65c12ae569dd8fcf4'
)

# A scheme needs to be appended to the beginning or urlsplit may fail
# on certain systems.
assert_url_equal('https://' + result, 'https://' + expected_result)

async def test_dsql_generate_db_connect_admin_auth_token(
self, client, hostname, time_machine
):
time_machine.move_to(DATE, tick=False)

result = await aiobotocore.signers.dsql_generate_db_connect_admin_auth_token(
client, hostname
)

expected_result = (
'test.dsql.us-east-1.on.aws/?Action=DbConnectAdmin'
'&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential='
'ACCESS_KEY%2F20241107%2Fus-east-1%2Fdsql%2Faws4_request'
'&X-Amz-Date=20241107T173933Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host'
'&X-Amz-Security-Token=SESSION_TOKEN&X-Amz-Signature='
'5ac084bc7cabccc19a52a5d1b5c24b50d3ce143f43b659bd484c91aaf555e190'
)

# A scheme needs to be appended to the beginning or urlsplit may fail
# on certain systems.
assert_url_equal('https://' + result, 'https://' + expected_result)

async def test_dsql_generate_db_auth_token_invalid_action(
self, client, hostname
):
with pytest.raises(ParamValidationError):
await aiobotocore.signers._dsql_generate_db_auth_token(
client, hostname, "FooBar"
)
16 changes: 16 additions & 0 deletions tests/test_patches.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,13 @@
from botocore.signers import (
RequestSigner,
S3PostPresigner,
_dsql_generate_db_auth_token,
add_dsql_generate_db_auth_token_methods,
add_generate_db_auth_token,
add_generate_presigned_post,
add_generate_presigned_url,
dsql_generate_db_connect_admin_auth_token,
dsql_generate_db_connect_auth_token,
generate_db_auth_token,
generate_presigned_post,
generate_presigned_url,
Expand Down Expand Up @@ -497,6 +501,18 @@
},
add_generate_db_auth_token: {'f61014e6fac4b5c7ee7ac2d2bec15fb16fa9fbe5'},
generate_db_auth_token: {'1f37e1e5982d8528841ce6b79f229b3e23a18959'},
add_dsql_generate_db_auth_token_methods: {
'95c68a1aac8ee549e11b5dc010b6bb03f9ea00ea',
},
_dsql_generate_db_auth_token: {
'53034b0475122209509db59fbd79a4ead70836cf',
},
dsql_generate_db_connect_auth_token: {
'29b5919b695113c55452f2325d0ff66dd719a647'
},
dsql_generate_db_connect_admin_auth_token: {
'd7e7a4899b8fd3a544dd1df95196517e2cfd5c84'
},
# tokens.py
create_token_resolver: {'b287f4879235a4292592a49b201d2b0bc2dbf401'},
DeferredRefreshableToken.__init__: {
Expand Down

0 comments on commit a73f8c2

Please sign in to comment.