Skip to content

Commit 1de20ba

Browse files
committed
Merge branch 'issue198-permission-issues-with-privatejsonfile-on-windows'
2 parents b23ef93 + 8f263eb commit 1de20ba

File tree

5 files changed

+39
-12
lines changed

5 files changed

+39
-12
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2424
- Fixed/improved math operator/process support for `DataCube`s in "apply" mode (non-"band math"),
2525
allowing expressions like `10 * cube.log10()` and `~(cube == 0)`
2626
([#123](https://github.com/Open-EO/openeo-python-client/issues/123))
27+
- Support `PrivateJsonFile` permissions properly on Windows, using oschmod library.
28+
([#198](https://github.com/Open-EO/openeo-python-client/issues/198))
29+
- Fixed some broken unit tests on Windows related to path (separator) handling.
30+
([#350](https://github.com/Open-EO/openeo-python-client/issues/350))
2731

2832

2933
## [0.13.0] - 2022-10-10 - "UDF UX" release

openeo/rest/auth/config.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
import json
1010
import logging
11-
import platform
1211
import stat
1312
from datetime import datetime
1413
from pathlib import Path
@@ -18,22 +17,42 @@
1817
from openeo.config import get_user_config_dir, get_user_data_dir
1918
from openeo.util import rfc3339, deep_get, deep_set
2019

20+
try:
21+
# Use oschmod when available (fall back to POSIX-only functionality from stdlib otherwise)
22+
# TODO: enforce oschmod as dependency for all platforms?
23+
import oschmod
24+
except ImportError:
25+
oschmod = None
26+
27+
2128
_PRIVATE_PERMS = stat.S_IRUSR | stat.S_IWUSR
2229

2330
log = logging.getLogger(__name__)
2431

2532

33+
def get_file_mode(path: Path) -> int:
34+
"""Get the file permission bits in a way that works on both *nix and Windows platforms."""
35+
if oschmod:
36+
return oschmod.get_mode(str(path))
37+
return path.stat().st_mode
38+
39+
40+
def set_file_mode(path: Path, mode: int):
41+
"""Set the file permission bits in a way that works on both *nix and Windows platforms."""
42+
if oschmod:
43+
oschmod.set_mode(str(path), mode=mode)
44+
else:
45+
path.chmod(mode=mode)
46+
47+
2648
def assert_private_file(path: Path):
2749
"""Check that given file is only readable by user."""
28-
mode = path.stat().st_mode
50+
mode = get_file_mode(path)
2951
if (mode & stat.S_IRWXG) or (mode & stat.S_IRWXO):
3052
message = "File {p} could be readable by others: mode {a:o} (expected: {e:o}).".format(
3153
p=path, a=mode & (stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO), e=_PRIVATE_PERMS
3254
)
33-
if platform.system() == 'Windows':
34-
log.info(message)
35-
else:
36-
raise PermissionError(message)
55+
raise PermissionError(message)
3756

3857

3958
def utcnow_rfc3339() -> str:
@@ -86,7 +105,7 @@ def _write(self, data: dict):
86105
# TODO: add file locking to avoid race conditions?
87106
with self._path.open("w", encoding="utf8") as f:
88107
json.dump(data, f, indent=2)
89-
self._path.chmod(mode=_PRIVATE_PERMS)
108+
set_file_mode(self._path, mode=_PRIVATE_PERMS)
90109
assert_private_file(self._path)
91110

92111
def get(self, *keys, default=None) -> Union[dict, str, int]:

setup.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
'xarray>=0.12.3',
4848
'pandas>0.20.0',
4949
'deprecated>=1.2.12',
50+
'oschmod>=0.3.12; sys_platform == "win32"',
5051
],
5152
extras_require={
5253
"dev": tests_require + [
@@ -55,6 +56,9 @@
5556
"sphinx-autodoc-typehints",
5657
"flake8",
5758
"myst-parser",
59+
],
60+
"oschmod": [ # install oschmod even when platform is not Windows, e.g. for testing in CI.
61+
"oschmod>=0.3.12"
5862
]
5963
},
6064
entry_points={

tests/rest/auth/test_config.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import pytest
55

66
import openeo.rest.auth.config
7-
from openeo.rest.auth.config import RefreshTokenStore, AuthConfig, PrivateJsonFile
7+
from openeo.rest.auth.config import RefreshTokenStore, AuthConfig, PrivateJsonFile, get_file_mode
88

99

1010
class TestPrivateJsonFile:
@@ -31,7 +31,7 @@ def test_permissions(self, tmp_path):
3131
assert not private.path.exists()
3232
private.set("foo", "bar", value=42)
3333
assert private.path.exists()
34-
st_mode = private.path.stat().st_mode
34+
st_mode = get_file_mode(private.path)
3535
assert st_mode & 0o777 == 0o600
3636

3737
def test_wrong_permissions(self, tmp_path):
@@ -151,7 +151,7 @@ def test_public_file(self, tmp_path):
151151
def test_permissions(self, tmp_path):
152152
r = RefreshTokenStore(path=tmp_path)
153153
r.set_refresh_token("foo", "bar", "imd6$3cr3t")
154-
st_mode = (tmp_path / RefreshTokenStore.DEFAULT_FILENAME).stat().st_mode
154+
st_mode = get_file_mode(tmp_path / RefreshTokenStore.DEFAULT_FILENAME)
155155
assert st_mode & 0o777 == 0o600
156156

157157
def test_get_set_refresh_token(self, tmp_path):

tests/test_config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import contextlib
22
import logging
33
import os
4-
import platform
54
import random
65
import re
76
import textwrap
@@ -168,6 +167,7 @@ def test_get_config_verbose(tmp_path, caplog, capsys, verbose, force_interactive
168167
config = get_config()
169168
assert config.get("general.verbose") == verbose
170169

171-
regex = re.compile(f"Loaded.*config from.*{config_path}")
170+
# re.escape config_path because Windows paths contain "\"
171+
regex = re.compile(f"Loaded.*config from.*{re.escape(str(config_path))}")
172172
assert regex.search(caplog.text)
173173
assert bool(regex.search(capsys.readouterr().out)) == on_stdout

0 commit comments

Comments
 (0)