Gate the USB kiosk the same way, and make an unreachable setting a test failure
The USB kiosk had a feature flag and nothing else. A flag is a decision, not a credential: with the kiosk on, anyone who could reach the app could move a drive between checked-out and checked-in and sign it with a badge they guessed. Same reasoning that closed the parts kiosk, smaller blast radius - which is why Darkwing did not raise it and why it was worth asking about anyway. Both kiosks now share one mechanism. The device token moved out of the printedparts api wrapper into a request interceptor that attaches X-Kiosk-Token by URL prefix, with one localStorage key per kiosk - they are separate screens in separate rooms, and a shared token would mean provisioning one grants the other. claimKioskToken() is shared too, so both views take the token from a ?token= URL once and strip it from the address bar identically. Then the part that matters more than either fix. I told you to set the new settings in the UI, and that was impossible: every plugin settings page carries a hardcoded key list, and nothing had added them. printedparts_kiosk_enabled, printedparts_kiosk_token and usb_kiosk_token were declared, seeded and read correctly, and rendered by no page - changeable only by writing SQL. tests/test_plugin_settings_reachable.py now fails when any declared setting appears nowhere in its plugin's frontend. It found seven MORE that were already in that state, all pre-existing: collector_corporate_ranges computers_configpolicies_enabled computers_machinelink_alerts computers_quietreporthours geenforce_reportstaleminutes printers_dashboardpercent warranty_expiringdays All seven now have fields. I first parked them in a KNOWN_GAPS ratchet, then fixed them and deleted the ratchet - a backlog list in a new check is a way of agreeing the check does not apply yet. There is no backlog; the check is absolute. Two of my own mistakes on the way, both caught by tests rather than by reading: the blanket rename of the USB guard also rewrote the call inside _kiosk_open, so it called itself until the stack ran out; and the first version of the reachable check skipped all 17 plugins because it looked for get_settings() when the method is get_settings_defaults() - it passed by testing nothing, which is the exact shape it exists to catch. Suite 2336, frontend 316, build clean. Scope: product
This commit is contained in:
@@ -24,6 +24,8 @@ import logging
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import hmac
|
||||
|
||||
from flask import Blueprint, request, Response
|
||||
from flask_jwt_extended import jwt_required
|
||||
|
||||
@@ -741,6 +743,31 @@ def _kiosk_enabled():
|
||||
return _kiosk_setting('usb_kiosk_enabled', False)
|
||||
|
||||
|
||||
def _kiosk_open():
|
||||
"""True when the kiosk is on for this site AND the caller has its token.
|
||||
|
||||
The flag alone was the whole gate, and a flag is a decision, not a
|
||||
credential: with the kiosk on, anyone who could reach the app could move a
|
||||
device between checked-out and checked-in and have it recorded against a
|
||||
badge they guessed. A badge is a short numeric SSO, so it is attribution,
|
||||
never authorization - the same reasoning that closed the parts kiosk.
|
||||
|
||||
The token is per-DEVICE. A kiosk is a browser on a screen by the cabinet, so
|
||||
a token served inside the page would be readable by anyone who could load
|
||||
it. An admin opens the screen once as /usb-kiosk?token=..., the page keeps
|
||||
it in that browser and sends it as X-Kiosk-Token.
|
||||
"""
|
||||
if not _kiosk_enabled():
|
||||
return False
|
||||
expected = (Setting.get('usb_kiosk_token') or '').strip()
|
||||
presented = (request.headers.get('X-Kiosk-Token') or '').strip()
|
||||
if not expected or not presented:
|
||||
return False
|
||||
# compare_digest, not ==: == leaks the token's length and matching prefix
|
||||
# through timing.
|
||||
return hmac.compare_digest(presented, expected)
|
||||
|
||||
|
||||
def _kiosk_disabled_response():
|
||||
# 404, not 403: when the kiosk is off these routes should not read as
|
||||
# "there is a door here and you may not use it".
|
||||
@@ -765,7 +792,7 @@ def kiosk_device(device_id):
|
||||
the person standing there cannot tell whether they are doing the right
|
||||
thing. No badge number, no history, no contact details.
|
||||
"""
|
||||
if not _kiosk_enabled():
|
||||
if not _kiosk_open():
|
||||
return _kiosk_disabled_response()
|
||||
if _usb_selfhosted():
|
||||
return selfhosted.kiosk_device(device_id)
|
||||
@@ -825,7 +852,7 @@ def kiosk_session():
|
||||
COUNT of everything else - names for those are a separate, deliberate step,
|
||||
so a passer-by does not read the whole cabinet's holders off a wall screen.
|
||||
"""
|
||||
if not _kiosk_enabled():
|
||||
if not _kiosk_open():
|
||||
return _kiosk_disabled_response()
|
||||
data = request.get_json() or {}
|
||||
if _usb_selfhosted():
|
||||
@@ -881,7 +908,7 @@ def kiosk_others():
|
||||
who is on leave - but the names that makes visible should not be the first
|
||||
thing on a shopfloor screen.
|
||||
"""
|
||||
if not _kiosk_enabled():
|
||||
if not _kiosk_open():
|
||||
return _kiosk_disabled_response()
|
||||
if not _kiosk_setting('usb_kiosk_allow_return_for_others', True):
|
||||
return _kiosk_disabled_response()
|
||||
@@ -925,7 +952,7 @@ def kiosk_others():
|
||||
@usb_bp.route('/kiosk/checkout', methods=['POST'])
|
||||
def kiosk_checkout():
|
||||
"""Check a device out at the kiosk. Body: {device_id, badge}."""
|
||||
if not _kiosk_enabled():
|
||||
if not _kiosk_open():
|
||||
return _kiosk_disabled_response()
|
||||
data = request.get_json() or {}
|
||||
if _usb_selfhosted():
|
||||
@@ -998,7 +1025,7 @@ def kiosk_checkin():
|
||||
handed the device back, which is the honest record and needs no change to a
|
||||
schema this app does not own.
|
||||
"""
|
||||
if not _kiosk_enabled():
|
||||
if not _kiosk_open():
|
||||
return _kiosk_disabled_response()
|
||||
data = request.get_json() or {}
|
||||
if _usb_selfhosted():
|
||||
|
||||
Reference in New Issue
Block a user