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";