Security hardening, multi-site portability, and a documentation and API-surface sweep

This commit is contained in:
2026-08-14 16:28:34 -04:00
parent 72f8da0e58
commit 265ca2eb01
76 changed files with 3684 additions and 811 deletions

View File

@@ -116,6 +116,45 @@ def hosts_below_libversion(scopename, phase, required):
return sorted(set(behind)), floor
# Manifest features and the client library that first understood them. A
# library older than this does not merely skip the feature, it MISREADS the
# entry: 2.6 added _CmmVersion, and an older lib treats a version-gated entry as
# ungated and installs every PC-DMIS build it cannot detect.
#
# Only features that NARROW behaviour belong here. An addition an old lib
# ignores harmlessly does not need a floor.
LIBVERSION_FEATURES = (
('cmmversion', '2.6'),
)
def required_libversion(scope):
"""Lowest client library that may enforce this scope.
An explicit minlibversion wins. Otherwise it is derived from the features
the draft actually uses, so a scope written before this column existed is
still gated on what it contains rather than on manifestversion - which is
the manifest's own 'Version' field and says nothing about the library.
Falls back to manifestversion when nothing else applies, preserving the
behaviour share-imported manifests already relied on, where the two
numbering schemes do coincide.
"""
declared = (scope.minlibversion or '').strip()
if declared:
return declared
floor = None
for attribute, version in LIBVERSION_FEATURES:
if any(getattr(entry, attribute, None) for entry in scope.entries):
candidate = parse_libversion(version)
if candidate and (floor is None or candidate > floor[0]):
floor = (candidate, version)
if floor:
return floor[1]
return scope.manifestversion
def _installed_app_model():
"""Lazily import the computers plugin's ComputerInstalledApp.
@@ -184,11 +223,11 @@ def publish_scope(scopename, phase, notes=None, publishedby=None, force=False):
raise ValueError(f'No scope {scopename}/{phase}')
if not force:
behind, floor = hosts_below_libversion(
scopename, phase, scope.manifestversion)
required = required_libversion(scope)
behind, floor = hosts_below_libversion(scopename, phase, required)
if behind:
raise LibVersionTooOldError(
scopename, scope.manifestversion, floor or 'unknown', behind)
scopename, required, floor or 'unknown', behind)
text = scope_to_json(scope)
maxversion = db.session.query(