324 lines
11 KiB
PHP
324 lines
11 KiB
PHP
<?php
|
|
/**
|
|
* Token Manager Class
|
|
* Handles secure token storage with encryption
|
|
*/
|
|
|
|
// Prevent direct access
|
|
if (!defined('WPINC')) {
|
|
die('Direct access not permitted.');
|
|
}
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
class MCB_Token_Manager {
|
|
|
|
/**
|
|
* Check if encryption is available
|
|
*/
|
|
public static function encryption_available() {
|
|
return function_exists('openssl_encrypt') && function_exists('openssl_decrypt');
|
|
}
|
|
|
|
/**
|
|
* Encrypt token for secure storage
|
|
*/
|
|
public static function encrypt_token($token) {
|
|
if (empty($token)) {
|
|
return '';
|
|
}
|
|
|
|
// Use WordPress salts for encryption
|
|
if (self::encryption_available()) {
|
|
$key = substr(hash('sha256', wp_salt('auth')), 0, 32);
|
|
$iv = substr(hash('sha256', wp_salt('secure')), 0, 16);
|
|
|
|
$encrypted = openssl_encrypt($token, 'AES-256-CBC', $key, 0, $iv);
|
|
if ($encrypted !== false) {
|
|
return base64_encode($encrypted);
|
|
}
|
|
}
|
|
|
|
// Fallback to base64 if encryption not available
|
|
return base64_encode($token);
|
|
}
|
|
|
|
/**
|
|
* Decrypt token for use
|
|
*/
|
|
public static function decrypt_token($encrypted_token) {
|
|
if (empty($encrypted_token)) {
|
|
return '';
|
|
}
|
|
|
|
// Use WordPress salts for decryption
|
|
if (self::encryption_available()) {
|
|
$key = substr(hash('sha256', wp_salt('auth')), 0, 32);
|
|
$iv = substr(hash('sha256', wp_salt('secure')), 0, 16);
|
|
|
|
$decoded = base64_decode($encrypted_token);
|
|
if ($decoded !== false) {
|
|
$decrypted = openssl_decrypt($decoded, 'AES-256-CBC', $key, 0, $iv);
|
|
if ($decrypted !== false) {
|
|
return $decrypted;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fallback to base64 if encryption not available
|
|
return base64_decode($encrypted_token);
|
|
}
|
|
|
|
/**
|
|
* Store encrypted token
|
|
*/
|
|
public static function store_token($platform, $token) {
|
|
if (empty($token)) {
|
|
delete_option('mcb_' . $platform . '_token_encrypted');
|
|
return true;
|
|
}
|
|
|
|
$encrypted = self::encrypt_token($token);
|
|
return update_option('mcb_' . $platform . '_token_encrypted', $encrypted);
|
|
}
|
|
|
|
/**
|
|
* Retrieve and decrypt token
|
|
*/
|
|
public static function get_token($platform) {
|
|
$encrypted = get_option('mcb_' . $platform . '_token_encrypted', '');
|
|
if (empty($encrypted)) {
|
|
// Check for legacy unencrypted token
|
|
$legacy = get_option('mcb_' . $platform . '_token', '');
|
|
if (!empty($legacy)) {
|
|
// Migrate to encrypted storage
|
|
self::store_token($platform, $legacy);
|
|
delete_option('mcb_' . $platform . '_token');
|
|
return $legacy;
|
|
}
|
|
return '';
|
|
}
|
|
|
|
return self::decrypt_token($encrypted);
|
|
}
|
|
|
|
/**
|
|
* Remove all tokens (for privacy/GDPR)
|
|
*/
|
|
public static function remove_all_tokens() {
|
|
$platforms = array('github', 'gitlab', 'bitbucket', 'codeberg');
|
|
foreach ($platforms as $platform) {
|
|
delete_option('mcb_' . $platform . '_token');
|
|
delete_option('mcb_' . $platform . '_token_encrypted');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Privacy Manager Class
|
|
* Handles GDPR compliance and privacy features
|
|
*/
|
|
class MCB_Privacy_Manager {
|
|
|
|
/**
|
|
* Initialize privacy features
|
|
*/
|
|
public static function init() {
|
|
// WordPress privacy policy content
|
|
add_action('admin_init', array(__CLASS__, 'privacy_policy_content'));
|
|
|
|
// Data exporter for GDPR
|
|
add_filter('wp_privacy_personal_data_exporters', array(__CLASS__, 'register_data_exporter'));
|
|
|
|
// Data eraser for GDPR
|
|
add_filter('wp_privacy_personal_data_erasers', array(__CLASS__, 'register_data_eraser'));
|
|
}
|
|
|
|
/**
|
|
* Check if privacy mode is enabled
|
|
*/
|
|
public static function is_privacy_mode() {
|
|
return get_option('mcb_privacy_mode', false);
|
|
}
|
|
|
|
/**
|
|
* Add suggested privacy policy content
|
|
*/
|
|
public static function privacy_policy_content() {
|
|
if (!function_exists('wp_add_privacy_policy_content')) {
|
|
return;
|
|
}
|
|
|
|
$content = '
|
|
<h3>Git Code Viewer Plugin</h3>
|
|
<p>When you visit pages that display code repositories using the Git Code Viewer plugin:</p>
|
|
<ul>
|
|
<li>We temporarily store an anonymized version of your IP address (for 60 seconds) to prevent abuse through rate limiting.</li>
|
|
<li>If security events occur, we may log your browser user agent (for 7 days) for security monitoring.</li>
|
|
<li>We do not use cookies or tracking technologies.</li>
|
|
<li>We only access publicly available repository data from GitHub, GitLab, Bitbucket, and Codeberg.</li>
|
|
<li>No personal information is shared with third parties.</li>
|
|
<li>All data is automatically deleted after the retention period.</li>
|
|
</ul>
|
|
<p>If Privacy Mode is enabled, no IP addresses or user agents are collected.</p>
|
|
';
|
|
|
|
wp_add_privacy_policy_content(
|
|
'Git Code Viewer',
|
|
wp_kses_post($content)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Register data exporter for GDPR requests
|
|
*/
|
|
public static function register_data_exporter($exporters) {
|
|
$exporters['git-code-viewer'] = array(
|
|
'exporter_friendly_name' => __('Git Code Viewer Plugin'),
|
|
'callback' => array(__CLASS__, 'data_exporter')
|
|
);
|
|
return $exporters;
|
|
}
|
|
|
|
/**
|
|
* Export user data for GDPR requests
|
|
*/
|
|
public static function data_exporter($email_address, $page = 1) {
|
|
$export_items = array();
|
|
|
|
// Get security logs that might contain this user's data
|
|
$logs = get_transient('mcb_security_logs');
|
|
|
|
if (is_array($logs)) {
|
|
$user_data = array();
|
|
|
|
// Note: We don't store email addresses, so we can't directly match
|
|
// This is actually good for privacy!
|
|
$user_data[] = array(
|
|
'name' => 'Security Logs Notice',
|
|
'value' => 'The Git Code Viewer plugin does not store email addresses. Any security logs contain only anonymized IP addresses and user agents.'
|
|
);
|
|
|
|
$export_items[] = array(
|
|
'group_id' => 'git-code-viewer',
|
|
'group_label' => 'Git Code Viewer Data',
|
|
'item_id' => 'gcv-notice',
|
|
'data' => $user_data
|
|
);
|
|
}
|
|
|
|
return array(
|
|
'data' => $export_items,
|
|
'done' => true
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Register data eraser for GDPR requests
|
|
*/
|
|
public static function register_data_eraser($erasers) {
|
|
$erasers['git-code-viewer'] = array(
|
|
'eraser_friendly_name' => __('Git Code Viewer Plugin'),
|
|
'callback' => array(__CLASS__, 'data_eraser')
|
|
);
|
|
return $erasers;
|
|
}
|
|
|
|
/**
|
|
* Erase user data for GDPR requests
|
|
*/
|
|
public static function data_eraser($email_address, $page = 1) {
|
|
// Since we don't store email addresses or persistent user data,
|
|
// we can only clear all transient data if requested
|
|
|
|
if ($page === 1) {
|
|
// Clear all security logs
|
|
delete_transient('mcb_security_logs');
|
|
|
|
// Clear all rate limit transients (they expire in 60 seconds anyway)
|
|
global $wpdb;
|
|
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_mcb_rate_%'");
|
|
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_mcb_rate_%'");
|
|
}
|
|
|
|
return array(
|
|
'items_removed' => true,
|
|
'items_retained' => false,
|
|
'messages' => array('All temporary Git Code Viewer data has been removed.'),
|
|
'done' => true
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get anonymized IP for privacy compliance
|
|
*/
|
|
public static function get_anonymized_ip($ip) {
|
|
if (self::is_privacy_mode()) {
|
|
return 'privacy-mode';
|
|
}
|
|
|
|
// Anonymize IP by removing last octet for IPv4 or last 80 bits for IPv6
|
|
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
|
$parts = explode('.', $ip);
|
|
$parts[3] = '0';
|
|
return implode('.', $parts);
|
|
} elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
|
// Zero out last 80 bits
|
|
return substr($ip, 0, strrpos($ip, ':')) . ':0:0:0:0:0';
|
|
}
|
|
|
|
return 'unknown';
|
|
}
|
|
|
|
/**
|
|
* Clean up old data automatically
|
|
*/
|
|
public static function cleanup_old_data() {
|
|
// This is called on a daily schedule
|
|
global $wpdb;
|
|
|
|
// Remove expired rate limit transients
|
|
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_mcb_%' AND option_value < UNIX_TIMESTAMP()");
|
|
|
|
// Remove orphaned transients (fixed MySQL compatibility issue)
|
|
// First get all valid transient names
|
|
$valid_transients = $wpdb->get_col(
|
|
"SELECT CONCAT('_transient_', SUBSTRING(option_name, 20))
|
|
FROM {$wpdb->options}
|
|
WHERE option_name LIKE '_transient_timeout_mcb_%'"
|
|
);
|
|
|
|
if (!empty($valid_transients)) {
|
|
// Build a safe IN clause
|
|
$placeholders = array_fill(0, count($valid_transients), '%s');
|
|
$in_clause = implode(',', $placeholders);
|
|
|
|
// Delete orphaned transients
|
|
$wpdb->query(
|
|
$wpdb->prepare(
|
|
"DELETE FROM {$wpdb->options}
|
|
WHERE option_name LIKE '_transient_mcb_%'
|
|
AND option_name NOT IN ($in_clause)",
|
|
$valid_transients
|
|
)
|
|
);
|
|
} else {
|
|
// No valid transients with timeouts, remove all MCB transients
|
|
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_mcb_%'");
|
|
}
|
|
|
|
// Also clean up expired timeout options
|
|
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_mcb_%' AND option_value < UNIX_TIMESTAMP()");
|
|
}
|
|
}
|
|
|
|
// Initialize privacy features
|
|
MCB_Privacy_Manager::init();
|
|
|
|
// Schedule cleanup (weekly is sufficient for transient cleanup)
|
|
if (!wp_next_scheduled('mcb_privacy_cleanup')) {
|
|
wp_schedule_event(time(), 'weekly', 'mcb_privacy_cleanup');
|
|
}
|
|
add_action('mcb_privacy_cleanup', array('MCB_Privacy_Manager', 'cleanup_old_data'));
|