diff --git a/frontend/index.html b/frontend/index.html index f83a653..1e9f268 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1904,7 +1904,7 @@ // ===== Version ===== // Single source of truth for the PWA version: shown in the header and used // by the server version check in pysimConnect(). -const SIMPLE_VERSION = '3.23.1'; +const SIMPLE_VERSION = '3.23.2'; document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION; // ===== view history (the browser Back/Forward buttons) ===== diff --git a/frontend/sw.js b/frontend/sw.js index 84e7e97..aa91ce0 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'simple-v414'; +const CACHE = 'simple-v415'; const URLS = [ 'index.html', 'help.html', diff --git a/pyproject.toml b/pyproject.toml index deab407..2492189 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "pysim-simple-server" -version = "3.23.1" +version = "3.23.2" description = "HTTP REST server wrapping pysim for the SIMple PWA" requires-python = ">=3.8" # pysim is a git-only dependency installed explicitly by setup.bat/setup.sh. diff --git a/pysim_simple_server/server.py b/pysim_simple_server/server.py index 3415e58..f89d30e 100644 --- a/pysim_simple_server/server.py +++ b/pysim_simple_server/server.py @@ -36,7 +36,7 @@ from osmocom.tlv import BER_TLV_IE from osmocom.utils import rpad -VERSION = '3.23.1' +VERSION = '3.23.2' MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE @@ -7738,12 +7738,21 @@ class PysimHandler(BaseHTTPRequestHandler): self._send_json(resp, 400) self._log_resp(resp) return + steps = [step for _entry, script in members for step in script['steps']] + if any(s['type'] == 'action' and s['kind'] == 'scp80' for s in steps): + err = _test_preset_error({'steps': steps}, preset) + if err: + resp = {'error': err} + self._send_json(resp, 400) + self._log_resp(resp) + return reset_info = None if suite.get('start_reset'): # A suite may start with a card reset: the physical reset + the # full equip re-initialization, so its scripts run against a - # freshly initialized card. This runs before the scp80 checks and - # the ADM gate: the card session changes, so the ADM latch is + # freshly initialized card. This runs after the preset checks - a + # run that cannot start must not reset the card - and before the + # ADM gate: the card session changes, so the ADM latch is # re-verified against the post-reset card, and the ICCID must # still match the run's preset (a reset can switch the active # profile on an eUICC). @@ -7777,14 +7786,6 @@ class PysimHandler(BaseHTTPRequestHandler): self._send_json(resp, 400) self._log_resp(resp) return - steps = [step for _entry, script in members for step in script['steps']] - if any(s['type'] == 'action' and s['kind'] == 'scp80' for s in steps): - err = _test_preset_error({'steps': steps}, preset) - if err: - resp = {'error': err} - self._send_json(resp, 400) - self._log_resp(resp) - return needed_adm = bool(suite.get('require_adm')) or any( bool(script.get('require_adm')) for _entry, script in members) ok, adm_info, adm_err = _ensure_adm(self.server, preset, needed_adm) diff --git a/tests/test_fetch_routes.py b/tests/test_fetch_routes.py index 118ee52..863f5b0 100644 --- a/tests/test_fetch_routes.py +++ b/tests/test_fetch_routes.py @@ -1702,6 +1702,21 @@ class TestScriptStoreHttpTests(unittest.TestCase): time.sleep(0.05) self.assertFalse(snap['running'], snap) + def test_a_bad_scp80_preset_refuses_before_the_reset(self): + # v3.23.2: the preset checks come first - a suite that cannot start + # must not reset the card + self.server.app = object() + suite = self._suite(name='reset me', start_reset=True) + self._post('/api/test/scripts', self._script( + suite_id=suite['id'], + steps=[{'type': 'action', 'kind': 'scp80', + 'params': {'apdu': '80E2900000'}}])) + with mock.patch.object(self.srv, '_card_reset_reinit') as r: + status, resp = self._post('/api/test/run', {'suite_id': suite['id']}) + self.assertEqual(status, 400, resp) + self.assertIn('incomplete', resp['error']) + r.assert_not_called() + def test_suite_start_reset_refuses_a_mismatched_iccid(self): self.server.app = object() suite = self._suite(name='reset me', start_reset=True)