fix: security remediation follow-up and OLT button layout improvements

- fix Casdoor JWT verification with correct PythonProject public key
- fix iMC TLS cert validation with custom SSL adapter (skip hostname check for non-DNS CN)
- add iMC CA cert and Casdoor public key to build context
- improve OLT manage page: unify button styles, fix mobile grid spacing, replace el-upload with native input for consistent alignment
- swap NTP sync button for duplicate MAC on mobile

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-30 15:05:52 +08:00
parent 26f6ca2d8d
commit f511e3e808
5 changed files with 135 additions and 35 deletions
+1 -1
View File
@@ -37,7 +37,7 @@ def verify_casdoor_token(token: str) -> dict:
header = pyjwt.get_unverified_header(token)
algorithm = header.get("alg")
if algorithm not in {"RS256", "RS384", "RS512"}:
raise ValueError("Unsupported Casdoor token algorithm")
raise ValueError(f"Unsupported Casdoor token algorithm: {algorithm}")
return pyjwt.decode(
token,
+37 -1
View File
@@ -39,16 +39,52 @@ class IMCService:
"""iMC REST API 服务封装"""
def __init__(self):
import os
import tempfile
import certifi
from requests.adapters import HTTPAdapter
self.base_url = settings.IMC_API_URL.rstrip('/')
self.username = settings.IMC_API_USERNAME
self.password = settings.IMC_API_PASSWORD
self.verify_ssl = settings.IMC_API_VERIFY_SSL
self.connect_timeout = settings.IMC_CONNECT_TIMEOUT
self.read_timeout = settings.IMC_READ_TIMEOUT
self.session = requests.Session()
self.realm = "iMC RESTful Web Services"
self.nonce = None
self.nc = 1
self._ca_bundle_file = None # temp file for certifi + iMC CA
if settings.IMC_API_VERIFY_SSL:
imc_ca_path = os.path.join(os.path.dirname(__file__), "..", "..", "imc_ca.pem")
if os.path.isfile(imc_ca_path):
# The iMC self-signed cert uses a non-DNS CN and zero SAN entries,
# so hostname matching is impossible. We still enforce full
# certificate-chain verification via a combined CA bundle, then
# tell urllib3 to skip its own hostname check.
with open(imc_ca_path, "rb") as fh:
imc_pem = fh.read()
self._ca_bundle_file = tempfile.NamedTemporaryFile(suffix=".pem", delete=False)
with open(certifi.where(), "rb") as fh:
self._ca_bundle_file.write(fh.read())
self._ca_bundle_file.write(b"\n")
self._ca_bundle_file.write(imc_pem)
self._ca_bundle_file.flush()
_ca_bundle = self._ca_bundle_file.name
class _IMCAdapter(HTTPAdapter):
def cert_verify(self, conn, url, verify, cert):
super().cert_verify(conn, url, verify=_ca_bundle, cert=cert)
conn.assert_hostname = False
self.session.mount("https://", _IMCAdapter())
self.verify_ssl = True
logger.info("iMC TLS verification enabled (hostname check relaxed)")
else:
self.verify_ssl = True
else:
self.verify_ssl = False
# ── Digest 认证 ──────────────────────────────────────────────────────────
+18
View File
@@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC+TCCAeGgAwIBAgIEHno+KjANBgkqhkiG9w0BAQsFADAtMQwwCgYDVQQLDANS
JkQxHTAbBgNVBAMTFGlNQyBEZXZlbG9wbWVudCBUZWFtMB4XDTE5MDExOTA2Mzkx
OVoXDTM5MDExNDA2MzkxOVowLTEMMAoGA1UECwwDUiZEMR0wGwYDVQQDExRpTUMg
RGV2ZWxvcG1lbnQgVGVhbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AIVGUvmtUD6Gx0AQLi+/voDIuSERGLyTLcQQOBmWVWLfxr0i1PGxuGODylN+30Ud
pBY1faRijvUZUWN51dxjXMRLnHUecBuPVyWOv1jPFMd1jcc7KmQ4KY6s43t1cC2R
pBcfyBhVmN+tArhcbDJb6MUuN1P5ZGi3E1vor9EY0Gt8TvJ/GgmqgWwlveAC0soH
yTTk8UU9m2OikbaZ0dKgQbshna4wrSWw9iyJ5Bao4rZgus3hGZUnznWpPD0/jHC4
lI3znCKQPAsnJculZcK/8dm1UYKFx7C3L5z4SnzsLY+rTYXvmGfb2x06wKkkJTIt
oepc1U5U/jNlUVbNdA9yhP0CAwEAAaMhMB8wHQYDVR0OBBYEFGErff6AvZVRmgqK
DLQxMBGOr0ObMA0GCSqGSIb3DQEBCwUAA4IBAQA9HhuDvrVI4ICyddy5g4DM+bv4
NaKoBS0Y5nhANbN/0f0J0Zj32OYtbsJlUZViFQ42LIR7b37x6OgMXRjHJIDg3q+9
tkH5S8Q5xmb5Tqq0WcOWQmg0o1OjW5iJNAfKiTffGtc5DjmrOBuCf1P23G3polRB
34QxGgnRygEDKy5aYumaXyiL1yMgDLkZ2adg2lWvsBSpvmgzMF5H75Spq4WvK60Z
o3EyCpB5ZS4SxwF3JH+LdpsyCc+UsyYW4/v/FVidtCp+nNTgsTA1yI8vcPVyPxPQ
SvVFIvuLYpo9cbHLcmQ95LBKjMbhPVHMzsBRVd85xKVjBspRMWeUp7F94W8V
-----END CERTIFICATE-----
+14
View File
@@ -0,0 +1,14 @@
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEArvxqOc2fCe611njyX3aG
aprZzyU/UhynIHsJhVMq6tDFLPmJCitg5WFHVTOW5KlTzKI894XJNEPSkf4POoai
1z4CW/GeRrkN8/XP8/RqyiCQAslei5FH5i1RjqLWijm0qUNsgYk07ZpBw45qT1ti
ipJF33L+mr9s+O17ujDDb2EZBzTcuBs1eYhv2fk0bOdYK17Vzzw1StPP0OWmfYev
edw6YemqnODk21e6CGcewXhgIX6k8iBaULQLoy0qTR+4nKwJjnCm4DrbSkYvjM7p
AW/3bQyJax1VbFw2jRo1CRcoaMRzLcutpe7HaT+wf4TR7cZFcaFZieSsruLySD5X
42bVgWDIZtjtXfJnSZc/Fv6IyUi/PzzlWIaxGcqqVHSk3W/w1ubNc+70SQA7fCJP
8vh/GcRNbWF3+mH4B1DSMSl7IAxGNyXeQyNq44Gp9T9MeXsvppQxcHd3Tn4krRi5
NzWetnRCOH/kQblfK9FK4o1XGkIHVdwCUBoFO4Tlqu3qNCQIkg28Wg6OgdkHrkoc
lW98y0y5Wvt0tJtg48cfpgkHZ9SKM0qhedyUBvGV1fd8QAuwL9JbvMwUX7cVOGBJ
6rc4Sk11uRGVCnmw5Ed5ORa0w70DrQYe9OoNqYiqmkLh+TvXMhGmYQ+V8aw6ejmA
2Efz40ofkK+Z+Pizv9L3wP8CAwEAAQ==
-----END PUBLIC KEY-----