1313)
1414from pulpcore .plugin .models import (
1515 AutoAddObjPermsMixin ,
16+ BaseModel ,
1617 Content ,
1718 Publication ,
1819 Distribution ,
2223from pulpcore .plugin .responses import ArtifactResponse
2324
2425from pathlib import PurePath
25- from .exceptions import PackageSubstitutionError
26+ from .exceptions import BlocklistedPackageError , PackageSubstitutionError
2627from .provenance import Provenance
2728from .utils import (
2829 artifact_to_python_content_data ,
@@ -399,9 +400,12 @@ def finalize_new_version(self, new_version):
399400
400401 When allow_package_substitution is False, reject any new version that would implicitly
401402 replace existing content with different checksums (content substitution).
403+
404+ Also checks newly added content against the repository's blocklist entries.
402405 """
403406 if not self .allow_package_substitution :
404407 self ._check_for_package_substitution (new_version )
408+ self ._check_blocklist (new_version )
405409 remove_duplicates (new_version )
406410 validate_repo_version (new_version )
407411
@@ -414,3 +418,60 @@ def _check_for_package_substitution(self, new_version):
414418 duplicates = collect_duplicates (qs , ("filename" ,))
415419 if duplicates :
416420 raise PackageSubstitutionError (duplicates )
421+
422+ def _check_blocklist (self , new_version ):
423+ """
424+ Check newly added content in a repository version against the blocklist.
425+ """
426+ added_content = PythonPackageContent .objects .filter (
427+ pk__in = new_version .added ().values_list ("pk" , flat = True )
428+ ).only ("filename" , "name_normalized" , "version" )
429+ if added_content .exists ():
430+ self .check_blocklist_for_packages (added_content )
431+
432+ def check_blocklist_for_packages (self , packages ):
433+ """
434+ Raise a ValidationError if any of the given packages match a blocklist entry.
435+ """
436+ entries = PythonBlocklistEntry .objects .filter (repository = self )
437+ if not entries .exists ():
438+ return
439+
440+ blocked = []
441+ for pkg in packages :
442+ for entry in entries :
443+ if entry .filename and entry .filename == pkg .filename :
444+ blocked .append (pkg .filename )
445+ break
446+ if entry .name == pkg .name_normalized :
447+ if not entry .version or entry .version == pkg .version :
448+ blocked .append (pkg .filename )
449+ break
450+ if blocked :
451+ raise BlocklistedPackageError (blocked )
452+
453+
454+ class PythonBlocklistEntry (BaseModel ):
455+ """
456+ An entry in a PythonRepository's package blocklist.
457+
458+ Blocklist entries prevent packages from being added to the repository.
459+ Entries can match by package `name` (all versions), package `name` + `version`,
460+ or exact `filename`. Exactly one of `name` or `filename` must be provided.
461+ """
462+
463+ name = models .TextField (null = True , default = None )
464+ version = models .TextField (null = True , default = None )
465+ filename = models .TextField (null = True , default = None )
466+ added_by = models .TextField (default = "" )
467+ repository = models .ForeignKey (
468+ PythonRepository , on_delete = models .CASCADE , related_name = "blocklist_entries"
469+ )
470+
471+ def __str__ (self ):
472+ if self .filename :
473+ return f"<{ self ._meta .object_name } : { self .filename } >"
474+ return f"<{ self ._meta .object_name } : { self .name } [{ self .version or 'all' } ]>"
475+
476+ class Meta :
477+ default_related_name = "%(app_label)s_%(model_name)s"
0 commit comments