/** * HVAC Testing Framework - WordPress Security Helpers * * Provides WordPress-specific security validation, nonce verification, * capability checking, and role management for secure testing. * * Security Features: * - WordPress nonce validation and generation * - User capability and role verification * - CSRF protection for WordPress actions * - SQL injection prevention for WordPress queries * - Authentication state validation * * @author Claude Code - Emergency Security Response * @version 1.0.0 * @security CRITICAL - WordPress-specific security validation */ const { getCredentialManager } = require('./SecureCredentialManager'); const { getInputValidator } = require('./SecureInputValidator'); const { getCommandExecutor } = require('./SecureCommandExecutor'); class WordPressSecurityHelpers { constructor() { this.credentialManager = getCredentialManager(); this.inputValidator = getInputValidator(); this.commandExecutor = getCommandExecutor(); this.nonceCache = new Map(); this.capabilityCache = new Map(); this.roleDefinitions = this.initializeRoleDefinitions(); } /** * Initialize WordPress role definitions for HVAC system * @returns {Map} Role definitions with capabilities */ initializeRoleDefinitions() { return new Map([ ['administrator', { capabilities: [ 'manage_options', 'edit_posts', 'edit_others_posts', 'publish_posts', 'edit_pages', 'edit_others_pages', 'publish_pages', 'delete_posts', 'delete_others_posts', 'delete_pages', 'delete_others_pages', 'manage_categories', 'manage_links', 'upload_files', 'edit_files', 'manage_users', 'install_plugins', 'activate_plugins', 'switch_themes', 'edit_themes', 'edit_users', 'delete_users', 'unfiltered_html', 'manage_hvac_system', 'manage_hvac_trainers', 'manage_hvac_events', 'view_hvac_reports', 'export_hvac_data', 'import_hvac_data' ], level: 10, description: 'Full system administrator' }], ['hvac_master_trainer', { capabilities: [ 'read', 'edit_posts', 'publish_posts', 'upload_files', 'manage_hvac_trainers', 'manage_hvac_events', 'view_hvac_reports', 'export_hvac_data', 'import_hvac_data', 'edit_trainer_profiles', 'approve_trainers', 'create_announcements', 'manage_communications', 'view_pending_approvals', 'access_master_dashboard' ], level: 8, description: 'Master trainer with management capabilities' }], ['hvac_trainer', { capabilities: [ 'read', 'edit_own_posts', 'upload_files', 'create_hvac_events', 'edit_own_events', 'manage_own_venues', 'manage_own_organizers', 'view_training_leads', 'edit_own_profile', 'access_trainer_dashboard', 'submit_for_approval' ], level: 5, description: 'Regular trainer with event management' }], ['subscriber', { capabilities: ['read'], level: 0, description: 'Basic read-only access' }] ]); } /** * Verify WordPress nonce for CSRF protection * @param {string} nonce - Nonce value to verify * @param {string} action - Action the nonce was generated for * @param {Object} context - Additional context (user, timestamp, etc.) * @returns {Promise} Verification result */ async verifyWordPressNonce(nonce, action, context = {}) { try { // Basic nonce format validation const validation = this.inputValidator.validate(nonce, 'wp_nonce'); if (!validation.valid) { throw new Error(`Invalid nonce format: ${validation.error}`); } // Check nonce cache for recent verifications const cacheKey = `${nonce}-${action}`; if (this.nonceCache.has(cacheKey)) { const cached = this.nonceCache.get(cacheKey); if (Date.now() - cached.timestamp < 300000) { // 5 minutes return { valid: true, cached: true, ...cached }; } this.nonceCache.delete(cacheKey); } // Verify nonce via WordPress CLI const wpCommand = `eval "echo wp_verify_nonce('${nonce}', '${action}');"`; const result = await this.commandExecutor.executeWordPressCommand(wpCommand); const isValid = result.stdout.trim() === '1'; // Cache result if (isValid) { this.nonceCache.set(cacheKey, { valid: true, action, timestamp: Date.now(), context }); } await this.credentialManager.auditLogger('NONCE_VERIFICATION', { nonce: nonce.substring(0, 6) + '...', action, valid: isValid, context }); return { valid: isValid, action, verified_at: new Date().toISOString() }; } catch (error) { await this.credentialManager.auditLogger('NONCE_VERIFICATION_ERROR', { action, error: error.message, context }); return { valid: false, error: error.message }; } } /** * Generate WordPress nonce for an action * @param {string} action - Action to generate nonce for * @param {Object} context - Generation context * @returns {Promise} Generated nonce */ async generateWordPressNonce(action, context = {}) { try { const wpCommand = `eval "echo wp_create_nonce('${action}');"`; const result = await this.commandExecutor.executeWordPressCommand(wpCommand); const nonce = result.stdout.trim(); // Validate generated nonce const validation = this.inputValidator.validate(nonce, 'wp_nonce'); if (!validation.valid) { throw new Error('Generated nonce is invalid'); } // Cache the generated nonce const cacheKey = `${nonce}-${action}`; this.nonceCache.set(cacheKey, { valid: true, action, timestamp: Date.now(), generated: true, context }); await this.credentialManager.auditLogger('NONCE_GENERATED', { action, nonce: nonce.substring(0, 6) + '...', context }); return nonce; } catch (error) { await this.credentialManager.auditLogger('NONCE_GENERATION_ERROR', { action, error: error.message, context }); throw error; } } /** * Verify user has required capability * @param {number|string} userId - WordPress user ID or username * @param {string} capability - Required capability * @returns {Promise} Capability check result */ async verifyUserCapability(userId, capability) { try { // Validate capability name const validation = this.inputValidator.validate(capability, 'wp_capability'); if (!validation.valid) { throw new Error(`Invalid capability name: ${validation.error}`); } // Check capability cache const cacheKey = `${userId}-${capability}`; if (this.capabilityCache.has(cacheKey)) { const cached = this.capabilityCache.get(cacheKey); if (Date.now() - cached.timestamp < 600000) { // 10 minutes return { ...cached, cached: true }; } this.capabilityCache.delete(cacheKey); } // Get user data via WordPress CLI const userCommand = `user get ${userId} --fields=roles,allcaps --format=json`; const userResult = await this.commandExecutor.executeWordPressCommand(userCommand); const userData = JSON.parse(userResult.stdout); const hasCapability = userData.allcaps && userData.allcaps[capability] === true; // Cache result const result = { userId, capability, hasCapability, userRoles: userData.roles || [], timestamp: Date.now() }; this.capabilityCache.set(cacheKey, result); await this.credentialManager.auditLogger('CAPABILITY_CHECK', { userId, capability, hasCapability, roles: userData.roles }); return result; } catch (error) { await this.credentialManager.auditLogger('CAPABILITY_CHECK_ERROR', { userId, capability, error: error.message }); return { userId, capability, hasCapability: false, error: error.message }; } } /** * Verify user has required role * @param {number|string} userId - WordPress user ID or username * @param {string|Array} requiredRoles - Required role(s) * @returns {Promise} Role verification result */ async verifyUserRole(userId, requiredRoles) { try { const roles = Array.isArray(requiredRoles) ? requiredRoles : [requiredRoles]; // Validate role names for (const role of roles) { const validation = this.inputValidator.validate(role, 'wp_role'); if (!validation.valid) { throw new Error(`Invalid role name: ${role}`); } } // Get user roles const userCommand = `user get ${userId} --fields=roles --format=json`; const result = await this.commandExecutor.executeWordPressCommand(userCommand); const userData = JSON.parse(result.stdout); const userRoles = userData.roles || []; // Check if user has any of the required roles const hasRequiredRole = roles.some(role => userRoles.includes(role)); const matchedRoles = roles.filter(role => userRoles.includes(role)); await this.credentialManager.auditLogger('ROLE_VERIFICATION', { userId, requiredRoles: roles, userRoles, hasRequiredRole, matchedRoles }); return { userId, requiredRoles: roles, userRoles, hasRequiredRole, matchedRoles }; } catch (error) { await this.credentialManager.auditLogger('ROLE_VERIFICATION_ERROR', { userId, requiredRoles, error: error.message }); return { userId, requiredRoles, hasRequiredRole: false, error: error.message }; } } /** * Validate WordPress database query for security * @param {string} query - SQL query to validate * @param {Object} context - Query context * @returns {Object} Query validation result */ validateWordPressQuery(query, context = {}) { try { // Basic SQL injection prevention const sanitized = this.inputValidator.sanitize(query, 'sql'); const validation = this.inputValidator.validate(sanitized, 'text_field', { maxLength: 10000 }); if (!validation.valid) { throw new Error(`Query validation failed: ${validation.error}`); } // WordPress-specific query validation this.validateWordPressQueryStructure(sanitized); this.credentialManager.auditLogger('QUERY_VALIDATION', { queryHash: require('crypto').createHash('sha256').update(query).digest('hex'), context, valid: true }); return { valid: true, sanitizedQuery: sanitized }; } catch (error) { this.credentialManager.auditLogger('QUERY_VALIDATION_ERROR', { error: error.message, context }); return { valid: false, error: error.message }; } } /** * Validate WordPress query structure * @param {string} query - Query to validate */ validateWordPressQueryStructure(query) { const normalizedQuery = query.trim().toLowerCase(); // Only allow SELECT queries for security if (!normalizedQuery.startsWith('select')) { throw new Error('Only SELECT queries are allowed'); } // Block dangerous SQL operations const forbiddenOperations = [ 'drop', 'delete', 'truncate', 'alter', 'create', 'insert', 'update', 'grant', 'revoke', 'exec', 'execute', 'sp_', 'xp_' ]; for (const operation of forbiddenOperations) { if (normalizedQuery.includes(operation)) { throw new Error(`SQL operation not allowed: ${operation}`); } } // Validate table names (must be WordPress tables) const wpTables = [ 'wp_posts', 'wp_users', 'wp_usermeta', 'wp_options', 'wp_postmeta', 'wp_comments', 'wp_commentmeta', 'wp_hvac_trainers', 'wp_hvac_events', 'wp_hvac_venues' ]; // Extract table names from query const tableMatches = query.match(/from\s+(\w+)/gi); if (tableMatches) { for (const match of tableMatches) { const tableName = match.replace(/from\s+/i, '').trim(); if (!wpTables.some(allowed => tableName.includes(allowed))) { throw new Error(`Table not allowed: ${tableName}`); } } } } /** * Validate authentication state for a page/context * @param {Object} page - Playwright page object * @param {string} expectedRole - Expected user role * @returns {Promise} Authentication validation result */ async validateAuthenticationState(page, expectedRole) { try { const currentUrl = page.url(); // Check if on login page (indicates not authenticated) if (currentUrl.includes('/wp-login.php') || currentUrl.includes('/training-login/')) { return { authenticated: false, reason: 'Currently on login page', currentUrl }; } // Role-specific URL validation const roleUrlMap = { 'hvac_master_trainer': /\/master-trainer\//, 'hvac_trainer': /\/trainer\//, 'administrator': /\/wp-admin\// }; if (expectedRole && roleUrlMap[expectedRole]) { if (!roleUrlMap[expectedRole].test(currentUrl)) { return { authenticated: false, reason: `URL does not match expected role: ${expectedRole}`, currentUrl, expectedPattern: roleUrlMap[expectedRole].toString() }; } } // Check for authentication indicators in page content const bodyText = await page.textContent('body').catch(() => ''); // Negative indicators (suggests not authenticated) const negativeIndicators = [ 'please log in', 'login required', 'access denied', 'unauthorized', 'permission denied' ]; for (const indicator of negativeIndicators) { if (bodyText.toLowerCase().includes(indicator)) { return { authenticated: false, reason: `Negative authentication indicator found: ${indicator}`, currentUrl }; } } // Positive indicators (suggests authenticated) const positiveIndicators = { 'hvac_master_trainer': ['master dashboard', 'manage trainers', 'announcements'], 'hvac_trainer': ['trainer dashboard', 'manage events', 'my venues'], 'administrator': ['dashboard', 'admin', 'settings'] }; if (expectedRole && positiveIndicators[expectedRole]) { const hasPositiveIndicator = positiveIndicators[expectedRole] .some(indicator => bodyText.toLowerCase().includes(indicator)); if (!hasPositiveIndicator) { return { authenticated: 'uncertain', reason: 'No positive authentication indicators found', currentUrl }; } } await this.credentialManager.auditLogger('AUTHENTICATION_VALIDATION', { expectedRole, currentUrl, authenticated: true }); return { authenticated: true, role: expectedRole, currentUrl }; } catch (error) { await this.credentialManager.auditLogger('AUTHENTICATION_VALIDATION_ERROR', { expectedRole, error: error.message }); return { authenticated: false, error: error.message }; } } /** * Create CSRF-protected form data * @param {Object} formData - Form data to protect * @param {string} action - WordPress action * @returns {Promise} Protected form data */ async createProtectedFormData(formData, action) { try { // Generate nonce for the action const nonce = await this.generateWordPressNonce(action); // Sanitize all form data const protectedData = {}; for (const [key, value] of Object.entries(formData)) { if (typeof value === 'string') { protectedData[key] = this.inputValidator.sanitize(value, 'wp_content'); } else { protectedData[key] = value; } } // Add security fields protectedData._wpnonce = nonce; protectedData.action = action; return { success: true, data: protectedData, nonce }; } catch (error) { return { success: false, error: error.message }; } } /** * Get role capabilities * @param {string} role - Role name * @returns {Object} Role information */ getRoleCapabilities(role) { const roleInfo = this.roleDefinitions.get(role); if (!roleInfo) { return { exists: false, error: `Unknown role: ${role}` }; } return { exists: true, role, capabilities: roleInfo.capabilities, level: roleInfo.level, description: roleInfo.description }; } /** * Clean up caches (call periodically or on memory pressure) */ cleanupCaches() { const now = Date.now(); // Clean nonce cache (older than 5 minutes) for (const [key, value] of this.nonceCache) { if (now - value.timestamp > 300000) { this.nonceCache.delete(key); } } // Clean capability cache (older than 10 minutes) for (const [key, value] of this.capabilityCache) { if (now - value.timestamp > 600000) { this.capabilityCache.delete(key); } } this.credentialManager.auditLogger('SECURITY_CACHE_CLEANUP', { nonceCacheSize: this.nonceCache.size, capabilityCacheSize: this.capabilityCache.size }); } /** * Get security status summary * @returns {Object} Security status */ getSecurityStatus() { return { timestamp: new Date().toISOString(), caches: { nonces: this.nonceCache.size, capabilities: this.capabilityCache.size }, roles: Array.from(this.roleDefinitions.keys()), securityFeatures: [ 'nonce_verification', 'capability_checking', 'role_validation', 'query_sanitization', 'auth_state_validation' ] }; } } // Singleton instance let wpSecurityHelpersInstance = null; /** * Get singleton instance of WordPressSecurityHelpers * @returns {WordPressSecurityHelpers} */ function getWordPressSecurityHelpers() { if (!wpSecurityHelpersInstance) { wpSecurityHelpersInstance = new WordPressSecurityHelpers(); } return wpSecurityHelpersInstance; } module.exports = { WordPressSecurityHelpers, getWordPressSecurityHelpers };