diff --git a/.storybook/storybook-data.js b/.storybook/storybook-data.js index 5f03240d825..50ddf923d13 100644 --- a/.storybook/storybook-data.js +++ b/.storybook/storybook-data.js @@ -529,10 +529,10 @@ module.exports = [ }, }, { - id: 'setup--client-id', + id: 'setup--step-one', kind: 'Setup', - name: 'Client ID', - story: 'Client ID', + name: 'Step One', + story: 'Step One', parameters: { fileName: './stories/setup.stories.js', options: { diff --git a/.travis.yml b/.travis.yml index 6d28968ade1..a356450e430 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,7 +34,7 @@ branches: only: - master - develop - - datapoint-refactoring + - feature/auth-proxy # Before install, failures in this section will result in build status 'errored' before_install: diff --git a/assets/js/components/dashboard-splash/dashboard-splash-app.js b/assets/js/components/dashboard-splash/dashboard-splash-app.js index 25c1210e70c..025ef88e5d5 100644 --- a/assets/js/components/dashboard-splash/dashboard-splash-app.js +++ b/assets/js/components/dashboard-splash/dashboard-splash-app.js @@ -114,6 +114,8 @@ class DashboardSplashApp extends Component { __webpack_public_path__ = window.googlesitekit.publicPath; } + const { proxySetupURL } = googlesitekit.admin; + if ( ! this.state.showAuthenticationSetupWizard && ! this.state.showModuleSetupWizard ) { let introDescription, outroDescription, buttonLabel, onButtonClick; @@ -150,7 +152,10 @@ class DashboardSplashApp extends Component { let Setup = null; - if ( this.state.showAuthenticationSetupWizard ) { + // proxySetupURL is only set if the proxy is in use. + if ( proxySetupURL ) { + Setup = lazy( () => import( /* webpackChunkName: "chunk-googlesitekit-setup-wizard-proxy" */'../setup/setup-proxy' ) ); + } else if ( this.state.showAuthenticationSetupWizard ) { Setup = lazy( () => import( /* webpackChunkName: "chunk-googlesitekit-setup-wizard" */'../setup' ) ); } else { Setup = lazy( () => import( /* webpackChunkName: "chunk-googlesitekit-setup-wrapper" */'../setup/setup-wrapper' ) ); diff --git a/assets/js/components/settings/settings-admin.js b/assets/js/components/settings/settings-admin.js index 7100ebb09bb..69e8653b41b 100644 --- a/assets/js/components/settings/settings-admin.js +++ b/assets/js/components/settings/settings-admin.js @@ -26,7 +26,6 @@ import Optin from 'GoogleComponents/optin'; import data, { TYPE_CORE } from 'GoogleComponents/data'; import { clearAppLocalStorage, - moduleIcon, getSiteKitAdminURL, } from 'GoogleUtil'; @@ -88,12 +87,6 @@ class SettingsAdmin extends Component { const { dialogActive, } = this.state; - const { - clientID, - clientSecret, - projectId, - projectUrl, - } = googlesitekit.admin; return ( @@ -118,8 +111,7 @@ class SettingsAdmin extends Component { googlesitekit-heading-4 googlesitekit-settings-module__title "> - { moduleIcon( 'logo-google-cloud', false, '24', '26', 'googlesitekit-settings-module__title-icon' ) } - { __( 'API Credentials', 'google-site-kit' ) } + { __( 'Plugin Status', 'google-site-kit' ) }
-

- { __( 'Site Kit is connected', 'google-site-kit' ) } - - - { __( 'Connected', 'google-site-kit' ) } - - -

-
-

- { __( 'Client ID', 'google-site-kit' ) } -

-
- { clientID } -
-
-
-

- { __( 'Client Secret', 'google-site-kit' ) } -

-
+ { __( 'Site Kit is connected', 'google-site-kit' ) } + - { clientSecret } -
-
+ + { __( 'Connected', 'google-site-kit' ) } + + +

- { ( projectId && projectUrl ) && -
-
-

- { __( 'Project ID', 'google-site-kit' ) } -

-
- { projectId + ' ' } - - - { __( 'Open in Google Cloud Platform', 'google-site-kit' ) } - - -
-
-
- }
diff --git a/assets/js/components/setup-wizard/wizard-step-client-credentials.js b/assets/js/components/setup-wizard/wizard-step-client-credentials.js deleted file mode 100644 index e2e3a88dfbc..00000000000 --- a/assets/js/components/setup-wizard/wizard-step-client-credentials.js +++ /dev/null @@ -1,259 +0,0 @@ -/** - * WizardStepClientCredentials component. - * - * Site Kit by Google, Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * External dependencies - */ -import PropTypes from 'prop-types'; -import data, { TYPE_CORE } from 'GoogleComponents/data'; -import Link from 'GoogleComponents/link'; -import { TextField, Input } from 'SiteKitCore/material-components'; -import Button from 'GoogleComponents/button'; -/** - * Internal dependencies - */ -import { sendAnalyticsTrackingEvent } from 'GoogleUtil'; -import HelpLink from 'GoogleComponents/help-link'; - -/** - * WordPress dependencies. - */ -const { __ } = wp.i18n; -const { Component } = wp.element; -const { addQueryArgs } = wp.url; - -class WizardStepClientCredentials extends Component { - constructor( props ) { - super( props ); - - this.state = { - clientConfiguration: '', - clientID: '', - clientSecret: '', - projectID: '', - isSaving: false, - errorMsg: '', - }; - - this.handleClientConfigurationEntry = this.handleClientConfigurationEntry.bind( this ); - this.onProceed = this.onProceed.bind( this ); - } - - async componentDidMount() { - const { - isSiteKitConnected, - siteConnectedSetup, - } = this.props; - - this._isMounted = true; - - // Double check isSiteKitConnected. - if ( ! isSiteKitConnected ) { - const response = await data.get( TYPE_CORE, 'site', 'credentials' ); - - if ( response && response.oauth2_client_id && response.oauth2_client_secret ) { - googlesitekit.setup.isSiteKitConnected = true; - siteConnectedSetup( true ); - } - } - } - - componentWillUnmount() { - this._isMounted = false; - } - - async onProceed() { - const { - clientID, - clientSecret, - projectID, - } = this.state; - - if ( 0 === clientID.length || 0 === clientSecret.length ) { - return; - } - - const credentials = { - clientID, - clientSecret, - }; - - sendAnalyticsTrackingEvent( 'plugin_setup', 'client_id_secret_entered' ); - - try { - this.setState( { isSaving: true } ); - - await data.set( TYPE_CORE, 'site', 'credentials', credentials ); - - if ( projectID && projectID.length ) { - await data.set( TYPE_CORE, 'site', 'gcpproject', { projectID } ); - } - - if ( this._isMounted ) { - this.setState( { - isSaving: false, - } ); - } - - // Go to next step. - this.props.siteConnectedSetup( true ); - } catch ( err ) { - if ( this._isMounted ) { - this.setState( { - errorMsg: err.message, - } ); - } - - this.props.setErrorMessage( err.message ); - } - } - - handleClientConfigurationEntry( e ) { - const value = e.target.value.trim(); - this.setState( { - clientConfiguration: value, - } ); - - if ( '' === value ) { - this.setState( { - errorMsg: '', - clientID: '', - clientSecret: '', - projectID: '', - } ); - return; - } - - let clientData = false; - try { - clientData = JSON.parse( value ); - } catch ( error ) { - this.setState( { - errorMsg: __( 'Unable to parse client configuration values.', 'google-site-kit' ), - clientID: '', - clientSecret: '', - projectID: '', - } ); - } - - if ( clientData && clientData.web ) { - const { - web: { - client_id: clientID, - client_secret: clientSecret, - project_id: projectID, - }, - } = clientData; - - this.setState( { - errorMsg: '', - clientID, - clientSecret, - projectID, - } ); - } - } - - render() { - let { externalCredentialsURL } = googlesitekit.admin; - - // If an Ad blocker is enabled, pass a quuery var in the external credentials URL. - const { canAdsRun } = googlesitekit; - if ( ! canAdsRun ) { - externalCredentialsURL = addQueryArgs( externalCredentialsURL, - { - warn_adblocker: 'true', /*eslint camelcase: 0*/ - } ); - } - const { - clientConfiguration, - clientID, - clientSecret, - errorMsg, - } = this.state; - - const externalCredentialsURLLabel = 'developers.google.com/web/sitekit'; - - return ( -
-
-
-
-

- { __( 'Welcome to Site Kit beta for developers.', 'google-site-kit' ) } -

-

- { __( 'To complete the setup, it will help if you\'re familiar with Google Cloud Platform and OAuth.', 'google-site-kit' ) } -

-

- { __( 'If that sounds like you, get started by creating a client configuration on ', 'google-site-kit' ) } - - { externalCredentialsURLLabel } - -

-

- { __( 'Once you paste it below, it will be valid for all other plugin users.', 'google-site-kit' ) } -

- { - errorMsg && errorMsg.length && -

- { errorMsg } -

- } - -
- - - -
-
- - -
-
-
-
-
- ); - } -} - -WizardStepClientCredentials.propTypes = { - siteConnectedSetup: PropTypes.func.isRequired, -}; - -export default WizardStepClientCredentials; diff --git a/assets/js/components/setup-wizard/wizard-steps.js b/assets/js/components/setup-wizard/wizard-steps.js index b06a336f062..fb0af32ca42 100644 --- a/assets/js/components/setup-wizard/wizard-steps.js +++ b/assets/js/components/setup-wizard/wizard-steps.js @@ -19,7 +19,6 @@ /** * External dependencies */ -import WizardStepClientCredentials from 'GoogleComponents/setup-wizard/wizard-step-client-credentials'; import WizardStepAuthentication from 'GoogleComponents/setup-wizard/wizard-step-authentication'; import WizardStepVerification from 'GoogleComponents/setup-wizard/wizard-step-verification'; import WizardStepSearchConsoleProperty from 'GoogleComponents/setup-wizard/wizard-step-search-console-property'; @@ -28,13 +27,6 @@ import WizardStepCompleteSetup from 'GoogleComponents/setup-wizard/wizard-step-c const { __ } = wp.i18n; const STEPS = { - clientCredentials: { - title: __( 'Create Client ID', 'google-site-kit' ), - required: true, - isApplicable: ( props ) => props.canSetup && ( ! props.isSiteKitConnected || ! props.hasSearchConsolePropertyFromTheStart ), - isCompleted: ( props ) => props.isSiteKitConnected, - Component: WizardStepClientCredentials, - }, authentication: { title: __( 'Authenticate', 'google-site-kit' ), required: true, diff --git a/assets/js/components/setup/setup-proxy.js b/assets/js/components/setup/setup-proxy.js new file mode 100644 index 00000000000..29b863f0b44 --- /dev/null +++ b/assets/js/components/setup/setup-proxy.js @@ -0,0 +1,121 @@ +/** + * Setup component. + * + * Site Kit by Google, Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import Header from 'GoogleComponents/header'; +import Button from 'GoogleComponents/button'; +import Layout from 'GoogleComponents/layout/layout'; +import { sendAnalyticsTrackingEvent } from 'GoogleUtil'; +import { getSiteKitAdminURL } from 'SiteKitCore/util'; + +const { __ } = wp.i18n; +const { Component, Fragment } = wp.element; +const { delay } = lodash; + +class SetupUsingProxy extends Component { + constructor( props ) { + super( props ); + + const { proxySetupURL } = googlesitekit.admin; + const { isSiteKitConnected } = googlesitekit.setup; + const { canSetup } = googlesitekit.permissions; + + this.state = { + canSetup, + isSiteKitConnected, + completeSetup: false, + proxySetupURL, + }; + } + + isSetupFinished() { + const { + isSiteKitConnected, + completeSetup, + } = this.state; + + return isSiteKitConnected && completeSetup; + } + + render() { + if ( this.isSetupFinished() ) { + const redirectUrl = getSiteKitAdminURL( + 'googlesitekit-dashboard', + { + notification: 'authentication_success', + }, + ); + + delay( () => { + window.location.replace( redirectUrl ); + }, 500, 'later' ); + } + + const { proxySetupURL } = this.state; + + return ( + +
+
+
+
+
+ +
+
+
+
+
+

+ { __( 'The Site Kit plugin is active but requires setup', 'google-site-kit' ) } +

+

+ { __( 'Site Kit Service will guide you through 3 simple setup steps.', 'google-site-kit' ) } +

+ +
+
+
+
+
+
+
+
+
+
+ + ); + } +} + +export default SetupUsingProxy; diff --git a/assets/js/components/user-menu.js b/assets/js/components/user-menu.js index 43e4d398a36..fe11b272c5f 100644 --- a/assets/js/components/user-menu.js +++ b/assets/js/components/user-menu.js @@ -78,6 +78,8 @@ class UserMenu extends Component { } handleMenuItemSelect( index, e ) { + const { proxyPermissionsURL } = googlesitekit.admin; + if ( ( ( 'keydown' === e.type && ( 13 === e.keyCode || // Enter @@ -89,6 +91,9 @@ class UserMenu extends Component { case 0: this.handleDialog(); break; + case 1: + window.location.assign( proxyPermissionsURL ); + break; default: this.handleMenu(); } @@ -136,7 +141,10 @@ class UserMenu extends Component { } render() { - const { userData: { email = '', picture = '' } } = googlesitekit.admin; + const { + userData: { email = '', picture = '' }, + proxyPermissionsURL, + } = googlesitekit.admin; const { dialogActive, menuOpen } = this.state; return ( @@ -160,7 +168,15 @@ class UserMenu extends Component { diff --git a/assets/js/modules/pagespeed-insights/dashboard/dashboard-cta.js b/assets/js/modules/pagespeed-insights/dashboard/dashboard-cta.js index be88e2b65dd..dd5c46c373f 100644 --- a/assets/js/modules/pagespeed-insights/dashboard/dashboard-cta.js +++ b/assets/js/modules/pagespeed-insights/dashboard/dashboard-cta.js @@ -11,7 +11,7 @@ import { /** * Internal dependencies */ -import { getSiteKitAdminURL } from '../../../util'; +import { getReAuthUrl } from '../../../util'; const { __ } = wp.i18n; @@ -30,16 +30,7 @@ const PageSpeedInsightsCTA = () => { const handleSetUpClick = async () => { try { await activateOrDeactivateModule( data, 'pagespeed-insights', true ); - - window.location.assign( - getSiteKitAdminURL( - 'googlesitekit-dashboard', - { - notification: 'authentication_success', - slug: 'pagespeed-insights', - }, - ) - ); + window.location = getReAuthUrl( 'pagespeed-insights' ); } catch ( err ) { showErrorNotification( GenericError, { id: 'pagespeed-insights-setup-error', diff --git a/assets/js/util/index.js b/assets/js/util/index.js index 6e7871af73a..fb3a100a201 100644 --- a/assets/js/util/index.js +++ b/assets/js/util/index.js @@ -364,13 +364,21 @@ export const getReAuthUrl = ( slug, status ) => { const { screenId } = googlesitekit.modules[ slug ]; - let redirect = addQueryArgs( - adminRoot, { + // Special case handling for PageSpeed Insights. + // TODO: Refactor this out. + const pageSpeedQueryArgs = 'pagespeed-insights' === slug ? { + notification: 'authentication_success', + reAuth: undefined, + } : {}; + let redirect = addQueryArgs( + adminRoot, + { // If the module has a submenu page, and is being activated, redirect back to the module page. page: ( slug && status && screenId ) ? screenId : 'googlesitekit-dashboard', - reAuth: status, slug, + reAuth: status, + ...pageSpeedQueryArgs, } ); diff --git a/includes/Core/Admin/Notice.php b/includes/Core/Admin/Notice.php index 3ee3a23321f..cc4fd67c590 100644 --- a/includes/Core/Admin/Notice.php +++ b/includes/Core/Admin/Notice.php @@ -124,7 +124,7 @@ public function render() { } ?> -
+
transients = $transients; $this->credentials = new Credentials( $this->options ); - $this->gcp_project = new GCP_Project( $this->options ); $this->verification = new Verification( $this->user_options ); $this->verification_tag = new Verification_Tag( $this->user_options, $this->transients ); $this->profile = new Profile( $user_options, $this->get_oauth_client() ); @@ -169,8 +160,14 @@ public function register() { 'init', function() { $this->handle_oauth(); - }, - 10 + } + ); + + add_action( + 'admin_init', + function() { + $this->handle_verification_token(); + } ); add_action( @@ -252,17 +249,6 @@ public function credentials() { return $this->credentials; } - /** - * Gets the GCP project instance. - * - * @since 1.0.0 - * - * @return GCP_Project Project ID instance. - */ - public function gcp_project() { - return $this->gcp_project; - } - /** * Gets the verification instance. * @@ -327,6 +313,7 @@ public function disconnect() { $this->user_options->delete( Clients\OAuth_Client::OPTION_REDIRECT_URL ); $this->user_options->delete( Clients\OAuth_Client::OPTION_AUTH_SCOPES ); $this->user_options->delete( Clients\OAuth_Client::OPTION_ERROR_CODE ); + $this->user_options->delete( Clients\OAuth_Client::OPTION_PROXY_ACCESS_CODE ); $this->user_options->delete( Verification::OPTION ); $this->user_options->delete( Verification_Tag::OPTION ); $this->user_options->delete( Profile::OPTION ); @@ -382,11 +369,9 @@ public function is_authenticated() { } /** - * Handle OAuth process. + * Handles receiving a temporary OAuth code. * * @since 1.0.0 - * - * @return void */ private function handle_oauth() { if ( defined( 'WP_CLI' ) && WP_CLI ) { @@ -461,6 +446,35 @@ private function handle_oauth() { } } + /** + * Handles receiving a verification token for a user by the authentication proxy. + * + * @since 1.0.0 + */ + private function handle_verification_token() { + $auth_client = $this->get_oauth_client(); + if ( ! $auth_client->using_proxy() ) { + return; + } + + $verification_token = filter_input( INPUT_GET, 'googlesitekit_verification_token' ); + if ( empty( $verification_token ) ) { + return; + } + + $verification_nonce = filter_input( INPUT_GET, 'googlesitekit_verification_nonce' ); + if ( empty( $verification_nonce ) || ! wp_verify_nonce( $verification_nonce, 'googlesitekit_verification' ) ) { + wp_die( esc_html__( 'Invalid nonce.', 'google-site-kit' ) ); + } + + $this->verification_tag->set( $verification_token ); + + $code = (string) filter_input( INPUT_GET, 'googlesitekit_code' ); + + wp_safe_redirect( add_query_arg( 'verify', 'true', $auth_client->get_proxy_setup_url( $code ) ) ); + exit; + } + /** * Refresh authentication token when user login. * @@ -504,45 +518,16 @@ private function inline_js_admin_data( $data ) { $data['userData']['picture'] = $profile_data['photo']; } - $client_data = $this->credentials->get(); - $gcp_project = $this->gcp_project->get(); - - if ( current_user_can( Permissions::MANAGE_OPTIONS ) && isset( $client_data['oauth2_client_id'] ) ) { - $data['clientID'] = $client_data['oauth2_client_id']; - } else { - $data['clientID'] = ''; - } - if ( current_user_can( Permissions::MANAGE_OPTIONS ) && isset( $client_data['oauth2_client_secret'] ) ) { - $data['clientSecret'] = str_repeat( '•', strlen( $client_data['oauth2_client_secret'] ) ); - } else { - $data['clientSecret'] = ''; - } - - // Make GCP project information available only to the creator. - if ( ! empty( $gcp_project['id'] ) && (int) get_current_user_id() === $gcp_project['wp_owner_id'] ) { - $data['projectId'] = $gcp_project['id']; - $data['projectUrl'] = add_query_arg( 'project', $gcp_project['id'], 'https://console.cloud.google.com/apis/credentials' ); - } else { - $data['projectId'] = false; - $data['projectUrl'] = false; + $auth_client = $this->get_oauth_client(); + if ( $auth_client->using_proxy() ) { + $access_code = (string) $this->user_options->get( Clients\OAuth_Client::OPTION_PROXY_ACCESS_CODE ); + $data['proxySetupURL'] = esc_url_raw( $auth_client->get_proxy_setup_url( $access_code ) ); + $data['proxyPermissionsURL'] = esc_url_raw( $auth_client->get_proxy_permissions_url() ); } $data['connectUrl'] = esc_url_raw( $this->get_connect_url() ); $data['disconnectUrl'] = esc_url_raw( $this->get_disconnect_url() ); - $external_sitename = html_entity_decode( get_bloginfo( 'name' ), ENT_QUOTES ); - $external_sitename = str_replace( '&', 'and', $external_sitename ); - $external_sitename = trim( preg_replace( '/([^A-Za-z0-9 ]+|google)/i', '', $external_sitename ) ); - if ( strlen( $external_sitename ) < 4 ) { - $external_sitename .= ' Site Kit'; - } - $external_page_params = array( - 'sitename' => substr( $external_sitename, 0, 30 ), // limit to 30 chars. - 'siteurl' => untrailingslashit( home_url() ), - ); - - $data['externalCredentialsURL'] = esc_url_raw( add_query_arg( $external_page_params, 'https://developers.google.com/web/site-kit' ) ); - return $data; } @@ -771,7 +756,18 @@ private function get_authentication_oauth_error_notice() { return ''; } - return '

' . esc_html( $message ) . '

'; + $message = wp_kses( + $message, + array( + 'a' => array( + 'href' => array(), + ), + 'strong' => array(), + 'em' => array(), + ) + ); + + return '

' . $message . '

'; }, 'type' => Notice::TYPE_ERROR, 'active_callback' => function() { diff --git a/includes/Core/Authentication/Clients/Google_Proxy_Client.php b/includes/Core/Authentication/Clients/Google_Proxy_Client.php new file mode 100644 index 00000000000..5b4c57e2487 --- /dev/null +++ b/includes/Core/Authentication/Clients/Google_Proxy_Client.php @@ -0,0 +1,198 @@ +getOAuth2Service(); + $auth->setCode( $code ); + $auth->setRedirectUri( $this->getRedirectUri() ); + + $http_handler = HttpHandlerFactory::build( $this->getHttpClient() ); + + $creds = $this->fetchAuthToken( $auth, $http_handler ); + if ( $creds && isset( $creds['access_token'] ) ) { + $creds['created'] = time(); + $this->setAccessToken( $creds ); + } + + return $creds; + } + + /** + * Fetches a fresh OAuth 2.0 access token by using a refresh token. + * + * @since 1.0.0 + * + * @param string $refresh_token Optional. Refresh token. Unused here. + * @return array Access token. + * + * @throws LogicException Thrown when no refresh token is available. + */ + public function fetchAccessTokenWithRefreshToken( $refresh_token = null ) { + if ( null === $refresh_token ) { + if ( ! isset( $this->token['refresh_token'] ) ) { + throw new LogicException( 'refresh token must be passed in or set as part of setAccessToken' ); + } + $refresh_token = $this->token['refresh_token']; + } + + $this->getLogger()->info( 'OAuth2 access token refresh' ); + $auth = $this->getOAuth2Service(); + $auth->setRefreshToken( $refresh_token ); + + $http_handler = HttpHandlerFactory::build( $this->getHttpClient() ); + + $creds = $this->fetchAuthToken( $auth, $http_handler ); + if ( $creds && isset( $creds['access_token'] ) ) { + $creds['created'] = time(); + if ( ! isset( $creds['refresh_token'] ) ) { + $creds['refresh_token'] = $refresh_token; + } + $this->setAccessToken( $creds ); + } + + return $creds; + } + + /** + * Revokes an OAuth2 access token using the authentication proxy. + * + * @since 1.0.0 + * + * @param string|array|null $token Optional. Access token. Default is the current one. + * @return bool True on success, false on failure. + */ + public function revokeToken( $token = null ) { + if ( ! $token ) { + $token = $this->getAccessToken(); + } + if ( is_array( $token ) ) { + $token = $token['access_token']; + } + + $body = Psr7\stream_for( + http_build_query( + array( + 'client_id' => $this->getClientId(), + 'token' => $token, + ) + ) + ); + $request = new Request( + 'POST', + self::OAUTH2_REVOKE_URI, + array( + 'Cache-Control' => 'no-store', + 'Content-Type' => 'application/x-www-form-urlencoded', + ), + $body + ); + + $http_handler = HttpHandlerFactory::build( $this->getHttpClient() ); + + $response = $http_handler( $request ); + + return 200 === (int) $response->getStatusCode(); + } + + /** + * Creates a Google auth object for the authentication proxy. + * + * @since 1.0.0 + */ + protected function createOAuth2Service() { + $auth = new OAuth2( + array( + 'clientId' => $this->getClientId(), + 'clientSecret' => $this->getClientSecret(), + 'authorizationUri' => self::OAUTH2_AUTH_URL, + 'tokenCredentialUri' => self::OAUTH2_TOKEN_URI, + 'redirectUri' => $this->getRedirectUri(), + 'issuer' => $this->getClientId(), + 'signingKey' => null, + 'signingAlgorithm' => null, + ) + ); + + return $auth; + } + + /** + * Fetches an OAuth 2.0 access token using a given auth object and HTTP handler. + * + * This method is used in place of {@see OAuth2::fetchAuthToken()}. + * + * @since 1.0.0 + * + * @param OAuth2 $auth OAuth2 instance. + * @param callable|null $http_handler Optional. HTTP handler callback. Default null. + * @return array Access token. + * + * @throws Google_Proxy_Exception Thrown when proxy returns an error accompanied by a temporary access code. + * @throws Exception Thrown when any other type of error occurs. + */ + private function fetchAuthToken( OAuth2 $auth, callable $http_handler = null ) { + if ( is_null( $http_handler ) ) { + $http_handler = HttpHandlerFactory::build( HttpClientCache::getHttpClient() ); + } + + $request = $auth->generateCredentialsRequest(); + $response = $http_handler( $request ); + $credentials = $auth->parseTokenResponse( $response ); + if ( ! empty( $credentials['error'] ) ) { + if ( ! empty( $credentials['code'] ) ) { + throw new Google_Proxy_Exception( $credentials['error'], 0, $credentials['code'] ); + } + throw new Exception( $credentials['error'] ); + } + + $auth->updateToken( $credentials ); + + return $credentials; + } +} diff --git a/includes/Core/Authentication/Clients/Google_Proxy_Exception.php b/includes/Core/Authentication/Clients/Google_Proxy_Exception.php new file mode 100644 index 00000000000..2b7d7bdd64d --- /dev/null +++ b/includes/Core/Authentication/Clients/Google_Proxy_Exception.php @@ -0,0 +1,49 @@ +access_code = $access_code; + } + + /** + * Gets the temporary access code for an undelegated proxy token. + * + * @since 1.0.0 + * + * @return string Temporary code. + */ + public function getAccessCode() { + return $this->access_code; + } +} diff --git a/includes/Core/Authentication/Clients/OAuth_Client.php b/includes/Core/Authentication/Clients/OAuth_Client.php index c3cd5b30b79..5f138826208 100644 --- a/includes/Core/Authentication/Clients/OAuth_Client.php +++ b/includes/Core/Authentication/Clients/OAuth_Client.php @@ -16,6 +16,8 @@ use Google\Site_Kit\Core\Storage\Encrypted_Options; use Google\Site_Kit\Core\Storage\Encrypted_User_Options; use Google\Site_Kit\Core\Authentication\Credentials; +use Google\Site_Kit\Core\Authentication\Verification; +use Google\Site_Kit\Modules\Search_Console; use Google_Client; use Exception; @@ -35,6 +37,9 @@ final class OAuth_Client { const OPTION_REDIRECT_URL = 'googlesitekit_redirect_url'; const OPTION_AUTH_SCOPES = 'googlesitekit_auth_scopes'; const OPTION_ERROR_CODE = 'googlesitekit_error_code'; + const OPTION_PROXY_NONCE = 'googlesitekit_proxy_nonce'; + const OPTION_PROXY_ACCESS_CODE = 'googlesitekit_proxy_access_code'; + const PROXY_URL = 'https://sitekit.withgoogle.com'; /** * Plugin context. @@ -165,7 +170,11 @@ public function get_client() { return $this->google_client; } - $this->google_client = new Google_Client(); + if ( $this->using_proxy() ) { + $this->google_client = new Google_Proxy_Client(); + } else { + $this->google_client = new Google_Client(); + } // Return unconfigured client if credentials not yet set. $client_credentials = $this->get_client_credentials(); @@ -181,8 +190,6 @@ public function get_client() { // Offline access so we can access the refresh token even when the user is logged out. $this->google_client->setAccessType( 'offline' ); - $this->google_client->setApprovalPrompt( 'force' ); - $this->google_client->setPrompt( 'consent' ); $this->google_client->setRedirectUri( $this->get_redirect_uri() ); @@ -197,11 +204,13 @@ public function get_client() { } $token = array( - 'access_token' => $access_token, - 'refresh_token' => $this->get_refresh_token(), - 'expires_in' => $this->user_options->get( self::OPTION_ACCESS_TOKEN_EXPIRES_IN ), - 'created' => $this->user_options->get( self::OPTION_ACCESS_TOKEN_CREATED ), + 'access_token' => $access_token, + 'expires_in' => $this->user_options->get( self::OPTION_ACCESS_TOKEN_EXPIRES_IN ), + 'created' => $this->user_options->get( self::OPTION_ACCESS_TOKEN_CREATED ), ); + if ( ! $this->using_proxy() ) { + $token['refresh_token'] = $this->get_refresh_token(); + } $this->google_client->setAccessToken( $token ); @@ -219,9 +228,7 @@ public function get_client() { * @since 1.0.0 */ public function refresh_token() { - // Check for a valid stored refresh token. If it's been set, grab the authentication token. $refresh_token = $this->get_refresh_token(); - if ( empty( $refresh_token ) ) { $this->user_options->set( self::OPTION_ERROR_CODE, 'refresh_token_not_exist' ); } @@ -233,26 +240,34 @@ public function refresh_token() { try { $authentication_token = $this->google_client->fetchAccessTokenWithRefreshToken( $refresh_token ); + } catch ( Google_Proxy_Exception $e ) { + $this->user_options->set( self::OPTION_ERROR_CODE, $e->getMessage() ); + $this->user_options->set( self::OPTION_PROXY_ACCESS_CODE, $e->getAccessCode() ); + return; + } catch ( \Exception $e ) { + $this->user_options->set( self::OPTION_ERROR_CODE, 'invalid_grant' ); + return; + } - // Refresh token is expired or revoked. - if ( ! empty( $authentication_token['error'] ) ) { - $this->user_options->set( self::OPTION_ERROR_CODE, $authentication_token['error'] ); - return; - } - - if ( ! isset( $authentication_token['access_token'] ) ) { - $this->user_options->set( self::OPTION_ERROR_CODE, 'access_token_not_received' ); - return; - } + // Refresh token is expired or revoked. + if ( ! empty( $authentication_token['error'] ) ) { + $this->user_options->set( self::OPTION_ERROR_CODE, $authentication_token['error'] ); + return; + } - $this->set_access_token( - $authentication_token['access_token'], - isset( $authentication_token['expires_in'] ) ? $authentication_token['expires_in'] : '', - isset( $authentication_token['created'] ) ? $authentication_token['created'] : 0 - ); - } catch ( \Exception $e ) { - $this->user_options->set( self::OPTION_ERROR_CODE, $e->getCode() ); + if ( ! isset( $authentication_token['access_token'] ) ) { + $this->user_options->set( self::OPTION_ERROR_CODE, 'access_token_not_received' ); + return; } + + $this->set_access_token( + $authentication_token['access_token'], + isset( $authentication_token['expires_in'] ) ? $authentication_token['expires_in'] : '', + isset( $authentication_token['created'] ) ? $authentication_token['created'] : 0 + ); + + $refresh_token = $this->get_client()->getRefreshToken(); + $this->set_refresh_token( $refresh_token ); } /** @@ -471,6 +486,9 @@ public function authorize_user() { try { $authentication_token = $this->get_client()->fetchAccessTokenWithAuthCode( $_GET['code'] ); // phpcs:ignore WordPress.Security.NonceVerification.NoNonceVerification + } catch ( Google_Proxy_Exception $e ) { + wp_safe_redirect( $this->get_proxy_setup_url( $e->getAccessCode(), $e->getMessage() ) ); + exit(); } catch ( Exception $e ) { $this->user_options->set( self::OPTION_ERROR_CODE, 'invalid_code' ); wp_safe_redirect( admin_url() ); @@ -521,6 +539,15 @@ function( $scope ) { ); $this->set_granted_scopes( $scopes ); + // If using the proxy, these values can reliably be set at this point because the proxy already took care of + // them. + // TODO: In the future, once the old authentication mechanism no longer exists, this should be resolved in + // another way. + if ( $this->using_proxy() ) { + $this->user_options->set( Verification::OPTION, 'verified' ); + $this->options->set( Search_Console::PROPERTY_OPTION, trailingslashit( $this->context->get_reference_site_url() ) ); + } + $redirect_url = $this->user_options->get( self::OPTION_REDIRECT_URL ); if ( $redirect_url ) { @@ -539,6 +566,134 @@ function( $scope ) { exit(); } + /** + * Determines whether the authentication proxy is used. + * + * In order to streamline the setup and authentication flow, the plugin uses a proxy mechanism based on an external + * service. This can be overridden by providing actual GCP credentials with the {@see 'googlesitekit_oauth_secret'} + * filter. + * + * @since 1.0.0 + * + * @return bool True if proxy authentication is used, false otherwise. + */ + public function using_proxy() { + $credentials = $this->get_client_credentials(); + + // If no credentials yet, assume true. + if ( ! is_object( $credentials ) || empty( $credentials->web->client_id ) ) { + return true; + } + + // If proxy credentials, return true. + if ( false !== strpos( $credentials->web->client_id, '.apps.sitekit.withgoogle.com' ) ) { + return true; + } + + return false; + } + + /** + * Returns the setup URL to the authentication proxy. + * + * @since 1.0.0 + * + * @param string $access_code Optional. Temporary access code for an undelegated access token. Default empty string. + * @param string $error_code Optional. Error code, if the user should be redirected because of an error. Default empty string. + * @return string URL to the setup page on the authentication proxy. + */ + public function get_proxy_setup_url( $access_code = '', $error_code = '' ) { + $url = self::PROXY_URL . '/site-management/setup/'; + + $credentials = $this->get_client_credentials(); + + $scope = implode( ' ', $this->get_required_scopes() ); + + if ( ! is_object( $credentials ) || empty( $credentials->web->client_id ) ) { + $home_url = home_url(); + $home_url_no_scheme = str_replace( array( 'http://', 'https://' ), '', $home_url ); + + $rest_root = str_replace( array( 'http://', 'https://', $home_url_no_scheme ), '', rest_url() ); + $admin_root = str_replace( array( 'http://', 'https://', $home_url_no_scheme ), '', admin_url() ); + + $nonce = $this->options->get( self::OPTION_PROXY_NONCE ); + if ( empty( $nonce ) ) { + $nonce = wp_create_nonce( 'googlesitekit_proxy' ); + $this->options->set( self::OPTION_PROXY_NONCE, $nonce ); + } + + return add_query_arg( + array( + 'nonce' => $nonce, + 'name' => rawurlencode( wp_specialchars_decode( get_bloginfo( 'name' ) ) ), + 'url' => rawurlencode( $home_url ), + 'rest_root' => rawurlencode( $rest_root ), + 'admin_root' => rawurlencode( $admin_root ), + 'scope' => rawurlencode( $scope ), + ), + $url + ); + } + + $query_args = array( + 'site_id' => $credentials->web->client_id, + 'code' => $access_code, + 'scope' => rawurlencode( $scope ), + ); + if ( 'missing_verification' === $error_code ) { + $query_args['verification_nonce'] = wp_create_nonce( 'googlesitekit_verification' ); + } + + return add_query_arg( $query_args, $url ); + } + + /** + * Returns the permissions URL to the authentication proxy. + * + * This only returns a URL if the user already has an access token set. + * + * @since 1.0.0 + * + * @return string URL to the permissions page on the authentication proxy on success, + * or empty string on failure. + */ + public function get_proxy_permissions_url() { + $access_token = $this->get_access_token(); + if ( empty( $access_token ) ) { + return ''; + } + + $query_args = array( 'token' => $access_token ); + + $credentials = $this->get_client_credentials(); + if ( is_object( $credentials ) && ! empty( $credentials->web->client_id ) ) { + $query_args['site_id'] = $credentials->web->client_id; + } + + return add_query_arg( + $query_args, + self::PROXY_URL . '/site-management/permissions/' + ); + } + + /** + * Checks whether the given proxy nonce is valid. + * + * @since 1.0.0 + * + * @param string $nonce Nonce to validate. + * @return bool True if nonce is valid, false otherwise. + */ + public function validate_proxy_nonce( $nonce ) { + $valid_nonce = $this->options->get( self::OPTION_PROXY_NONCE ); + if ( $nonce !== $valid_nonce ) { + return false; + } + + $this->options->delete( self::OPTION_PROXY_NONCE ); + return true; + } + /** * Converts the given error code to a user-facing message. * @@ -581,6 +736,19 @@ public function get_error_message( $error_code ) { $message = __( 'Unable to receive access token because of an unsupported grant type.', 'google-site-kit' ); break; default: + if ( $this->using_proxy() ) { + $access_code = $this->user_options->get( self::OPTION_PROXY_ACCESS_CODE ); + if ( ! empty( $access_code ) ) { + $message = sprintf( + /* translators: 1: error code from API, 2: URL to re-authenticate */ + __( 'Setup Error (code: %1$s). Re-authenticate with Google', 'google-site-kit' ), + $error_code, + esc_url( $this->get_proxy_setup_url( $access_code, $error_code ) ) + ); + $this->user_options->delete( self::OPTION_PROXY_ACCESS_CODE ); + return $message; + } + } /* translators: %s: error code from API */ $message = sprintf( __( 'Unknown Error (code: %s).', 'google-site-kit' ), $error_code ); break; @@ -601,40 +769,33 @@ private function get_redirect_uri() { } /** - * Retrieve the Site Kit oAuth secret. + * Retrieves the OAuth credentials object. + * + * @since 1.0.0 + * + * @return object|null Credentials object with `web` property, or null if no credentials available. */ private function get_client_credentials() { if ( false !== $this->client_credentials ) { return $this->client_credentials; } - /** - * Site Kit oAuth Secret is a string of the JSON for the Google Cloud Platform web application used for Site Kit - * that will be associated with this account. This is meant to be a temporary way to specify the client secret - * until the authentication proxy has been completed. This filter can be specified from a separate theme or plugin. - * - * To retrieve the JSON secret, use the following instructions: - * - Go to the Google Cloud Platform and create a new project or use an existing one - * - In the APIs & Services section, enable the APIs that are used within Site Kit - * - Under 'credentials' either create new oAuth Client ID credentials or use an existing set of credentials - * - Set the authorizes redirect URIs to be the URL to the oAuth callback for Site Kit, eg. https://?oauth2callback=1 (this must be public) - * - Click the 'Download JSON' button to download the JSON file that can be copied and pasted into the filter - */ - $credentials = trim( apply_filters( 'googlesitekit_oauth_secret', '' ) ); - - if ( empty( $credentials ) && $this->credentials->has() ) { - $redirect_uri = $this->get_redirect_uri(); - $credentials = $this->credentials->get(); - $credentials = '{"web":{"client_id":"' . $credentials['oauth2_client_id'] . '","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"' . $credentials['oauth2_client_secret'] . '","redirect_uris":["' . $redirect_uri . '"]}}'; + if ( ! $this->credentials->has() ) { + return null; } - if ( ! empty( $credentials ) ) { - $this->client_credentials = json_decode( $credentials ); - } + $credentials = $this->credentials->get(); - if ( ! is_object( $this->client_credentials ) || empty( $this->client_credentials->web ) ) { - $this->client_credentials = null; - } + $this->client_credentials = (object) array( + 'web' => (object) array( + 'client_id' => $credentials['oauth2_client_id'], + 'client_secret' => $credentials['oauth2_client_secret'], + 'auth_uri' => 'https://accounts.google.com/o/oauth2/auth', + 'token_uri' => 'https://oauth2.googleapis.com/token', + 'auth_provider_x509_cert_url' => 'https://www.googleapis.com/oauth2/v1/certs', + 'redirect_uris' => array( $this->get_redirect_uri() ), + ), + ); return $this->client_credentials; } diff --git a/includes/Core/Authentication/Credentials.php b/includes/Core/Authentication/Credentials.php index b90b7b719c0..250db312127 100644 --- a/includes/Core/Authentication/Credentials.php +++ b/includes/Core/Authentication/Credentials.php @@ -54,6 +54,33 @@ public function __construct( Options $options ) { * @return array|bool Value set for the credentials, or false if not set. */ public function get() { + /** + * Site Kit oAuth Secret is a JSON string of the Google Cloud Platform web application used for Site Kit + * that will be associated with this account. This is meant to be a temporary way to specify the client secret + * until the authentication proxy has been completed. This filter can be specified from a separate theme or plugin. + * + * To retrieve the JSON secret, use the following instructions: + * - Go to the Google Cloud Platform and create a new project or use an existing one + * - In the APIs & Services section, enable the APIs that are used within Site Kit + * - Under 'credentials' either create new oAuth Client ID credentials or use an existing set of credentials + * - Set the authorizes redirect URIs to be the URL to the oAuth callback for Site Kit, eg. https://?oauth2callback=1 (this must be public) + * - Click the 'Download JSON' button to download the JSON file that can be copied and pasted into the filter + */ + $credentials = apply_filters( 'googlesitekit_oauth_secret', '' ); + + if ( is_string( $credentials ) && trim( $credentials ) ) { + $credentials = json_decode( $credentials, true ); + } + + if ( isset( $credentials['web']['client_id'], $credentials['web']['client_secret'] ) ) { + return $this->parse_defaults( + array( + 'oauth2_client_id' => $credentials['web']['client_id'], + 'oauth2_client_secret' => $credentials['web']['client_secret'], + ) + ); + } + return $this->parse_defaults( $this->encrypted_options->get( self::OPTION ) ); diff --git a/includes/Core/Authentication/GCP_Project.php b/includes/Core/Authentication/GCP_Project.php deleted file mode 100644 index de243936978..00000000000 --- a/includes/Core/Authentication/GCP_Project.php +++ /dev/null @@ -1,110 +0,0 @@ -options = $options; - } - - /** - * Checks whether a GCP project is set. - * - * @since 1.0.0 - * - * @return bool True if a project is set. - */ - public function has() { - $data = $this->get(); - return ! empty( $data['id'] ); - } - - /** - * Retrieves the GCP project. - * - * @since 1.0.0 - * - * @return array Project data. - */ - public function get() { - $data = $this->options->get( self::OPTION ); - - return $this->parse_defaults( $data ); - } - - /** - * Saves the GCP project. - * - * @since 1.0.0 - * - * @param array $data { - * Project data. - * - * @type string $id The project ID. - * @type int $wp_owner_id The WordPress user ID of the owner. - * } - * @return bool True on success, false on failure. - */ - public function set( $data ) { - $data = $this->parse_defaults( $data ); - - return $this->options->set( self::OPTION, $data ); - } - - /** - * Parses GCP project data with its defaults. - * - * @since 1.0.0 - * - * @param mixed $data Project data. - * @return array Parsed $data. - */ - private function parse_defaults( $data ) { - $defaults = array( - 'id' => '', - 'wp_owner_id' => 0, - ); - - if ( ! is_array( $data ) ) { - return $defaults; - } - - $data = wp_parse_args( $data, $defaults ); - $data['wp_owner_id'] = (int) $data['wp_owner_id']; - - return $data; - } -} diff --git a/includes/Core/Permissions/Permissions.php b/includes/Core/Permissions/Permissions.php index d00e82f8807..6ab559c6c23 100644 --- a/includes/Core/Permissions/Permissions.php +++ b/includes/Core/Permissions/Permissions.php @@ -104,8 +104,8 @@ public function __construct( Context $context, Authentication $authentication = $this->authentication = $authentication; $this->primitive_to_core = array( - // Allow contributors and up to authenticate. - self::AUTHENTICATE => 'edit_posts', + // By default, only allow administrators to authenticate. + self::AUTHENTICATE => 'manage_options', // Allow contributors and up to view their own post's insights. self::VIEW_POSTS_INSIGHTS => 'edit_posts', diff --git a/includes/Core/REST_API/REST_Routes.php b/includes/Core/REST_API/REST_Routes.php index c9d9e232ef1..84b99a0be1a 100644 --- a/includes/Core/REST_API/REST_Routes.php +++ b/includes/Core/REST_API/REST_Routes.php @@ -16,6 +16,7 @@ use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\Storage\User_Options; use Google\Site_Kit\Core\Authentication\Authentication; +use Google\Site_Kit\Core\Authentication\Clients\OAuth_Client; use Google\Site_Kit\Core\Util\Reset; use WP_Post; use WP_REST_Server; @@ -195,87 +196,6 @@ private function get_routes() { ) ), // This route is forward-compatible with a potential 'core/(?P[a-z\-]+)/data/(?P[a-z\-]+)'. - new REST_Route( - 'core/site/data/gcpproject', - array( - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => function() { - return new WP_REST_Response( $this->authentication->gcp_project()->get() ); - }, - 'permission_callback' => $can_setup, - ), - array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => function( WP_REST_Request $request ) { - $data = isset( $request['data'] ) ? $request['data'] : array(); - if ( ! isset( $data['projectID'] ) ) { - /* translators: %s: Missing parameter name */ - return new WP_Error( 'missing_required_param', sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'projectID' ), array( 'status' => 400 ) ); - } - $data = array( - 'id' => sanitize_text_field( $data['projectID'] ), - 'wp_owner_id' => get_current_user_id(), - ); - return new WP_REST_Response( $this->authentication->gcp_project()->set( $data ) ); - }, - 'permission_callback' => $can_setup, - 'args' => array( - 'data' => array( - 'type' => 'object', - 'description' => __( 'Data to set.', 'google-site-kit' ), - 'validate_callback' => function( $value ) { - return is_array( $value ); - }, - ), - ), - ), - ) - ), - // This route is forward-compatible with a potential 'core/(?P[a-z\-]+)/data/(?P[a-z\-]+)'. - new REST_Route( - 'core/site/data/credentials', - array( - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => function() { - return new WP_REST_Response( $this->authentication->credentials()->get() ); - }, - 'permission_callback' => $can_setup, - ), - array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => function( WP_REST_Request $request ) { - $data = isset( $request['data'] ) ? $request['data'] : array(); - if ( ! isset( $data['clientID'] ) ) { - /* translators: %s: Missing parameter name */ - return new WP_Error( 'missing_required_param', sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'clientID' ), array( 'status' => 400 ) ); - } - if ( ! isset( $data['clientSecret'] ) ) { - /* translators: %s: Missing parameter name */ - return new WP_Error( 'missing_required_param', sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'clientSecret' ), array( 'status' => 400 ) ); - } - $data = array( - 'oauth2_client_id' => sanitize_text_field( $data['clientID'] ), - 'oauth2_client_secret' => sanitize_text_field( $data['clientSecret'] ), - ); - $credentials = $this->authentication->credentials(); - return new WP_REST_Response( $credentials->set( $data ) ); - }, - 'permission_callback' => $can_setup, - 'args' => array( - 'data' => array( - 'type' => 'object', - 'description' => __( 'Data to set.', 'google-site-kit' ), - 'validate_callback' => function( $value ) { - return is_array( $value ); - }, - ), - ), - ), - ) - ), - // This route is forward-compatible with a potential 'core/(?P[a-z\-]+)/data/(?P[a-z\-]+)'. new REST_Route( 'core/user/data/authentication', array( @@ -544,6 +464,49 @@ function( $dataset ) use ( $module ) { ), ) ), + new REST_Route( + 'oauth/site', + array( + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => function( WP_REST_Request $request ) { + $auth_client = $this->authentication->get_oauth_client(); + if ( ! $auth_client->using_proxy() ) { + return new WP_Error( 'invalid_authentication_mode', __( 'Invalid authentication mode.', 'google-site-kit' ), array( 'status' => 500 ) ); + } + if ( ! $auth_client->validate_proxy_nonce( $request['nonce'] ) ) { + return new WP_Error( 'invalid_nonce', __( 'Invalid nonce.', 'google-site-kit' ), array( 'status' => 400 ) ); + } + $data = array( + 'oauth2_client_id' => sanitize_text_field( $request['site_id'] ), + 'oauth2_client_secret' => sanitize_text_field( $request['site_secret'] ), + ); + $credentials = $this->authentication->credentials(); + if ( ! $credentials->set( $data ) ) { + return new WP_Error( 'set_credentials_failed', __( 'Failed to set credentials.', 'google-site-kit' ), array( 'status' => 500 ) ); + } + return new WP_REST_Response( array( 'status' => true ), 200 ); + }, + 'args' => array( + 'nonce' => array( + 'type' => 'string', + 'description' => __( 'WordPress nonce for the authentication proxy setup.', 'google-site-kit' ), + 'required' => true, + ), + 'site_id' => array( + 'type' => 'string', + 'description' => __( 'Site ID for the authentication proxy.', 'google-site-kit' ), + 'required' => true, + ), + 'site_secret' => array( + 'type' => 'string', + 'description' => __( 'Site secret for the authentication proxy.', 'google-site-kit' ), + 'required' => true, + ), + ), + ), + ) + ), // TODO: Remove this and replace usage with calls to wp/v1/posts. new REST_Route( 'core/search/data/post-search', diff --git a/includes/Core/Util/Beta_Migration.php b/includes/Core/Util/Beta_Migration.php new file mode 100644 index 00000000000..b93f05af49e --- /dev/null +++ b/includes/Core/Util/Beta_Migration.php @@ -0,0 +1,163 @@ +options = new Options( $context ); + $this->oauth_client = new OAuth_Client( $context ); + } + + /** + * Registers hooks. + */ + public function register() { + add_filter( + 'googlesitekit_admin_notices', + function ( $notices ) { + $notices[] = new Notice( + 'beta-migration', + array( + 'content' => function () { + return $this->get_notice_content(); + }, + 'active_callback' => function () { + return $this->options->get( self::OPTION_IS_PRE_PROXY_INSTALL ) && current_user_can( Permissions::SETUP ); + }, + ) + ); + + return $notices; + } + ); + + add_action( + 'wp_ajax_' . self::ACTION_DISMISS, + function () { + check_ajax_referer( self::ACTION_DISMISS ); + + $this->options->delete( self::OPTION_IS_PRE_PROXY_INSTALL ); + + wp_send_json_success(); + } + ); + + add_action( 'admin_init', array( $this, 'migrate_old_credentials' ) ); + } + + /** + * Migrates old GCP credentials if saved in the option. + * + * GCP credentials are still possible to use (for now), but only via filter + * so they should never be present in the option / Credentials anymore. + */ + public function migrate_old_credentials() { + $credentials = ( new Encrypted_Options( $this->options ) )->get( Credentials::OPTION ); + + // Credentials can be filtered in so we must also check if there is a saved option present. + if ( isset( $credentials['oauth2_client_id'] ) && ! strpos( $credentials['oauth2_client_id'], '.apps.sitekit.withgoogle.com' ) ) { + $this->options->delete( Credentials::OPTION ); + $this->options->set( self::OPTION_IS_PRE_PROXY_INSTALL, 1 ); + } + } + + /** + * Gets the content to render in the reconnect notice. + * + * Mirrors behavior of core's dismissible notices, while dismissing DB flag asynchronously. + * + * @link https://github.com/WordPress/WordPress/blob/956725990f075cb6b8b5a0b8a480c4c823a3fd99/wp-admin/js/common.js#L765-L770 + * + * @return string + */ + private function get_notice_content() { + ob_start(); + ?> +

+ +

+

+ + + + + + + +

+ + + options->delete( Activation::OPTION_SHOW_ACTIVATION_NOTICE ); $this->options->delete( Activation::OPTION_NEW_SITE_POSTS ); $this->options->delete( Credentials::OPTION ); - $this->options->delete( GCP_Project::OPTION ); $this->options->delete( 'googlesitekit-active-modules' ); $this->options->delete( Search_Console::PROPERTY_OPTION ); $this->options->delete( AdSense::OPTION ); @@ -118,6 +116,8 @@ private function delete_all_plugin_options() { $this->options->delete( Optimize::OPTION ); $this->options->delete( TagManager::OPTION ); $this->options->delete( First_Admin::OPTION ); + $this->options->delete( OAuth_Client::OPTION_PROXY_NONCE ); + $this->options->delete( Beta_Migration::OPTION_IS_PRE_PROXY_INSTALL ); // Clean up old site verification data, moved to user options. // Also clean up other old unused options. @@ -128,6 +128,7 @@ private function delete_all_plugin_options() { $this->options->delete( 'googlesitekit_available_modules' ); $this->options->delete( 'googlesitekit_secret_token' ); $this->options->delete( 'googlesitekit_project_id' ); + $this->options->delete( 'googlesitekit_gcp_project' ); } /** @@ -160,6 +161,7 @@ private function delete_all_user_metas() { $user_options->delete( OAuth_Client::OPTION_REDIRECT_URL ); $user_options->delete( OAuth_Client::OPTION_AUTH_SCOPES ); $user_options->delete( OAuth_Client::OPTION_ERROR_CODE ); + $user_options->delete( OAuth_Client::OPTION_PROXY_ACCESS_CODE ); $user_options->delete( Verification::OPTION ); $user_options->delete( Verification_Tag::OPTION ); $user_options->delete( Profile::OPTION ); diff --git a/includes/Plugin.php b/includes/Plugin.php index 560c807409e..77723493103 100644 --- a/includes/Plugin.php +++ b/includes/Plugin.php @@ -151,6 +151,7 @@ function( $username, $user ) use ( $user_options ) { $reset = new Core\Util\Reset( $this->context, $options ); ( new Core\Util\Activation( $this->context, $options, $assets ) )->register(); + ( new Core\Util\Beta_Migration( $this->context ) )->register(); ( new Core\Util\Uninstallation( $reset ) )->register(); ( new Core\Util\Updater() )->register(); ( new Core\Util\Deactivation() )->register(); diff --git a/stories/setup.stories.js b/stories/setup.stories.js index 0ab4aca5335..300d496409f 100644 --- a/stories/setup.stories.js +++ b/stories/setup.stories.js @@ -5,7 +5,7 @@ import { storiesOf } from '@storybook/react'; import Setup from 'GoogleComponents/setup'; storiesOf( 'Setup', module ) - .add( 'Client ID', () => { + .add( 'Step one', () => { googlesitekit.setup.isSiteKitConnected = false; googlesitekit.setup.isAuthenticated = false; googlesitekit.setup.isVerified = false; diff --git a/tests/backstop/reference/google-site-kit_Settings_Admin_Settings_0_document_0_small.png b/tests/backstop/reference/google-site-kit_Settings_Admin_Settings_0_document_0_small.png index 44788909f87..b7fc8ac668e 100644 Binary files a/tests/backstop/reference/google-site-kit_Settings_Admin_Settings_0_document_0_small.png and b/tests/backstop/reference/google-site-kit_Settings_Admin_Settings_0_document_0_small.png differ diff --git a/tests/backstop/reference/google-site-kit_Settings_Admin_Settings_0_document_1_medium.png b/tests/backstop/reference/google-site-kit_Settings_Admin_Settings_0_document_1_medium.png index b957cb1b7cd..c3bbd02b819 100644 Binary files a/tests/backstop/reference/google-site-kit_Settings_Admin_Settings_0_document_1_medium.png and b/tests/backstop/reference/google-site-kit_Settings_Admin_Settings_0_document_1_medium.png differ diff --git a/tests/backstop/reference/google-site-kit_Settings_Admin_Settings_0_document_2_large.png b/tests/backstop/reference/google-site-kit_Settings_Admin_Settings_0_document_2_large.png index ec27b1c0011..2eb2e8cca50 100644 Binary files a/tests/backstop/reference/google-site-kit_Settings_Admin_Settings_0_document_2_large.png and b/tests/backstop/reference/google-site-kit_Settings_Admin_Settings_0_document_2_large.png differ diff --git a/tests/backstop/reference/google-site-kit_Setup_Client_ID_0_document_0_small.png b/tests/backstop/reference/google-site-kit_Setup_Client_ID_0_document_0_small.png deleted file mode 100644 index 5be55c5097f..00000000000 Binary files a/tests/backstop/reference/google-site-kit_Setup_Client_ID_0_document_0_small.png and /dev/null differ diff --git a/tests/backstop/reference/google-site-kit_Setup_Client_ID_0_document_1_medium.png b/tests/backstop/reference/google-site-kit_Setup_Client_ID_0_document_1_medium.png deleted file mode 100644 index 13cbad7861f..00000000000 Binary files a/tests/backstop/reference/google-site-kit_Setup_Client_ID_0_document_1_medium.png and /dev/null differ diff --git a/tests/backstop/reference/google-site-kit_Setup_Client_ID_0_document_2_large.png b/tests/backstop/reference/google-site-kit_Setup_Client_ID_0_document_2_large.png deleted file mode 100644 index 962f1d1bda2..00000000000 Binary files a/tests/backstop/reference/google-site-kit_Setup_Client_ID_0_document_2_large.png and /dev/null differ diff --git a/tests/backstop/reference/google-site-kit_Setup_Step_One_0_document_0_small.png b/tests/backstop/reference/google-site-kit_Setup_Step_One_0_document_0_small.png new file mode 100644 index 00000000000..49bfcff5a35 Binary files /dev/null and b/tests/backstop/reference/google-site-kit_Setup_Step_One_0_document_0_small.png differ diff --git a/tests/backstop/reference/google-site-kit_Setup_Step_One_0_document_1_medium.png b/tests/backstop/reference/google-site-kit_Setup_Step_One_0_document_1_medium.png new file mode 100644 index 00000000000..e9dd7d9c8d9 Binary files /dev/null and b/tests/backstop/reference/google-site-kit_Setup_Step_One_0_document_1_medium.png differ diff --git a/tests/backstop/reference/google-site-kit_Setup_Step_One_0_document_2_large.png b/tests/backstop/reference/google-site-kit_Setup_Step_One_0_document_2_large.png new file mode 100644 index 00000000000..c66ea1f3ba2 Binary files /dev/null and b/tests/backstop/reference/google-site-kit_Setup_Step_One_0_document_2_large.png differ diff --git a/tests/e2e/plugins/auth.php b/tests/e2e/plugins/auth.php index ee32f61d26b..b67b6f7c645 100644 --- a/tests/e2e/plugins/auth.php +++ b/tests/e2e/plugins/auth.php @@ -19,16 +19,7 @@ /** * Provide dummy client configuration, normally provided in step 1 of the set up. */ -add_filter( 'pre_option_googlesitekit_credentials', function () { - return ( new Data_Encryption() )->encrypt( - serialize( - array( - 'oauth2_client_id' => '1234567890-asdfasdfasdfasdfzxcvzxcvzxcvzxcv.apps.googleusercontent.com', - 'oauth2_client_secret' => 'x_xxxxxxxxxxxxxxxxxxxxxx', - ) - ) - ); -} ); +require_once __DIR__ . '/gcp-credentials.php'; /** * Provide a dummy access token to fake an authenticated state. diff --git a/tests/e2e/plugins/gcp-credentials.php b/tests/e2e/plugins/gcp-credentials.php new file mode 100644 index 00000000000..0793a2ace0b --- /dev/null +++ b/tests/e2e/plugins/gcp-credentials.php @@ -0,0 +1,34 @@ + array( + 'client_id' => '1234567890-asdfasdfasdfasdfzxcvzxcvzxcvzxcv.apps.googleusercontent.com', + 'client_secret' => 'x_xxxxxxxxxxxxxxxxxxxxxx', + 'auth_uri' => 'https://accounts.google.com/o/oauth2/auth', + 'token_uri' => 'https://oauth2.googleapis.com/token', + 'auth_provider_x509_cert_url' => 'https://www.googleapis.com/oauth2/v1/certs', + 'redirect_uris' => array( add_query_arg( 'oauth2callback', '1', home_url() ) ), + ), + ) + ); + } +); diff --git a/tests/e2e/specs/auth-client-configuration.test.js b/tests/e2e/specs/auth-client-configuration.test.js deleted file mode 100644 index ecbe18fa2ba..00000000000 --- a/tests/e2e/specs/auth-client-configuration.test.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * WordPress dependencies - */ -import { visitAdminPage } from '@wordpress/e2e-test-utils'; - -/** - * Internal dependencies - */ -import { pasteText, testClientConfig } from '../utils'; - -describe( 'Providing client configuration', () => { - beforeEach( async () => { - await visitAdminPage( 'admin.php', 'page=googlesitekit-splash' ); - } ); - - it( 'Should have disabled button on load', async () => { - await page.waitForSelector( '#wizard-step-one-proceed' ); - - expect( await page.$eval( '#wizard-step-one-proceed', ( el ) => el.matches( '[disabled]' ) ) ).toBe( true ); - } ); - - it( 'Should have disabled button and display error when input is invalid', async () => { - await page.waitForSelector( '#client-configuration' ); - await pasteText( '#client-configuration', '{ invalid json }' ); - - await page.waitForSelector( '.googlesitekit-error-text' ); - await expect( page ).toMatchElement( '.googlesitekit-error-text', { text: 'Unable to parse client configuration values' } ); - - expect( await page.$eval( '#wizard-step-one-proceed', ( el ) => el.matches( '[disabled]' ) ) ).toBe( true ); - } ); - - it( 'Should have enabled button with valid value', async () => { - await page.waitForSelector( '#client-configuration' ); - await pasteText( '#client-configuration', JSON.stringify( testClientConfig ) ); - - await expect( page ).not.toMatchElement( '.googlesitekit-error-text', { text: 'Unable to parse client configuration values' } ); - - expect( await page.$eval( '#wizard-step-one-proceed', ( el ) => el.matches( '[disabled]' ) ) ).toBe( false ); - - await page.click( '#wizard-step-one-proceed' ); - - await page.waitForSelector( '.googlesitekit-wizard-step--two' ); - - await expect( page ).toMatchElement( '.googlesitekit-wizard-step__title', { text: 'Authenticate with Google' } ); - } ); -} ); diff --git a/tests/e2e/specs/auth-flow-admin-2.test.js b/tests/e2e/specs/auth-flow-admin-2.test.js index a7e205f7d0e..baa52000997 100644 --- a/tests/e2e/specs/auth-flow-admin-2.test.js +++ b/tests/e2e/specs/auth-flow-admin-2.test.js @@ -14,7 +14,6 @@ import { import { logoutUser, setAuthToken, - setClientConfig, setSearchConsoleProperty, setSiteVerification, useRequestInterception, @@ -39,9 +38,9 @@ describe( 'the set up flow for the second administrator', () => { } ); beforeEach( async () => { + await activatePlugin( 'e2e-tests-gcp-credentials-plugin' ); await activatePlugin( 'e2e-tests-oauth-callback-plugin' ); await activatePlugin( 'e2e-tests-site-verification-api-mock' ); - await setClientConfig(); await setAuthToken(); await setSiteVerification(); await setSearchConsoleProperty(); diff --git a/tests/e2e/specs/auth-flow-editor.test.js b/tests/e2e/specs/auth-flow-editor.test.js deleted file mode 100644 index 743c74dd98d..00000000000 --- a/tests/e2e/specs/auth-flow-editor.test.js +++ /dev/null @@ -1,72 +0,0 @@ -/** - * WordPress dependencies - */ -import { - activatePlugin, - loginUser, - createURL, - visitAdminPage, -} from '@wordpress/e2e-test-utils'; - -/** - * Internal dependencies - */ -import { - logoutUser, - setAuthToken, - setClientConfig, - setSearchConsoleProperty, - setSiteVerification, - useRequestInterception, -} from '../utils'; - -describe( 'the set up flow for an editor', () => { - beforeAll( async () => { - await page.setRequestInterception( true ); - useRequestInterception( ( request ) => { - if ( request.url().startsWith( 'https://accounts.google.com/o/oauth2/auth' ) ) { - request.respond( { - status: 302, - headers: { - location: createURL( '/', 'oauth2callback=1&code=valid-test-code' ), - }, - } ); - } else { - request.continue(); - } - } ); - } ); - - beforeEach( async () => { - await activatePlugin( 'e2e-tests-oauth-callback-plugin' ); - await setClientConfig(); - await setAuthToken(); - await setSiteVerification(); - await setSearchConsoleProperty(); - await logoutUser(); - } ); - - afterEach( async () => { - await logoutUser(); - - // Restore the default/admin user - // (switchToAdmin will not work as it is not aware of the current user) - await loginUser(); - } ); - - it( 'allows an editor to connect their Google account from the splash page', async () => { - await loginUser( 'editor', 'password' ); - await visitAdminPage( 'admin.php', 'page=googlesitekit-splash' ); - - await page.waitForSelector( '.googlesitekit-splash-intro button' ); - await expect( page ).toMatchElement( '.googlesitekit-splash-intro__title', { text: /Welcome to Site Kit/i } ); - - await Promise.all( [ - expect( page ).toClick( '.googlesitekit-splash-intro button', { text: /connect your account/i } ), - page.waitForNavigation(), - ] ); - - await expect( page ).toMatchElement( '#js-googlesitekit-dashboard' ); - } ); -} ); - diff --git a/tests/e2e/specs/auth-flow.test.js b/tests/e2e/specs/auth-flow.test.js index e6fc3f08ff1..91cf75dfdd8 100644 --- a/tests/e2e/specs/auth-flow.test.js +++ b/tests/e2e/specs/auth-flow.test.js @@ -9,11 +9,8 @@ import { activatePlugin, createURL, visitAdminPage } from '@wordpress/e2e-test-u import { deactivateUtilityPlugins, resetSiteKit, - pasteText, setSearchConsoleProperty, - testClientConfig, useRequestInterception, - setClientConfig, setAuthToken, setSiteVerification, } from '../utils'; @@ -55,27 +52,25 @@ const signOut = async () => { describe( 'Site Kit set up flow for the first time', () => { beforeAll( async () => { - await activatePlugin( 'e2e-tests-oauth-callback-plugin' ); await setSearchConsoleProperty(); } ); + beforeEach( async () => { + await activatePlugin( 'e2e-tests-gcp-credentials-plugin' ); + } ); + afterEach( async () => { await deactivateUtilityPlugins(); await resetSiteKit(); } ); it( 'authenticates from splash page', async () => { + await activatePlugin( 'e2e-tests-oauth-callback-plugin' ); await visitAdminPage( 'admin.php', 'page=googlesitekit-splash' ); - await page.waitForSelector( '#client-configuration' ); - - await pasteText( '#client-configuration', JSON.stringify( testClientConfig ) ); - await page.click( '#wizard-step-one-proceed' ); - await page.waitForSelector( '.googlesitekit-wizard-step--two .mdc-button' ); - // Sign in with Google await page.setRequestInterception( true ); useRequestInterception( stubGoogleSignIn ); - await page.click( '.googlesitekit-wizard-step--two .mdc-button' ); + await expect( page ).toClick( '.googlesitekit-wizard-step button', { text: /sign in with Google/i } ); await page.waitForNavigation(); await expect( page ).toMatchElement( '#js-googlesitekit-dashboard' ); @@ -83,7 +78,6 @@ describe( 'Site Kit set up flow for the first time', () => { } ); it( 'disconnects user from Site Kit', async () => { - await setClientConfig(); await setAuthToken(); await setSiteVerification(); await setSearchConsoleProperty(); diff --git a/tests/e2e/specs/auth-setup-search-console.test.js b/tests/e2e/specs/auth-setup-search-console.test.js index f2acc40afca..5cb31baa4eb 100644 --- a/tests/e2e/specs/auth-setup-search-console.test.js +++ b/tests/e2e/specs/auth-setup-search-console.test.js @@ -8,9 +8,7 @@ import { activatePlugin, createURL, visitAdminPage } from '@wordpress/e2e-test-u */ import { deactivateUtilityPlugins, - pasteText, resetSiteKit, - testClientConfig, useRequestInterception, wpApiFetch, } from '../utils'; @@ -37,6 +35,7 @@ describe( 'Site Kit set up flow for the first time with search console setup', ( } ); beforeEach( async () => { + await activatePlugin( 'e2e-tests-gcp-credentials-plugin' ); await activatePlugin( 'e2e-tests-oauth-callback-plugin' ); await activatePlugin( 'e2e-tests-site-verification-api-mock' ); @@ -54,13 +53,8 @@ describe( 'Site Kit set up flow for the first time with search console setup', ( it( 'inserts property to search console when site does not exist', async () => { await visitAdminPage( 'admin.php', 'page=googlesitekit-splash' ); - await page.waitForSelector( '#client-configuration' ); - await pasteText( '#client-configuration', JSON.stringify( testClientConfig ) ); - await expect( page ).toClick( '#wizard-step-one-proceed' ); - await page.waitForSelector( '.googlesitekit-wizard-step--two button' ); - - await expect( page ).toClick( '.googlesitekit-wizard-step--two button', { text: /sign in with Google/i } ); + await expect( page ).toClick( '.googlesitekit-wizard-step button', { text: /sign in with Google/i } ); await page.waitForNavigation(); await page.waitForSelector( '.googlesitekit-setup-module__title' ); @@ -77,13 +71,8 @@ describe( 'Site Kit set up flow for the first time with search console setup', ( it( 'saves search console property when site exists', async () => { await visitAdminPage( 'admin.php', 'page=googlesitekit-splash' ); - await page.waitForSelector( '#client-configuration' ); - - await pasteText( '#client-configuration', JSON.stringify( testClientConfig ) ); - await expect( page ).toClick( '#wizard-step-one-proceed' ); - await page.waitForSelector( '.googlesitekit-wizard-step--two button' ); - await expect( page ).toClick( '.googlesitekit-wizard-step--two button', { text: /sign in with Google/i } ); + await expect( page ).toClick( '.googlesitekit-wizard-step button', { text: /sign in with Google/i } ); await page.waitForNavigation(); await page.waitForSelector( '.googlesitekit-setup-module__title' ); diff --git a/tests/e2e/specs/auth-setup-verification.test.js b/tests/e2e/specs/auth-setup-verification.test.js index 81a369cd7fd..02700ccd685 100644 --- a/tests/e2e/specs/auth-setup-verification.test.js +++ b/tests/e2e/specs/auth-setup-verification.test.js @@ -8,9 +8,7 @@ import { activatePlugin, createURL, visitAdminPage } from '@wordpress/e2e-test-u */ import { deactivateUtilityPlugins, - pasteText, resetSiteKit, - testClientConfig, useRequestInterception, wpApiFetch, } from '../utils'; @@ -37,6 +35,7 @@ describe( 'Site Kit set up flow for the first time with site verification', () = } ); beforeEach( async () => { + await activatePlugin( 'e2e-tests-gcp-credentials-plugin' ); await activatePlugin( 'e2e-tests-oauth-callback-plugin' ); await activatePlugin( 'e2e-tests-site-verification-api-mock' ); } ); @@ -52,13 +51,8 @@ describe( 'Site Kit set up flow for the first time with site verification', () = it( 'prompts for confirmation if user is not verified for the site', async () => { await visitAdminPage( 'admin.php', 'page=googlesitekit-splash' ); - await page.waitForSelector( '#client-configuration' ); - await pasteText( '#client-configuration', JSON.stringify( testClientConfig ) ); - await expect( page ).toClick( '#wizard-step-one-proceed' ); - await page.waitForSelector( '.googlesitekit-wizard-step--two button' ); - - await expect( page ).toClick( '.googlesitekit-wizard-step--two button', { text: /sign in with Google/i } ); + await expect( page ).toClick( '.googlesitekit-wizard-step button', { text: /sign in with Google/i } ); await page.waitForNavigation(); await expect( page ).toMatchElement( '.googlesitekit-wizard-step__title', { text: /Verify URL/i } ); @@ -85,13 +79,8 @@ describe( 'Site Kit set up flow for the first time with site verification', () = } ); await visitAdminPage( 'admin.php', 'page=googlesitekit-splash' ); - await page.waitForSelector( '#client-configuration' ); - - await pasteText( '#client-configuration', JSON.stringify( testClientConfig ) ); - await expect( page ).toClick( '#wizard-step-one-proceed' ); - await page.waitForSelector( '.googlesitekit-wizard-step--two button' ); - await expect( page ).toClick( '.googlesitekit-wizard-step--two button', { text: /sign in with Google/i } ); + await expect( page ).toClick( '.googlesitekit-wizard-step button', { text: /sign in with Google/i } ); await page.waitForNavigation(); await page.waitForSelector( '.googlesitekit-wizard-step__action button' ); diff --git a/tests/e2e/specs/modules/optimize/activation.test.js b/tests/e2e/specs/modules/optimize/activation.test.js new file mode 100644 index 00000000000..37dbcbf3e02 --- /dev/null +++ b/tests/e2e/specs/modules/optimize/activation.test.js @@ -0,0 +1,90 @@ +/** + * WordPress dependencies + */ +import { visitAdminPage, activatePlugin } from '@wordpress/e2e-test-utils'; + +/** + * Internal dependencies + */ +import { + deactivateUtilityPlugins, + resetSiteKit, + setSearchConsoleProperty, + setSiteVerification, + setupAnalytics, +} from '../../../utils'; + +async function proceedToOptimizeSetup() { + await visitAdminPage( 'admin.php', 'page=googlesitekit-settings' ); + + await page.waitForSelector( '.mdc-tab-bar' ); + await expect( page ).toClick( '.mdc-tab', { text: /connect more services/i } ); + + await page.waitForSelector( '.googlesitekit-settings-connect-module--optimize' ); + + await Promise.all( [ + page.waitForNavigation(), + page.waitForSelector( '.googlesitekit-setup-module--optimize .googlesitekit-setup-module__title' ), + expect( page ).toClick( '.googlesitekit-cta-link', { text: /set up optimize/i } ), + ] ); +} + +async function finishOptimizeSetup() { + await Promise.all( [ + page.waitForNavigation(), + expect( page ).toClick( '.googlesitekit-setup-module--optimize button', { text: /Configure Optimize/i } ), + ] ); + await expect( page ).toMatchElement( '.googlesitekit-publisher-win__title', { text: /Congrats on completing the setup for Optimize!/i } ); +} + +describe( 'Optimize Activation', () => { + beforeEach( async () => { + await activatePlugin( 'e2e-tests-auth-plugin' ); + await setSiteVerification(); + await setSearchConsoleProperty(); + } ); + + afterEach( async () => { + await deactivateUtilityPlugins(); + await resetSiteKit(); + } ); + + it( 'prompts to insert your Optimize ID when Analytics snippet is enabled', async () => { + await setupAnalytics( { useSnippet: true } ); + await proceedToOptimizeSetup(); + + const setupHandle = await page.$( '.googlesitekit-setup-module--optimize' ); + await expect( setupHandle ).toMatchElement( '.googlesitekit-setup-module__title', { text: /Optimize/i } ); + await expect( setupHandle ).toMatchElement( 'p', { text: /Please copy and paste your Optimize ID to complete your setup/i } ); + // Not able to use negation here for some reason. + // await expect( setupHandle ).not.toMatchElement( 'p', { text: /You disabled analytics auto insert snippet. If You are using Google Analytics code snippet, add the code below/i, visible: true } ); + // await expect( setupHandle ).not.toMatchElement( 'p', { text: /Click here for how to implement Optimize tag in Google Analytics Code Snippet/i } ); + + await expect( setupHandle ).toFill( 'input', 'gtm' ); + await expect( setupHandle ).toMatchElement( '.googlesitekit-error-text', { text: /Error: Not a valid Optimize ID./i } ); + await expect( setupHandle ).toFill( 'input', 'GTM-1234567' ); + await expect( setupHandle ).not.toMatchElement( '.googlesitekit-error-text', { text: /Error: Not a valid Optimize ID./i } ); + await setupHandle.dispose(); + + await finishOptimizeSetup(); + } ); + + it( 'prompts to insert your Optimize ID when Analytics snippet is disabled, with extra instructions', async () => { + await setupAnalytics( { useSnippet: false } ); + await proceedToOptimizeSetup(); + + const setupHandle = await page.$( '.googlesitekit-setup-module--optimize' ); + await expect( setupHandle ).toMatchElement( '.googlesitekit-setup-module__title', { text: /Optimize/i } ); + await expect( setupHandle ).toMatchElement( 'p', { text: /Please copy and paste your Optimize ID to complete your setup/i } ); + await expect( setupHandle ).toMatchElement( 'p', { text: /You disabled analytics auto insert snippet. If You are using Google Analytics code snippet, add the code below/i } ); + await expect( setupHandle ).toMatchElement( 'p', { text: /Click here for how to implement Optimize tag in Google Analytics Code Snippet/i } ); + + await expect( setupHandle ).toFill( 'input', 'gtm' ); + await expect( setupHandle ).toMatchElement( '.googlesitekit-error-text', { text: /Error: Not a valid Optimize ID./i } ); + await expect( setupHandle ).toFill( 'input', 'GTM-1234567' ); + await expect( setupHandle ).not.toMatchElement( '.googlesitekit-error-text', { text: /Error: Not a valid Optimize ID./i } ); + await setupHandle.dispose(); + + await finishOptimizeSetup(); + } ); +} ); diff --git a/tests/e2e/specs/modules/pagespeed-insights/activation.test.js b/tests/e2e/specs/modules/pagespeed-insights/activation.test.js index bb9ed471606..cc37c274897 100644 --- a/tests/e2e/specs/modules/pagespeed-insights/activation.test.js +++ b/tests/e2e/specs/modules/pagespeed-insights/activation.test.js @@ -38,12 +38,27 @@ describe( 'PageSpeed Insights Activation', () => { await resetSiteKit(); } ); - it( 'leads you to the Site Kit dashboard after activation', async () => { + it( 'leads you to the Site Kit dashboard after activation via CTA', async () => { await visitAdminPage( 'admin.php', 'page=googlesitekit-dashboard' ); + await Promise.all( [ + page.waitForNavigation(), + expect( page ).toClick( '.googlesitekit-cta-link', { text: /Activate PageSpeed Insights/i } ), + ] ); + + await page.waitForSelector( '.googlesitekit-publisher-win__title' ); + await expect( page ).toMatchElement( '.googlesitekit-publisher-win__title', { text: /Congrats on completing the setup for PageSpeed Insights!/i } ); + } ); + + it( 'leads you to the Site Kit dashboard after activation via the settings page', async () => { + await visitAdminPage( 'admin.php', 'page=googlesitekit-settings' ); + + await page.waitForSelector( '.mdc-tab-bar' ); + await expect( page ).toClick( '.mdc-tab', { text: /connect more services/i } ); + await page.waitForSelector( '.googlesitekit-settings-connect-module--pagespeed-insights' ); await Promise.all( [ page.waitForNavigation(), - expect( page ).toClick( '.googlesitekit-cta-link', { text: 'Activate PageSpeed Insights' } ), + expect( page ).toClick( '.googlesitekit-cta-link', { text: /Set up PageSpeed Insights/i } ), ] ); await page.waitForSelector( '.googlesitekit-publisher-win__title' ); diff --git a/tests/e2e/specs/plugin-activation.test.js b/tests/e2e/specs/plugin-activation.test.js index 85ed0cf6de6..b24ea617033 100644 --- a/tests/e2e/specs/plugin-activation.test.js +++ b/tests/e2e/specs/plugin-activation.test.js @@ -6,9 +6,11 @@ import { deactivatePlugin, activatePlugin } from '@wordpress/e2e-test-utils'; describe( 'Plugin Activation Notice', () => { beforeEach( async () => { await deactivatePlugin( 'google-site-kit' ); + await activatePlugin( 'e2e-tests-gcp-credentials-plugin' ); } ); afterEach( async () => { + await deactivatePlugin( 'e2e-tests-gcp-credentials-plugin' ); await activatePlugin( 'google-site-kit' ); } ); @@ -32,8 +34,7 @@ describe( 'Plugin Activation Notice', () => { await page.click( '.googlesitekit-activation__button' ); await page.waitForSelector( '.googlesitekit-wizard-step__title' ); - await expect( page ).toMatchElement( 'h2.googlesitekit-wizard-step__title', { text: 'Welcome to Site Kit beta for developers.' } ); - - await deactivatePlugin( 'google-site-kit' ); + // Ensure we're on the first step. + await expect( page ).toMatchElement( '.googlesitekit-wizard-progress-step__number--inprogress', { text: '1' } ); } ); } ); diff --git a/tests/e2e/specs/plugin-reset.test.js b/tests/e2e/specs/plugin-reset.test.js index 22b5655a847..cc93a8a4357 100644 --- a/tests/e2e/specs/plugin-reset.test.js +++ b/tests/e2e/specs/plugin-reset.test.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { visitAdminPage } from '@wordpress/e2e-test-utils'; +import { activatePlugin, deactivatePlugin, visitAdminPage } from '@wordpress/e2e-test-utils'; /** * Internal dependencies @@ -19,6 +19,7 @@ describe( 'Plugin Reset', () => { await setAuthToken(); await setSiteVerification(); await setSearchConsoleProperty(); + await activatePlugin( 'e2e-tests-gcp-credentials-plugin' ); } ); beforeEach( async () => { @@ -32,6 +33,10 @@ describe( 'Plugin Reset', () => { await page.waitForSelector( '.googlesitekit-settings-module__footer' ); } ); + afterAll( async () => { + await deactivatePlugin( 'e2e-tests-gcp-credentials-plugin' ); + } ); + it( 'displays a confirmation dialog when clicking the "Reset Site Kit" link', async () => { await expect( page ).toClick( 'button.googlesitekit-cta-link', { text: 'Reset Site Kit' } ); await page.waitForSelector( '.mdc-dialog--open .mdc-button' ); @@ -47,6 +52,7 @@ describe( 'Plugin Reset', () => { } ); it( 'disconnects Site Kit by clicking the "Reset" button in the confirmation dialog', async () => { + await page.waitForSelector( 'button.googlesitekit-cta-link' ); await expect( page ).toClick( 'button.googlesitekit-cta-link', { text: 'Reset Site Kit' } ); await page.waitForSelector( '.mdc-dialog--open .mdc-button' ); @@ -55,7 +61,7 @@ describe( 'Plugin Reset', () => { expect( page ).toClick( '.mdc-dialog--open .mdc-button', { text: 'Reset' } ), ] ); - await page.waitForSelector( '.googlesitekit-wizard-step--one' ); - await expect( page ).toMatchElement( '.googlesitekit-wizard-step__title', { text: /Welcome to Site Kit beta for developers/i } ); + // Ensure we're on the first step. + await expect( page ).toMatchElement( '.googlesitekit-wizard-progress-step__number--inprogress', { text: '1' } ); } ); } ); diff --git a/tests/e2e/utils/setup-analytics.js b/tests/e2e/utils/setup-analytics.js index 369813bc8c3..5cf4a27f080 100644 --- a/tests/e2e/utils/setup-analytics.js +++ b/tests/e2e/utils/setup-analytics.js @@ -3,18 +3,24 @@ */ import { wpApiFetch } from './wp-api-fetch'; -const defaultConnection = { +const defaultSettings = { accountId: 100, propertyId: 200, profileId: 300, internalWebPropertyId: 400, + useSnippet: true, + // ampClientIdOptIn: (bool) }; /** * Activate and set up the Analytics module. - * @param {Object} config Optional configuration to use for module set up. + * @param {Object} settingsOverrides Optional settings to override the defaults. */ -export async function setupAnalytics( config = { connection: defaultConnection } ) { +export async function setupAnalytics( settingsOverrides = {} ) { + const settings = { + ...defaultSettings, + ...settingsOverrides, + }; // Activate the module. await wpApiFetch( { method: 'post', @@ -24,9 +30,9 @@ export async function setupAnalytics( config = { connection: defaultConnection } // Set dummy connection data. await wpApiFetch( { method: 'post', - path: 'google-site-kit/v1/modules/analytics/data/connection', + path: 'google-site-kit/v1/modules/analytics/data/settings', data: { - data: config.connection, + data: settings, }, parse: false, } ); diff --git a/tests/e2e/utils/test-client-config.js b/tests/e2e/utils/test-client-config.js index 57ffffa0c2d..c4743fd56fe 100644 --- a/tests/e2e/utils/test-client-config.js +++ b/tests/e2e/utils/test-client-config.js @@ -1,7 +1,7 @@ /* eslint-disable camelcase */ export const testClientConfig = { web: { - client_id: '1234567890-asdfasdfasdfasdfzxcvzxcvzxcvzxcv.apps.googleusercontent.com', + client_id: '1234567890-asdfasdfasdfasdfzxcvzxcvzxcvzxcv.apps.sitekit.withgoogle.com', client_secret: 'x_xxxxxxxxxxxxxxxxxxxxxx', project_id: 'test-project-id', auth_uri: 'https://accounts.google.com/o/oauth2/auth', diff --git a/tests/phpunit/integration/Core/Authentication/AuthenticationTest.php b/tests/phpunit/integration/Core/Authentication/AuthenticationTest.php index 01f891c0edb..c30da985c5e 100644 --- a/tests/phpunit/integration/Core/Authentication/AuthenticationTest.php +++ b/tests/phpunit/integration/Core/Authentication/AuthenticationTest.php @@ -68,13 +68,10 @@ protected function assertAdminDataExtended() { $this->assertEqualSets( array( - 'clientID', - 'clientSecret', 'connectUrl', 'disconnectUrl', - 'externalCredentialsURL', - 'projectId', - 'projectUrl', + 'proxySetupURL', + 'proxyPermissionsURL', 'userData', ), array_keys( $data ) diff --git a/tests/phpunit/integration/Core/Authentication/Clients/OAuth_ClientTest.php b/tests/phpunit/integration/Core/Authentication/Clients/OAuth_ClientTest.php index 799adb967ac..5a8a0f37947 100644 --- a/tests/phpunit/integration/Core/Authentication/Clients/OAuth_ClientTest.php +++ b/tests/phpunit/integration/Core/Authentication/Clients/OAuth_ClientTest.php @@ -20,6 +20,9 @@ */ class OAuth_ClientTest extends TestCase { + const SITE_ID = '12345678.apps.sitekit.withgoogle.com'; + const CLIENT_ID = 'test-client-id'; + public function test_get_client() { $client = new OAuth_Client( new Context( GOOGLESITEKIT_PLUGIN_MAIN_FILE ) ); @@ -50,19 +53,8 @@ public function test_refresh_token() { $client->refresh_token(); // At this point an error is triggered internally due to undefined indexes on $authentication_token - // and the saved error code is an integer from a PHPUnit exception. - // Let's just make sure the error code is not one related to client error handling. - $this->assertNotContains( - get_user_option( OAuth_Client::OPTION_ERROR_CODE, $user_id ), - array( - 'oauth_credentials_not_exist', - 'refresh_token_not_exist', - 'cannot_log_in', - 'invalid_grant', - 'invalid_code', - 'access_token_not_received', - ) - ); + // and the saved error code is 'invalid_grant' by default. + $this->assertEquals( 'invalid_grant', get_user_option( OAuth_Client::OPTION_ERROR_CODE, $user_id ) ); } public function test_revoke_token() { @@ -206,7 +198,7 @@ public function test_get_authentication_url() { * @see \Google\Site_Kit\Core\Authentication\Authentication::handle_oauth */ $this->assertEquals( add_query_arg( 'oauth2callback', 1, home_url() ), $params['redirect_uri'] ); - $this->assertEquals( 'test-client-id', $params['client_id'] ); + $this->assertEquals( self::CLIENT_ID, $params['client_id'] ); } public function test_authorize_user() { @@ -240,9 +232,6 @@ public function test_authorize_user() { $_GET['code'] = 'test-code'; $this->fake_authentication(); - $credentials_mock = $this->getMock( 'MockClass', array( 'has' ) ); - $credentials_mock->method( 'has' )->willReturn( true ); - $this->force_set_property( $client, 'credentials', $credentials_mock ); // If all goes smooth, we expect to be redirected to $success_redirect $success_redirect = admin_url( 'success-redirect' ); $client->get_authentication_url( $success_redirect ); @@ -259,6 +248,73 @@ public function test_authorize_user() { } } + public function test_using_proxy() { + $context = new Context( GOOGLESITEKIT_PLUGIN_MAIN_FILE ); + + // Use proxy by default. + $client = new OAuth_Client( $context ); + $this->assertTrue( $client->using_proxy() ); + + // Don't use proxy when regular OAuth client ID is used. + $this->fake_authentication(); + $client = new OAuth_Client( $context ); + $this->assertFalse( $client->using_proxy() ); + + // Use proxy when proxy site ID is used. + $this->fake_proxy_authentication(); + $client = new OAuth_Client( $context ); + $this->assertTrue( $client->using_proxy() ); + } + + public function test_get_proxy_setup_url() { + $context = new Context( GOOGLESITEKIT_PLUGIN_MAIN_FILE ); + + // If no site ID, pass site registration args. + $client = new OAuth_Client( $context ); + $url = $client->get_proxy_setup_url(); + $this->assertContains( 'name=', $url ); + $this->assertContains( 'url=', $url ); + $this->assertContains( 'rest_root=', $url ); + $this->assertContains( 'admin_root=', $url ); + $this->assertContains( 'scope=', $url ); + $this->assertNotContains( 'site_id=', $url ); + + // Otherwise, pass site ID and given temporary access code. + $this->fake_proxy_authentication(); + $client = new OAuth_Client( $context ); + $url = $client->get_proxy_setup_url( 'temp-code' ); + $this->assertContains( 'site_id=' . self::SITE_ID, $url ); + $this->assertContains( 'code=temp-code', $url ); + $this->assertContains( 'scope=', $url ); + $this->assertNotContains( 'name=', $url ); + $this->assertNotContains( 'url=', $url ); + $this->assertNotContains( 'rest_root=', $url ); + $this->assertNotContains( 'admin_root=', $url ); + } + + public function test_get_proxy_permissions_url() { + $context = new Context( GOOGLESITEKIT_PLUGIN_MAIN_FILE ); + + // If no access token, this does not work. + $client = new OAuth_Client( $context ); + $url = $client->get_proxy_permissions_url(); + $this->assertEmpty( $url ); + + // The URL has to include the access token. + $client = new OAuth_Client( $context ); + $client->set_access_token( 'test-access-token', 3600 ); + $url = $client->get_proxy_permissions_url(); + $this->assertContains( 'token=test-access-token', $url ); + + // If there is a site ID, it should also include that. + $this->fake_proxy_authentication(); + $client = new OAuth_Client( $context ); + $client->set_access_token( 'test-access-token', 3600 ); + $url = $client->get_proxy_permissions_url(); + $this->assertContains( 'token=test-access-token', $url ); + $this->assertContains( 'site_id=' . self::SITE_ID, $url ); + } + public function test_get_error_message_unknown() { $client = new OAuth_Client( new Context( GOOGLESITEKIT_PLUGIN_MAIN_FILE ) ); @@ -294,7 +350,18 @@ protected function fake_authentication() { add_filter( 'googlesitekit_oauth_secret', function () { return json_encode( array( 'web' => array( - 'client_id' => 'test-client-id', + 'client_id' => self::CLIENT_ID, + 'client_secret' => 'test-client-secret', + ), + ) ); + } ); + } + + protected function fake_proxy_authentication() { + add_filter( 'googlesitekit_oauth_secret', function () { + return json_encode( array( + 'web' => array( + 'client_id' => self::SITE_ID, 'client_secret' => 'test-client-secret', ), ) ); diff --git a/tests/phpunit/integration/Core/Authentication/GCP_ProjectTest.php b/tests/phpunit/integration/Core/Authentication/GCP_ProjectTest.php deleted file mode 100644 index a75c2fce688..00000000000 --- a/tests/phpunit/integration/Core/Authentication/GCP_ProjectTest.php +++ /dev/null @@ -1,90 +0,0 @@ -gcp_project = new GCP_Project( $options ); - } - - /** - * Test get() method. - */ - public function test_get() { - $defaults = array( - 'id' => '', - 'wp_owner_id' => 0, - ); - - $this->assertEqualSetsWithIndex( $defaults, $this->gcp_project->get() ); - - $data = $defaults; - $data['id'] = 'fake-gcp-project-id'; - - $this->gcp_project->set( array( 'id' => $data['id'] ) ); - $this->assertEqualSetsWithIndex( $data, $this->gcp_project->get() ); - } - - /** - * Test set() method. - */ - public function test_set() { - $defaults = array( - 'id' => '', - 'wp_owner_id' => 0, - ); - - $data = $defaults; - $data['id'] = 'fake-gcp-project-id'; - $data['wp_owner_id'] = 3; - - $this->assertTrue( $this->gcp_project->set( $data ) ); - $this->assertEqualSetsWithIndex( $data, $this->gcp_project->get() ); - - $this->assertTrue( $this->gcp_project->set( array() ) ); - $this->assertEqualSetsWithIndex( $defaults, $this->gcp_project->get() ); - } - - /** - * Test has() method. - */ - public function test_has() { - $this->assertFalse( $this->gcp_project->has() ); - - $this->gcp_project->set( array( 'id' => 'fake-gcp-project-id' ) ); - $this->assertTrue( $this->gcp_project->has() ); - } -} diff --git a/tests/phpunit/integration/Core/REST_API/REST_RoutesTest.php b/tests/phpunit/integration/Core/REST_API/REST_RoutesTest.php index 2212710e599..b343aa7640b 100644 --- a/tests/phpunit/integration/Core/REST_API/REST_RoutesTest.php +++ b/tests/phpunit/integration/Core/REST_API/REST_RoutesTest.php @@ -41,8 +41,6 @@ public function test_register() { '/', '/' . REST_Routes::REST_ROOT, '/' . REST_Routes::REST_ROOT . '/core/site/data/reset', - '/' . REST_Routes::REST_ROOT . '/core/site/data/credentials', - '/' . REST_Routes::REST_ROOT . '/core/site/data/gcpproject', '/' . REST_Routes::REST_ROOT . '/core/user/data/disconnect', '/' . REST_Routes::REST_ROOT . '/core/user/data/authentication', '/' . REST_Routes::REST_ROOT . '/modules', @@ -50,6 +48,7 @@ public function test_register() { '/' . REST_Routes::REST_ROOT . '/modules/(?P[a-z\\-]+)/data/(?P[a-z\\-]+)', '/' . REST_Routes::REST_ROOT . '/data', '/' . REST_Routes::REST_ROOT . '/modules/(?P[a-z\\-]+)/notifications', + '/' . REST_Routes::REST_ROOT . '/oauth/site', '/' . REST_Routes::REST_ROOT . '/core/search/data/post-search', ); diff --git a/tests/phpunit/integration/Core/Util/Beta_MigrationTest.php b/tests/phpunit/integration/Core/Util/Beta_MigrationTest.php new file mode 100644 index 00000000000..79b4a0d2458 --- /dev/null +++ b/tests/phpunit/integration/Core/Util/Beta_MigrationTest.php @@ -0,0 +1,81 @@ +register(); + + $this->assertTrue( has_action( 'admin_init' ) ); + $this->assertTrue( has_filter( 'googlesitekit_admin_notices' ) ); + $this->assertTrue( has_action( 'wp_ajax_' . Beta_Migration::ACTION_DISMISS ) ); + } + + public function test_maybe_run_upgrade() { + $context = new Context( GOOGLESITEKIT_PLUGIN_MAIN_FILE ); + $options = new Options( $context ); + $credentials = new Credentials( $options ); + $migration = new Beta_Migration( $context ); + + // Upgrade will update the DB version if run. + $this->delete_credentials(); + + $migration->migrate_old_credentials(); + + $this->assertFalse( $credentials->has() ); + + // The upgrade will delete old GCP credentials if present. + $this->set_gcp_credentials(); + $this->assertTrue( $credentials->has() ); + + $migration->migrate_old_credentials(); + + $this->assertFalse( $credentials->has() ); + + // The upgrade will not delete proxy credentials if present. + $this->set_proxy_credentials(); + $this->assertTrue( $credentials->has() ); + + $migration->migrate_old_credentials(); + + $this->assertTrue( $credentials->has() ); + } + + private function delete_credentials() { + ( new Options( new Context( GOOGLESITEKIT_PLUGIN_MAIN_FILE ) ) )->delete( Credentials::OPTION ); + } + + private function set_gcp_credentials() { + ( new Credentials( new Options( new Context( GOOGLESITEKIT_PLUGIN_MAIN_FILE ) ) ) )->set( array( + 'oauth2_client_id' => 'test-client-id.apps.googleusercontent.com', + 'oauth2_client_secret' => 'test-client-secret', + ) ); + } + + private function set_proxy_credentials() { + ( new Credentials( new Options( new Context( GOOGLESITEKIT_PLUGIN_MAIN_FILE ) ) ) )->set( array( + 'oauth2_client_id' => 'test-site-id.apps.sitekit.withgoogle.com', + 'oauth2_client_secret' => 'test-site-secret', + ) ); + } +} diff --git a/tests/phpunit/integration/Core/Util/ResetTest.php b/tests/phpunit/integration/Core/Util/ResetTest.php index ff1b1ec386c..dce71e6c6da 100644 --- a/tests/phpunit/integration/Core/Util/ResetTest.php +++ b/tests/phpunit/integration/Core/Util/ResetTest.php @@ -14,11 +14,11 @@ use Google\Site_Kit\Core\Authentication\Clients\OAuth_Client; use Google\Site_Kit\Core\Authentication\Credentials; use Google\Site_Kit\Core\Authentication\First_Admin; -use Google\Site_Kit\Core\Authentication\GCP_Project; use Google\Site_Kit\Core\Authentication\Profile; use Google\Site_Kit\Core\Authentication\Verification; use Google\Site_Kit\Core\Authentication\Verification_Tag; use Google\Site_Kit\Core\Util\Activation; +use Google\Site_Kit\Core\Util\Beta_Migration; use Google\Site_Kit\Core\Util\Reset; use Google\Site_Kit\Modules\AdSense; use Google\Site_Kit\Modules\Analytics; @@ -80,11 +80,11 @@ protected function get_option_keys() { Analytics::OPTION, Credentials::OPTION, First_Admin::OPTION, - GCP_Project::OPTION, Optimize::OPTION, PageSpeed_Insights::OPTION, Search_Console::PROPERTY_OPTION, TagManager::OPTION, + Beta_Migration::OPTION_IS_PRE_PROXY_INSTALL, ); } diff --git a/tests/qunit/assets/js/util/index.js b/tests/qunit/assets/js/util/index.js index 8d37f358dcc..473562027d0 100644 --- a/tests/qunit/assets/js/util/index.js +++ b/tests/qunit/assets/js/util/index.js @@ -407,30 +407,29 @@ QUnit.test( 'fillFilterWithComponent::', function ( assert ) { * Test getReAuthUrl. */ valuesToTest = [ - { slug: 'pagespeed-insights', status: false, apikey: false, - expected: 'http://sitekit.withgoogle.com/wp-admin/admin.php?page=googlesitekit-dashboard&reAuth=false&slug=pagespeed-insights' + expected: 'http://sitekit.withgoogle.com/wp-admin/admin.php?page=googlesitekit-dashboard&slug=pagespeed-insights¬ification=authentication_success' }, { slug: 'pagespeed-insights', status: true, apikey: false, - expected: 'http://sitekit.withgoogle.com/wp-admin/admin.php?page=googlesitekit-module-pagespeed-insights&reAuth=true&slug=pagespeed-insights' + expected: 'http://sitekit.withgoogle.com/wp-admin/admin.php?page=googlesitekit-module-pagespeed-insights&slug=pagespeed-insights¬ification=authentication_success' }, { slug: 'pagespeed-insights', status: false, apikey: 'abc123', - expected: 'http://sitekit.withgoogle.com/wp-admin/admin.php?page=googlesitekit-dashboard&reAuth=false&slug=pagespeed-insights' + expected: 'http://sitekit.withgoogle.com/wp-admin/admin.php?page=googlesitekit-dashboard&slug=pagespeed-insights¬ification=authentication_success' }, { slug: 'pagespeed-insights', status: true, apikey: 'abc123', - expected: 'http://sitekit.withgoogle.com/wp-admin/admin.php?page=googlesitekit-module-pagespeed-insights&reAuth=true&slug=pagespeed-insights' + expected: 'http://sitekit.withgoogle.com/wp-admin/admin.php?page=googlesitekit-module-pagespeed-insights&slug=pagespeed-insights¬ification=authentication_success' }, ];