232 lines
7.4 KiB
Python
232 lines
7.4 KiB
Python
|
|
import re
|
|||
|
|
import hashlib
|
|||
|
|
from datetime import datetime, timedelta
|
|||
|
|
from typing import Tuple, Optional, Dict, Any
|
|||
|
|
|
|||
|
|
class LicenseValidator:
|
|||
|
|
"""许可证验证器"""
|
|||
|
|
|
|||
|
|
def __init__(self, config=None):
|
|||
|
|
"""
|
|||
|
|
初始化验证器
|
|||
|
|
:param config: 配置字典
|
|||
|
|
"""
|
|||
|
|
self.config = config or {}
|
|||
|
|
self.max_failed_attempts = self.config.get('MAX_FAILED_ATTEMPTS', 5)
|
|||
|
|
self.lockout_minutes = self.config.get('LOCKOUT_MINUTES', 10)
|
|||
|
|
|
|||
|
|
def validate_license_key(self, license_key: str) -> bool:
|
|||
|
|
"""
|
|||
|
|
验证卡密格式(支持XXXX-XXXX-XXXX-XXXX格式)
|
|||
|
|
:param license_key: 卡密字符串
|
|||
|
|
:return: 是否有效
|
|||
|
|
"""
|
|||
|
|
if not license_key:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 去除空格和制表符,并转为大写
|
|||
|
|
license_key = license_key.strip().replace(' ', '').replace('\t', '').upper()
|
|||
|
|
|
|||
|
|
# 检查是否为XXXX-XXXX-XXXX-XXXX格式
|
|||
|
|
if '-' in license_key:
|
|||
|
|
parts = license_key.split('-')
|
|||
|
|
# 应该有4部分,每部分8个字符
|
|||
|
|
if len(parts) == 4 and all(len(part) == 8 for part in parts):
|
|||
|
|
# 检查所有字符是否为大写字母或数字
|
|||
|
|
combined = ''.join(parts)
|
|||
|
|
if len(combined) == 32:
|
|||
|
|
pattern = r'^[A-Z0-9]+$'
|
|||
|
|
import re
|
|||
|
|
return bool(re.match(pattern, combined))
|
|||
|
|
return False
|
|||
|
|
else:
|
|||
|
|
# 兼容旧格式:检查长度(16-32位)
|
|||
|
|
if len(license_key) < 16 or len(license_key) > 32:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 检查字符(只允许大写字母和数字)
|
|||
|
|
pattern = r'^[A-Z0-9_]+$'
|
|||
|
|
import re
|
|||
|
|
return bool(re.match(pattern, license_key))
|
|||
|
|
|
|||
|
|
def format_license_key(self, license_key: str) -> str:
|
|||
|
|
"""
|
|||
|
|
格式化卡密为XXXX-XXXX-XXXX-XXXX格式
|
|||
|
|
:param license_key: 原始卡密
|
|||
|
|
:return: 格式化后的卡密
|
|||
|
|
"""
|
|||
|
|
if not license_key:
|
|||
|
|
return ''
|
|||
|
|
|
|||
|
|
# 去除空格、制表符和连字符,并转为大写
|
|||
|
|
clean_key = license_key.strip().replace(' ', '').replace('\t', '').replace('-', '').upper()
|
|||
|
|
|
|||
|
|
# 如果长度不足32位,右补0
|
|||
|
|
if len(clean_key) < 32:
|
|||
|
|
clean_key = clean_key.ljust(32, '0')
|
|||
|
|
# 如果长度超过32位,截取前32位
|
|||
|
|
elif len(clean_key) > 32:
|
|||
|
|
clean_key = clean_key[:32]
|
|||
|
|
|
|||
|
|
# 格式化为XXXX-XXXX-XXXX-XXXX格式
|
|||
|
|
formatted_key = '-'.join([
|
|||
|
|
clean_key[i:i+8] for i in range(0, len(clean_key), 8)
|
|||
|
|
])
|
|||
|
|
|
|||
|
|
return formatted_key
|
|||
|
|
|
|||
|
|
def check_failed_attempts(self, failed_attempts: int, last_attempt_time: datetime) -> Tuple[bool, int]:
|
|||
|
|
"""
|
|||
|
|
检查失败尝试次数和时间
|
|||
|
|
:param failed_attempts: 失败次数
|
|||
|
|
:param last_attempt_time: 最后尝试时间
|
|||
|
|
:return: (是否允许尝试, 剩余锁定时间(秒))
|
|||
|
|
"""
|
|||
|
|
if failed_attempts < self.max_failed_attempts:
|
|||
|
|
return True, 0
|
|||
|
|
|
|||
|
|
# 检查锁定时间是否已过
|
|||
|
|
lock_time = timedelta(minutes=self.lockout_minutes)
|
|||
|
|
time_passed = datetime.utcnow() - last_attempt_time
|
|||
|
|
|
|||
|
|
if time_passed >= lock_time:
|
|||
|
|
return True, 0
|
|||
|
|
|
|||
|
|
remaining_seconds = int((lock_time - time_passed).total_seconds())
|
|||
|
|
return False, remaining_seconds
|
|||
|
|
|
|||
|
|
def validate_software_version(self, version: str) -> bool:
|
|||
|
|
"""
|
|||
|
|
验证软件版本格式
|
|||
|
|
:param version: 版本字符串
|
|||
|
|
:return: 是否有效
|
|||
|
|
"""
|
|||
|
|
if not version:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 语义化版本格式:主版本号.次版本号.修订号
|
|||
|
|
pattern = r'^\d+\.\d+\.\d+$'
|
|||
|
|
return bool(re.match(pattern, version))
|
|||
|
|
|
|||
|
|
def compare_versions(self, version1: str, version2: str) -> int:
|
|||
|
|
"""
|
|||
|
|
比较版本号
|
|||
|
|
:param version1: 版本1
|
|||
|
|
:param version2: 版本2
|
|||
|
|
:return: -1(version1<version2), 0(version1==version2), 1(version1>version2)
|
|||
|
|
"""
|
|||
|
|
try:
|
|||
|
|
v1_parts = [int(x) for x in version1.split('.')]
|
|||
|
|
v2_parts = [int(x) for x in version2.split('.')]
|
|||
|
|
|
|||
|
|
# 补齐版本号长度
|
|||
|
|
max_len = max(len(v1_parts), len(v2_parts))
|
|||
|
|
v1_parts.extend([0] * (max_len - len(v1_parts)))
|
|||
|
|
v2_parts.extend([0] * (max_len - len(v2_parts)))
|
|||
|
|
|
|||
|
|
for v1, v2 in zip(v1_parts, v2_parts):
|
|||
|
|
if v1 < v2:
|
|||
|
|
return -1
|
|||
|
|
elif v1 > v2:
|
|||
|
|
return 1
|
|||
|
|
|
|||
|
|
return 0
|
|||
|
|
except (ValueError, AttributeError):
|
|||
|
|
return -1
|
|||
|
|
|
|||
|
|
def validate_machine_code(self, machine_code: str) -> bool:
|
|||
|
|
"""
|
|||
|
|
验证机器码格式
|
|||
|
|
:param machine_code: 机器码字符串
|
|||
|
|
:return: 是否有效
|
|||
|
|
"""
|
|||
|
|
if not machine_code:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 机器码应该是32位大写字母和数字的组合
|
|||
|
|
if len(machine_code) != 32:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
pattern = r'^[A-F0-9]+$'
|
|||
|
|
return bool(re.match(pattern, machine_code))
|
|||
|
|
|
|||
|
|
def create_verification_hash(self, data: Dict[str, Any], secret_key: str) -> str:
|
|||
|
|
"""
|
|||
|
|
创建验证哈希
|
|||
|
|
:param data: 要验证的数据字典
|
|||
|
|
:param secret_key: 密钥
|
|||
|
|
:return: 哈希值
|
|||
|
|
"""
|
|||
|
|
# 按键排序确保一致性
|
|||
|
|
sorted_data = sorted(data.items())
|
|||
|
|
combined = '&'.join([f"{k}={v}" for k, v in sorted_data])
|
|||
|
|
combined += f"&key={secret_key}"
|
|||
|
|
|
|||
|
|
hash_obj = hashlib.sha256(combined.encode('utf-8'))
|
|||
|
|
return hash_obj.hexdigest()
|
|||
|
|
|
|||
|
|
def verify_hash(self, data: Dict[str, Any], hash_value: str, secret_key: str) -> bool:
|
|||
|
|
"""
|
|||
|
|
验证哈希值
|
|||
|
|
:param data: 原始数据字典
|
|||
|
|
:param hash_value: 要验证的哈希值
|
|||
|
|
:param secret_key: 密钥
|
|||
|
|
:return: 验证结果
|
|||
|
|
"""
|
|||
|
|
computed_hash = self.create_verification_hash(data, secret_key)
|
|||
|
|
return computed_hash == hash_value
|
|||
|
|
|
|||
|
|
def is_url_safe(self, url: str) -> bool:
|
|||
|
|
"""
|
|||
|
|
检查URL是否安全
|
|||
|
|
:param url: URL字符串
|
|||
|
|
:return: 是否安全
|
|||
|
|
"""
|
|||
|
|
if not url:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 基本URL格式检查
|
|||
|
|
pattern = r'^https?://[^\s/$.?#].[^\s]*$'
|
|||
|
|
if not re.match(pattern, url):
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 检查协议
|
|||
|
|
if not url.startswith(('http://', 'https://')):
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
def sanitize_input(self, input_str: str) -> str:
|
|||
|
|
"""
|
|||
|
|
清理输入字符串
|
|||
|
|
:param input_str: 输入字符串
|
|||
|
|
:return: 清理后的字符串
|
|||
|
|
"""
|
|||
|
|
if not input_str:
|
|||
|
|
return ''
|
|||
|
|
|
|||
|
|
# 移除特殊字符
|
|||
|
|
dangerous_chars = ['<', '>', '"', "'", '&', '\x00']
|
|||
|
|
for char in dangerous_chars:
|
|||
|
|
input_str = input_str.replace(char, '')
|
|||
|
|
|
|||
|
|
# 限制长度
|
|||
|
|
return input_str[:1000]
|
|||
|
|
|
|||
|
|
def format_license_key(license_key: str) -> str:
|
|||
|
|
"""
|
|||
|
|
格式化卡密的便捷函数
|
|||
|
|
:param license_key: 原始卡密
|
|||
|
|
:return: 格式化后的卡密
|
|||
|
|
"""
|
|||
|
|
validator = LicenseValidator()
|
|||
|
|
return validator.format_license_key(license_key)
|
|||
|
|
|
|||
|
|
def validate_license_key(license_key: str) -> bool:
|
|||
|
|
"""
|
|||
|
|
验证卡密格式的便捷函数
|
|||
|
|
:param license_key: 卡密字符串
|
|||
|
|
:return: 是否有效
|
|||
|
|
"""
|
|||
|
|
validator = LicenseValidator()
|
|||
|
|
return validator.validate_license_key(license_key)
|