Skip to content

Commit c4c2870

Browse files
authored
Merge pull request #8144 from uranusjr/wheel-install-permissions
2 parents 45f762e + 2245fce commit c4c2870

File tree

3 files changed

+27
-10
lines changed

3 files changed

+27
-10
lines changed

Diff for: news/8139.bugfix

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Correctly set permissions on metadata files during wheel installation,
2+
to permit non-privileged users to read from system site-packages.

Diff for: src/pip/_internal/operations/install/wheel.py

+17-8
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import collections
1010
import compileall
11+
import contextlib
1112
import csv
1213
import logging
1314
import os.path
@@ -32,17 +33,18 @@
3233
from pip._internal.utils.misc import captured_stdout, ensure_dir, hash_file
3334
from pip._internal.utils.temp_dir import TempDirectory
3435
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
35-
from pip._internal.utils.unpacking import unpack_file
36+
from pip._internal.utils.unpacking import current_umask, unpack_file
3637
from pip._internal.utils.wheel import parse_wheel
3738

3839
if MYPY_CHECK_RUNNING:
3940
from email.message import Message
4041
from typing import (
4142
Dict, List, Optional, Sequence, Tuple, Any,
42-
Iterable, Callable, Set,
43+
Iterable, Iterator, Callable, Set,
4344
)
4445

4546
from pip._internal.models.scheme import Scheme
47+
from pip._internal.utils.filesystem import NamedTemporaryFileResult
4648

4749
InstalledCSVRow = Tuple[str, ...]
4850

@@ -565,19 +567,27 @@ def is_entrypoint_wrapper(name):
565567
if msg is not None:
566568
logger.warning(msg)
567569

570+
generated_file_mode = 0o666 - current_umask()
571+
572+
@contextlib.contextmanager
573+
def _generate_file(path, **kwargs):
574+
# type: (str, **Any) -> Iterator[NamedTemporaryFileResult]
575+
with adjacent_tmp_file(path, **kwargs) as f:
576+
yield f
577+
os.chmod(f.name, generated_file_mode)
578+
replace(f.name, path)
579+
568580
# Record pip as the installer
569581
installer_path = os.path.join(dest_info_dir, 'INSTALLER')
570-
with adjacent_tmp_file(installer_path) as installer_file:
582+
with _generate_file(installer_path) as installer_file:
571583
installer_file.write(b'pip\n')
572-
replace(installer_file.name, installer_path)
573584
generated.append(installer_path)
574585

575586
# Record the PEP 610 direct URL reference
576587
if direct_url is not None:
577588
direct_url_path = os.path.join(dest_info_dir, DIRECT_URL_METADATA_NAME)
578-
with adjacent_tmp_file(direct_url_path) as direct_url_file:
589+
with _generate_file(direct_url_path) as direct_url_file:
579590
direct_url_file.write(direct_url.to_json().encode("utf-8"))
580-
replace(direct_url_file.name, direct_url_path)
581591
generated.append(direct_url_path)
582592

583593
# Record details of all files installed
@@ -589,10 +599,9 @@ def is_entrypoint_wrapper(name):
589599
changed=changed,
590600
generated=generated,
591601
lib_dir=lib_dir)
592-
with adjacent_tmp_file(record_path, **csv_io_kwargs('w')) as record_file:
602+
with _generate_file(record_path, **csv_io_kwargs('w')) as record_file:
593603
writer = csv.writer(record_file)
594604
writer.writerows(sorted_outrows(rows)) # sort to simplify testing
595-
replace(record_file.name, record_path)
596605

597606

598607
def install_wheel(

Diff for: tests/unit/test_wheel.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -239,13 +239,19 @@ def prep(self, data, tmpdir):
239239
self.dest_dist_info = os.path.join(
240240
self.scheme.purelib, 'sample-1.2.0.dist-info')
241241

242+
def assert_permission(self, path, mode):
243+
target_mode = os.stat(path).st_mode & 0o777
244+
assert (target_mode & mode) == mode, oct(target_mode)
245+
242246
def assert_installed(self):
243247
# lib
244248
assert os.path.isdir(
245249
os.path.join(self.scheme.purelib, 'sample'))
246250
# dist-info
247251
metadata = os.path.join(self.dest_dist_info, 'METADATA')
248-
assert os.path.isfile(metadata)
252+
self.assert_permission(metadata, 0o644)
253+
record = os.path.join(self.dest_dist_info, 'RECORD')
254+
self.assert_permission(record, 0o644)
249255
# data files
250256
data_file = os.path.join(self.scheme.data, 'my_data', 'data_file')
251257
assert os.path.isfile(data_file)
@@ -286,7 +292,7 @@ def test_std_install_with_direct_url(self, data, tmpdir):
286292
direct_url_path = os.path.join(
287293
self.dest_dist_info, DIRECT_URL_METADATA_NAME
288294
)
289-
assert os.path.isfile(direct_url_path)
295+
self.assert_permission(direct_url_path, 0o644)
290296
with open(direct_url_path, 'rb') as f:
291297
expected_direct_url_json = direct_url.to_json()
292298
direct_url_json = f.read().decode("utf-8")

0 commit comments

Comments
 (0)