Skip to content

Commit de07c27

Browse files
Issue #198 Code review: simplify and encapsulate getting/setting file permissions.
1 parent 50c00f2 commit de07c27

File tree

4 files changed

+31
-45
lines changed

4 files changed

+31
-45
lines changed

openeo/rest/auth/config.py

Lines changed: 25 additions & 15 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,24 +17,40 @@
1817
from openeo.config import get_user_config_dir, get_user_data_dir
1918
from openeo.util import rfc3339, deep_get, deep_set
2019

21-
if platform.system() == 'Windows':
20+
# Use oschmod if it is installed.
21+
# Otherwise we fall back on Python's chmod implementation.
22+
# On Windows we have to use oschmod because Python chmod implementation doesn't work on Windows.
23+
# TODO: Can we use oschmod transparantly on all platforms?
24+
# Seems like that is the intention of oschmod, but for now we will play it safe.
25+
try:
2226
import oschmod
27+
except ImportError:
28+
oschmod = None
2329

2430

2531
_PRIVATE_PERMS = stat.S_IRUSR | stat.S_IWUSR
2632

2733
log = logging.getLogger(__name__)
2834

2935

30-
def assert_private_file(path: Path):
31-
"""Check that given file is only readable by user."""
36+
def get_file_mode(path: Path):
37+
"""Get the file permission bits in a way that works on both *nix and Windows platforms."""
38+
if oschmod:
39+
return oschmod.get_mode(str(path))
40+
return path.stat().st_mode
3241

33-
# use oschmod on Windows
34-
# TODO: can we use oschmod for all operating systems, make the code simpler and consitent?
35-
if platform.system() == "Windows":
36-
mode = oschmod.get_mode(str(path))
42+
43+
def set_file_mode(path: Path, mode):
44+
"""Set the file permission bits in a way that works on both *nix and Windows platforms."""
45+
if oschmod:
46+
oschmod.set_mode(str(path), mode=mode)
3747
else:
38-
mode = path.stat().st_mode
48+
path.chmod(mode=mode)
49+
50+
51+
def assert_private_file(path: Path):
52+
"""Check that given file is only readable by user."""
53+
mode = get_file_mode(path)
3954
if (mode & stat.S_IRWXG) or (mode & stat.S_IRWXO):
4055
message = "File {p} could be readable by others: mode {a:o} (expected: {e:o}).".format(
4156
p=path, a=mode & (stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO), e=_PRIVATE_PERMS
@@ -93,12 +108,7 @@ def _write(self, data: dict):
93108
# TODO: add file locking to avoid race conditions?
94109
with self._path.open("w", encoding="utf8") as f:
95110
json.dump(data, f, indent=2)
96-
# On Windows we use oschmod because Python chmod implementation doesn't work on Windows.
97-
# TODO: can we use oschmod on all platforms? Seems like that is the intention of oschmod.
98-
if platform.system() == "Windows":
99-
oschmod.set_mode(str(self._path), mode=_PRIVATE_PERMS)
100-
else:
101-
self._path.chmod(mode=_PRIVATE_PERMS)
111+
set_file_mode(self._path, mode=_PRIVATE_PERMS)
102112
assert_private_file(self._path)
103113

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

setup.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,7 @@
4747
'xarray>=0.12.3',
4848
'pandas>0.20.0',
4949
'deprecated>=1.2.12',
50-
'oschmod; sys_platform == "win32"',
51-
'pywin32; sys_platform == "win32"',
50+
'oschmod>=0.3.12',
5251
],
5352
extras_require={
5453
"dev": tests_require + [

tests/rest/auth/test_config.py

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,10 @@
11
import json
22
from unittest import mock
3-
import platform
43

54
import pytest
65

76
import openeo.rest.auth.config
8-
from openeo.rest.auth.config import RefreshTokenStore, AuthConfig, PrivateJsonFile
9-
10-
# TODO: We could simplify the code if we can verify oschmod works transparently on Linux/Mac as well.
11-
# I think oschmod does work on Linux and Mac, but since it we use it for a
12-
# security sensitive feature, let's make sure there are no problems first.
13-
# For now, it is fine to have some Windows specific code.
14-
if platform.system() == 'Windows':
15-
import oschmod
7+
from openeo.rest.auth.config import RefreshTokenStore, AuthConfig, PrivateJsonFile, get_file_mode
168

179

1810
class TestPrivateJsonFile:
@@ -39,10 +31,7 @@ def test_permissions(self, tmp_path):
3931
assert not private.path.exists()
4032
private.set("foo", "bar", value=42)
4133
assert private.path.exists()
42-
if platform.system() == 'Windows':
43-
st_mode = oschmod.get_mode(str(private.path))
44-
else:
45-
st_mode = private.path.stat().st_mode
34+
st_mode = get_file_mode(private.path)
4635
assert st_mode & 0o777 == 0o600
4736

4837
def test_wrong_permissions(self, tmp_path):
@@ -162,12 +151,7 @@ def test_public_file(self, tmp_path):
162151
def test_permissions(self, tmp_path):
163152
r = RefreshTokenStore(path=tmp_path)
164153
r.set_refresh_token("foo", "bar", "imd6$3cr3t")
165-
token_path = (tmp_path / RefreshTokenStore.DEFAULT_FILENAME)
166-
if platform.system() == 'Windows':
167-
st_mode = oschmod.get_mode(str(token_path))
168-
else:
169-
token_path.stat().st_mode
170-
st_mode = (tmp_path / RefreshTokenStore.DEFAULT_FILENAME).stat().st_mode
154+
st_mode = get_file_mode(tmp_path / RefreshTokenStore.DEFAULT_FILENAME)
171155
assert st_mode & 0o777 == 0o600
172156

173157
def test_get_set_refresh_token(self, tmp_path):

tests/test_config.py

Lines changed: 2 additions & 9 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
@@ -160,13 +159,7 @@ def test_get_config_verbose(tmp_path, caplog, capsys, verbose, force_interactive
160159
config = get_config()
161160
assert config.get("general.verbose") == verbose
162161

163-
# On Windows the path in config_path would make the regex invalid because
164-
# windows paths contain \ and a regex interprets that as an escape sequence.
165-
# Therefore we have to escape the \ to \\ .
166-
if platform.system() == "Windows":
167-
config_path_escaped = str(config_path).replace("\\", "\\\\")
168-
else:
169-
config_path_escaped = config_path
170-
regex = re.compile(f"Loaded.*config from.*{config_path_escaped}")
162+
# re.escape config_path because Windows paths contain "\"
163+
regex = re.compile(f"Loaded.*config from.*{re.escape(config_path)}")
171164
assert regex.search(caplog.text)
172165
assert bool(regex.search(capsys.readouterr().out)) == on_stdout

0 commit comments

Comments
 (0)