Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: WordPress.org Feedback (2nd pass) #210

Merged
merged 7 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/hip-flies-camp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"wpgraphql-ide": patch
---

### Added

- Introduced `wp_localize_escaped_data()` function for recursively escaping data before localizing it in WordPress. This ensures safe output of strings, URLs, integers, and nested arrays when passing data to JavaScript, using native WordPress functions like `wp_kses_post()` and `esc_url()`.

### Improved

- Enhanced security by ensuring all localized data is properly sanitized before being passed to `wp_localize_script()`, preventing potential XSS vulnerabilities and ensuring safe use of dynamic data in JavaScript.
73 changes: 57 additions & 16 deletions wpgraphql-ide.php
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ function reorder_graphql_submenu_items(): void {
$ordered_submenu[] = $graphql_ide;
}
if ( 'on' === $show_legacy_editor && $graphiql_ide ) {
$graphiql_ide[0] = 'Legacy GraphQL IDE';
$graphiql_ide[0] = esc_html__( 'Legacy GraphQL IDE', 'wpgraphql-ide' );
$ordered_submenu[] = $graphiql_ide;
}
if ( $extensions ) {
Expand Down Expand Up @@ -386,7 +386,7 @@ function enqueue_graphql_ide_menu_icon_css(): void {
}
';

wp_add_inline_style( 'admin-bar', $custom_css );
wp_add_inline_style( 'admin-bar', wp_kses_post( $custom_css ) );
}

/**
Expand Down Expand Up @@ -445,10 +445,12 @@ function enqueue_react_app_with_styles(): void {
'dedicatedIdeBaseUrl' => get_dedicated_ide_base_url(),
];

$escaped_data = wp_localize_escaped_data( $localized_data );

wp_localize_script(
'wpgraphql-ide',
'WPGRAPHQL_IDE_DATA',
$localized_data
$escaped_data
);

// Extensions looking to extend GraphiQL can hook in here,
Expand Down Expand Up @@ -505,6 +507,46 @@ function get_plugin_header( string $key = '' ): ?string {
return is_string( $plugin_header ) ? $plugin_header : null;
}

/**
* Retrieves and sanitizes external fragments.
*
* @return array<string> The sanitized array of external fragments.
*/
function get_external_fragments(): array {
// Retrieve external fragments using the filter.
$external_fragments = apply_filters( 'wpgraphql_ide_external_fragments', [] );

// Loop through each fragment, sanitize, and ensure it's a valid GraphQL fragment.
return array_filter(
array_map( 'sanitize_text_field', $external_fragments ),
static function ( string $fragment ): bool {
// Check if the fragment starts with "fragment" and contains "on" (basic GraphQL fragment validation).
return preg_match( '/^fragment\s+\w+\s+on\s+\w+\s*{/', trim( $fragment ) ) === 1;
}
);
}

/**
* Recursive function to escape an array or value for safe output, specifically for localizing data in WordPress.
*
* @param mixed $data The data to escape.
* @return mixed The escaped data.
*/
function wp_localize_escaped_data( $data ) {
if ( is_array( $data ) ) {
return array_map( __NAMESPACE__ . '\wp_localize_escaped_data', $data );
} elseif ( is_string( $data ) ) {
// Use wp_kses_post to allow basic HTML for content and esc_url for URLs
return filter_var( $data, FILTER_VALIDATE_URL ) ? esc_url( $data ) : wp_kses_post( $data );
} elseif ( is_int( $data ) ) {
return absint( $data );
} elseif ( is_bool( $data ) ) {
return (bool) $data;
}

return $data; // Return original value if it's not a string, int, or bool.
}

/**
* Retrieves app context.
*
Expand All @@ -514,18 +556,17 @@ function get_app_context(): array {
$current_user = wp_get_current_user();

// Get the avatar URL for the current user. Returns an empty string if no user is logged in.
$avatar_url = $current_user->exists() ? get_avatar_url( $current_user->ID ) : '';

return apply_filters(
'wpgraphql_ide_context',
[
'pluginVersion' => get_plugin_header( 'Version' ),
'pluginName' => get_plugin_header( 'Name' ),
'externalFragments' => apply_filters( 'wpgraphql_ide_external_fragments', [] ),
'avatarUrl' => $avatar_url,
'drawerButtonLabel' => __( 'GraphQL IDE', 'wpgraphql-ide' ),
]
);
$avatar_url = $current_user->exists() ? ( get_avatar_url( $current_user->ID ) ?: '' ) : '';

$app_context = [
'pluginVersion' => get_plugin_header( 'Version' ),
'pluginName' => get_plugin_header( 'Name' ),
'externalFragments' => get_external_fragments(),
'avatarUrl' => $avatar_url,
'drawerButtonLabel' => __( 'GraphQL IDE', 'wpgraphql-ide' ),
];

return apply_filters( 'wpgraphql_ide_context', $app_context );
}

/**
Expand Down Expand Up @@ -558,7 +599,7 @@ function graphql_admin_notices_render_notices( array $notices ): void {
// phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
wp_register_style( 'wpgraphql-ide-admin-notices', false );
wp_enqueue_style( 'wpgraphql-ide-admin-notices' );
wp_add_inline_style( 'wpgraphql-ide-admin-notices', $custom_css );
wp_add_inline_style( 'wpgraphql-ide-admin-notices', wp_kses_post( $custom_css ) );
}

/**
Expand Down
Loading