upskill-event-manager/tests/e2e/security-framework.test.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

707 lines
No EOL
29 KiB
JavaScript

/**
* Security Framework Test Suite
*
* Tests the new security framework implementation:
* - Role-based access control (trainer, master_trainer, admin)
* - CSRF protection via nonce verification
* - Input sanitization validation
* - Authentication boundary testing
* - Permission escalation prevention
* - Session security
*
* @package HVAC_Community_Events
* @version 3.0.0
* @created 2025-08-20
*/
const { test, expect, authHelpers, authScenarios } = require('../helpers/auth-fixtures');
const path = require('path');
// Test configuration
const BASE_URL = process.env.UPSKILL_STAGING_URL || 'https://upskill-staging.measurequick.com';
const TEST_TIMEOUT = 90000;
// Test users with different privilege levels (now handled by auth system)
const USER_ACCESS_MATRIX = {
trainer: {
role: 'hvac_trainer',
expectedPages: ['/trainer/dashboard/', '/trainer/profile/', '/trainer/events/']
},
master_trainer: {
role: 'hvac_master_trainer',
expectedPages: ['/master-trainer/master-dashboard/', '/trainer/dashboard/', '/trainer/events/']
},
admin: {
role: 'administrator',
expectedPages: ['/wp-admin/', '/trainer/dashboard/', '/master-trainer/master-dashboard/']
}
};
// Security test payloads
const SECURITY_PAYLOADS = {
xss: [
'<script>alert("xss")</script>',
'"><script>alert("xss")</script>',
'javascript:alert("xss")',
'<img src=x onerror=alert("xss")>',
'\"><svg onload=alert("xss")>'
],
sqlInjection: [
"'; DROP TABLE wp_users; --",
"' OR '1'='1",
"1' UNION SELECT * FROM wp_users --",
"'; INSERT INTO wp_users VALUES('test'); --"
],
pathTraversal: [
'../../../etc/passwd',
'..\\..\\..\\windows\\system32\\drivers\\etc\\hosts',
'....//....//....//etc/passwd',
'/var/www/wp-config.php'
],
commandInjection: [
'; cat /etc/passwd',
'| whoami',
'&& ls -la',
'`rm -rf /`'
]
};
// Helper functions - updated to use new auth system
async function loginAsUser(page, userType = 'trainer') {
await authHelpers.loginAs(page, userType);
}
async function attemptUnauthorizedAccess(page, url, expectedResult = 'denied') {
const response = await page.goto(url);
await page.waitForTimeout(2000);
const statusCode = response?.status() || 0;
const content = await page.content();
const currentUrl = page.url();
return {
statusCode,
content,
currentUrl,
isAccessDenied: content.includes('Access denied') ||
content.includes('Insufficient permissions') ||
content.includes('403 Forbidden') ||
statusCode === 403 ||
currentUrl.includes('wp-login.php'),
isRedirected: currentUrl !== url
};
}
async function testFormWithPayloads(page, formSelector, fieldMappings, payloads) {
const results = [];
for (const payload of payloads) {
try {
// Fill form with malicious payload
for (const [fieldSelector, payloadValue] of Object.entries(fieldMappings)) {
const field = await page.$(fieldSelector);
if (field) {
await field.fill(typeof payloadValue === 'function' ? payloadValue(payload) : payload);
}
}
// Submit form
const submitButton = await page.$(`${formSelector} button[type="submit"], ${formSelector} input[type="submit"]`);
if (submitButton) {
await submitButton.click();
await page.waitForTimeout(2000);
// Check response
const content = await page.content();
const hasPayload = content.includes(payload);
const hasError = content.includes('error') || content.includes('Error');
results.push({
payload,
reflected: hasPayload,
hasError,
sanitized: !hasPayload || hasError
});
}
} catch (error) {
results.push({
payload,
reflected: false,
hasError: true,
sanitized: true,
error: error.message
});
}
}
return results;
}
async function extractNonceFields(page) {
return await page.evaluate(() => {
const nonceFields = Array.from(document.querySelectorAll('input[name*="nonce"], input[name*="_token"], input[name*="_wpnonce"]'));
return nonceFields.map(field => ({
name: field.name,
value: field.value,
form: field.closest('form')?.id || 'unknown'
}));
});
}
async function takeSecurityScreenshot(page, name) {
const screenshotDir = path.join(__dirname, '../../screenshots/security');
await require('fs').promises.mkdir(screenshotDir, { recursive: true });
await page.screenshot({
path: path.join(screenshotDir, `${name}-${Date.now()}.png`),
fullPage: true
});
}
test.describe('Security Framework Tests', () => {
test.setTimeout(TEST_TIMEOUT);
test.beforeEach(async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 720 });
});
test.describe('Role-Based Access Control Tests', () => {
test('should enforce trainer role access restrictions', async ({ page }) => {
await loginAsUser(page, 'trainer');
// Test allowed pages
const allowedPages = USER_ACCESS_MATRIX.trainer.expectedPages;
for (const allowedPage of allowedPages) {
const response = await page.goto(`${BASE_URL}${allowedPage}`);
expect(response?.status()).toBeLessThan(400);
console.log(`Trainer access to ${allowedPage}: OK`);
}
// Test restricted pages
const restrictedPages = [
'/wp-admin/',
'/master-trainer/master-dashboard/',
'/wp-admin/users.php',
'/wp-admin/plugins.php'
];
for (const restrictedPage of restrictedPages) {
const result = await attemptUnauthorizedAccess(page, `${BASE_URL}${restrictedPage}`);
// Should be denied or redirected
const isProperlyRestricted = result.isAccessDenied ||
result.statusCode >= 400 ||
result.isRedirected;
expect(isProperlyRestricted).toBeTruthy();
console.log(`Trainer restriction for ${restrictedPage}:`, isProperlyRestricted ? 'BLOCKED' : 'ALLOWED');
}
await takeSecurityScreenshot(page, 'trainer-access-control');
});
test('should enforce master trainer role privileges', async ({ page }) => {
try {
await loginAsUser(page, 'master_trainer');
// Master trainer should access both trainer and master pages
const allowedPages = [
'/trainer/dashboard/',
'/master-trainer/master-dashboard/',
'/trainer/events/',
'/trainer/profile/'
];
for (const allowedPage of allowedPages) {
const response = await page.goto(`${BASE_URL}${allowedPage}`);
expect(response?.status()).toBeLessThan(400);
console.log(`Master trainer access to ${allowedPage}: OK`);
}
// Still should not access admin pages
const restrictedPages = [
'/wp-admin/users.php',
'/wp-admin/plugins.php',
'/wp-admin/themes.php'
];
for (const restrictedPage of restrictedPages) {
const result = await attemptUnauthorizedAccess(page, `${BASE_URL}${restrictedPage}`);
const isProperlyRestricted = result.isAccessDenied ||
result.statusCode >= 400 ||
result.isRedirected;
// Note: Master trainers might have some admin access
console.log(`Master trainer restriction for ${restrictedPage}:`, isProperlyRestricted ? 'BLOCKED' : 'ALLOWED');
}
} catch (error) {
console.log('Master trainer test skipped - user may not exist:', error.message);
}
});
test('should prevent privilege escalation attempts', async ({ page }) => {
await loginAsUser(page, 'trainer');
// Attempt to modify user role via form manipulation
await page.goto(`${BASE_URL}/trainer/profile/edit/`);
// Try to inject role escalation parameters
const escalationAttempts = [
{ field: 'role', value: 'administrator' },
{ field: 'wp_capabilities', value: 'administrator' },
{ field: 'user_role', value: 'hvac_master_trainer' }
];
for (const attempt of escalationAttempts) {
try {
// Inject hidden field via JavaScript
await page.evaluate((field, value) => {
const form = document.querySelector('form');
if (form) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = field;
input.value = value;
form.appendChild(input);
}
}, attempt.field, attempt.value);
console.log(`Injected privilege escalation attempt: ${attempt.field} = ${attempt.value}`);
} catch (error) {
console.log(`Failed to inject ${attempt.field}:`, error.message);
}
}
// Submit form and check if escalation was prevented
const submitButton = await page.$('button[type="submit"], input[type="submit"]');
if (submitButton) {
await submitButton.click();
await page.waitForTimeout(3000);
// User should still be trainer role
await page.goto(`${BASE_URL}/master-trainer/master-dashboard/`);
const result = await attemptUnauthorizedAccess(page, `${BASE_URL}/master-trainer/master-dashboard/`);
expect(result.isAccessDenied || result.isRedirected).toBeTruthy();
}
});
test('should validate session security', async ({ page }) => {
await loginAsUser(page, 'trainer');
// Check for secure session cookies
const cookies = await page.context().cookies();
const sessionCookie = cookies.find(c => c.name.includes('session') || c.name.includes('wordpress'));
if (sessionCookie) {
expect(sessionCookie.httpOnly).toBeTruthy();
// Note: Secure flag may not be set in development
console.log('Session cookie security:', {
httpOnly: sessionCookie.httpOnly,
secure: sessionCookie.secure
});
}
// Test session timeout
await page.goto(`${BASE_URL}/trainer/dashboard/`);
// Simulate session manipulation
await page.evaluate(() => {
// Try to access session storage
try {
sessionStorage.setItem('test_key', 'test_value');
localStorage.setItem('test_key', 'test_value');
} catch (e) {
console.log('Storage access restricted:', e.message);
}
});
});
});
test.describe('CSRF Protection Tests', () => {
test('should include nonce fields in forms', async ({ page }) => {
await loginAsUser(page, 'trainer');
const formsToCheck = [
'/trainer/profile/edit/',
'/trainer/events/create/',
'/trainer/organizer/manage/'
];
for (const formPage of formsToCheck) {
await page.goto(`${BASE_URL}${formPage}`);
await page.waitForTimeout(2000);
const nonceFields = await extractNonceFields(page);
expect(nonceFields.length).toBeGreaterThan(0);
console.log(`Nonce fields found on ${formPage}:`, nonceFields.length);
// Verify nonce values are not empty
nonceFields.forEach(field => {
expect(field.value).toBeTruthy();
expect(field.value.length).toBeGreaterThan(5);
});
}
});
test('should reject forms without valid nonces', async ({ page }) => {
await loginAsUser(page, 'trainer');
await page.goto(`${BASE_URL}/trainer/profile/edit/`);
// Remove or modify nonce fields
await page.evaluate(() => {
const nonceFields = document.querySelectorAll('input[name*="nonce"]');
nonceFields.forEach(field => {
field.value = 'invalid_nonce_value';
});
});
// Try to submit form
const submitButton = await page.$('button[type="submit"], input[type="submit"]');
if (submitButton) {
await submitButton.click();
await page.waitForTimeout(3000);
// Should show error or reject submission
const content = await page.content();
const hasError = content.includes('error') ||
content.includes('Error') ||
content.includes('nonce') ||
content.includes('Security check failed');
console.log('Invalid nonce rejection:', hasError ? 'BLOCKED' : 'ALLOWED');
// Note: Some forms might not validate nonces in development
}
});
test('should prevent CSRF attacks via external submission', async ({ page, context }) => {
await loginAsUser(page, 'trainer');
// Get a legitimate form's nonce
await page.goto(`${BASE_URL}/trainer/profile/edit/`);
const legitimateNonce = await page.evaluate(() => {
const nonceField = document.querySelector('input[name*="nonce"]');
return nonceField ? nonceField.value : null;
});
// Create new context (simulating external site)
const externalContext = await page.context().browser().newContext();
const externalPage = await externalContext.newPage();
// Try to submit form from external context using stolen nonce
const formData = new URLSearchParams({
'user_email': 'hacker@evil.com',
'nonce': legitimateNonce || 'fake_nonce'
});
try {
const response = await externalPage.request.post(`${BASE_URL}/trainer/profile/edit/`, {
data: formData.toString(),
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
// Should be rejected (403/405 or redirect)
const isBlocked = response.status() >= 400 ||
response.headers()['location']?.includes('login');
expect(isBlocked).toBeTruthy();
console.log('External CSRF attempt blocked:', isBlocked);
} catch (error) {
console.log('CSRF attempt failed (good):', error.message);
}
await externalContext.close();
});
});
test.describe('Input Sanitization Tests', () => {
test('should sanitize XSS payloads in event creation', async ({ page }) => {
await loginAsUser(page, 'trainer');
await page.goto(`${BASE_URL}/trainer/events/create/`);
// Wait for form to load
await page.waitForSelector('.hvac-event-form-wrapper, iframe#tec-create-frame', { timeout: 10000 });
const iframe = await page.$('iframe#tec-create-frame');
const context = iframe ? await iframe.contentFrame() : page;
const fieldMappings = {
'input[name="post_title"], #tribe-events-title': (payload) => `XSS Test ${payload}`,
'#content, textarea[name="post_content"]': (payload) => `Description ${payload}`,
'input[name="venue[Venue]"], #venue-name': (payload) => `Venue ${payload}`
};
const results = await testFormWithPayloads(context, 'form', fieldMappings, SECURITY_PAYLOADS.xss);
// All XSS payloads should be sanitized
results.forEach(result => {
expect(result.sanitized).toBeTruthy();
console.log(`XSS payload "${result.payload}" sanitized:`, result.sanitized);
});
await takeSecurityScreenshot(page, 'xss-sanitization-test');
});
test('should handle SQL injection attempts safely', async ({ page }) => {
await loginAsUser(page, 'trainer');
await page.goto(`${BASE_URL}/trainer/profile/edit/`);
const sqlPayloads = SECURITY_PAYLOADS.sqlInjection;
for (const payload of sqlPayloads) {
// Try SQL injection in various fields
const emailField = await page.$('input[type="email"], input[name="email"]');
if (emailField) {
await emailField.fill(`test${payload}@example.com`);
}
const nameField = await page.$('input[name="first_name"], input[name="display_name"]');
if (nameField) {
await nameField.fill(`Test${payload}`);
}
// Submit and check for errors
const submitButton = await page.$('button[type="submit"], input[type="submit"]');
if (submitButton) {
await submitButton.click();
await page.waitForTimeout(2000);
const content = await page.content();
const hasSqlError = content.includes('SQL') ||
content.includes('mysql') ||
content.includes('database error');
// Should not expose SQL errors
expect(hasSqlError).toBeFalsy();
console.log(`SQL injection payload "${payload}" handled safely`);
}
// Reset form for next test
await page.reload();
await page.waitForTimeout(1000);
}
});
test('should prevent path traversal attacks', async ({ page }) => {
await loginAsUser(page, 'trainer');
const pathPayloads = SECURITY_PAYLOADS.pathTraversal;
for (const payload of pathPayloads) {
// Test path traversal in file upload fields
await page.goto(`${BASE_URL}/trainer/profile/edit/`);
const fileInput = await page.$('input[type="file"]');
if (fileInput) {
try {
// Create temporary file with malicious name
const fs = require('fs');
const tmpPath = path.join(__dirname, 'temp_test_file.txt');
fs.writeFileSync(tmpPath, 'test content');
await fileInput.setInputFiles(tmpPath);
const submitButton = await page.$('button[type="submit"], input[type="submit"]');
if (submitButton) {
await submitButton.click();
await page.waitForTimeout(3000);
// Check if file was processed safely
const content = await page.content();
const hasTraversalContent = content.includes('/etc/passwd') ||
content.includes('windows/system32');
expect(hasTraversalContent).toBeFalsy();
console.log(`Path traversal payload "${payload}" blocked`);
}
// Cleanup
if (fs.existsSync(tmpPath)) {
fs.unlinkSync(tmpPath);
}
} catch (error) {
console.log(`Path traversal test for "${payload}" failed safely:`, error.message);
}
}
}
});
test('should escape output in templates', async ({ page }) => {
await loginAsUser(page, 'trainer');
// Test output escaping in various templates
const pagesToCheck = [
'/trainer/dashboard/',
'/trainer/profile/',
'/trainer/events/'
];
for (const pagePath of pagesToCheck) {
await page.goto(`${BASE_URL}${pagePath}`);
// Check for unescaped content patterns
const unsafePatterns = await page.evaluate(() => {
const content = document.body.innerHTML;
const patterns = [
/<script[^>]*>[^<]*<\/script>/gi,
/javascript:[^"'>\s]*/gi,
/on\w+\s*=\s*[^>]*/gi
];
return patterns.map(pattern => ({
pattern: pattern.toString(),
matches: content.match(pattern) || []
}));
});
unsafePatterns.forEach(result => {
if (result.matches.length > 0) {
console.log(`Potentially unsafe content on ${pagePath}:`, result.matches);
}
});
}
});
});
test.describe('Authentication Security Tests', () => {
test('should enforce strong password requirements', async ({ page }) => {
await page.goto(`${BASE_URL}/wp-login.php?action=register`);
// If registration is available, test password requirements
const registerForm = await page.$('#registerform, .register-form');
if (registerForm) {
const weakPasswords = [
'123456',
'password',
'abc123',
'1234',
'test'
];
for (const weakPassword of weakPasswords) {
await page.fill('#user_pass, input[name="pass1"]', weakPassword);
// Check for password strength indicator
await page.waitForTimeout(500);
const strengthIndicator = await page.evaluate(() => {
const indicator = document.querySelector('.password-strength, #pass-strength-result');
return indicator ? indicator.textContent : '';
});
console.log(`Password "${weakPassword}" strength:`, strengthIndicator);
}
} else {
console.log('Registration form not available for testing');
}
});
test('should implement account lockout after failed attempts', async ({ page }) => {
// Test with intentionally wrong credentials
const fakeUser = {
email: 'nonexistent@example.com',
password: 'WrongPassword123!'
};
for (let attempt = 1; attempt <= 5; attempt++) {
await page.goto(`${BASE_URL}/wp-login.php`);
await page.fill('#user_login', fakeUser.email);
await page.fill('#user_pass', fakeUser.password);
await page.click('#wp-submit');
await page.waitForTimeout(1000);
const errorMessage = await page.evaluate(() => {
const error = document.querySelector('#login_error, .login-error');
return error ? error.textContent : '';
});
console.log(`Failed login attempt ${attempt}:`, errorMessage);
// Check if account gets locked after multiple attempts
if (attempt >= 3 && errorMessage.includes('locked')) {
console.log('Account lockout mechanism detected');
break;
}
}
});
test('should secure password reset process', async ({ page }) => {
await page.goto(`${BASE_URL}/wp-login.php?action=lostpassword`);
const resetForm = await page.$('#lostpasswordform');
if (resetForm) {
// Test with valid email
await page.fill('#user_login', 'test_trainer@example.com');
await page.click('#wp-submit');
await page.waitForTimeout(2000);
const message = await page.evaluate(() => {
return document.body.textContent;
});
// Should not reveal whether email exists
const isSecure = !message.includes('does not exist') &&
!message.includes('not found') &&
message.includes('sent') || message.includes('check');
expect(isSecure).toBeTruthy();
console.log('Password reset process secure:', isSecure);
}
});
test('should validate session hijacking protection', async ({ browser }) => {
// Create two contexts to simulate session hijacking
const context1 = await browser.newContext();
const context2 = await browser.newContext();
const page1 = await context1.newPage();
const page2 = await context2.newPage();
// Login in first context
await loginAsUser(page1, 'trainer');
// Get session cookies from first context
const cookies = await context1.cookies();
const sessionCookies = cookies.filter(c =>
c.name.includes('wordpress') || c.name.includes('session')
);
// Try to use session cookies in second context
if (sessionCookies.length > 0) {
await context2.addCookies(sessionCookies);
// Try to access protected page in second context
await page2.goto(`${BASE_URL}/trainer/dashboard/`);
const isRedirected = page2.url().includes('wp-login.php');
console.log('Session hijacking protection:', isRedirected ? 'ACTIVE' : 'WEAK');
}
await context1.close();
await context2.close();
});
});
});
// Export security test configuration
module.exports = {
testDir: __dirname,
timeout: TEST_TIMEOUT,
retries: 1,
workers: 1, // Sequential for security tests
use: {
baseURL: BASE_URL,
screenshot: 'only-on-failure',
video: 'retain-on-failure',
trace: 'on-first-retry',
ignoreHTTPSErrors: true // For testing purposes
}
};