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

Add logging and an admin notice for Legacy REST API usages #41804

Merged
merged 11 commits into from
Dec 20, 2023
4 changes: 4 additions & 0 deletions plugins/woocommerce/changelog/pr-41804
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: add

Add logging and an admin notice for Legacy REST API usages
13 changes: 13 additions & 0 deletions plugins/woocommerce/includes/admin/class-wc-admin-notices.php
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,19 @@ private static function hide_notice( $name ) {
do_action( 'woocommerce_hide_' . $name . '_notice' );
}

/**
* Check if a given user has dismissed a given admin notice.
*
* @since 8.5.0
*
* @param string $name The name of the admin notice to check.
* @param int|null $user_id User id, or null for the current user.
* @return bool True if the user has dismissed the notice.
*/
jorgeatorres marked this conversation as resolved.
Show resolved Hide resolved
public static function user_has_dismissed_notice( string $name, ?int $user_id = null ): bool {
return (bool) get_user_meta( $user_id ?? get_current_user_id(), "dismissed_{$name}_notice", true );
}

/**
* Add notices + styles if needed.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -393,11 +393,12 @@ protected function get_settings_for_legacy_api_section() {

if ( ! is_plugin_active( 'woocommerce-legacy-rest-api/woocommerce-legacy-rest-api.php' ) ) {
$enable_legacy_api_setting['desc_tip'] = sprintf(
// translators: Placeholder is a URL.
// translators: Placeholders are URLs.
__(
'⚠️ <b>️The Legacy REST API will be removed in WooCommerce 9.0.</b> A separate WooCommerce extension will soon be available to keep it enabled. <b><a target="_blank" href="%s">Learn more about this change.</a></b>',
'⚠️ <b>️The Legacy REST API will be removed in WooCommerce 9.0.</b> A separate WooCommerce extension will soon be available to keep it enabled. You can check Legacy REST API usages in <b><a target="_blank" href="%1$s">the WooCommerce log files</a></b> (file names start with <code>legacy_rest_api_usages</code>). <b><a target="_blank" href="%2$s">Learn more about this change.</a></b>',
'woocommerce'
),
admin_url( 'admin.php?page=wc-status&tab=logs' ),
'https://developer.woo.com/2023/10/03/the-legacy-rest-api-will-move-to-a-dedicated-extension-in-woocommerce-9-0/'
);
}
Expand Down
212 changes: 156 additions & 56 deletions plugins/woocommerce/includes/legacy/class-wc-legacy-api.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php

Check notice on line 1 in plugins/woocommerce/includes/legacy/class-wc-legacy-api.php

View workflow job for this annotation

GitHub Actions / Analyze Branch Changes

new filter found - woocommerce_log_legacy_rest_api_usages

\'woocommerce_log_legacy_rest_api_usages\' introduced in 8.5.0
/**
* WooCommerce Legacy API. Was deprecated with 2.6.0.
*
Expand All @@ -8,6 +8,8 @@
* @since 2.6
*/

use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods;

if ( ! defined( 'ABSPATH' ) ) {
exit;
}
Expand All @@ -17,6 +19,8 @@
*/
class WC_Legacy_API {

use AccessiblePrivateMethods;

/**
* This is the major version for the REST API and takes
* first-order position in endpoint URLs.
Expand Down Expand Up @@ -47,6 +51,8 @@
*/
public function init() {
add_action( 'parse_request', array( $this, 'handle_rest_api_requests' ), 0 );
$this->mark_method_as_accessible( 'maybe_display_legacy_wc_api_usage_notice' );
self::add_action( 'admin_notices', array( $this, 'maybe_display_legacy_wc_api_usage_notice' ), 0 );
}

/**
Expand All @@ -62,6 +68,56 @@
return $vars;
}

/**
* Write a log entry and update the last usage options, for a Legacy REST API request.
*
* @param string $route The Legacy REST API route requested.
* @param string|null $user_agent The content of the user agent HTTP header in the request, null if not available.
*/
private function maybe_log_rest_api_request( string $route, ?string $user_agent ) {
if ( is_plugin_active( 'woocommerce-legacy-rest-api/woocommerce-legacy-rest-api.php' ) ) {
return;
}

$user_agent = $user_agent ?? 'unknown';

$current_date = wp_date( 'Y-m-d H:i:s' );
$stored_api_accesses = get_transient( 'wc_legacy_rest_api_usages' );
if ( false === $stored_api_accesses ) {
$stored_api_accesses = array(
'user_agents' => array(),
'first_usage' => $current_date,
'total_count' => 0,
);
}

$stored_api_accesses_for_user_agent = $stored_api_accesses['user_agents'][ $user_agent ] ?? null;
if ( is_null( $stored_api_accesses_for_user_agent ) ) {
$stored_api_accesses['user_agents'][ $user_agent ] = array(
'first_date' => $current_date,
'last_date' => $current_date,
'count' => 1,
);
} else {
$stored_api_accesses['user_agents'][ $user_agent ]['count']++;
$stored_api_accesses['user_agents'][ $user_agent ]['last_date'] = $current_date;
}
$stored_api_accesses['total_count']++;

set_transient( 'wc_legacy_rest_api_usages', $stored_api_accesses, time() + 2 * WEEK_IN_SECONDS );

/**
* This filter allows disabling the logging of Legacy REST API usages.
*
* @param bool $do_logging True to enable the logging of all the Legacy REST API usages (default), false to disable.
*
* @since 8.5.0
*/
if ( apply_filters( 'woocommerce_log_legacy_rest_api_usages', true ) ) {
wc_get_logger()->info( 'Version: ' . WC_API_REQUEST_VERSION . ", Route: $route, User agent: $user_agent", array( 'source' => 'legacy_rest_api_usages' ) );
}
}

/**
* Add new endpoints.
*
Expand Down Expand Up @@ -90,30 +146,71 @@
$wp->query_vars['wc-api-route'] = $_GET['wc-api-route'];
}

if ( empty( $wp->query_vars['wc-api-version'] ) || empty( $wp->query_vars['wc-api-route'] ) ) {
return;
}

// REST API request.
if ( ! empty( $wp->query_vars['wc-api-version'] ) && ! empty( $wp->query_vars['wc-api-route'] ) ) {

wc_maybe_define_constant( 'WC_API_REQUEST', true );
wc_maybe_define_constant( 'WC_API_REQUEST_VERSION', absint( $wp->query_vars['wc-api-version'] ) );
wc_maybe_define_constant( 'WC_API_REQUEST', true );
wc_maybe_define_constant( 'WC_API_REQUEST_VERSION', absint( $wp->query_vars['wc-api-version'] ) );

// Legacy v1 API request.
if ( 1 === WC_API_REQUEST_VERSION ) {
$this->handle_v1_rest_api_request();
} elseif ( 2 === WC_API_REQUEST_VERSION ) {
$this->handle_v2_rest_api_request();
} else {
$this->includes();
$route = $wp->query_vars['wc-api-route'];
$this->maybe_log_rest_api_request( $route, $_SERVER['HTTP_USER_AGENT'] ?? null );

$this->server = new WC_API_Server( $wp->query_vars['wc-api-route'] );
// Legacy v1 API request.
if ( 1 === WC_API_REQUEST_VERSION ) {
$this->handle_v1_rest_api_request();
} elseif ( 2 === WC_API_REQUEST_VERSION ) {
$this->handle_v2_rest_api_request();
} else {
$this->includes();

// load API resource classes.
$this->register_resources( $this->server );
$this->server = new WC_API_Server( $route );

// Fire off the request.
$this->server->serve_request();
}
// load API resource classes.
$this->register_resources( $this->server );

// Fire off the request.
$this->server->serve_request();
}

exit;
}
jorgeatorres marked this conversation as resolved.
Show resolved Hide resolved

exit;
/**
* Display an admin notice with information about the last Legacy REST API usage,
* if the corresponding transient is available and unless the Legacy REST API
* extension is installed or the user has dismissed the notice.
*/
private function maybe_display_legacy_wc_api_usage_notice(): void {
$legacy_api_usages = get_transient( 'wc_legacy_rest_api_usages' );
if ( false === $legacy_api_usages || is_plugin_active( 'woocommerce-legacy-rest-api/woocommerce-legacy-rest-api.php' ) || 'yes' !== get_option( 'woocommerce_api_enabled' ) ) {
if ( WC_Admin_Notices::has_notice( 'legacy_api_usages_detected' ) ) {
WC_Admin_Notices::remove_notice( 'legacy_api_usages_detected' );
}
} elseif ( ! WC_Admin_Notices::user_has_dismissed_notice( 'legacy_api_usages_detected' ) ) {
unset( $legacy_api_usages['user_agents']['unknown'] );
$user_agents = array_keys( $legacy_api_usages['user_agents'] );

WC_Admin_Notices::add_custom_notice(
'legacy_api_usages_detected',
sprintf(
'%s%s',
sprintf(
'<h4>%s</h4>',
esc_html__( 'WooCommerce Legacy REST API access detected', 'woocommerce' )
),
sprintf(
// translators: %1$d = count of Legacy REST API usages recorded, %2$s = date and time of first access, %3$d = count of known user agents registered, %4$s = URL.
wpautop( wp_kses_data( __( '<p>The WooCommerce Legacy REST API has been accessed <b>%1$d</b> time(s) since <b>%2$s</b>. There are <b>%3$d</b> known user agent(s) registered. There are more details in <b><a target="_blank" href="%4$s">the WooCommerce log files</a></b> (file names start with <code>legacy_rest_api_usages</code>).', 'woocommerce' ) ) ),
$legacy_api_usages['total_count'],
$legacy_api_usages['first_usage'],
count( $user_agents ),
admin_url( 'admin.php?page=wc-status&tab=logs' ),
)
)
);
}
}

Expand All @@ -126,23 +223,23 @@
public function includes() {

// API server / response handlers.
include_once( dirname( __FILE__ ) . '/api/v3/class-wc-api-exception.php' );
include_once( dirname( __FILE__ ) . '/api/v3/class-wc-api-server.php' );
include_once( dirname( __FILE__ ) . '/api/v3/interface-wc-api-handler.php' );
include_once( dirname( __FILE__ ) . '/api/v3/class-wc-api-json-handler.php' );
include_once dirname( __FILE__ ) . '/api/v3/class-wc-api-exception.php';
include_once dirname( __FILE__ ) . '/api/v3/class-wc-api-server.php';
include_once dirname( __FILE__ ) . '/api/v3/interface-wc-api-handler.php';
include_once dirname( __FILE__ ) . '/api/v3/class-wc-api-json-handler.php';

// Authentication.
include_once( dirname( __FILE__ ) . '/api/v3/class-wc-api-authentication.php' );
include_once dirname( __FILE__ ) . '/api/v3/class-wc-api-authentication.php';
$this->authentication = new WC_API_Authentication();

include_once( dirname( __FILE__ ) . '/api/v3/class-wc-api-resource.php' );
include_once( dirname( __FILE__ ) . '/api/v3/class-wc-api-coupons.php' );
include_once( dirname( __FILE__ ) . '/api/v3/class-wc-api-customers.php' );
include_once( dirname( __FILE__ ) . '/api/v3/class-wc-api-orders.php' );
include_once( dirname( __FILE__ ) . '/api/v3/class-wc-api-products.php' );
include_once( dirname( __FILE__ ) . '/api/v3/class-wc-api-reports.php' );
include_once( dirname( __FILE__ ) . '/api/v3/class-wc-api-taxes.php' );
include_once( dirname( __FILE__ ) . '/api/v3/class-wc-api-webhooks.php' );
include_once dirname( __FILE__ ) . '/api/v3/class-wc-api-resource.php';
include_once dirname( __FILE__ ) . '/api/v3/class-wc-api-coupons.php';
include_once dirname( __FILE__ ) . '/api/v3/class-wc-api-customers.php';
include_once dirname( __FILE__ ) . '/api/v3/class-wc-api-orders.php';
include_once dirname( __FILE__ ) . '/api/v3/class-wc-api-products.php';
include_once dirname( __FILE__ ) . '/api/v3/class-wc-api-reports.php';
include_once dirname( __FILE__ ) . '/api/v3/class-wc-api-taxes.php';
include_once dirname( __FILE__ ) . '/api/v3/class-wc-api-webhooks.php';

// Allow plugins to load other response handlers or resource classes.
do_action( 'woocommerce_api_loaded' );
Expand All @@ -157,7 +254,8 @@
*/
public function register_resources( $server ) {

$api_classes = apply_filters( 'woocommerce_api_classes',
$api_classes = apply_filters(
'woocommerce_api_classes',
array(
'WC_API_Coupons',
'WC_API_Customers',
Expand All @@ -184,28 +282,29 @@
private function handle_v1_rest_api_request() {

// Include legacy required files for v1 REST API request.
include_once( dirname( __FILE__ ) . '/api/v1/class-wc-api-server.php' );
include_once( dirname( __FILE__ ) . '/api/v1/interface-wc-api-handler.php' );
include_once( dirname( __FILE__ ) . '/api/v1/class-wc-api-json-handler.php' );
include_once( dirname( __FILE__ ) . '/api/v1/class-wc-api-xml-handler.php' );
include_once dirname( __FILE__ ) . '/api/v1/class-wc-api-server.php';
include_once dirname( __FILE__ ) . '/api/v1/interface-wc-api-handler.php';
include_once dirname( __FILE__ ) . '/api/v1/class-wc-api-json-handler.php';
include_once dirname( __FILE__ ) . '/api/v1/class-wc-api-xml-handler.php';

include_once( dirname( __FILE__ ) . '/api/v1/class-wc-api-authentication.php' );
include_once dirname( __FILE__ ) . '/api/v1/class-wc-api-authentication.php';
$this->authentication = new WC_API_Authentication();

include_once( dirname( __FILE__ ) . '/api/v1/class-wc-api-resource.php' );
include_once( dirname( __FILE__ ) . '/api/v1/class-wc-api-coupons.php' );
include_once( dirname( __FILE__ ) . '/api/v1/class-wc-api-customers.php' );
include_once( dirname( __FILE__ ) . '/api/v1/class-wc-api-orders.php' );
include_once( dirname( __FILE__ ) . '/api/v1/class-wc-api-products.php' );
include_once( dirname( __FILE__ ) . '/api/v1/class-wc-api-reports.php' );
include_once dirname( __FILE__ ) . '/api/v1/class-wc-api-resource.php';
include_once dirname( __FILE__ ) . '/api/v1/class-wc-api-coupons.php';
include_once dirname( __FILE__ ) . '/api/v1/class-wc-api-customers.php';
include_once dirname( __FILE__ ) . '/api/v1/class-wc-api-orders.php';
include_once dirname( __FILE__ ) . '/api/v1/class-wc-api-products.php';
include_once dirname( __FILE__ ) . '/api/v1/class-wc-api-reports.php';

// Allow plugins to load other response handlers or resource classes.
do_action( 'woocommerce_api_loaded' );

$this->server = new WC_API_Server( $GLOBALS['wp']->query_vars['wc-api-route'] );

// Register available resources for legacy v1 REST API request.
$api_classes = apply_filters( 'woocommerce_api_classes',
$api_classes = apply_filters(
'woocommerce_api_classes',
array(
'WC_API_Customers',
'WC_API_Orders',
Expand All @@ -230,29 +329,30 @@
* @deprecated 2.6.0
*/
private function handle_v2_rest_api_request() {
include_once( dirname( __FILE__ ) . '/api/v2/class-wc-api-exception.php' );
include_once( dirname( __FILE__ ) . '/api/v2/class-wc-api-server.php' );
include_once( dirname( __FILE__ ) . '/api/v2/interface-wc-api-handler.php' );
include_once( dirname( __FILE__ ) . '/api/v2/class-wc-api-json-handler.php' );
include_once dirname( __FILE__ ) . '/api/v2/class-wc-api-exception.php';
include_once dirname( __FILE__ ) . '/api/v2/class-wc-api-server.php';
include_once dirname( __FILE__ ) . '/api/v2/interface-wc-api-handler.php';
include_once dirname( __FILE__ ) . '/api/v2/class-wc-api-json-handler.php';

include_once( dirname( __FILE__ ) . '/api/v2/class-wc-api-authentication.php' );
include_once dirname( __FILE__ ) . '/api/v2/class-wc-api-authentication.php';
$this->authentication = new WC_API_Authentication();

include_once( dirname( __FILE__ ) . '/api/v2/class-wc-api-resource.php' );
include_once( dirname( __FILE__ ) . '/api/v2/class-wc-api-coupons.php' );
include_once( dirname( __FILE__ ) . '/api/v2/class-wc-api-customers.php' );
include_once( dirname( __FILE__ ) . '/api/v2/class-wc-api-orders.php' );
include_once( dirname( __FILE__ ) . '/api/v2/class-wc-api-products.php' );
include_once( dirname( __FILE__ ) . '/api/v2/class-wc-api-reports.php' );
include_once( dirname( __FILE__ ) . '/api/v2/class-wc-api-webhooks.php' );
include_once dirname( __FILE__ ) . '/api/v2/class-wc-api-resource.php';
include_once dirname( __FILE__ ) . '/api/v2/class-wc-api-coupons.php';
include_once dirname( __FILE__ ) . '/api/v2/class-wc-api-customers.php';
include_once dirname( __FILE__ ) . '/api/v2/class-wc-api-orders.php';
include_once dirname( __FILE__ ) . '/api/v2/class-wc-api-products.php';
include_once dirname( __FILE__ ) . '/api/v2/class-wc-api-reports.php';
include_once dirname( __FILE__ ) . '/api/v2/class-wc-api-webhooks.php';

// allow plugins to load other response handlers or resource classes.
do_action( 'woocommerce_api_loaded' );

$this->server = new WC_API_Server( $GLOBALS['wp']->query_vars['wc-api-route'] );

// Register available resources for legacy v2 REST API request.
$api_classes = apply_filters( 'woocommerce_api_classes',
$api_classes = apply_filters(
'woocommerce_api_classes',
array(
'WC_API_Customers',
'WC_API_Orders',
Expand Down