remote-access protocol collection; dashboard site timezone + badge alignment

This commit is contained in:
2026-08-10 08:36:39 -04:00
parent e68f522132
commit d9d08f53ac
4 changed files with 228 additions and 20 deletions

View File

@@ -432,3 +432,99 @@ def test_repurposed_pc_archives_tool_link(client, db, collector_key,
label='collector:measuringtool').all()
assert len(rels) == 1
assert rels[0].isactive is False
# =============================================================================
# Remote-access protocol sync (RealVNC and friends)
# =============================================================================
@pytest.fixture
def access_protocols(db):
"""Seed the admin-managed protocol catalog the collector matches against."""
from plugins.computers.models import AccessProtocol
db.session.add_all([
AccessProtocol(name='VNC', scheme='vnc', defaultport=5900,
linktemplate='vnc://{host}:{port}'),
AccessProtocol(name='WinRM', scheme='https', defaultport=5986,
linktemplate='https://{host}:{port}'),
])
db.session.commit()
def _post(client, collector_key, payload):
return client.post('/api/collector/computers', json=payload,
headers={'X-API-Key': collector_key})
def _active_protocols(hostname):
from plugins.computers.models import Computer, ComputerAccess, AccessProtocol
comp = Computer.query.filter(Computer.hostname.ilike(hostname)).first()
rows = ComputerAccess.query.filter_by(computerid=comp.computerid,
isactive=True).all()
ids = [r.protocolid for r in rows]
return sorted(p.name for p in AccessProtocol.query.filter(
AccessProtocol.protocolid.in_(ids)).all()) if ids else []
def test_reported_vnc_is_recorded(client, db, collector_key,
computer_assettype, access_protocols):
response = _post(client, collector_key,
{'hostname': 'BAY001', 'accessprotocols': ['VNC']})
assert response.status_code == 200, response.get_json()
assert _active_protocols('BAY001') == ['VNC']
def test_protocol_match_is_case_insensitive(client, db, collector_key,
computer_assettype, access_protocols):
_post(client, collector_key,
{'hostname': 'BAY002', 'accessprotocols': ['vnc']})
assert _active_protocols('BAY002') == ['VNC']
def test_protocol_no_longer_reported_is_deactivated(client, db, collector_key,
computer_assettype,
access_protocols):
"""RealVNC removed from a bay must stop showing as available."""
_post(client, collector_key,
{'hostname': 'BAY003', 'accessprotocols': ['VNC', 'WinRM']})
assert _active_protocols('BAY003') == ['VNC', 'WinRM']
_post(client, collector_key,
{'hostname': 'BAY003', 'accessprotocols': ['WinRM']})
assert _active_protocols('BAY003') == ['WinRM']
def test_omitting_the_key_leaves_existing_rows_alone(client, db, collector_key,
computer_assettype,
access_protocols):
"""Most rows came from the legacy isvnc/iswinrm migration. A collector that
does not report protocols yet must not wipe them."""
_post(client, collector_key,
{'hostname': 'BAY004', 'accessprotocols': ['VNC']})
_post(client, collector_key, {'hostname': 'BAY004', 'loggedinuser': 'someone'})
assert _active_protocols('BAY004') == ['VNC']
def test_empty_list_deactivates_everything(client, db, collector_key,
computer_assettype, access_protocols):
"""An explicit empty list means 'this PC exposes nothing' - distinct from
omitting the key."""
_post(client, collector_key,
{'hostname': 'BAY005', 'accessprotocols': ['VNC']})
_post(client, collector_key, {'hostname': 'BAY005', 'accessprotocols': []})
assert _active_protocols('BAY005') == []
def test_unknown_protocol_warns_and_does_not_create_one(client, db, collector_key,
computer_assettype,
access_protocols):
"""The catalog is admin-managed: a typo on one bay must not invent a
protocol for the whole site."""
from plugins.computers.models import AccessProtocol
response = _post(client, collector_key,
{'hostname': 'BAY006', 'accessprotocols': ['TeamViewer']})
body = response.get_json()['data']
assert any('TeamViewer' in w for w in body['warnings'])
assert AccessProtocol.query.filter(
AccessProtocol.name.ilike('TeamViewer')).first() is None