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

Improve umask handling and ability to mock for tests #106

Merged
merged 5 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ['3.10', '3.11']
python-version: ['3.10', '3.11', '3.12']

steps:
- name: Checkout repository
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
[//]: # (current developments)

* Add Python 3.12 to test matrix.
* Improve umask handling.
dholth marked this conversation as resolved.
Show resolved Hide resolved
* Add `transmute_stream(...)` to create `.conda` from `(TarFile, TarInfo)`. (#90)
iterators, allowing more creative data sources than just `.tar.bz2` inputs.
* Add `create` module with `TarFile` interface for creating `.conda`
Expand Down
6 changes: 3 additions & 3 deletions conda_package_streaming/package_streaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def __str__(self):


class TarfileNoSameOwner(tarfile.TarFile):
def __init__(self, *args, umask=UMASK, **kwargs):
def __init__(self, *args, umask: int | None = None, **kwargs):
"""Open an (uncompressed) tar archive `name'. `mode' is either 'r' to
read from an existing archive, 'a' to append data to an existing
file or 'w' to create a new file overwriting an existing one. `mode'
Expand All @@ -46,7 +46,7 @@ def __init__(self, *args, umask=UMASK, **kwargs):
`fileobj' is not closed, when TarFile is closed.
"""
super().__init__(*args, **kwargs)
self.umask = umask
self.umask = umask if umask is not None else UMASK

def chown(self, tarinfo, targetpath, numeric_owner):
"""
Expand All @@ -61,7 +61,7 @@ def chmod(self, tarinfo, targetpath):
umask.
"""
try:
os.chmod(targetpath, tarinfo.mode & (0o777 - self.umask))
os.chmod(targetpath, tarinfo.mode & (-1 & (~self.umask)))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

improved mask calculation

except OSError as e:
raise tarfile.ExtractError("could not change mode") from e

Expand Down
35 changes: 31 additions & 4 deletions tests/test_extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,11 @@ def test_oserror(tmp_path):
tar = empty_tarfile("empty-test")

class TarELOOP(tarfile.TarFile):
def extractall(self, path=None, members=None):
def extractall(self, path=None, members=None, filter=None):
raise OSError(ELOOP, "case sensitivity")

class TarOSError(tarfile.TarFile):
def extractall(self, path=None, members=None):
def extractall(self, path=None, members=None, filter=None):
raise OSError("not eloop")

def stream(cls):
Expand Down Expand Up @@ -105,6 +105,7 @@ def test_slip(tmp_path):
with pytest.raises(exceptions.SafetyError):
extract.extract_stream(stream(tar), tmp_path)

# If we are using tarfile.filter, the leading / will be stripped instead.
tar2 = empty_tarfile(name="/absolute")

with pytest.raises(exceptions.SafetyError):
Expand All @@ -130,16 +131,42 @@ def test_umask(tmp_path, mocker):

Mock umask in case it is different on your system.
"""
mocker.patch("conda_package_streaming.package_streaming.UMASK", new=0o22)
MOCK_UMASK = 0o022
mocker.patch("conda_package_streaming.package_streaming.UMASK", new=MOCK_UMASK)

assert (
package_streaming.TarfileNoSameOwner(fileobj=empty_tarfile("file.txt")).umask
== MOCK_UMASK
)

# [('S_IFREG', 32768), ('UF_HIDDEN', 32768), ('FILE_ATTRIBUTE_INTEGRITY_STREAM', 32768)]

# Of the high bits 100755 highest bit 1 can mean just "is regular file"

tar3 = empty_tarfile(name="naughty_umask", mode=0o777)

stat_check = stat.S_IRGRP
stat_name = "S_IRGRP"

extract.extract_stream(stream_stdlib(tar3), tmp_path)
mode = (tmp_path / "naughty_umask").stat().st_mode
assert mode & stat.S_IWGRP, "%o" % mode
# is the new .extractall(filter=) erasing group-writable?
assert mode & stat.S_IRGRP, f"Has {stat_name}? %o != %o" % (
mode,
mode & stat_check,
)

# specifically forbid that stat bit
MOCK_UMASK |= stat_check
mocker.patch("conda_package_streaming.package_streaming.UMASK", new=MOCK_UMASK)

tar3.seek(0)
extract.extract_stream(stream(tar3), tmp_path)
mode = (tmp_path / "naughty_umask").stat().st_mode
assert not mode & stat_check, f"No {stat_name} due to umask? %o != %o" % (
mode,
mode & stat_check,
)
assert not mode & stat.S_IWGRP, "%o" % mode


Expand Down
Loading