From b652f68893c0a6a0b18ca37119373348c4b94b83 Mon Sep 17 00:00:00 2001 From: Jason Crist Date: Mon, 25 Aug 2025 13:52:50 -0400 Subject: [PATCH] Fix #31: Use consistent custom capabilities throughout codebase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updates all capability checks to use custom capabilities instead of generic WordPress capabilities for better security and consistency. Changes: - REST API permission callbacks now use `read_tbell_pattern_block` and `edit_tbell_pattern_blocks` - Controller methods add proper capability checks for theme and user pattern operations - Theme operations require `edit_theme_options` capability - User pattern operations require appropriate `tbell_pattern_block` capabilities - Test setup updated to properly initialize custom capabilities This provides granular control over pattern permissions and follows the principle of least privilege. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- includes/class-pattern-builder-api.php | 10 +++--- includes/class-pattern-builder-controller.php | 36 ++++++++++++++++++- tests/php/test-pattern-builder-api.php | 23 ++++++++++++ 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/includes/class-pattern-builder-api.php b/includes/class-pattern-builder-api.php index 0728dc9..9a90ba4 100644 --- a/includes/class-pattern-builder-api.php +++ b/includes/class-pattern-builder-api.php @@ -63,17 +63,17 @@ public function register_routes(): void { /** * Permission callback for read operations. - * Allows access to all logged-in users who can edit posts. + * Allows access to users who can read pattern blocks. * * @return bool True if the user can read patterns, false otherwise. */ public function read_permission_callback() { - return current_user_can( 'edit_posts' ); + return current_user_can( 'read_tbell_pattern_block' ); } /** * Permission callback for write operations (PUT, POST, DELETE). - * Restricts access to administrators and editors only. + * Restricts access to users with pattern editing capabilities. * Also verifies the REST API nonce for additional security. * * @param WP_REST_Request $request The REST request object. @@ -81,7 +81,7 @@ public function read_permission_callback() { */ public function write_permission_callback( $request ) { // First check if user has the required capability - if ( ! current_user_can( 'edit_others_posts' ) ) { + if ( ! current_user_can( 'edit_tbell_pattern_blocks' ) ) { return new WP_Error( 'rest_forbidden', __( 'You do not have permission to modify patterns.', 'pattern-builder' ), @@ -421,7 +421,7 @@ function handle_hijack_block_update( $response, $handler, $request ) { if ( $post->post_type === 'tbell_pattern_block' || $convert_user_pattern_to_theme_pattern ) { // Check write permissions before allowing update - if ( ! current_user_can( 'edit_others_posts' ) ) { + if ( ! current_user_can( 'edit_tbell_pattern_blocks' ) ) { return new WP_Error( 'rest_forbidden', __( 'You do not have permission to edit patterns.', 'pattern-builder' ), diff --git a/includes/class-pattern-builder-controller.php b/includes/class-pattern-builder-controller.php index d1f05c4..bca7e11 100644 --- a/includes/class-pattern-builder-controller.php +++ b/includes/class-pattern-builder-controller.php @@ -132,6 +132,15 @@ public function create_tbell_pattern_block_post_for_pattern( $pattern ) { } public function update_theme_pattern( Abstract_Pattern $pattern, $options = array() ) { + // Check if user has permission to modify theme patterns + if ( ! current_user_can( 'edit_theme_options' ) ) { + return new WP_Error( + 'insufficient_permissions', + __( 'You do not have permission to modify theme patterns.', 'pattern-builder' ), + array( 'status' => 403 ) + ); + } + // get the tbell_pattern_block post if it already exists $post = get_page_by_path( $this->format_pattern_slug_for_post( $pattern->name ), OBJECT, array( 'tbell_pattern_block', 'wp_block' ) ); @@ -430,6 +439,14 @@ function ( $matches ) use ( $download_and_save_image ) { * @return Abstract_Pattern|WP_Error */ public function update_user_pattern( Abstract_Pattern $pattern ) { + // Check if user has permission to edit pattern blocks + if ( ! current_user_can( 'edit_tbell_pattern_blocks' ) ) { + return new WP_Error( + 'insufficient_permissions', + __( 'You do not have permission to modify patterns.', 'pattern-builder' ), + array( 'status' => 403 ) + ); + } $post = get_page_by_path( $pattern->name, OBJECT, 'wp_block' ); $convert_from_theme_pattern = false; @@ -527,6 +544,15 @@ public function get_block_patterns_from_database(): array { } public function delete_user_pattern( Abstract_Pattern $pattern ) { + // Check if user has permission to delete pattern blocks + if ( ! current_user_can( 'delete_tbell_pattern_blocks' ) ) { + return new WP_Error( + 'insufficient_permissions', + __( 'You do not have permission to delete patterns.', 'pattern-builder' ), + array( 'status' => 403 ) + ); + } + $post = get_page_by_path( $pattern->name, OBJECT, 'wp_block' ); if ( empty( $post ) ) { @@ -575,8 +601,16 @@ function ( $p ) use ( $pattern ) { } public function delete_theme_pattern( Abstract_Pattern $pattern ) { + // Check if user has permission to modify theme patterns + if ( ! current_user_can( 'edit_theme_options' ) ) { + return new WP_Error( + 'insufficient_permissions', + __( 'You do not have permission to delete theme patterns.', 'pattern-builder' ), + array( 'status' => 403 ) + ); + } - $path = $this->get_pattern_filepath( $pattern ); + $path = $this->get_pattern_filepath( $pattern ); if ( ! $path ) { return new WP_Error( 'pattern_not_found', 'Pattern not found', array( 'status' => 404 ) ); diff --git a/tests/php/test-pattern-builder-api.php b/tests/php/test-pattern-builder-api.php index 09684b7..19c86de 100644 --- a/tests/php/test-pattern-builder-api.php +++ b/tests/php/test-pattern-builder-api.php @@ -13,6 +13,21 @@ public function setUp(): void { $admin_id = self::factory()->user->create(['role' => 'administrator']); wp_set_current_user($admin_id); + // Initialize the post type class to set up custom capabilities + new \TwentyBellows\PatternBuilder\Pattern_Builder_Post_Type(); + do_action('init'); + + // Directly grant capabilities to the test user + $admin_user = new WP_User($admin_id); + $admin_user->add_cap('read_tbell_pattern_block'); + $admin_user->add_cap('edit_tbell_pattern_blocks'); + $admin_user->add_cap('delete_tbell_pattern_blocks'); + + // Refresh current user to pick up new capabilities + wp_cache_delete( $admin_id, 'users' ); + wp_cache_delete( $admin_id, 'user_meta' ); + wp_set_current_user( $admin_id ); + // Create a temporary directory for the test patterns $this->test_dir = sys_get_temp_dir() . '/pattern-builder-test'; $this->remove_test_directory($this->test_dir); @@ -32,6 +47,14 @@ public function setUp(): void { * Clean up after each test */ public function tearDown(): void { + // Clean up custom capabilities from administrator role + $admin_role = get_role('administrator'); + if ($admin_role) { + $admin_role->remove_cap('read_tbell_pattern_block'); + $admin_role->remove_cap('edit_tbell_pattern_blocks'); + $admin_role->remove_cap('delete_tbell_pattern_blocks'); + } + $this->remove_test_directory($this->test_dir); remove_filter('stylesheet_directory', [$this, 'get_test_directory']); parent::tearDown();