Skip to content
Open
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
12 changes: 9 additions & 3 deletions src/wp-includes/connectors.php
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,7 @@ function _wp_connectors_rest_settings_dispatch( WP_REST_Response $response, WP_R
}

$is_update = 'POST' === $request->get_method() || 'PUT' === $request->get_method();
$submitted = $is_update ? $request->get_params() : array();

foreach ( wp_get_connectors() as $connector_id => $connector_data ) {
$auth = $connector_data['authentication'];
Expand All @@ -731,9 +732,14 @@ function _wp_connectors_rest_settings_dispatch( WP_REST_Response $response, WP_R

$value = $data[ $setting_name ];

// On update, validate AI provider keys before masking.
// Non-AI connectors accept keys as-is; the service plugin handles its own validation.
if ( $is_update && is_string( $value ) && '' !== $value && 'ai_provider' === $connector_data['type'] ) {
// On update, validate AI provider keys before masking, but only when the
// key was actually submitted in this request. Non-AI connectors accept keys
// as-is; the service plugin handles its own validation.
if ( $is_update
&& array_key_exists( $setting_name, $submitted )
&& is_string( $value ) && '' !== $value
&& 'ai_provider' === $connector_data['type']
) {
if ( true !== _wp_connectors_is_ai_api_key_valid( $value, $connector_id ) ) {
update_option( $setting_name, '' );
$data[ $setting_name ] = '';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<?php

require_once dirname( __DIR__, 2 ) . '/includes/wp-ai-client-mock-provider-trait.php';

/**
* Tests for _wp_connectors_rest_settings_dispatch().
*
Expand All @@ -7,8 +10,19 @@
*/
class Tests_Connectors_WpConnectorsRestSettingsDispatch extends WP_UnitTestCase {

use WP_AI_Client_Mock_Provider_Trait;

const CONNECTOR_ID = 'wp_test_application_password_connector';
const CREDENTIALS_SETTING_NAME = 'connectors_test_remote_credentials';
const AI_KEY_SETTING_NAME = 'connectors_ai_mock_connectors_test_api_key';

/**
* Registers the mock AI provider connector once for the class.
*/
public static function set_up_before_class() {
parent::set_up_before_class();
self::register_mock_connectors_provider();
}

/**
* Registers an application password connector before each test.
Expand Down Expand Up @@ -63,4 +77,41 @@ public function test_masks_application_password_but_not_username(): void {
$this->assertSame( str_repeat( "\u{2022}", 16 ), $data[ self::CREDENTIALS_SETTING_NAME ]['password'] );
$this->assertNotSame( $application_password, $data[ self::CREDENTIALS_SETTING_NAME ]['password'] );
}

/**
* Ensures a stored AI provider API key is not re-validated, and therefore not
* reset, when it is not part of the current settings update.
*
* @ticket 65867
*/
public function test_does_not_validate_or_reset_unsubmitted_ai_key(): void {
$stored_key = 'sk-stored-valid-key';
update_option( self::AI_KEY_SETTING_NAME, $stored_key );

self::set_mock_provider_configured( false );

// An update that does not submit the AI key (e.g. saving another setting).
$request = new WP_REST_Request( 'POST', '/wp/v2/settings' );
$request->set_param( 'title', 'New Site Title' );

// The settings endpoint response always contains every registered setting.
$response = new WP_REST_Response( array( self::AI_KEY_SETTING_NAME => $stored_key ) );

$result = _wp_connectors_rest_settings_dispatch( $response, rest_get_server(), $request );
$data = $result->get_data();

$this->assertSame(
$stored_key,
get_option( self::AI_KEY_SETTING_NAME ),
'An AI provider key that was not submitted should not be reset.'
);
$this->assertSame(
_wp_connectors_mask_api_key( $stored_key ),
$data[ self::AI_KEY_SETTING_NAME ],
'The stored AI provider key should still be masked in the response.'
);

self::set_mock_provider_configured( true );
delete_option( self::AI_KEY_SETTING_NAME );
}
}
Loading