feat(map): add protected operations access

This commit is contained in:
lingniu
2026-08-11 18:30:16 +08:00
parent f535b1570b
commit d4593c44cd
9 changed files with 738 additions and 37 deletions
+28
View File
@@ -80,6 +80,29 @@ assert.equal(points.length, 2);
assert.ok(points.every(node => node.kind === 'vehiclePoint'));
assert.equal(points.find(node => node.nameZh === '粤A00001').online, 1);
// A selected district is a semantic filter, not a viewport filter. Its full
// fleet must survive a camera fit even when a valid vehicle sits outside the
// initial district-centroid viewport.
currentMode = 'vehicle';
dashboard = {
vehicles: [
{ vin: 'VIN-BY-1', plateNumber: '粤A10001', activeToday: true, locationAvailable: true, longitude: 113.22, latitude: 23.18 },
{ vin: 'VIN-BY-2', plateNumber: '粤A10002', activeToday: true, locationAvailable: true, longitude: 113.41, latitude: 23.38 }
]
};
vehicleFilterGroups.districts = [{ adcode: '440111', vehicles: dashboard.vehicles }];
filterState.district = '440111';
map = {
getZoom: () => 11.6,
getBounds: () => ({
getSouthWest: () => ({ getLng: () => 113.1, getLat: () => 23.1 }),
getNorthEast: () => ({ getLng: () => 113.3, getLat: () => 23.3 })
})
};
assert.equal(hierarchyLevelForZoom(), 'vehicle');
assert.equal(vehiclePointNodes().length, 2);
filterState.district = '';
dashboard = {
stations: [
{ id: 'GD-1', name: '广州合作站', province: '广东省', city: '广州市', address: '广东省广州市黄埔区开源大道1号', longitude: 113.2, latitude: 23.1, cooperative: true, totalHydrogenKg: 20 },
@@ -110,6 +133,11 @@ map = {
assert.equal(stationNodes(false).length, 3);
assert.equal(stationNodes(true).length, 2);
assert.deepEqual(stationNodes(true).map(node => node.name).sort(), ['佛山外部站', '广州合作站']);
const stationNavigationUrl = navigationUrlForEntity('station', dashboard.stations[0]);
assert.ok(stationNavigationUrl.startsWith('https://uri.amap.com/navigation?to=113.200000,23.100000,'));
assert.match(stationNavigationUrl, /%E5%B9%BF%E5%B7%9E/);
assert.match(stationNavigationUrl, /coordinate=gaode&callnative=1$/);
assert.equal(navigationUrlForEntity('vehicle', { plateNumber: '无位置车辆' }), null);
const stationView = stationViewportSummary();
assert.equal(stationView.level, 'station');
assert.equal(stationView.visibleNodes.length, 2);
+47
View File
@@ -1,5 +1,9 @@
import importlib.util
import hashlib
import hmac
import json
from pathlib import Path
import time
import unittest
from unittest.mock import patch
@@ -41,6 +45,49 @@ class DashboardTest(unittest.TestCase):
self.assertEqual(result["vehicles"][0]["dailyMileageKm"], 12.345)
self.assertEqual(result["vehicles"][2]["dailyMileageKm"], 0)
def test_public_station_directory_uses_an_allowlist(self):
stations = [{
"id": "GD-1", "name": "广州合作站", "shortName": "合作站", "province": "广东省",
"city": "广州市", "district": "黄埔区", "address": "开源大道1号", "longitude": 113.2,
"latitude": 23.1, "cooperative": True, "monthlyHydrogenKg": 88.8,
"totalHydrogenKg": 999.9, "internalOwner": "must-not-leak",
}]
with patch.object(server, "_post_open_platform", return_value=stations):
with patch.object(server, "_cache", {}):
result = server._load_public_stations()
station = result["stations"][0]
self.assertEqual(result["access"]["level"], "public")
self.assertEqual(station["name"], "广州合作站")
self.assertTrue(station["cooperative"])
self.assertNotIn("monthlyHydrogenKg", station)
self.assertNotIn("totalHydrogenKg", station)
self.assertNotIn("internalOwner", station)
def test_signed_operations_session_requires_the_operations_scope(self):
with patch.object(server, "LOCAL_ACCESS_CODE", "LN-map-test-code"), patch.object(server, "SESSION_SECRET", b"test-session-secret"), patch.object(server, "SESSION_TTL_SECONDS", 600):
principal = server._authorizer().authorize_access_code("LN-map-test-code")
token = server._create_session(principal)
session = server._read_session(f"{server.SESSION_COOKIE_NAME}={token}")
self.assertEqual(session["subject"], "local-access-code")
self.assertIn(server.OPERATIONS_READ_SCOPE, session["scopes"])
self.assertIsNone(server._read_session(f"{server.SESSION_COOKIE_NAME}={token}tampered"))
legacy_payload = {"v": 1, "sub": "legacy", "scopes": [server.OPERATIONS_READ_SCOPE], "exp": 4_102_444_800}
legacy_encoded = server._b64encode(server._json_bytes(legacy_payload))
legacy_signature = server._b64encode(hmac.new(server.SESSION_SECRET, legacy_encoded.encode("ascii"), hashlib.sha256).digest())
self.assertIsNone(server._read_session(f"{server.SESSION_COOKIE_NAME}={legacy_encoded}.{legacy_signature}"))
def test_default_operations_session_expires_after_thirty_minutes(self):
self.assertEqual(server.SESSION_TTL_SECONDS, 1800)
with patch.object(server, "SESSION_SECRET", b"test-session-secret"), patch.object(server, "SESSION_TTL_SECONDS", 1800):
token = server._create_session({"subject": "test", "scopes": [server.OPERATIONS_READ_SCOPE]})
encoded, _ = token.split(".", 1)
payload = json.loads(server._b64decode(encoded))
self.assertGreaterEqual(payload["exp"], int(time.time()) + 1798)
self.assertLessEqual(payload["exp"], int(time.time()) + 1801)
if __name__ == "__main__":
unittest.main()