Merge the wilm work, act on the five-agent review, verify the launch-path policy on Windows
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
import logging
|
||||
import re
|
||||
|
||||
from flask import Blueprint, request, Response
|
||||
from flask import Blueprint, request, Response, current_app
|
||||
from flask_jwt_extended import jwt_required
|
||||
|
||||
from shopdb.api import db, cache, Setting, Asset, AssetType, Vendor, Model, Communication, CommunicationType, AssetRelationship, RelationshipType, success_response, error_response, paginated_response, ErrorCodes, get_pagination_params, paginate_query
|
||||
@@ -380,6 +380,24 @@ def printer_install_list():
|
||||
UNIVERSAL_INSTALL_VENDORS = frozenset({'HP', 'Xerox'})
|
||||
|
||||
|
||||
# A hostname and nothing else: letters, digits, dot, hyphen, optional :port.
|
||||
# Anything outside this cannot reach the generated script, so the quote that
|
||||
# would end the PowerShell literal never gets the chance.
|
||||
_HOST_RE = re.compile(r'^[A-Za-z0-9.-]+(?::[0-9]{1,5})?$')
|
||||
|
||||
|
||||
def _safe_host(value):
|
||||
"""A request-supplied host, or None when it is not one.
|
||||
|
||||
The value reaches a single-quoted PowerShell -Uri literal in the generated
|
||||
.bat, so it is validated rather than escaped: an allowlist of what a host
|
||||
may contain is a smaller thing to get right than enumerating what a shell
|
||||
does with the rest.
|
||||
"""
|
||||
host = (value or '').strip()
|
||||
return host if host and _HOST_RE.match(host) else None
|
||||
|
||||
|
||||
def _batch_base_url():
|
||||
"""Base URL the generated .bat downloads installers from. Prefer the
|
||||
configured site_base_url (it already includes scheme + the /shopdb mount).
|
||||
@@ -387,12 +405,34 @@ def _batch_base_url():
|
||||
Fallback matters: behind IIS the app sees http on a loopback port and its
|
||||
url_root drops the mount, so a naive request.url_root yields a broken
|
||||
http://127.0.0.1/installers/... URL. Rebuild from the forwarded Host + the
|
||||
mount (script_root) and force https instead."""
|
||||
mount (script_root) and force https instead.
|
||||
|
||||
THE FALLBACK IS THE DEFAULT PATH, not an edge case: site_base_url ships
|
||||
blank. So the host it uses is caller-supplied on every fresh install, and
|
||||
it lands unescaped inside a single-quoted PowerShell literal. X-Forwarded-Host
|
||||
is only believed when a proxy is actually declared (TRUSTED_PROXY_COUNT) -
|
||||
the same rule shopdb/utils/clientip.py applies to X-Forwarded-For, which was
|
||||
centralised for this exact reason and did not cover the Host variant. Either
|
||||
way the value is validated as a hostname before it is interpolated, and a
|
||||
request that supplies neither a usable forwarded host nor a usable Host gets
|
||||
a relative base rather than a poisoned absolute one.
|
||||
"""
|
||||
base = (Setting.get('site_base_url') or '').strip().rstrip('/')
|
||||
if base:
|
||||
return base
|
||||
host = request.headers.get('X-Forwarded-Host') or request.host
|
||||
|
||||
host = None
|
||||
if current_app.config.get('TRUSTED_PROXY_COUNT'):
|
||||
host = _safe_host(request.headers.get('X-Forwarded-Host'))
|
||||
if not host:
|
||||
host = _safe_host(request.host)
|
||||
|
||||
root = (request.script_root or '').rstrip('/')
|
||||
if not host:
|
||||
# Same-origin relative URLs still download correctly from the browser
|
||||
# that fetched the script. Better than emitting a host we cannot vouch
|
||||
# for into a file that runs commands.
|
||||
return root
|
||||
return 'https://%s%s' % (host, root)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user