v3.10.0 review fixes: repair /api/ram-install-app, escape quotes in esc
- /api/ram-install-app referenced undefined spi1/tar (the resolution block
was only added to /api/ram-install and /api/cap-compat), so every request
answered 500. The same preset/MSL resolution is now in place, with
regression tests for both modes (install / make selectable) and for the
no-MSL refusal before any card I/O.
- esc() now escapes " and ' too: the TAR editor renders free-text
descriptions (and other values) into attribute contexts, so a crafted
preset import could inject attributes. Three tests asserting the old
unescaped output updated; a dedicated esc test added.
- _spi1_for_tar names the no-TAR case in its hint (a pre-built packet
without tar/spi1 used to report "TAR ?").
- preset free TAR rows no longer store an empty "role" key (the documented
shape); a non-list `tars` in an update is refused (400) instead of being
silently ignored.
Live: the no-MSL request answers 400 with the hint; a make_selectable with
a bogus AID runs the format probe and the INSTALL (derived SPI1 16 from the
preset MSL), the card answers remote SW 6A88 and the counter advances - no
500. Suites: 658 Python + 775 frontend.
No version bump: completes the unreleased v3.10.0 (6360dc5 was never
pushed). sw cache simple-v359.
This commit is contained in:
@@ -737,6 +737,53 @@ class PresetStoreHttpTests(unittest.TestCase):
|
||||
self.assertTrue(captured)
|
||||
self.assertEqual(set(captured), {'1A'})
|
||||
|
||||
def test_an_install_app_operation_derives_the_spi1_from_the_msl(self):
|
||||
p = self.store.add(self._preset(iccid='8970119000004600098'))
|
||||
self.store.update(p['id'], {'tars': [
|
||||
{'role': 'isd', 'tar': '000000', 'msl': '1A', 'desc': ''},
|
||||
{'role': 'uiccRfm', 'tar': 'B00000', 'msl': '16', 'desc': ''},
|
||||
{'role': 'usimRfm', 'tar': 'B00001', 'msl': '16', 'desc': ''}]})
|
||||
captured = []
|
||||
|
||||
def fake_step(server, scc, sp, state, name, apdu, silent=False):
|
||||
captured.append((sp['spi1'], sp['tar']))
|
||||
state['steps'].append({'name': name, 'por_status': 'por_ok', 'por_sw': '9000'})
|
||||
return True
|
||||
|
||||
patches = [
|
||||
mock.patch.object(self.srv, '_ram_detect_format', lambda *a, **k: 'compact'),
|
||||
mock.patch.object(self.srv, '_ram_send_gp_apdu', fake_step),
|
||||
]
|
||||
for patch in patches:
|
||||
patch.start()
|
||||
try:
|
||||
# INSTALL [for install]
|
||||
status, resp = self._post('/api/ram-install-app', {
|
||||
'mode': 'install', 'loadfile_aid': 'F0414C46416101',
|
||||
'module_aid': 'F0414C4641610101', 'preset_id': p['id'],
|
||||
'kic': '15', 'kid': '15'})
|
||||
self.assertEqual(status, 200, resp)
|
||||
self.assertTrue(resp['success'], resp)
|
||||
# INSTALL [for make selectable]
|
||||
status, resp = self._post('/api/ram-install-app', {
|
||||
'mode': 'make_selectable', 'instance_aid': 'F0414C4641610101',
|
||||
'preset_id': p['id'], 'kic': '15', 'kid': '15'})
|
||||
self.assertEqual(status, 200, resp)
|
||||
self.assertTrue(resp['success'], resp)
|
||||
finally:
|
||||
for patch in reversed(patches):
|
||||
patch.stop()
|
||||
self.assertEqual(captured, [('1A', '000000'), ('1A', '000000')])
|
||||
|
||||
def test_an_install_app_without_an_msl_is_refused_before_the_card(self):
|
||||
# no preset and no spi1: the resolution must refuse with the hint
|
||||
# (v3.10.0 review: this endpoint once 500'd on an undefined spi1)
|
||||
status, resp = self._post('/api/ram-install-app', {
|
||||
'mode': 'make_selectable', 'instance_aid': 'F0414C4641610101',
|
||||
'kic': '15', 'kid': '15'})
|
||||
self.assertEqual(status, 400, resp)
|
||||
self.assertIn('MSL', resp['error'])
|
||||
|
||||
def test_a_counter_less_send_ota_body_still_answers(self):
|
||||
# a pre-built packet without a counter: no final_cntr, no persist,
|
||||
# and above all no int('') crash (v3.8.0 review fix)
|
||||
|
||||
@@ -2341,6 +2341,10 @@ class Spi1ForTarTests(unittest.TestCase):
|
||||
self.assertIn('MSL', str(cm.exception))
|
||||
with self.assertRaises(ValueError):
|
||||
_spi1_for_tar({}, '000000')
|
||||
# no TAR at all (a pre-built packet without one): the hint names that
|
||||
with self.assertRaises(ValueError) as cm:
|
||||
_spi1_for_tar(self.PRESET, '')
|
||||
self.assertIn('no TAR', str(cm.exception))
|
||||
|
||||
|
||||
class CounterProbeTests(unittest.TestCase):
|
||||
|
||||
@@ -110,10 +110,11 @@ class StoreCrudTests(unittest.TestCase):
|
||||
{'tar': 'AF4D02', 'msl': '0A', 'desc': 'My applet'},
|
||||
{'tar': '', 'msl': '', 'desc': ''}, # a blank editor row
|
||||
]))
|
||||
self.assertEqual([(t['role'], t['tar'], t['msl']) for t in p['tars']],
|
||||
self.assertEqual([(t.get('role', ''), t['tar'], t['msl']) for t in p['tars']],
|
||||
[('isd', 'AF4D01', '1A'), ('uiccRfm', 'B00000', '16'),
|
||||
('usimRfm', 'B00001', '16'), ('', 'AF4D02', '0A')])
|
||||
self.assertEqual(p['tars'][3]['desc'], 'My applet')
|
||||
self.assertNotIn('role', p['tars'][3]) # free rows carry no role
|
||||
self.assertEqual(len(p['tars']), 4) # the blank row was dropped
|
||||
|
||||
def test_every_tar_needs_its_msl_and_a_unique_tar(self):
|
||||
@@ -161,9 +162,10 @@ class StoreCrudTests(unittest.TestCase):
|
||||
{'role': 'usimRfm', 'tar': 'B00001', 'msl': '16'}]})
|
||||
self.assertEqual(presets.role_tar(upd, 'isd'), 'AF4D01')
|
||||
self.assertEqual(presets.tar_msl(upd, 'AF4D01'), '1A')
|
||||
# a malformed tars value must not silently reset the table
|
||||
again = self.store.update(p['id'], {'tars': 'nonsense'})
|
||||
self.assertEqual(presets.role_tar(again, 'isd'), 'AF4D01')
|
||||
# a malformed tars value is refused, not silently ignored or reset
|
||||
with self.assertRaises(presets.PresetError):
|
||||
self.store.update(p['id'], {'tars': 'nonsense'})
|
||||
self.assertEqual(presets.role_tar(self.store.get(p['id']), 'isd'), 'AF4D01')
|
||||
|
||||
def test_a_preset_carries_several_keysets(self):
|
||||
p = self.store.add(preset(keysets=[keyset(kic='15', kid='15'),
|
||||
|
||||
Reference in New Issue
Block a user