wpbridge/includes/Commercial/LicenseProxy.php
feibisi 39b30af242 feat(commercial): add vendor system for third-party GPL plugin sources
- Add VendorInterface, AbstractVendor, VendorManager for vendor abstraction
- Add WooCommerceVendor for WooCommerce API Manager integration
- Add BridgeManager with hybrid mode (official + vendor + custom sources)
- Add GPLValidator for GPL compliance checking
- Add LicenseProxy for license key management
- Add VendorAdmin with AJAX handlers for vendor CRUD operations
- Add vendors.php admin tab with stats, vendor list, custom plugins
- Add vendor management CSS styles and JS module
- Update main.php to include vendors tab

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-15 08:39:37 +08:00

572 lines
15 KiB
PHP

<?php
/**
* 商业插件授权代理
*
* 拦截商业插件的授权验证请求,转发到文派授权服务
*
* @package WPBridge
* @since 0.9.7
*/
declare(strict_types=1);
namespace WPBridge\Commercial;
use WPBridge\Core\Logger;
use WPBridge\Core\Settings;
// 防止直接访问
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* LicenseProxy 类
*/
class LicenseProxy {
/**
* 支持的授权系统配置
*/
private const VENDORS = [
'edd' => [
'name' => 'EDD Software Licensing',
'patterns' => [
'/edd-sl/',
'/edd-api/',
'action=activate_license',
'action=check_license',
'action=deactivate_license',
],
'response_format' => 'edd',
],
'freemius' => [
'name' => 'Freemius',
'patterns' => [
'api.freemius.com',
'wp-json/freemius',
],
'response_format' => 'freemius',
],
'wc_am' => [
'name' => 'WooCommerce API Manager',
'patterns' => [
'wc-api/wc-am-api',
'wc-api/am-software-api',
],
'response_format' => 'wc_am',
],
];
/**
* 敏感参数列表(用于日志过滤)
*/
private const SENSITIVE_PARAMS = [
'license_key',
'license',
'key',
'password',
'secret',
'token',
'api_key',
'apikey',
];
/**
* 设置实例
*
* @var Settings
*/
private Settings $settings;
/**
* 已桥接的插件列表
*
* @var array
*/
private array $bridged_plugins = [];
/**
* 构造函数
*
* @param Settings $settings 设置实例
*/
public function __construct( Settings $settings ) {
$this->settings = $settings;
$this->bridged_plugins = $this->settings->get( 'bridged_plugins', [] );
}
/**
* 初始化钩子
*/
public function init(): void {
if ( ! $this->is_enabled() ) {
return;
}
add_filter( 'pre_http_request', [ $this, 'intercept_request' ], 5, 3 );
}
/**
* 检查是否启用
*
* @return bool
*/
private function is_enabled(): bool {
return (bool) $this->settings->get( 'license_proxy_enabled', false );
}
/**
* 拦截 HTTP 请求
*
* @param false|array|\WP_Error $preempt 预处理结果
* @param array $args 请求参数
* @param string $url 请求 URL
* @return false|array|\WP_Error
*/
public function intercept_request( $preempt, array $args, string $url ) {
// 1. 检测授权系统
$vendor = $this->detect_vendor( $url );
if ( $vendor === null ) {
return $preempt;
}
// 2. 提取插件标识
$plugin_slug = $this->extract_plugin_slug( $url, $args, $vendor );
if ( $plugin_slug === null ) {
return $preempt;
}
// 3. 检查是否在桥接列表
if ( ! $this->is_bridged( $plugin_slug ) ) {
return $preempt;
}
// H2 修复: 过滤敏感信息后再记录日志
Logger::debug( 'License proxy intercepting', [
'vendor' => $vendor,
'plugin' => $plugin_slug,
'url' => $this->sanitize_url_for_log( $url ),
] );
// 4. 代理到文派服务
return $this->proxy_request( $vendor, $plugin_slug, $url, $args );
}
/**
* H2 修复: 过滤 URL 中的敏感参数
*
* @param string $url 原始 URL
* @return string 过滤后的 URL
*/
private function sanitize_url_for_log( string $url ): string {
$pattern = '/(' . implode( '|', self::SENSITIVE_PARAMS ) . ')=[^&]+/i';
return preg_replace( $pattern, '$1=[REDACTED]', $url );
}
/**
* H2 修复: 过滤请求体中的敏感参数
*
* @param array $body 请求体
* @return array 过滤后的请求体
*/
private function sanitize_body_for_log( array $body ): array {
$sanitized = [];
foreach ( $body as $key => $value ) {
if ( in_array( strtolower( $key ), self::SENSITIVE_PARAMS, true ) ) {
$sanitized[ $key ] = '[REDACTED]';
} else {
$sanitized[ $key ] = $value;
}
}
return $sanitized;
}
/**
* 检测授权系统供应商
*
* @param string $url 请求 URL
* @return string|null
*/
private function detect_vendor( string $url ): ?string {
foreach ( self::VENDORS as $vendor_key => $config ) {
foreach ( $config['patterns'] as $pattern ) {
if ( stripos( $url, $pattern ) !== false ) {
return $vendor_key;
}
}
}
return null;
}
/**
* 提取插件 slug
*
* @param string $url 请求 URL
* @param array $args 请求参数
* @param string $vendor 供应商
* @return string|null
*/
private function extract_plugin_slug( string $url, array $args, string $vendor ): ?string {
// 从 URL 参数提取
$parsed = wp_parse_url( $url );
if ( isset( $parsed['query'] ) ) {
parse_str( $parsed['query'], $query );
// EDD 格式
if ( isset( $query['item_name'] ) ) {
return sanitize_title( $query['item_name'] );
}
if ( isset( $query['item_id'] ) ) {
return $this->resolve_item_id( (string) $query['item_id'] );
}
}
// 从 POST body 提取
if ( isset( $args['body'] ) && is_array( $args['body'] ) ) {
if ( isset( $args['body']['item_name'] ) ) {
return sanitize_title( $args['body']['item_name'] );
}
if ( isset( $args['body']['product_id'] ) ) {
return $this->resolve_item_id( (string) $args['body']['product_id'] );
}
}
// Freemius 格式: /v1/plugins/{id}/...
if ( $vendor === 'freemius' && preg_match( '#/plugins/(\d+)/#', $url, $matches ) ) {
return $this->resolve_freemius_id( $matches[1] );
}
return null;
}
/**
* 解析 item_id 到 slug
*
* @param string $item_id 项目 ID
* @return string|null
*/
private function resolve_item_id( string $item_id ): ?string {
// 从远程配置获取 ID 到 slug 的映射
$mapping = $this->settings->get( 'item_id_mapping', [] );
return $mapping[ $item_id ] ?? null;
}
/**
* 解析 Freemius ID 到 slug
*
* @param string $freemius_id Freemius ID
* @return string|null
*/
private function resolve_freemius_id( string $freemius_id ): ?string {
$mapping = $this->settings->get( 'freemius_id_mapping', [] );
return $mapping[ $freemius_id ] ?? null;
}
/**
* 检查插件是否在桥接列表
*
* @param string $plugin_slug 插件 slug
* @return bool
*/
private function is_bridged( string $plugin_slug ): bool {
return in_array( $plugin_slug, $this->bridged_plugins, true );
}
/**
* H1 修复: 生成安全的站点指纹
*
* 使用多因素生成站点指纹,防止伪造
*
* @return string
*/
private function generate_site_fingerprint(): string {
$factors = [
home_url(),
defined( 'DB_NAME' ) ? DB_NAME : '',
defined( 'AUTH_KEY' ) ? AUTH_KEY : '',
defined( 'SECURE_AUTH_KEY' ) ? SECURE_AUTH_KEY : '',
php_uname( 'n' ), // 主机名
];
return hash( 'sha256', implode( '|', $factors ) );
}
/**
* H3 修复: 生成请求签名
*
* @param string $api_key API Key
* @param string $plugin_slug 插件 slug
* @param string $action 操作类型
* @param string $timestamp 时间戳
* @return string
*/
private function generate_request_signature( string $api_key, string $plugin_slug, string $action, string $timestamp ): string {
$data = implode( '|', [
$plugin_slug,
$this->generate_site_fingerprint(),
$action,
$timestamp,
] );
return hash_hmac( 'sha256', $data, $api_key );
}
/**
* 代理请求到文派服务
*
* @param string $vendor 供应商
* @param string $plugin_slug 插件 slug
* @param string $original_url 原始 URL
* @param array $args 请求参数
* @return array|false
*/
private function proxy_request( string $vendor, string $plugin_slug, string $original_url, array $args ) {
$proxy_url = $this->settings->get(
'license_proxy_url',
'https://updates.wenpai.net/api/v1/license/proxy'
);
$api_key = $this->get_api_key();
$timestamp = (string) time();
$action = $this->extract_action( $original_url, $args );
// H1 + H3 修复: 使用站点指纹和请求签名
$site_fingerprint = $this->generate_site_fingerprint();
$signature = $this->generate_request_signature( $api_key, $plugin_slug, $action, $timestamp );
$response = wp_remote_post( $proxy_url, [
'timeout' => 15,
'headers' => [
'Content-Type' => 'application/json',
'X-WPBridge-Key' => $api_key,
'X-WPBridge-Signature' => $signature,
'X-WPBridge-Timestamp' => $timestamp,
'X-WPBridge-Fingerprint' => $site_fingerprint,
],
'body' => wp_json_encode( [
'vendor' => $vendor,
'plugin_slug' => $plugin_slug,
'action' => $action,
'site_url' => home_url(),
'site_fingerprint' => $site_fingerprint,
] ),
] );
if ( is_wp_error( $response ) ) {
Logger::error( 'License proxy failed', [
'error' => $response->get_error_message(),
] );
// 失败时不拦截,让原始请求继续
return false;
}
// 转换响应格式
return $this->transform_response( $vendor, $plugin_slug, $response );
}
/**
* H4 修复: 转换响应格式以匹配原厂 API
*
* @param string $vendor 供应商
* @param string $plugin_slug 插件 slug
* @param array $response 响应
* @return array|false
*/
private function transform_response( string $vendor, string $plugin_slug, array $response ) {
$body = json_decode( wp_remote_retrieve_body( $response ), true );
if ( ! isset( $body['success'] ) || ! $body['success'] ) {
return false; // 让原始请求继续
}
$license = $body['license'] ?? [];
// 根据不同授权系统返回不同格式
switch ( $vendor ) {
case 'edd':
return $this->format_edd_response( $license, $plugin_slug );
case 'freemius':
return $this->format_freemius_response( $license, $plugin_slug );
case 'wc_am':
return $this->format_wc_am_response( $license );
default:
return $this->format_generic_response( $license );
}
}
/**
* H4 修复: 格式化 EDD 响应(完整字段)
*
* @param array $license 授权信息
* @param string $plugin_slug 插件 slug
* @return array
*/
private function format_edd_response( array $license, string $plugin_slug ): array {
// 获取插件特定的响应配置
$plugin_config = $this->get_plugin_response_config( $plugin_slug, 'edd' );
$body = wp_json_encode( array_merge( [
'success' => true,
'license' => $license['status'] ?? 'valid',
'item_id' => $license['item_id'] ?? '',
'item_name' => $license['item_name'] ?? $plugin_config['item_name'] ?? '',
'license_limit' => $license['license_limit'] ?? 0,
'site_count' => $license['site_count'] ?? 1,
'expires' => $license['expires'] ?? 'lifetime',
'activations_left' => $license['activations_left'] ?? 'unlimited',
'checksum' => $license['checksum'] ?? $this->generate_checksum( $license ),
'payment_id' => $license['payment_id'] ?? 0,
'customer_name' => $license['customer_name'] ?? '',
'customer_email' => $license['customer_email'] ?? '',
'price_id' => $license['price_id'] ?? false,
], $plugin_config['extra_fields'] ?? [] ) );
return [
'response' => [ 'code' => 200, 'message' => 'OK' ],
'body' => $body,
'headers' => [ 'content-type' => 'application/json' ],
];
}
/**
* H4 修复: 格式化 Freemius 响应(完整字段)
*
* @param array $license 授权信息
* @param string $plugin_slug 插件 slug
* @return array
*/
private function format_freemius_response( array $license, string $plugin_slug ): array {
$plugin_config = $this->get_plugin_response_config( $plugin_slug, 'freemius' );
$body = wp_json_encode( array_merge( [
'id' => $license['id'] ?? 0,
'plugin_id' => $license['plugin_id'] ?? $plugin_config['plugin_id'] ?? 0,
'user_id' => $license['user_id'] ?? 0,
'plan_id' => $license['plan_id'] ?? $plugin_config['plan_id'] ?? 0,
'pricing_id' => $license['pricing_id'] ?? 0,
'quota' => $license['license_limit'] ?? null,
'activated' => $license['site_count'] ?? 1,
'activated_local' => 1,
'expiration' => $license['expires'] ?? null,
'secret_key' => $license['secret_key'] ?? $this->generate_secret_key(),
'public_key' => $license['public_key'] ?? $plugin_config['public_key'] ?? '',
'is_free_localhost' => false,
'is_block_features' => false,
'is_cancelled' => false,
'is_whitelabeled' => $license['is_whitelabeled'] ?? false,
], $plugin_config['extra_fields'] ?? [] ) );
return [
'response' => [ 'code' => 200, 'message' => 'OK' ],
'body' => $body,
'headers' => [ 'content-type' => 'application/json' ],
];
}
/**
* 格式化 WC API Manager 响应
*
* @param array $license 授权信息
* @return array
*/
private function format_wc_am_response( array $license ): array {
$body = wp_json_encode( [
'success' => true,
'status_check' => 'active',
'data' => 'active',
'activations' => (string) ( $license['site_count'] ?? 1 ),
'activations_limit' => (string) ( $license['license_limit'] ?? 'unlimited' ),
'activations_remaining'=> (string) ( $license['activations_left'] ?? 'unlimited' ),
] );
return [
'response' => [ 'code' => 200, 'message' => 'OK' ],
'body' => $body,
'headers' => [ 'content-type' => 'application/json' ],
];
}
/**
* 格式化通用响应
*
* @param array $license 授权信息
* @return array
*/
private function format_generic_response( array $license ): array {
$body = wp_json_encode( [
'success' => true,
'license' => $license['status'] ?? 'valid',
'expires' => $license['expires'] ?? 'lifetime',
] );
return [
'response' => [ 'code' => 200, 'message' => 'OK' ],
'body' => $body,
'headers' => [ 'content-type' => 'application/json' ],
];
}
/**
* H4 修复: 获取插件特定的响应配置
*
* @param string $plugin_slug 插件 slug
* @param string $vendor 供应商
* @return array
*/
private function get_plugin_response_config( string $plugin_slug, string $vendor ): array {
$configs = $this->settings->get( 'plugin_response_configs', [] );
return $configs[ $plugin_slug ][ $vendor ] ?? [];
}
/**
* 生成校验和
*
* @param array $license 授权信息
* @return string
*/
private function generate_checksum( array $license ): string {
return md5( wp_json_encode( $license ) . home_url() );
}
/**
* 生成 Freemius secret_key
*
* @return string
*/
private function generate_secret_key(): string {
return 'sk_' . bin2hex( random_bytes( 16 ) );
}
/**
* 获取 API Key
*
* @return string
*/
private function get_api_key(): string {
return $this->settings->get( 'wenpai_api_key', '' );
}
/**
* 提取操作类型
*
* @param string $url 请求 URL
* @param array $args 请求参数
* @return string
*/
private function extract_action( string $url, array $args ): string {
// 从 URL 提取
if ( preg_match( '/action=(\w+)/', $url, $matches ) ) {
return $matches[1];
}
// 从 body 提取
if ( isset( $args['body']['edd_action'] ) ) {
return $args['body']['edd_action'];
}
if ( isset( $args['body']['wc-api'] ) ) {
return $args['body']['request'] ?? 'status';
}
return 'check_license';
}
}