-
Notifications
You must be signed in to change notification settings - Fork 136
Fix alt text upload URL matching #621
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
base: develop
Are you sure you want to change the base?
Changes from all commits
de43b65
d05a6b9
01c67af
1323ab9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -265,6 +265,45 @@ public function test_execute_callback_returns_not_an_image() { | |
| $this->assertEquals( 'not_an_image', $result->get_error_code(), 'Error code should be not_an_image' ); | ||
| } | ||
|
|
||
| /** | ||
| * Test that lookalike upload URLs do not map to local files. | ||
| * | ||
| * @since x.x.x | ||
| */ | ||
| public function test_maybe_map_url_to_local_path_rejects_lookalike_upload_url() { | ||
| $uploads = wp_get_upload_dir(); | ||
| if ( empty( $uploads['baseurl'] ) || empty( $uploads['basedir'] ) ) { | ||
| $this->markTestSkipped( 'Uploads directory is not available.' ); | ||
| return; | ||
| } | ||
|
|
||
| $normalized_baseurl = $this->invoke_normalize_upload_url( $uploads['baseurl'] ); | ||
| $relative_path = substr( $normalized_baseurl, -3 ) . '/ai-test-image.jpg'; | ||
| $file_path = trailingslashit( $uploads['basedir'] ) . $relative_path; | ||
|
|
||
| wp_mkdir_p( dirname( $file_path ) ); | ||
| $bytes_written = file_put_contents( $file_path, 'test image' ); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.file_ops_file_put_contents | ||
| if ( false === $bytes_written ) { | ||
| $this->markTestSkipped( 'Could not create upload fixture.' ); | ||
| return; | ||
| } | ||
|
|
||
| $reflection = new \ReflectionClass( $this->ability ); | ||
| $method = $reflection->getMethod( 'maybe_map_url_to_local_path' ); | ||
| $method->setAccessible( true ); | ||
|
|
||
| $lookalike_url = 'https://bad' . $normalized_baseurl . '/ai-test-image.jpg'; | ||
|
|
||
|
Comment on lines
+295
to
+296
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I verified the constructed URL against the current normalizer: |
||
| try { | ||
| $this->assertNull( | ||
| $method->invoke( $this->ability, $lookalike_url ), | ||
| 'Lookalike upload URLs must not resolve to local upload files.' | ||
| ); | ||
| } finally { | ||
| wp_delete_file( $file_path ); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Test that permission_callback() returns true for user with upload_files when using image_url only. | ||
| * | ||
|
|
@@ -283,6 +322,22 @@ public function test_permission_callback_with_image_url_and_upload_files() { | |
| $this->assertTrue( $result, 'Permission should be granted for user with upload_files when using image_url' ); | ||
| } | ||
|
|
||
| /** | ||
| * Invokes normalize_upload_url() for test setup. | ||
| * | ||
| * @since x.x.x | ||
| * | ||
| * @param string $url URL to normalize. | ||
| * @return string Normalized URL. | ||
| */ | ||
| private function invoke_normalize_upload_url( string $url ): string { | ||
| $reflection = new \ReflectionClass( $this->ability ); | ||
| $method = $reflection->getMethod( 'normalize_upload_url' ); | ||
| $method->setAccessible( true ); | ||
|
|
||
| return $method->invoke( $this->ability, $url ); | ||
| } | ||
|
|
||
| /** | ||
| * Test that permission_callback() returns error for user without upload_files when using image_url only. | ||
| * | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); | |
| */ | ||
| const { | ||
| clearConnectors, | ||
| clearCredentials, | ||
| seedCredentials, | ||
| disableExperiments, | ||
| disableExperiment, | ||
|
|
@@ -75,6 +76,7 @@ test.describe( 'Plugin settings', () => { | |
| } ) => { | ||
| // Activate the request mocking plugin. | ||
| await requestUtils.activatePlugin( 'e2e-test-request-mocking' ); | ||
| await clearCredentials( requestUtils ); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Curious why this was needed for this particular PR? Seems unrelated to the changes made here
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not required by the alt-text production change itself. I added it after this PR’s first CI run exposed an existing retry-isolation issue in this settings E2E test: global setup seeds the OpenAI credential, and when Playwright retried this test in isolation, the connector field was already masked/disabled so the fill failed. Clearing credentials here makes the test self-contained and prevents that unrelated flake from blocking the PR. If you prefer keeping this PR strictly scoped to the alt-text fix, I can split/drop this E2E isolation change. |
||
|
|
||
| await visitConnectorsPage( admin ); | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I checked this path and the current code already rejects the exact uploads base URL before any filesystem return: after normalization the relative path is empty, and the existing empty-relative-path guard returns null. Even beyond that, the return path is guarded by is_file(), so the uploads directory would not be returned as a valid local image file. I reran the targeted integration test successfully (33 tests, 84 assertions, 1 skipped).