Remediate the 2026-09-03 feature-test findings
This commit is contained in:
@@ -7,7 +7,7 @@ from shopdb.api import db, Asset, AssetType, OperatingSystem, Application, Audit
|
||||
|
||||
from ..models import Computer, ComputerType, ComputerInstalledApp, AccessProtocol, ComputerAccess
|
||||
|
||||
from shopdb.api import require_permission, require_role, apply_import_timestamps
|
||||
from shopdb.api import require_permission, require_role, apply_import_timestamps, intarg
|
||||
|
||||
computers_bp = Blueprint('computers', __name__)
|
||||
|
||||
@@ -258,13 +258,42 @@ def _computer_access_links(comp, domain=None):
|
||||
return links
|
||||
|
||||
|
||||
def _ip_comtype():
|
||||
"""The 'IP' communication type, created if this site has none.
|
||||
|
||||
Both the create and the update path did `if ip_comtype:` and silently did
|
||||
nothing when the row was missing - so a person typed an address, saved, got
|
||||
a 200, and the address was gone. A missing reference row is a seeding gap,
|
||||
not a reason to discard what somebody typed.
|
||||
"""
|
||||
comtype = CommunicationType.query.filter_by(comtype='IP').first()
|
||||
if comtype is None:
|
||||
comtype = CommunicationType(comtype='IP', description='IP address')
|
||||
db.session.add(comtype)
|
||||
db.session.flush()
|
||||
return comtype
|
||||
|
||||
|
||||
def _sync_access_methods(comp, data):
|
||||
"""Replace a computer's enabled protocols from data['accessmethods'] (a list
|
||||
of {protocolid, portoverride?}). No-op if the key is absent, so callers that
|
||||
don't touch access aren't affected."""
|
||||
don't touch access aren't affected.
|
||||
|
||||
Returns a {'old': [...], 'new': [...]} change entry when the set actually
|
||||
moved, or None. The caller records it: which remote-access protocols reach
|
||||
a shop-floor PC, and on which ports, is exactly the kind of change an audit
|
||||
log exists to remember, and this wrote nothing at all.
|
||||
"""
|
||||
if 'accessmethods' not in data:
|
||||
return
|
||||
return None
|
||||
desired = data.get('accessmethods') or []
|
||||
|
||||
def describe(rows):
|
||||
return sorted('%s:%s' % (row.protocolid, row.portoverride or 'default')
|
||||
for row in rows)
|
||||
|
||||
before = describe(ComputerAccess.query.filter_by(
|
||||
computerid=comp.computerid).all())
|
||||
ComputerAccess.query.filter_by(computerid=comp.computerid).delete()
|
||||
seen = set()
|
||||
for m in desired:
|
||||
@@ -287,6 +316,13 @@ def _sync_access_methods(comp, data):
|
||||
isactive=True,
|
||||
))
|
||||
|
||||
db.session.flush()
|
||||
after = describe(ComputerAccess.query.filter_by(
|
||||
computerid=comp.computerid).all())
|
||||
if before != after:
|
||||
return {'old': before, 'new': after}
|
||||
return None
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Computers CRUD
|
||||
@@ -380,20 +416,20 @@ def list_computers():
|
||||
)
|
||||
|
||||
# Computer type filter
|
||||
if type_id := request.args.get('typeid', request.args.get('type_id')):
|
||||
query = query.filter(Computer.computertypeid == int(type_id))
|
||||
if (type_id := intarg('typeid', intarg('type_id'))) is not None:
|
||||
query = query.filter(Computer.computertypeid == type_id)
|
||||
|
||||
# OS filter
|
||||
if os_id := request.args.get('osid', request.args.get('os_id')):
|
||||
query = query.filter(Computer.osid == int(os_id))
|
||||
if (os_id := intarg('osid', intarg('os_id'))) is not None:
|
||||
query = query.filter(Computer.osid == os_id)
|
||||
|
||||
# Location filter
|
||||
if location_id := request.args.get('locationid', request.args.get('location_id')):
|
||||
query = query.filter(Asset.locationid == int(location_id))
|
||||
if (location_id := intarg('locationid', intarg('location_id'))) is not None:
|
||||
query = query.filter(Asset.locationid == location_id)
|
||||
|
||||
# Business unit filter
|
||||
if bu_id := request.args.get('businessunitid', request.args.get('businessunit_id')):
|
||||
query = query.filter(Asset.businessunitid == int(bu_id))
|
||||
if (bu_id := intarg('businessunitid', intarg('businessunit_id'))) is not None:
|
||||
query = query.filter(Asset.businessunitid == bu_id)
|
||||
|
||||
# Shopfloor filter (by the Shopfloor computer type)
|
||||
if shopfloor := request.args.get('shopfloor'):
|
||||
@@ -587,7 +623,7 @@ def create_computer():
|
||||
|
||||
# Optional primary IP communication
|
||||
if data.get('ipaddress'):
|
||||
ip_comtype = CommunicationType.query.filter_by(comtype='IP').first()
|
||||
ip_comtype = _ip_comtype()
|
||||
if ip_comtype:
|
||||
db.session.add(Communication(
|
||||
assetid=asset.assetid,
|
||||
@@ -596,7 +632,9 @@ def create_computer():
|
||||
isprimary=True,
|
||||
))
|
||||
|
||||
# Remote-access protocols
|
||||
# Remote-access protocols. The return value is not recorded here: a create
|
||||
# is already described in full by its 'created' entry, and there is no
|
||||
# previous state to compare against.
|
||||
_sync_access_methods(comp, data)
|
||||
|
||||
# Preserve legacy timestamps in import mode (no-op otherwise)
|
||||
@@ -697,20 +735,27 @@ def update_computer(computer_id: int):
|
||||
ip = (data.get('ipaddress') or '').strip()
|
||||
primary = Communication.query.filter_by(
|
||||
assetid=asset.assetid, isprimary=True).first()
|
||||
# Recorded, like every other field on this form. The address a PC
|
||||
# answers on is how somebody finds it later; changing it wrote no audit
|
||||
# entry at all, so a machine that moved had no history of moving.
|
||||
oldip = primary.ipaddress if primary else None
|
||||
if (oldip or None) != (ip or None):
|
||||
changes['ipaddress'] = {'old': oldip, 'new': ip or None}
|
||||
if ip:
|
||||
if primary:
|
||||
primary.ipaddress = ip
|
||||
else:
|
||||
ip_comtype = CommunicationType.query.filter_by(comtype='IP').first()
|
||||
if ip_comtype:
|
||||
db.session.add(Communication(
|
||||
assetid=asset.assetid, comtypeid=ip_comtype.comtypeid,
|
||||
ipaddress=ip, isprimary=True))
|
||||
db.session.add(Communication(
|
||||
assetid=asset.assetid,
|
||||
comtypeid=_ip_comtype().comtypeid,
|
||||
ipaddress=ip, isprimary=True))
|
||||
elif primary:
|
||||
primary.ipaddress = None
|
||||
|
||||
# Remote-access protocols
|
||||
_sync_access_methods(comp, data)
|
||||
accesschange = _sync_access_methods(comp, data)
|
||||
if accesschange:
|
||||
changes['accessmethods'] = accesschange
|
||||
|
||||
# Audit log if there were changes
|
||||
if changes:
|
||||
|
||||
Reference in New Issue
Block a user