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) |