Device security and robustness (openpanel C0): check code, pnc, lenient report
- POST /devices/pair answers a 2-character `check`, listed by /devices/pending and shown beside each waiting entry; five codes a minute per address, and at 50 waiting the oldest gives way instead of a 429 - portal-minted device tokens are asked for with the device's nonce (`pnc`) and held against it, so unpair and delete revoke them; one without `pnc` stands only while the nonce is 0 - a device credential acts as the device (`device_principal`), not as the approving superuser, so deleting that account unpairs nothing - the report is validated port by port: unknown enum values read as `unknown`, unreadable ports and sections are left out and logged, `config` may name an entry of `config_schemas`; 422 only for no schema 1 or no object - devices.json is parsed once per change (cached by stat), delete drops the report and keeps the nonce, a busy recheck runs in the background - scope and io only from enabled flows (start/stop tells the devices), a port bound twice gives one io row - the Devices tab's saves cancel and hold back device refetches until settled
This commit is contained in:
@@ -619,6 +619,8 @@ class FlowController:
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
# A stopped flow wires no device ports.
|
||||
self._rewired()
|
||||
self._publish(
|
||||
{
|
||||
"type": "pipeline_rebuilt",
|
||||
@@ -836,7 +838,7 @@ class FlowController:
|
||||
)
|
||||
self._rewired()
|
||||
|
||||
async def recheck(self) -> None:
|
||||
async def recheck(self, wait: float | None = FLOW_REBUILD_WAIT) -> None:
|
||||
"""Validate the built graph again without rebuilding anything.
|
||||
|
||||
For what validation reads from outside the flows: a device created or
|
||||
@@ -844,7 +846,7 @@ class FlowController:
|
||||
"""
|
||||
if self.pipeline is None:
|
||||
return
|
||||
await self._acquire(FLOW_REBUILD_WAIT)
|
||||
await self._acquire(wait)
|
||||
try:
|
||||
self.issues = _collect_issues(
|
||||
self.loaded, self.pipeline, self._all_flow_inputs(), self.devices
|
||||
@@ -2124,6 +2126,14 @@ def _collect_issues(
|
||||
_NUMBERS = frozenset({"int", "float"})
|
||||
|
||||
|
||||
def _ways(said: dict[str, Any]) -> list[str]:
|
||||
"""The directions a reported port may be wired in, none when it does not
|
||||
say: a direction this version does not know is no reason to refuse one."""
|
||||
if said.get("modes"):
|
||||
return list(said["modes"])
|
||||
return [said["direction"]] if said.get("direction") in ("in", "out") else []
|
||||
|
||||
|
||||
def _device_issues(
|
||||
loaded: dict[str, LoadedNode], devices: Any
|
||||
) -> list[ValidationIssue]:
|
||||
@@ -2190,9 +2200,7 @@ def _device_issues(
|
||||
elif said is None:
|
||||
problem = f"'{device}' does not report a port '{spec.port}'."
|
||||
advisory = True
|
||||
elif said.get("direction") and direction not in (
|
||||
said.get("modes") or [said["direction"]]
|
||||
):
|
||||
elif (ways := _ways(said)) and direction not in ways:
|
||||
problem = (
|
||||
f"'{spec.port}' of '{device}' is an {said['direction']} "
|
||||
f"port, wired here as {direction}."
|
||||
|
||||
Reference in New Issue
Block a user