Skip to content

Commit

Permalink
Add "force_unix_mode" flag to FakeFilesystem.chmod
Browse files Browse the repository at this point in the history
- makes it possible to simulate inaccessible paths under Windows
- see pytest-dev#720
  • Loading branch information
mrbean-bremen committed Jan 23, 2023
1 parent e2d0f6e commit f2fdab2
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 13 deletions.
4 changes: 4 additions & 0 deletions CHANGES.md
Expand Up @@ -3,6 +3,10 @@ The released versions correspond to PyPI releases.

## Unreleased

### Features
* added possibility to set a path inaccessible under Windows by using `chown()` with
the `force_unix_mode` flag (see [#720](../../issues/720))

## [Version 5.1.0](https://pypi.python.org/pypi/pyfakefs/5.1.0) (2023-01-12)
New version before Debian freeze

Expand Down
23 changes: 23 additions & 0 deletions docs/usage.rst
Expand Up @@ -911,6 +911,29 @@ The following test works both under Windows and Linux:
assert os.path.splitdrive(r"C:\foo\bar") == ("C:", r"\foo\bar")
assert os.path.ismount("C:")
Set file as inaccessible under Windows
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Normally, if you try to set a file or directory as inaccessible using ``chmod`` under
Windows, the value you provide is masked by a value that always ensures that no read
permissions for any user are removed. In reality, there is the possibility to make
a file or directory unreadable using the Windows ACL API, which is not directly
supported in the Python filesystem API. To make this possible to test, there is the
possibility to use the ``force_unix_mode`` argument to ``FakeFilesystem.chmod``:

.. code:: python
def test_is_file_for_unreadable_dir_windows(fs):
fs.os = OSType.WINDOWS
path = pathlib.Path("/foo/bar")
fs.create_file(path)
# normal chmod does not really set the mode to 0
self.fs.chmod("/foo", 0o000)
assert path.is_file()
# but it does in forced UNIX mode
fs.chmod("/foo", 0o000, force_unix_mode=True)
with pytest.raises(PermissionError):
path.is_file()
.. _`example.py`: https://github.com/pytest-dev/pyfakefs/blob/main/pyfakefs/tests/example.py
.. _`example_test.py`: https://github.com/pytest-dev/pyfakefs/blob/main/pyfakefs/tests/example_test.py
Expand Down
14 changes: 11 additions & 3 deletions pyfakefs/fake_filesystem.py
Expand Up @@ -1438,19 +1438,27 @@ def raise_for_filepath_ending_with_separator(
error_nr = errno.EINVAL if self.is_windows_fs else errno.ENOTDIR
self.raise_os_error(error_nr, entry_path)

def chmod(self, path: AnyStr, mode: int, follow_symlinks: bool = True) -> None:
def chmod(
self,
path: AnyStr,
mode: int,
follow_symlinks: bool = True,
force_unix_mode: bool = False,
) -> None:
"""Change the permissions of a file as encoded in integer mode.
Args:
path: (str) Path to the file.
mode: (int) Permissions.
follow_symlinks: If `False` and `path` points to a symlink,
the link itself is affected instead of the linked object.
force_unix_mode: if True and run under Windows, the mode is not
adapted for Windows to allow making dirs unreadable
"""
file_object = self.resolve(
path, follow_symlinks, allow_fd=True, check_owner=True
)
if self.is_windows_fs:
if self.is_windows_fs and not force_unix_mode:
if mode & PERM_WRITE:
file_object.st_mode = file_object.st_mode | 0o222
else:
Expand Down Expand Up @@ -2355,7 +2363,7 @@ def resolve(

if follow_symlinks:
return self.get_object_from_normpath(
self.resolve_path(file_path, check_read_perm),
self.resolve_path(file_path, allow_fd),
check_read_perm,
check_owner,
)
Expand Down
24 changes: 17 additions & 7 deletions pyfakefs/tests/fake_os_test.py
Expand Up @@ -5366,14 +5366,21 @@ def test_default_path(self):
class FakeOsUnreadableDirTest(FakeOsModuleTestBase):
def setUp(self):
if self.use_real_fs():
# make sure no dir is created if skipped
# unreadable dirs in Windows are only simulated
# and cannot be created in the real OS using file system
# functions only
self.check_posix_only()
super(FakeOsUnreadableDirTest, self).setUp()
self.check_posix_only()
self.dir_path = self.make_path("some_dir")
self.file_path = self.os.path.join(self.dir_path, "some_file")
self.create_file(self.file_path)
self.os.chmod(self.dir_path, 0o000)
self.chmod(self.dir_path, 0o000)

def chmod(self, path, mode):
if self.is_windows_fs:
self.filesystem.chmod(path, mode, force_unix_mode=True)
else:
self.os.chmod(path, mode)

def test_listdir_unreadable_dir(self):
if not is_root():
Expand All @@ -5382,12 +5389,13 @@ def test_listdir_unreadable_dir(self):
self.assertEqual(["some_file"], self.os.listdir(self.dir_path))

def test_listdir_user_readable_dir(self):
self.os.chmod(self.dir_path, 0o600)
self.chmod(self.dir_path, 0o600)
self.assertEqual(["some_file"], self.os.listdir(self.dir_path))
self.os.chmod(self.dir_path, 0o000)
self.chmod(self.dir_path, 0o000)

def test_listdir_user_readable_dir_from_other_user(self):
self.skip_real_fs() # won't change user in real fs
self.check_posix_only()
user_id = USER_ID
set_uid(user_id + 1)
dir_path = self.make_path("dir1")
Expand All @@ -5412,6 +5420,7 @@ def test_listdir_group_readable_dir_from_other_user(self):

def test_listdir_group_readable_dir_from_other_group(self):
self.skip_real_fs() # won't change user in real fs
self.check_posix_only()
group_id = GROUP_ID
set_gid(group_id + 1)
dir_path = self.make_path("dir1")
Expand All @@ -5438,9 +5447,9 @@ def test_stat_unreadable_dir(self):
self.assertEqual(0, self.os.stat(self.dir_path).st_mode & 0o666)

def test_chmod_unreadable_dir(self):
self.os.chmod(self.dir_path, 0o666)
self.chmod(self.dir_path, 0o666)
self.assertEqual(0o666, self.os.stat(self.dir_path).st_mode & 0o666)
self.os.chmod(self.dir_path, 0o000)
self.chmod(self.dir_path, 0o000)
self.assertEqual(0, self.os.stat(self.dir_path).st_mode & 0o666)

def test_stat_file_in_unreadable_dir(self):
Expand All @@ -5450,6 +5459,7 @@ def test_stat_file_in_unreadable_dir(self):
self.assertEqual(0, self.os.stat(self.file_path).st_size)

def test_remove_unreadable_dir(self):
self.check_posix_only()
dir_path = self.make_path("dir1")
self.create_dir(dir_path, perm=0o000)
self.assertTrue(self.os.path.exists(dir_path))
Expand Down
22 changes: 19 additions & 3 deletions pyfakefs/tests/fake_pathlib_test.py
Expand Up @@ -29,7 +29,7 @@
from unittest import mock

from pyfakefs import fake_pathlib, fake_filesystem, fake_filesystem_unittest
from pyfakefs.fake_filesystem import is_root
from pyfakefs.fake_filesystem import is_root, OSType
from pyfakefs.helpers import IS_PYPY
from pyfakefs.tests.test_utils import RealFsTestMixin

Expand Down Expand Up @@ -762,7 +762,6 @@ def use_real_fs(self):
return True


@unittest.skipIf(sys.version_info < (3, 6), "path-like objects new in Python 3.6")
class FakePathlibUsageInOsFunctionsTest(RealPathlibTestCase):
"""Test that many os / os.path functions accept a path-like object
since Python 3.6. The functionality of these functions is tested
Expand Down Expand Up @@ -1121,7 +1120,6 @@ def use_real_fs(self):
return True


@unittest.skipIf(sys.version_info < (3, 6), "Path-like objects new in Python 3.6")
class FakeFilesystemPathLikeObjectTest(unittest.TestCase):
def setUp(self):
self.filesystem = fake_filesystem.FakeFilesystem(path_separator="/")
Expand Down Expand Up @@ -1180,5 +1178,23 @@ def test_add_existing_real_directory_with_pathlib_path(self):
)


class FakeFilesystemChmodTest(fake_filesystem_unittest.TestCase):
def setUp(self) -> None:
self.setUpPyfakefs()

@unittest.skipIf(sys.platform != "win32", "Windows specific test")
def test_is_file_for_unreadable_dir_windows(self):
self.fs.os = OSType.WINDOWS
path = pathlib.Path("/foo/bar")
self.fs.create_file(path)
# normal chmod does not really set the mode to 0
self.fs.chmod("/foo", 0o000)
self.assertTrue(path.is_file())
# but it does in forced UNIX mode
self.fs.chmod("/foo", 0o000, force_unix_mode=True)
with self.assertRaises(PermissionError):
path.is_file()


if __name__ == "__main__":
unittest.main(verbosity=2)

0 comments on commit f2fdab2

Please sign in to comment.