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
15 changes: 14 additions & 1 deletion includes/Core/Modules/Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -753,7 +753,20 @@ function ( $item ) {
* @return bool TRUE if the request is for shared data, otherwise FALSE.
*/
protected function is_shared_data_request( Data_Request $data ) {
$datapoint = $this->get_datapoint_definition( "{$data->method}:{$data->datapoint}" );
$datapoint = $this->get_datapoint_definition( "{$data->method}:{$data->datapoint}" );
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The refactored is_shared_data_request() no longer accounts for the scope validation fallback in get_oauth_client_for_datapoint().

Previously, if all sharing conditions were met but the owner's scopes failed validation, get_oauth_client_for_datapoint() would fall back to the default client, and is_shared_data_request() would return false (since the clients matched). Now it returns true regardless of scope validation.

This means shared-data restrictions (e.g. date range limits in GET:batch-report, metric/dimension validation in AdSense) could be applied to requests that are actually using the current user's own credentials — potentially restricting data the user should have full access to.

Should this method preserve the old scope-aware behavior?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing that out. I thought checking the clients was just a quick way to determine if the request was shared. I reverted some of the changes and only left the new is_shared_datapoint_request. Please take another look.


return $this->is_shared_datapoint_request( $datapoint );
}

/**
* Determines whether the current datapoint request is for shared data.
*
* @since n.e.x.t
*
* @param Datapoint $datapoint Datapoint instance.
* @return bool TRUE if the request is for shared data, otherwise FALSE.
*/
protected function is_shared_datapoint_request( Datapoint $datapoint ) {
$oauth_client = $this->get_oauth_client_for_datapoint( $datapoint );

if ( $this->authentication->get_oauth_client() !== $oauth_client ) {
Expand Down
114 changes: 29 additions & 85 deletions includes/Modules/Analytics_4.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@
use Google\Site_Kit\Modules\Analytics_4\Datapoints\Get_Container_Destinations;
use Google\Site_Kit\Modules\Analytics_4\Datapoints\Get_Enhanced_Measurement_Settings;
use Google\Site_Kit\Modules\Analytics_4\Datapoints\Get_Google_Tag_Settings;
use Google\Site_Kit\Modules\Analytics_4\Datapoints\Get_Has_Property_Access;
use Google\Site_Kit\Modules\Analytics_4\Datapoints\Get_Key_Events;
use Google\Site_Kit\Modules\Analytics_4\Datapoints\Get_Report;
use Google\Site_Kit\Modules\Analytics_4\Datapoints\Get_Webdatastreams;
use Google\Site_Kit\Modules\Analytics_4\Datapoints\Get_Webdatastreams_Batch;
use Google\Site_Kit\Modules\Analytics_4\Datapoints\Save_Audience_Settings;
Expand All @@ -88,7 +91,6 @@
use Google\Site_Kit\Modules\Analytics_4\Synchronize_AdSenseLinked;
use Google\Site_Kit\Modules\Analytics_4\GoogleAnalyticsAdmin\AccountProvisioningService;
use Google\Site_Kit\Modules\Analytics_4\Report\Request as Analytics_4_Report_Request;
use Google\Site_Kit\Modules\Analytics_4\Report\Response as Analytics_4_Report_Response;
use Google\Site_Kit\Modules\Analytics_4\Resource_Data_Availability_Date;
use Google\Site_Kit\Modules\Analytics_4\Settings;
use Google\Site_Kit\Modules\Analytics_4\Synchronize_AdsLinked;
Expand All @@ -97,10 +99,6 @@
use Google\Site_Kit\Modules\Analytics_4\Web_Tag;
use Google\Site_Kit_Dependencies\Google\Model as Google_Model;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData as Google_Service_AnalyticsData;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\RunReportRequest as Google_Service_AnalyticsData_RunReportRequest;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\DateRange as Google_Service_AnalyticsData_DateRange;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\Dimension as Google_Service_AnalyticsData_Dimension;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\Metric as Google_Service_AnalyticsData_Metric;
use Google\Site_Kit_Dependencies\Google\Service\GoogleAnalyticsAdmin as Google_Service_GoogleAnalyticsAdmin;
use Google\Site_Kit_Dependencies\Google\Service\GoogleAnalyticsAdmin\GoogleAnalyticsAdminV1betaDataStream;
use Google\Site_Kit_Dependencies\Google\Service\GoogleAnalyticsAdmin\GoogleAnalyticsAdminV1betaDataStreamWebStreamData;
Expand All @@ -118,6 +116,7 @@
use Google\Site_Kit\Modules\Analytics_4\Conversion_Reporting\Conversion_Reporting_New_Badge_Events_Sync;
use Google\Site_Kit\Modules\Analytics_4\Conversion_Reporting\Conversion_Reporting_Provider;
use Google\Site_Kit\Modules\Analytics_4\Reset_Audiences;
use Google\Site_Kit\Core\Modules\Datapoint;
use stdClass;
use WP_Error;
use WP_Post;
Expand Down Expand Up @@ -730,9 +729,13 @@ protected function get_datapoint_definitions() {
),
)
),
'GET:key-events' => array(
'service' => 'analyticsadmin',
'shareable' => true,
'GET:key-events' => new Get_Key_Events(
array(
'service' => function () {
return $this->get_service( 'analyticsadmin' );
},
'settings' => $this->get_settings(),
)
),
'POST:create-account-ticket' => new Create_Account_Ticket(
array(
Expand Down Expand Up @@ -777,10 +780,24 @@ protected function get_datapoint_definitions() {
),
'GET:properties' => array( 'service' => 'analyticsadmin' ),
'GET:property' => array( 'service' => 'analyticsadmin' ),
'GET:has-property-access' => array( 'service' => 'analyticsdata' ),
'GET:report' => array(
'service' => 'analyticsdata',
'shareable' => true,
'GET:has-property-access' => new Get_Has_Property_Access(
array(
'service' => function () {
return $this->get_service( 'analyticsdata' );
},
)
),
'GET:report' => new Get_Report(
array(
'service' => function () {
return $this->get_service( 'analyticsdata' );
},
'settings' => $this->get_settings(),
'context' => $this->context,
'is_shared_request' => function ( Datapoint $datapoint ) {
return $this->is_shared_datapoint_request( $datapoint );
},
),
),
'GET:batch-report' => array(
'service' => 'analyticsdata',
Expand Down Expand Up @@ -1295,58 +1312,6 @@ protected function create_data_request( Data_Request $data ) {
}

return $this->get_service( 'analyticsadmin' )->properties->get( self::normalize_property_id( $data['propertyID'] ) );
case 'GET:has-property-access':
if ( ! isset( $data['propertyID'] ) ) {
throw new Missing_Required_Param_Exception( 'propertyID' );
}

// A simple way to check for property access is to attempt a minimal report request.
// If the user does not have access, this will return a 403 error.
$request = new Google_Service_AnalyticsData_RunReportRequest();
$request->setDimensions( array( new Google_Service_AnalyticsData_Dimension( array( 'name' => 'date' ) ) ) );
$request->setMetrics( array( new Google_Service_AnalyticsData_Metric( array( 'name' => 'sessions' ) ) ) );
$request->setDateRanges(
array(
new Google_Service_AnalyticsData_DateRange(
array(
'start_date' => 'yesterday',
'end_date' => 'today',
)
),
)
);
$request->setLimit( 0 );

return $this->get_analyticsdata_service()->properties->runReport( $data['propertyID'], $request );
case 'GET:report':
if ( empty( $data['metrics'] ) ) {
return new WP_Error(
'missing_required_param',
/* translators: %s: Missing parameter name */
sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'metrics' ),
array( 'status' => 400 )
);
}

$settings = $this->get_settings()->get();
if ( empty( $settings['propertyID'] ) ) {
return new WP_Error(
'missing_required_setting',
__( 'No connected Google Analytics property ID.', 'google-site-kit' ),
array( 'status' => 500 )
);
}

$report = new Analytics_4_Report_Request( $this->context );
$request = $report->create_request( $data, $this->is_shared_data_request( $data ) );
if ( is_wp_error( $request ) ) {
return $request;
}

$property_id = self::normalize_property_id( $settings['propertyID'] );
$request->setProperty( $property_id );

return $this->get_analyticsdata_service()->properties->runReport( $property_id, $request );

case 'GET:batch-report':
if ( empty( $data['requests'] ) ) {
Expand Down Expand Up @@ -1399,22 +1364,6 @@ protected function create_data_request( Data_Request $data ) {
$property_id,
$batch_request
);
case 'GET:key-events':
$settings = $this->get_settings()->get();
if ( empty( $settings['propertyID'] ) ) {
return new WP_Error(
'missing_required_setting',
__( 'No connected Google Analytics property ID.', 'google-site-kit' ),
array( 'status' => 500 )
);
}

$analyticsadmin = $this->get_service( 'analyticsadmin' );
$property_id = self::normalize_property_id( $settings['propertyID'] );

return $analyticsadmin
->properties_keyEvents // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
->listPropertiesKeyEvents( $property_id );
}

return parent::create_data_request( $data );
Expand All @@ -1441,11 +1390,6 @@ protected function parse_data_response( Data_Request $data, $response ) {
);
case 'GET:property':
return self::filter_property_with_ids( $response );
case 'GET:key-events':
return (array) $response->getKeyEvents();
case 'GET:report':
$report = new Analytics_4_Report_Response( $this->context );
return $report->parse_response( $data, $response );
}

return parent::parse_data_response( $data, $response );
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php
/**
* Class Google\Site_Kit\Modules\Analytics_4\Datapoints\Get_Has_Property_Access
*
* @package Google\Site_Kit\Modules\Analytics_4\Datapoints
* @copyright 2026 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/

namespace Google\Site_Kit\Modules\Analytics_4\Datapoints;

use Google\Site_Kit\Core\Modules\Datapoint;
use Google\Site_Kit\Core\Modules\Executable_Datapoint;
use Google\Site_Kit\Core\REST_API\Data_Request;
use Google\Site_Kit\Core\REST_API\Exception\Missing_Required_Param_Exception;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\RunReportRequest as Google_Service_AnalyticsData_RunReportRequest;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\DateRange as Google_Service_AnalyticsData_DateRange;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\Dimension as Google_Service_AnalyticsData_Dimension;
use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\Metric as Google_Service_AnalyticsData_Metric;

/**
* Has property access datapoint.
*
* @since n.e.x.t
* @access private
* @ignore
*/
class Get_Has_Property_Access extends Datapoint implements Executable_Datapoint {

/**
* Creates a request object.
*
* @since n.e.x.t
*
* @param Data_Request $data Data request object.
* @return mixed Request object on success, or WP_Error on failure.
* @throws Missing_Required_Param_Exception Thrown if a required parameter is missing.
*/
public function create_request( Data_Request $data ) {
if ( ! isset( $data['propertyID'] ) ) {
throw new Missing_Required_Param_Exception( 'propertyID' );
}

// A simple way to check for property access is to attempt a minimal report request.
// If the user does not have access, this will return a 403 error.
$request = new Google_Service_AnalyticsData_RunReportRequest();
$request->setDimensions( array( new Google_Service_AnalyticsData_Dimension( array( 'name' => 'date' ) ) ) );
$request->setMetrics( array( new Google_Service_AnalyticsData_Metric( array( 'name' => 'sessions' ) ) ) );
$request->setDateRanges(
array(
new Google_Service_AnalyticsData_DateRange(
array(
'start_date' => 'yesterday',
'end_date' => 'today',
)
),
)
);
$request->setLimit( 0 );

return $this->get_service()->properties->runReport( $data['propertyID'], $request );
}

/**
* Parses a response.
*
* @since n.e.x.t
*
* @param mixed $response Request response.
* @param Data_Request $data Data request object.
* @return mixed The original response without any modifications.
*/
public function parse_response( $response, Data_Request $data ) {
return $response;
}
}
88 changes: 88 additions & 0 deletions includes/Modules/Analytics_4/Datapoints/Get_Key_Events.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php
/**
* Class Google\Site_Kit\Modules\Analytics_4\Datapoints\Get_Key_Events
*
* @package Google\Site_Kit\Modules\Analytics_4\Datapoints
* @copyright 2026 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
* @link https://sitekit.withgoogle.com
*/

namespace Google\Site_Kit\Modules\Analytics_4\Datapoints;

use Google\Site_Kit\Core\Modules\Executable_Datapoint;
use Google\Site_Kit\Core\Modules\Shareable_Datapoint;
use Google\Site_Kit\Core\REST_API\Data_Request;
use Google\Site_Kit\Modules\Analytics_4;
use Google\Site_Kit_Dependencies\Google\Service\GoogleAnalyticsAdmin\GoogleAnalyticsAdminV1betaKeyEvent;
use Google\Site_Kit\Modules\Analytics_4\Settings;
use WP_Error;

/**
* Get key events datapoint class.
*
* @since n.e.x.t
* @access private
* @ignore
*/
class Get_Key_Events extends Shareable_Datapoint implements Executable_Datapoint {

/**
* Module settings instance.
*
* @since n.e.x.t
* @var Settings
*/
private $settings;

/**
* Constructor.
*
* @since n.e.x.t
*
* @param array $definition Definition fields.
*/
public function __construct( array $definition ) {
parent::__construct( $definition );
$this->settings = $definition['settings'];
}

/**
* Creates a request object.
*
* @since n.e.x.t
*
* @param Data_Request $data Data request object.
* @return mixed Request object on success, or WP_Error on failure.
*/
public function create_request( Data_Request $data ) {
$settings = $this->settings->get();

if ( empty( $settings['propertyID'] ) ) {
return new WP_Error(
'missing_required_setting',
__( 'No connected Google Analytics property ID.', 'google-site-kit' ),
array( 'status' => 500 )
);
}

$property_id = Analytics_4::normalize_property_id( $settings['propertyID'] );

return $this->get_service()
->properties_keyEvents // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
->listPropertiesKeyEvents( $property_id );
}

/**
* Parses a response.
*
* @since n.e.x.t
*
* @param mixed $response Request response.
* @param Data_Request $data Data request object.
* @return GoogleAnalyticsAdminV1betaKeyEvent[] Array of key events.
*/
public function parse_response( $response, Data_Request $data ) {
return (array) $response->getKeyEvents();
}
}
Loading
Loading