55import tarfile
66import zipfile
77
8+ from uuid import uuid4
9+
10+ import pymacaroons
811import pytest
912import yara_x
1013
14+ from warehouse .macaroons import caveats
1115from warehouse .utils import scanner
1216
1317
@@ -18,6 +22,41 @@ def rules():
1822 return compiled
1923
2024
25+ def _generate_token (domain = "pypi.org" , projects_scope = False ):
26+ raw_macaroon = pymacaroons .Macaroon (
27+ location = domain ,
28+ identifier = str (uuid4 ()),
29+ key = b"fake key" ,
30+ version = pymacaroons .MACAROON_V2 ,
31+ )
32+
33+ if projects_scope :
34+ caveats_ = [caveats .ProjectID (project_ids = [str (uuid4 ()) for _ in range (3 )])]
35+ else :
36+ caveats_ = [
37+ caveats .ProjectName (normalized_names = [f"project-{ i } " for i in range (3 )]),
38+ caveats .RequestUser (user_id = str (uuid4 ())),
39+ ]
40+ for caveat in caveats_ :
41+ raw_macaroon .add_first_party_caveat (caveats .serialize (caveat ))
42+
43+ return f"pypi-{ raw_macaroon .serialize ()} "
44+
45+
46+ @pytest .fixture (
47+ scope = "module" , params = [False , True ], ids = ["user-scope" , "projects-scope" ]
48+ )
49+ def pypi_token (request ):
50+ return _generate_token (domain = "pypi.org" , projects_scope = request .param )
51+
52+
53+ @pytest .fixture (
54+ scope = "module" , params = [False , True ], ids = ["user-scope" , "projects-scope" ]
55+ )
56+ def localhost_token (request ):
57+ return _generate_token (domain = "localhost" , projects_scope = request .param )
58+
59+
2160def _make_wheel (tmp_path , files_dict , name = "fake_package" , version = "1.0" ):
2261 whl_path = str (tmp_path / f"{ name } -{ version } -py3-none-any.whl" )
2362 with zipfile .ZipFile (whl_path , "w" ) as zfp :
@@ -248,22 +287,22 @@ def test_clean_archive_no_matches(self, tmp_path, rules):
248287 )
249288 assert scanner .scan_archive (whl , rules = rules ) == []
250289
251- def test_skips_non_python_files_in_wheel (self , tmp_path , rules ):
290+ def test_skips_excluded_files_in_wheel (self , tmp_path , rules ):
252291 whl = _make_wheel (
253292 tmp_path ,
254293 {
255294 "pkg/data.json" : "__pyarmor__(__name__, __file__, b'x')" ,
256- "pkg/readme.txt " : "__pyarmor_enter__()" ,
295+ "pkg/module.so " : b "__pyarmor_enter__()" ,
257296 },
258297 )
259298 assert scanner .scan_archive (whl , rules = rules ) == []
260299
261- def test_skips_non_python_files_in_tarball (self , tmp_path , rules ):
300+ def test_skips_excluded_files_in_tarball (self , tmp_path , rules ):
262301 tar = _make_tarball (
263302 tmp_path ,
264303 {
265304 "fake-1.0/data.json" : "__pyarmor__(__name__, __file__, b'x')" ,
266- "fake-1.0/readme.txt " : "__pyarmor_enter__()" ,
305+ "fake-1.0/module.so " : b "__pyarmor_enter__()" ,
267306 },
268307 )
269308 assert scanner .scan_archive (tar , rules = rules ) == []
@@ -396,3 +435,48 @@ def test_spoofed_file_size_does_not_bypass_scan(self, tmp_path, rules):
396435 assert len (matches ) == 1
397436 assert matches [0 ][0 ] == "pkg/__init__.py"
398437 assert "pyarmor_encrypted" in matches [0 ][1 ]
438+
439+
440+ _FILENAMES_TO_SCAN = [
441+ "setup.py" ,
442+ "README.md" ,
443+ "PUBLISHING.RST" ,
444+ "publish.sh" ,
445+ "info.txt" ,
446+ ".env" ,
447+ ]
448+
449+
450+ class TestPyPITokenDetection :
451+ # TODO: separated METADATA/PKG-INFO tests with correct paths
452+ @pytest .mark .parametrize ("filename" , [* _FILENAMES_TO_SCAN , "METADATA" ])
453+ def test_detects_pypi_token_in_wheel (self , tmp_path , rules , pypi_token , filename ):
454+ whl = _make_wheel (tmp_path , {f"pkg/{ filename } " : pypi_token })
455+ matches = scanner .scan_archive (whl , rules = rules )
456+ assert len (matches ) == 1
457+ assert matches [0 ][0 ] == f"pkg/{ filename } "
458+ assert "secrets_pypi_token" in matches [0 ][1 ]
459+
460+ @pytest .mark .parametrize ("filename" , [* _FILENAMES_TO_SCAN , "PKG-INFO" ])
461+ def test_detects_pypi_token_in_tarball (self , tmp_path , rules , pypi_token , filename ):
462+ tar = _make_tarball (tmp_path , {f"fake-1.0/pkg/{ filename } " : pypi_token })
463+ matches = scanner .scan_archive (tar , rules = rules )
464+ assert len (matches ) == 1
465+ assert matches [0 ][0 ] == f"fake-1.0/pkg/{ filename } "
466+ assert "secrets_pypi_token" in matches [0 ][1 ]
467+
468+ @pytest .mark .parametrize ("filename" , [* _FILENAMES_TO_SCAN , "METADATA" ])
469+ def test_ignores_localhost_token_in_wheel (
470+ self , tmp_path , rules , localhost_token , filename
471+ ):
472+ whl = _make_wheel (tmp_path , {f"pkg/{ filename } " : localhost_token })
473+ matches = scanner .scan_archive (whl , rules = rules )
474+ assert len (matches ) == 0
475+
476+ @pytest .mark .parametrize ("filename" , [* _FILENAMES_TO_SCAN , "PKG-INFO" ])
477+ def test_ignores_localhost_token_in_tarball (
478+ self , tmp_path , rules , localhost_token , filename
479+ ):
480+ tar = _make_tarball (tmp_path , {f"fake-1.0/pkg/{ filename } " : localhost_token })
481+ matches = scanner .scan_archive (tar , rules = rules )
482+ assert len (matches ) == 0
0 commit comments