upskill-event-manager/tests/utilities/security/SecurityTestFramework.js
Ben 7c9ca65cf2
Some checks are pending
HVAC Plugin CI/CD Pipeline / Security Analysis (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Code Quality & Standards (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Unit Tests (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Integration Tests (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Deploy to Staging (push) Blocked by required conditions
HVAC Plugin CI/CD Pipeline / Deploy to Production (push) Blocked by required conditions
HVAC Plugin CI/CD Pipeline / Notification (push) Blocked by required conditions
Security Monitoring & Compliance / Dependency Vulnerability Scan (push) Waiting to run
Security Monitoring & Compliance / Secrets & Credential Scan (push) Waiting to run
Security Monitoring & Compliance / WordPress Security Analysis (push) Waiting to run
Security Monitoring & Compliance / Static Code Security Analysis (push) Waiting to run
Security Monitoring & Compliance / Security Compliance Validation (push) Waiting to run
Security Monitoring & Compliance / Security Summary Report (push) Blocked by required conditions
Security Monitoring & Compliance / Security Team Notification (push) Blocked by required conditions
feat: add comprehensive test framework and test files
- Add 90+ test files including E2E, unit, and integration tests
- Implement Page Object Model (POM) architecture
- Add Docker testing environment with comprehensive services
- Include modernized test framework with error recovery
- Add specialized test suites for master trainer and trainer workflows
- Update .gitignore to properly track test infrastructure

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

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

605 lines
No EOL
20 KiB
JavaScript

/**
* Security Test Framework
* Comprehensive security testing utilities for WordPress HVAC plugin
*/
const { getBrowserManager } = require('../../framework/browser/BrowserManager');
const { getAuthManager } = require('../../framework/authentication/AuthManager');
const BasePage = require('../../framework/base/BasePage');
class SecurityTestFramework {
constructor(config = {}) {
this.config = {
baseUrl: 'https://upskill-staging.measurequick.com',
timeout: 30000,
screenshotOnFailure: true,
...config
};
this.browserManager = getBrowserManager();
this.authManager = getAuthManager();
this.testResults = [];
}
/**
* Initialize security test framework
* @param {Object} config - Configuration object
* @returns {Promise<void>}
*/
async initialize(config = {}) {
this.config = { ...this.config, ...config };
await this.browserManager.initialize(this.config);
await this.authManager.initialize(this.config);
}
/**
* Test authentication redirects for protected URLs
* @param {string[]} protectedUrls - Array of URLs that should require authentication
* @returns {Promise<Object>}
*/
async testAuthenticationRedirects(protectedUrls) {
console.log('🔒 Testing authentication redirects...');
const results = {
passed: 0,
failed: 0,
tests: []
};
for (const url of protectedUrls) {
const testResult = await this.testSingleUrlAuthRedirect(url);
results.tests.push(testResult);
if (testResult.passed) {
results.passed++;
} else {
results.failed++;
}
}
console.log(` ✅ Passed: ${results.passed}, ❌ Failed: ${results.failed}`);
return results;
}
/**
* Test single URL for proper authentication redirect
* @param {string} url - URL to test
* @returns {Promise<Object>}
* @private
*/
async testSingleUrlAuthRedirect(url) {
const fullUrl = url.startsWith('http') ? url : this.config.baseUrl + url;
const testResult = {
url: url,
fullUrl: fullUrl,
passed: false,
statusCode: null,
redirectUrl: null,
error: null
};
try {
console.log(` 🔍 Testing: ${url}`);
const page = this.browserManager.getCurrentPage();
// Navigate to protected URL without authentication
const response = await page.goto(fullUrl, {
waitUntil: 'networkidle',
timeout: this.config.timeout
});
testResult.statusCode = response.status();
testResult.redirectUrl = page.url();
// Check if properly redirected to login
const isRedirected = this.isRedirectedToLogin(testResult.redirectUrl, fullUrl);
// Check for proper status codes
const hasValidStatus = response.status() === 302 ||
response.status() === 200 && isRedirected;
testResult.passed = isRedirected && hasValidStatus;
if (testResult.passed) {
console.log(` ✅ Properly redirected to login`);
} else {
console.log(` ❌ Not properly protected - Status: ${response.status()}, URL: ${page.url()}`);
if (this.config.screenshotOnFailure) {
await this.takeSecurityScreenshot(`auth-fail-${url.replace(/[^a-zA-Z0-9]/g, '-')}.png`);
}
}
} catch (error) {
testResult.error = error.message;
console.log(` ❌ Error testing ${url}: ${error.message}`);
}
return testResult;
}
/**
* Check if URL is redirected to login page
* @param {string} currentUrl - Current page URL
* @param {string} originalUrl - Original requested URL
* @returns {boolean}
* @private
*/
isRedirectedToLogin(currentUrl, originalUrl) {
// If still on the same URL, not redirected
if (currentUrl === originalUrl) {
return false;
}
// Check for login page indicators
const loginIndicators = [
'community-login',
'wp-login.php',
'login',
'auth',
'signin'
];
return loginIndicators.some(indicator =>
currentUrl.toLowerCase().includes(indicator.toLowerCase())
);
}
/**
* Test role-based access control
* @param {Object} testScenarios - Test scenarios with roles and expected access
* @returns {Promise<Object>}
*/
async testRoleBasedAccess(testScenarios) {
console.log('👥 Testing role-based access control...');
const results = {
passed: 0,
failed: 0,
tests: []
};
for (const scenario of testScenarios) {
const testResult = await this.testRoleAccess(scenario);
results.tests.push(testResult);
if (testResult.passed) {
results.passed++;
} else {
results.failed++;
}
}
console.log(` ✅ Passed: ${results.passed}, ❌ Failed: ${results.failed}`);
return results;
}
/**
* Test access control for specific role
* @param {Object} scenario - Test scenario
* @returns {Promise<Object>}
* @private
*/
async testRoleAccess(scenario) {
const { user, url, expectedAccess } = scenario;
const testResult = {
user: user.username,
role: user.role,
url: url,
expectedAccess: expectedAccess,
actualAccess: false,
passed: false,
error: null
};
try {
console.log(` 🔍 Testing ${user.role} access to ${url}`);
// Login as specified user
await this.authManager.login(user);
const page = this.browserManager.getCurrentPage();
const fullUrl = url.startsWith('http') ? url : this.config.baseUrl + url;
// Navigate to URL
const response = await page.goto(fullUrl, {
waitUntil: 'networkidle',
timeout: this.config.timeout
});
// Determine if user has access
testResult.actualAccess = this.determineAccess(response, page.url(), fullUrl);
testResult.passed = testResult.actualAccess === expectedAccess;
if (testResult.passed) {
console.log(` ✅ Access control working correctly`);
} else {
console.log(` ❌ Access control failed - Expected: ${expectedAccess}, Actual: ${testResult.actualAccess}`);
if (this.config.screenshotOnFailure) {
await this.takeSecurityScreenshot(`role-fail-${user.role}-${url.replace(/[^a-zA-Z0-9]/g, '-')}.png`);
}
}
// Logout
await this.authManager.logout();
} catch (error) {
testResult.error = error.message;
console.log(` ❌ Error testing role access: ${error.message}`);
}
return testResult;
}
/**
* Determine if user has access based on response and URL
* @param {Response} response - Page response
* @param {string} currentUrl - Current page URL
* @param {string} requestedUrl - Originally requested URL
* @returns {boolean}
* @private
*/
determineAccess(response, currentUrl, requestedUrl) {
// If redirected to login, no access
if (this.isRedirectedToLogin(currentUrl, requestedUrl)) {
return false;
}
// If status is 403 (Forbidden), no access
if (response.status() === 403) {
return false;
}
// If status is 404 (Not Found), might indicate access control
if (response.status() === 404) {
return false;
}
// If status is 200 and on the same URL, has access
if (response.status() === 200 && currentUrl === requestedUrl) {
return true;
}
// Default to no access for safety
return false;
}
/**
* Test for common WordPress security vulnerabilities
* @returns {Promise<Object>}
*/
async testCommonVulnerabilities() {
console.log('🛡️ Testing common security vulnerabilities...');
const results = {
passed: 0,
failed: 0,
tests: []
};
// Test directory browsing
const directoryTest = await this.testDirectoryBrowsing();
results.tests.push(directoryTest);
// Test file inclusion vulnerabilities
const fileInclusionTest = await this.testFileInclusion();
results.tests.push(fileInclusionTest);
// Test WordPress version disclosure
const versionDisclosureTest = await this.testVersionDisclosure();
results.tests.push(versionDisclosureTest);
// Test admin access without authentication
const adminAccessTest = await this.testUnauthenticatedAdminAccess();
results.tests.push(adminAccessTest);
// Count results
results.tests.forEach(test => {
if (test.passed) results.passed++;
else results.failed++;
});
console.log(` ✅ Passed: ${results.passed}, ❌ Failed: ${results.failed}`);
return results;
}
/**
* Test directory browsing vulnerabilities
* @returns {Promise<Object>}
* @private
*/
async testDirectoryBrowsing() {
const testResult = {
testName: 'Directory Browsing',
passed: true,
vulnerabilities: []
};
const directoriesToTest = [
'/wp-content/',
'/wp-content/plugins/',
'/wp-content/themes/',
'/wp-content/uploads/',
'/wp-includes/',
'/wp-admin/'
];
for (const dir of directoriesToTest) {
try {
const page = this.browserManager.getCurrentPage();
const response = await page.goto(this.config.baseUrl + dir, { timeout: 10000 });
if (response.status() === 200) {
const content = await page.content();
if (content.includes('Index of') || content.includes('Directory listing')) {
testResult.passed = false;
testResult.vulnerabilities.push(`Directory browsing enabled for ${dir}`);
}
}
} catch (error) {
// Directory might be properly protected - continue
}
}
return testResult;
}
/**
* Test file inclusion vulnerabilities
* @returns {Promise<Object>}
* @private
*/
async testFileInclusion() {
const testResult = {
testName: 'File Inclusion',
passed: true,
vulnerabilities: []
};
const payloads = [
'../../../../etc/passwd',
'..\\..\\..\\..\\windows\\system32\\drivers\\etc\\hosts',
'php://filter/read=convert.base64-encode/resource=wp-config.php'
];
for (const payload of payloads) {
try {
const page = this.browserManager.getCurrentPage();
const testUrl = `${this.config.baseUrl}/?file=${encodeURIComponent(payload)}`;
const response = await page.goto(testUrl, { timeout: 10000 });
if (response.status() === 200) {
const content = await page.content();
if (content.includes('root:') || content.includes('# localhost')) {
testResult.passed = false;
testResult.vulnerabilities.push(`File inclusion vulnerability with payload: ${payload}`);
}
}
} catch (error) {
// Error might indicate protection - continue
}
}
return testResult;
}
/**
* Test WordPress version disclosure
* @returns {Promise<Object>}
* @private
*/
async testVersionDisclosure() {
const testResult = {
testName: 'Version Disclosure',
passed: true,
disclosures: []
};
try {
const page = this.browserManager.getCurrentPage();
await page.goto(this.config.baseUrl, { timeout: 10000 });
const content = await page.content();
// Check for WordPress version in meta tags
const versionRegex = /<meta name="generator" content="WordPress (\d+\.\d+(?:\.\d+)?)"/i;
const versionMatch = content.match(versionRegex);
if (versionMatch) {
testResult.passed = false;
testResult.disclosures.push(`WordPress version disclosed in meta tag: ${versionMatch[1]}`);
}
// Check readme.html file
try {
await page.goto(`${this.config.baseUrl}/readme.html`, { timeout: 10000 });
const readmeContent = await page.content();
if (readmeContent.includes('WordPress') && readmeContent.includes('Version')) {
testResult.passed = false;
testResult.disclosures.push('WordPress version disclosed in readme.html');
}
} catch (error) {
// Readme might be removed - good practice
}
} catch (error) {
console.warn(`Version disclosure test error: ${error.message}`);
}
return testResult;
}
/**
* Test unauthenticated admin access
* @returns {Promise<Object>}
* @private
*/
async testUnauthenticatedAdminAccess() {
const testResult = {
testName: 'Unauthenticated Admin Access',
passed: true,
vulnerabilities: []
};
try {
const page = this.browserManager.getCurrentPage();
const response = await page.goto(`${this.config.baseUrl}/wp-admin/`, { timeout: 10000 });
// Should be redirected to login
if (!this.isRedirectedToLogin(page.url(), `${this.config.baseUrl}/wp-admin/`)) {
testResult.passed = false;
testResult.vulnerabilities.push('Admin area accessible without authentication');
}
} catch (error) {
console.warn(`Admin access test error: ${error.message}`);
}
return testResult;
}
/**
* Test CSRF protection
* @param {Object} formData - Form data to test
* @returns {Promise<Object>}
*/
async testCSRFProtection(formData) {
console.log('🔐 Testing CSRF protection...');
const testResult = {
testName: 'CSRF Protection',
passed: false,
error: null
};
try {
// Login first
await this.authManager.loginAsMasterTrainer();
const page = this.browserManager.getCurrentPage();
await page.goto(formData.formUrl);
// Try to submit form without CSRF token
const response = await page.evaluate(async (data) => {
const form = new FormData();
Object.keys(data.fields).forEach(key => {
form.append(key, data.fields[key]);
});
return await fetch(data.submitUrl, {
method: 'POST',
body: form
});
}, formData);
// If request is rejected (403/400), CSRF protection is working
testResult.passed = response.status === 403 || response.status === 400;
} catch (error) {
testResult.error = error.message;
}
return testResult;
}
/**
* Take security-related screenshot
* @param {string} filename - Screenshot filename
* @returns {Promise<string>}
* @private
*/
async takeSecurityScreenshot(filename) {
try {
return await this.browserManager.takeScreenshot(`security-${filename}`);
} catch (error) {
console.warn(`Failed to take security screenshot: ${error.message}`);
return null;
}
}
/**
* Generate comprehensive security test report
* @param {Object} allTestResults - All test results
* @returns {Object}
*/
generateSecurityReport(allTestResults) {
const report = {
timestamp: new Date().toISOString(),
baseUrl: this.config.baseUrl,
totalTests: 0,
totalPassed: 0,
totalFailed: 0,
securityScore: 0,
categories: {},
recommendations: [],
criticalIssues: []
};
// Process results by category
Object.keys(allTestResults).forEach(category => {
const categoryResults = allTestResults[category];
report.categories[category] = {
tests: categoryResults.tests || [categoryResults],
passed: categoryResults.passed || (categoryResults.passed ? 1 : 0),
failed: categoryResults.failed || (categoryResults.passed ? 0 : 1)
};
report.totalTests += report.categories[category].tests.length;
report.totalPassed += report.categories[category].passed;
report.totalFailed += report.categories[category].failed;
});
// Calculate security score (0-100)
report.securityScore = report.totalTests > 0
? Math.round((report.totalPassed / report.totalTests) * 100)
: 0;
// Generate recommendations based on failures
this.generateSecurityRecommendations(report, allTestResults);
return report;
}
/**
* Generate security recommendations
* @param {Object} report - Security report
* @param {Object} testResults - Test results
* @private
*/
generateSecurityRecommendations(report, testResults) {
if (report.securityScore < 100) {
report.recommendations.push('Review and strengthen security configurations');
}
if (testResults.authRedirects && testResults.authRedirects.failed > 0) {
report.criticalIssues.push('Some protected URLs are not properly secured');
report.recommendations.push('Implement proper authentication checks for all protected pages');
}
if (testResults.roleAccess && testResults.roleAccess.failed > 0) {
report.criticalIssues.push('Role-based access control has issues');
report.recommendations.push('Review and fix user role permissions');
}
if (testResults.vulnerabilities) {
testResults.vulnerabilities.tests.forEach(test => {
if (!test.passed && test.vulnerabilities) {
test.vulnerabilities.forEach(vuln => {
report.criticalIssues.push(vuln);
});
}
});
}
}
/**
* Clean up security test framework
* @returns {Promise<void>}
*/
async cleanup() {
await this.authManager.cleanup();
await this.browserManager.cleanup();
}
}
module.exports = SecurityTestFramework;