Skip to content

Commit

Permalink
Fixed the issue when a secret key contains special chars
Browse files Browse the repository at this point in the history
"Bump a new version: 0.3.0 → 0.3.1"

Fixed Amazon S3 parser issue with empty host

"Bump a new version: 0.3.1 → 0.3.2"
  • Loading branch information
marazmiki committed Jul 31, 2022
1 parent 02aae0e commit fa6ecdb
Show file tree
Hide file tree
Showing 12 changed files with 105 additions and 12 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.3.0
current_version = 0.3.2
commit = True
tag = True
message = "Bump a new version: {current_version} → {new_version}"
Expand Down
3 changes: 3 additions & 0 deletions docs/source/authors.rst
@@ -1,2 +1,5 @@
Authors
=======

* Mikhail Porokhnovnichenko `@marazmiki <https://github.com/marazmiki>`_
* Viktor Nikel `@nikell28 <https://github.com/nikell28>`_
37 changes: 36 additions & 1 deletion docs/source/changelog.rst
@@ -1,6 +1,42 @@
Changelog
#########

0.3.x
=====

0.3.1
-----

Released 2022-06-06

* Fixed the issue when a secret key contains special chars (fixed by urlquoting). The solution authored by `@nikell28 <https://github.com/nikell28>`_ from the refused `PR <https://github.com/marazmiki/s3-parse-url/pull/2>`_
* Filled ``docs/source/authors.rst`` a bit :)

0.3.0
-----

Released 2022-01-06

A technical minor release to check if it will be automatically delivered to PyPI.

0.2.x
=====

0.2.1
-----

Released 2022-01-06

* Fixed a misspell in README that caused a fail while publishing the package
* Fine tuned the ``.github/workflows/pypi.yml``

0.2.0
-----

* Prettified the project metadata inside ``pyproject.toml`` to make pypi release more sexy
* A new action to automatic publish the package on PYPI
* ce32a12 2022-01-06 | Decorate our readme with badges [Mikhail Porokhovnichenko]

0.1.x
=====

Expand All @@ -19,4 +55,3 @@ The initial release issued 2021-10-05
* Yandex Cloud (``yandex://``)

* Ability to add an arbitrary scheme: wanna ``linode://``? Or ``digitalocean://``? Fill free to add

2 changes: 1 addition & 1 deletion docs/source/conf.py
Expand Up @@ -2,7 +2,7 @@
copyright = "2021, Mikhail Porokhovnichenko <marazmiki@gmail.com>"
author = 'Mikhail Porokhovnichenko <marazmiki@gmail.com>'

release = "0.3.0"
release = "0.3.2"
extensions = ["sphinx.ext.autodoc"]
templates_path = ["_templates"]
source_suffix = ".rst"
Expand Down
14 changes: 14 additions & 0 deletions docs/source/examples.rst
Expand Up @@ -57,3 +57,17 @@ Examples
asyncio.run(test_aiobotocore())
test_boto3()
.. warning::

Sometimes, Amazon can generate secret access keys containing special chars
like /. If so, you should prepare a safe version of the key by quoting these
chars manually (in the example you should get %25). Feel free to use this
handy snippet to get a safe (i.e. quoted) secret access key.
Example:

.. code::
echo "pass\/\/ord" | python -c "import sys; from urllib.parse import quote_plus; print(quote_plus(sys.stdin.read().strip()))"
pass%5C%2F%5C%2Ford
2 changes: 1 addition & 1 deletion pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "s3-parse-url"
version = "0.3.0"
version = "0.3.2"
description = "A small tool to parse URLs for S3 compatible storage services"
license = "MIT"
readme = "README.rst"
Expand Down
2 changes: 1 addition & 1 deletion src/s3_parse_url/__init__.py
Expand Up @@ -4,7 +4,7 @@
from s3_parse_url.exceptions import UnsupportedStorage
from s3_parse_url.storages import SUPPORTED_STORAGES

__version__ = "0.3.0"
__version__ = "0.3.2"
__all__ = ["parse_s3_dsn", "parse_s3_url", "UnsupportedStorage"]


Expand Down
6 changes: 3 additions & 3 deletions src/s3_parse_url/base.py
@@ -1,5 +1,5 @@
from typing import List, Optional
from urllib.parse import parse_qs, urlparse
from urllib.parse import parse_qs, unquote, urlparse

from s3_parse_url.exceptions import InsecureNotAllowed, UnsupportedStorage

Expand Down Expand Up @@ -42,7 +42,7 @@ def access_key_id(self) -> str:

@property
def secret_access_key(self) -> str:
return self._parsed_bits["aws_secret_access_key"]
return unquote(self._parsed_bits["aws_secret_access_key"])

@property
def key(self) -> str:
Expand Down Expand Up @@ -70,7 +70,7 @@ def _is_host_given(self):
"Checks if in the DSN there is an explicitly given domain name"
return any((
self._raw_bits.port is not None,
self._raw_bits.hostname.count(".") > 0
self._raw_bits.hostname and self._raw_bits.hostname.count(".") > 0
))

def _parse_endpoint_url(self):
Expand Down
6 changes: 3 additions & 3 deletions src/s3_parse_url/storages/amazon.py
Expand Up @@ -10,10 +10,10 @@ class AmazonS3(S3DataSource):
)

def _is_amazon_host(self):
return all((
self._raw_bits.hostname,
return (
self._raw_bits.hostname is not None and
self._raw_bits.hostname.lower().endswith(self.amazon_s3_domains)
))
)

def _strip_amazon_prefix(self):
for d in self.amazon_s3_domains:
Expand Down
5 changes: 5 additions & 0 deletions tests/storages/test_amazon.py
Expand Up @@ -36,3 +36,8 @@ def test_amazon_specified_parse_url(dsn, expected_values):
def test_just_for_coverage():
dsn = AmazonS3("s3://bucket-not")
dsn._strip_amazon_prefix()


def test_regression_empty_endpoiont():
dsn = "s3://user:pass@/bucket/?region_name=us-east-1"
parse_s3_dsn(dsn)
36 changes: 36 additions & 0 deletions tests/test_s3_parse_url.py
Expand Up @@ -23,6 +23,28 @@
("s3://my-bucket/my/key_#1.txt", {"bucket_name": "my-bucket"}),
("s3://my-bucket/my/key_#1.txt", {"key": "my/key_#1.txt"}),
("minio+insecure://my-bucket", {"endpoint_url": None}),
("s3://xxx:yyy@my-bucket/key.txt?region=us-east-2", {
"bucket_name": "my-bucket",
"key": "key.txt",
"region": "us-east-2",
"access_key_id": "xxx",
"secret_access_key": "yyy",
"endpoint_url": None,
}),
("s3://AKIAxxxMOO:XxxPKC%2Byyy@audio?region=us-east-1", {
"bucket_name": "audio",
"access_key_id": "AKIAxxxMOO",
"secret_access_key": "XxxPKC+yyy",
"region": "us-east-1",
}),
(
"s3://XXXX:YYYY%2FZZZZ@bucket53?region=us-east-3", {
"bucket_name": "bucket53",
"region": "us-east-3",
"access_key_id": "XXXX",
"secret_access_key": "YYYY/ZZZZ"
}
),
],
)
def test_s3_parse_url(dsn, expected_values):
Expand Down Expand Up @@ -55,3 +77,17 @@ def test_check_if_compatible(monkeypatch, dsn, allowed_schemas, expect_error):
AmazonS3(dsn)
else:
AmazonS3(dsn)


@pytest.mark.parametrize(
argnames="dsn, expected_password",
argvalues=[
("s3://xxx:yy%2Fy@my-bucket", "yy/y"),
("s3://xxx:yy%23y@my-bucket", "yy#y"),
("s3://xxx:yy%3Ay@my-bucket", "yy:y"),
],
)
def test_s3_parse_url_when_secret_key_contains_special_chars(
dsn, expected_password
):
assert parse_s3_dsn(dsn).secret_access_key == expected_password
2 changes: 1 addition & 1 deletion tests/test_version.py
Expand Up @@ -2,4 +2,4 @@


def test_version():
assert __version__ == "0.3.0"
assert __version__ == "0.3.2"

0 comments on commit fa6ecdb

Please sign in to comment.