upskill-event-manager/lib/security/WordPressSecurityHelpers.js
Ben c3e7fe9140 feat: comprehensive HVAC plugin development framework and modernization
## Major Enhancements

### 🏗️ Architecture & Infrastructure
- Implement comprehensive Docker testing infrastructure with hermetic environment
- Add Forgejo Actions CI/CD pipeline for automated deployments
- Create Page Object Model (POM) testing architecture reducing test duplication by 90%
- Establish security-first development patterns with input validation and output escaping

### 🧪 Testing Framework Modernization
- Migrate 146+ tests from 80 duplicate files to centralized architecture
- Add comprehensive E2E test suites for all user roles and workflows
- Implement WordPress error detection with automatic site health monitoring
- Create robust browser lifecycle management with proper cleanup

### 📚 Documentation & Guides
- Add comprehensive development best practices guide
- Create detailed administrator setup documentation
- Establish user guides for trainers and master trainers
- Document security incident reports and migration guides

### 🔧 Core Plugin Features
- Enhance trainer profile management with certification system
- Improve find trainer functionality with advanced filtering
- Strengthen master trainer area with content management
- Add comprehensive venue and organizer management

### 🛡️ Security & Reliability
- Implement security-first patterns throughout codebase
- Add comprehensive input validation and output escaping
- Create secure credential management system
- Establish proper WordPress role-based access control

### 🎯 WordPress Integration
- Strengthen singleton pattern implementation across all classes
- Enhance template hierarchy with proper WordPress integration
- Improve page manager with hierarchical URL structure
- Add comprehensive shortcode and menu system

### 🔍 Developer Experience
- Add extensive debugging and troubleshooting tools
- Create comprehensive test data seeding scripts
- Implement proper error handling and logging
- Establish consistent code patterns and standards

### 📊 Performance & Optimization
- Optimize database queries and caching strategies
- Improve asset loading and script management
- Enhance template rendering performance
- Streamline user experience across all interfaces

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-29 11:26:10 -03:00

649 lines
No EOL
22 KiB
JavaScript

/**
* 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<Object>} 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<string>} 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<Object>} 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<Object>} 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<Object>} 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<Object>} 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
};