- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
206 lines
7.7 KiB
PHP
206 lines
7.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/../../vendor/autoload.php';
|
|
|
|
use App\Framework\DateTime\SystemClock;
|
|
use App\Framework\Http\Session\Session;
|
|
use App\Framework\Http\Session\SessionId;
|
|
use App\Framework\LiveComponents\Attributes\RequiresPermission;
|
|
use App\Framework\LiveComponents\ComponentEventDispatcher;
|
|
use App\Framework\LiveComponents\Contracts\LiveComponentContract;
|
|
use App\Framework\LiveComponents\Exceptions\UnauthorizedActionException;
|
|
use App\Framework\LiveComponents\LiveComponentHandler;
|
|
use App\Framework\LiveComponents\Security\SessionBasedAuthorizationChecker;
|
|
use App\Framework\LiveComponents\ValueObjects\ActionParameters;
|
|
use App\Framework\LiveComponents\ValueObjects\ComponentData;
|
|
use App\Framework\LiveComponents\ValueObjects\ComponentId;
|
|
use App\Framework\LiveComponents\ValueObjects\ComponentRenderData;
|
|
use App\Framework\Random\SecureRandomGenerator;
|
|
use App\Framework\Security\CsrfTokenGenerator;
|
|
|
|
echo "=== Action Authorization Test ===\n\n";
|
|
|
|
// Setup Session
|
|
$sessionId = SessionId::fromString(bin2hex(random_bytes(16)));
|
|
$clock = new SystemClock();
|
|
$randomGenerator = new SecureRandomGenerator();
|
|
$csrfGenerator = new CsrfTokenGenerator($randomGenerator);
|
|
|
|
$session = Session::fromArray($sessionId, $clock, $csrfGenerator, []);
|
|
|
|
// Setup Handler
|
|
$eventDispatcher = new ComponentEventDispatcher();
|
|
$authChecker = new SessionBasedAuthorizationChecker($session);
|
|
$handler = new LiveComponentHandler($eventDispatcher, $session, $authChecker);
|
|
|
|
// Test Component with protected action
|
|
$componentId = ComponentId::fromString('posts:manager');
|
|
|
|
$component = new class ($componentId) implements LiveComponentContract {
|
|
public function __construct(private ComponentId $id)
|
|
{
|
|
}
|
|
|
|
public function getId(): ComponentId
|
|
{
|
|
return $this->id;
|
|
}
|
|
|
|
public function getData(): ComponentData
|
|
{
|
|
return ComponentData::fromArray(['posts' => []]);
|
|
}
|
|
|
|
public function getRenderData(): ComponentRenderData
|
|
{
|
|
return new ComponentRenderData('posts-manager', ['posts' => []]);
|
|
}
|
|
|
|
// Public action - no permission required
|
|
public function viewPosts(): ComponentData
|
|
{
|
|
return ComponentData::fromArray(['posts' => ['Post 1', 'Post 2']]);
|
|
}
|
|
|
|
// Protected action - requires permission
|
|
#[RequiresPermission('posts.delete')]
|
|
public function deletePost(string $postId): ComponentData
|
|
{
|
|
return ComponentData::fromArray([
|
|
'deleted' => true,
|
|
'postId' => $postId,
|
|
]);
|
|
}
|
|
|
|
// Multiple permissions (OR logic)
|
|
#[RequiresPermission('posts.edit', 'posts.admin')]
|
|
public function editPost(string $postId): ComponentData
|
|
{
|
|
return ComponentData::fromArray([
|
|
'edited' => true,
|
|
'postId' => $postId,
|
|
]);
|
|
}
|
|
};
|
|
|
|
// Generate CSRF token
|
|
$formId = 'livecomponent:' . $componentId->toString();
|
|
$csrfToken = $session->csrf->generateToken($formId);
|
|
|
|
// Test 1: Public action without authentication
|
|
echo "Test 1: Public action without authentication\n";
|
|
echo "--------------------------------------------\n";
|
|
|
|
try {
|
|
$params = ActionParameters::fromArray([], $csrfToken);
|
|
$result = $handler->handle($component, 'viewPosts', $params);
|
|
echo "✓ Public action executed successfully\n";
|
|
echo " Posts: " . json_encode($result->state->data['posts']) . "\n";
|
|
} catch (\Exception $e) {
|
|
echo "✗ Failed: " . $e->getMessage() . "\n";
|
|
exit(1);
|
|
}
|
|
|
|
// Test 2: Protected action without authentication
|
|
echo "\nTest 2: Protected action without authentication\n";
|
|
echo "------------------------------------------------\n";
|
|
|
|
try {
|
|
$params = ActionParameters::fromArray(['postId' => '123'], $csrfToken);
|
|
$handler->handle($component, 'deletePost', $params);
|
|
echo "✗ Should have thrown UnauthorizedActionException!\n";
|
|
exit(1);
|
|
} catch (UnauthorizedActionException $e) {
|
|
echo "✓ Correctly rejected unauthenticated user\n";
|
|
echo " Error: " . $e->getUserMessage() . "\n";
|
|
echo " Is authentication issue: " . ($e->isAuthenticationIssue() ? 'yes' : 'no') . "\n";
|
|
}
|
|
|
|
// Test 3: Protected action with authentication but missing permission
|
|
echo "\nTest 3: Protected action with permission check (missing permission)\n";
|
|
echo "--------------------------------------------------------------------\n";
|
|
$session->set('user', [
|
|
'id' => 123,
|
|
'permissions' => ['posts.view', 'posts.edit'], // No 'posts.delete'
|
|
]);
|
|
|
|
try {
|
|
$params = ActionParameters::fromArray(['postId' => '456'], $csrfToken);
|
|
$handler->handle($component, 'deletePost', $params);
|
|
echo "✗ Should have thrown UnauthorizedActionException!\n";
|
|
exit(1);
|
|
} catch (UnauthorizedActionException $e) {
|
|
echo "✓ Correctly rejected user without permission\n";
|
|
echo " Error: " . $e->getUserMessage() . "\n";
|
|
echo " Missing permissions: " . json_encode($e->getMissingPermissions()) . "\n";
|
|
}
|
|
|
|
// Test 4: Protected action with correct permission
|
|
echo "\nTest 4: Protected action with correct permission\n";
|
|
echo "------------------------------------------------\n";
|
|
$session->set('user', [
|
|
'id' => 123,
|
|
'permissions' => ['posts.view', 'posts.edit', 'posts.delete'],
|
|
]);
|
|
|
|
try {
|
|
$params = ActionParameters::fromArray(['postId' => '789'], $csrfToken);
|
|
$result = $handler->handle($component, 'deletePost', $params);
|
|
echo "✓ Action executed successfully with permission\n";
|
|
echo " Result: " . json_encode($result->state->data) . "\n";
|
|
} catch (\Exception $e) {
|
|
echo "✗ Failed: " . $e->getMessage() . "\n";
|
|
exit(1);
|
|
}
|
|
|
|
// Test 5: Multiple permissions (OR logic)
|
|
echo "\nTest 5: Multiple permissions with OR logic\n";
|
|
echo "-------------------------------------------\n";
|
|
$session->set('user', [
|
|
'id' => 123,
|
|
'permissions' => ['posts.admin'], // Has 'posts.admin', not 'posts.edit'
|
|
]);
|
|
|
|
try {
|
|
$params = ActionParameters::fromArray(['postId' => '999'], $csrfToken);
|
|
$result = $handler->handle($component, 'editPost', $params);
|
|
echo "✓ Action executed with alternative permission\n";
|
|
echo " User has 'posts.admin' instead of 'posts.edit'\n";
|
|
echo " Result: " . json_encode($result->state->data) . "\n";
|
|
} catch (\Exception $e) {
|
|
echo "✗ Failed: " . $e->getMessage() . "\n";
|
|
exit(1);
|
|
}
|
|
|
|
// Test 6: RequiresPermission attribute validation
|
|
echo "\nTest 6: RequiresPermission attribute behavior\n";
|
|
echo "----------------------------------------------\n";
|
|
$attr1 = new RequiresPermission('posts.edit');
|
|
echo "Single permission attribute:\n";
|
|
echo " Permissions: " . json_encode($attr1->getPermissions()) . "\n";
|
|
echo " Primary: " . $attr1->getPrimaryPermission() . "\n";
|
|
echo " Has multiple: " . ($attr1->hasMultiplePermissions() ? 'yes' : 'no') . "\n";
|
|
|
|
$attr2 = new RequiresPermission('posts.edit', 'posts.admin');
|
|
echo "\nMultiple permissions attribute:\n";
|
|
echo " Permissions: " . json_encode($attr2->getPermissions()) . "\n";
|
|
echo " Primary: " . $attr2->getPrimaryPermission() . "\n";
|
|
echo " Has multiple: " . ($attr2->hasMultiplePermissions() ? 'yes' : 'no') . "\n";
|
|
|
|
echo "\nPermission checking:\n";
|
|
echo " User with ['posts.edit']: " . ($attr2->isAuthorized(['posts.edit']) ? 'authorized' : 'denied') . "\n";
|
|
echo " User with ['posts.admin']: " . ($attr2->isAuthorized(['posts.admin']) ? 'authorized' : 'denied') . "\n";
|
|
echo " User with ['posts.view']: " . ($attr2->isAuthorized(['posts.view']) ? 'authorized' : 'denied') . "\n";
|
|
|
|
echo "\n=== All Tests Passed! ===\n";
|
|
echo "\nSummary:\n";
|
|
echo " ✓ Public actions work without authentication\n";
|
|
echo " ✓ Protected actions require authentication\n";
|
|
echo " ✓ Permission checks work correctly\n";
|
|
echo " ✓ Missing permissions are rejected\n";
|
|
echo " ✓ Multiple permissions support OR logic\n";
|
|
echo " ✓ RequiresPermission attribute works as expected\n";
|
|
echo "\nAction Guards Implementation: COMPLETE\n";
|