update
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Authentication\Clients\Client_Factory
|
||||
*
|
||||
* @package Google\Site_Kit
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Authentication\Clients;
|
||||
|
||||
use Exception;
|
||||
use Google\Site_Kit\Core\Authentication\Google_Proxy;
|
||||
use Google\Site_Kit_Dependencies\GuzzleHttp\Client;
|
||||
use WP_HTTP_Proxy;
|
||||
|
||||
/**
|
||||
* Class for creating Site Kit-specific Google_Client instances.
|
||||
*
|
||||
* @since 1.39.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
final class Client_Factory {
|
||||
|
||||
/**
|
||||
* Creates a new Google client instance for the given arguments.
|
||||
*
|
||||
* @since 1.39.0
|
||||
*
|
||||
* @param array $args Associative array of arguments.
|
||||
* @return Google_Site_Kit_Client|Google_Site_Kit_Proxy_Client The created Google client instance.
|
||||
*/
|
||||
public static function create_client( array $args ) {
|
||||
$args = array_merge(
|
||||
array(
|
||||
'client_id' => '',
|
||||
'client_secret' => '',
|
||||
'redirect_uri' => '',
|
||||
'token' => array(),
|
||||
'token_callback' => null,
|
||||
'token_exception_callback' => null,
|
||||
'required_scopes' => array(),
|
||||
'login_hint_email' => '',
|
||||
'using_proxy' => true,
|
||||
'proxy_url' => Google_Proxy::PRODUCTION_BASE_URL,
|
||||
),
|
||||
$args
|
||||
);
|
||||
|
||||
if ( $args['using_proxy'] ) {
|
||||
$client = new Google_Site_Kit_Proxy_Client(
|
||||
array( 'proxy_base_path' => $args['proxy_url'] )
|
||||
);
|
||||
} else {
|
||||
$client = new Google_Site_Kit_Client();
|
||||
}
|
||||
|
||||
// Enable exponential retries, try up to three times.
|
||||
$client->setConfig( 'retry', array( 'retries' => 3 ) );
|
||||
|
||||
$http_client = $client->getHttpClient();
|
||||
$http_client_config = self::get_http_client_config( $http_client->getConfig() );
|
||||
// In Guzzle 6+, the HTTP client is immutable, so only a new instance can be set.
|
||||
$client->setHttpClient( new Client( $http_client_config ) );
|
||||
|
||||
$auth_config = self::get_auth_config( $args['client_id'], $args['client_secret'], $args['redirect_uri'] );
|
||||
if ( ! empty( $auth_config ) ) {
|
||||
try {
|
||||
$client->setAuthConfig( $auth_config );
|
||||
} catch ( Exception $e ) {
|
||||
return $client;
|
||||
}
|
||||
}
|
||||
|
||||
// Offline access so we can access the refresh token even when the user is logged out.
|
||||
$client->setAccessType( 'offline' );
|
||||
$client->setPrompt( 'consent' );
|
||||
$client->setRedirectUri( $args['redirect_uri'] );
|
||||
$client->setScopes( (array) $args['required_scopes'] );
|
||||
|
||||
// Set the full token data.
|
||||
if ( ! empty( $args['token'] ) ) {
|
||||
$client->setAccessToken( $args['token'] );
|
||||
}
|
||||
|
||||
// Set the callback which is called when the client refreshes the access token on-the-fly.
|
||||
$token_callback = $args['token_callback'];
|
||||
if ( $token_callback ) {
|
||||
$client->setTokenCallback(
|
||||
function( $cache_key, $access_token ) use ( $client, $token_callback ) {
|
||||
// The same token from this callback should also already be set in the client object, which is useful
|
||||
// to get the full token data, all of which needs to be saved. Just in case, if that is not the same,
|
||||
// we save the passed token only, relying on defaults for the other values.
|
||||
$token = $client->getAccessToken();
|
||||
if ( $access_token !== $token['access_token'] ) {
|
||||
$token = array( 'access_token' => $access_token );
|
||||
}
|
||||
|
||||
$token_callback( $token );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Set the callback which is called when refreshing the access token on-the-fly fails.
|
||||
$token_exception_callback = $args['token_exception_callback'];
|
||||
if ( ! empty( $token_exception_callback ) ) {
|
||||
$client->setTokenExceptionCallback( $token_exception_callback );
|
||||
}
|
||||
|
||||
if ( ! empty( $args['login_hint_email'] ) ) {
|
||||
$client->setLoginHint( $args['login_hint_email'] );
|
||||
}
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get HTTP client configuration.
|
||||
*
|
||||
* @since 1.115.0
|
||||
*
|
||||
* @param array $config Initial configuration.
|
||||
* @return array The new HTTP client configuration.
|
||||
*/
|
||||
private static function get_http_client_config( $config ) {
|
||||
// Override the default user-agent for the Guzzle client. This is used for oauth/token requests.
|
||||
// By default this header uses the generic Guzzle client's user-agent and includes
|
||||
// Guzzle, cURL, and PHP versions as it is normally shared.
|
||||
// In our case however, the client is namespaced to be used by Site Kit only.
|
||||
$config['headers']['User-Agent'] = Google_Proxy::get_application_name();
|
||||
|
||||
/** This filter is documented in wp-includes/class-http.php */
|
||||
$ssl_verify = apply_filters( 'https_ssl_verify', true, null );
|
||||
// If SSL verification is enabled (default) use the SSL certificate bundle included with WP.
|
||||
if ( $ssl_verify ) {
|
||||
$config['verify'] = ABSPATH . WPINC . '/certificates/ca-bundle.crt';
|
||||
} else {
|
||||
$config['verify'] = false;
|
||||
}
|
||||
|
||||
// Configure the Google_Client's HTTP client to use the same HTTP proxy as WordPress HTTP, if set.
|
||||
$http_proxy = new WP_HTTP_Proxy();
|
||||
if ( $http_proxy->is_enabled() ) {
|
||||
// See https://docs.guzzlephp.org/en/6.5/request-options.html#proxy for reference.
|
||||
$auth = $http_proxy->use_authentication() ? "{$http_proxy->authentication()}@" : '';
|
||||
|
||||
$config['proxy'] = "{$auth}{$http_proxy->host()}:{$http_proxy->port()}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the IP version to force hostname resolution with.
|
||||
*
|
||||
* @since 1.115.0
|
||||
*
|
||||
* @param $force_ip_resolve null|string IP version to force. Default: null.
|
||||
*/
|
||||
$force_ip_resolve = apply_filters( 'googlesitekit_force_ip_resolve', null );
|
||||
if ( in_array( $force_ip_resolve, array( null, 'v4', 'v6' ), true ) ) {
|
||||
$config['force_ip_resolve'] = $force_ip_resolve;
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full OAuth credentials configuration data based on the given client ID and secret.
|
||||
*
|
||||
* @since 1.39.0
|
||||
*
|
||||
* @param string $client_id OAuth client ID.
|
||||
* @param string $client_secret OAuth client secret.
|
||||
* @param string $redirect_uri OAuth redirect URI.
|
||||
* @return array Credentials data, or empty array if any of the given values is empty.
|
||||
*/
|
||||
private static function get_auth_config( $client_id, $client_secret, $redirect_uri ) {
|
||||
if ( ! $client_id || ! $client_secret || ! $redirect_uri ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return array(
|
||||
'client_id' => $client_id,
|
||||
'client_secret' => $client_secret,
|
||||
'auth_uri' => 'https://accounts.google.com/o/oauth2/v2/auth',
|
||||
'token_uri' => 'https://oauth2.googleapis.com/token',
|
||||
'auth_provider_x509_cert_url' => 'https://www.googleapis.com/oauth2/v1/certs',
|
||||
'redirect_uris' => array( $redirect_uri ),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Authentication\Clients\Google_Site_Kit_Client
|
||||
*
|
||||
* @package Google\Site_Kit
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Authentication\Clients;
|
||||
|
||||
use Google\Site_Kit\Core\Authentication\Clients\OAuth2;
|
||||
use Google\Site_Kit\Core\Authentication\Exception\Google_OAuth_Exception;
|
||||
use Google\Site_Kit_Dependencies\Google_Client;
|
||||
use Google\Site_Kit_Dependencies\Google\Auth\HttpHandler\HttpHandlerFactory;
|
||||
use Google\Site_Kit_Dependencies\Google\Auth\HttpHandler\HttpClientCache;
|
||||
use Google\Site_Kit_Dependencies\GuzzleHttp\ClientInterface;
|
||||
use Google\Site_Kit_Dependencies\Psr\Http\Message\RequestInterface;
|
||||
use Google\Site_Kit\Core\Util\URL;
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
use WP_User;
|
||||
|
||||
/**
|
||||
* Extended Google API client with custom functionality for Site Kit.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class Google_Site_Kit_Client extends Google_Client {
|
||||
|
||||
/**
|
||||
* Callback to pass a potential exception to while refreshing an access token.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @var callable|null
|
||||
*/
|
||||
protected $token_exception_callback;
|
||||
|
||||
/**
|
||||
* Construct the Google client.
|
||||
*
|
||||
* @since 1.2.0
|
||||
*
|
||||
* @param array $config Client configuration.
|
||||
*/
|
||||
public function __construct( array $config = array() ) {
|
||||
if ( isset( $config['token_exception_callback'] ) ) {
|
||||
$this->setTokenExceptionCallback( $config['token_exception_callback'] );
|
||||
}
|
||||
|
||||
unset( $config['token_exception_callback'] );
|
||||
|
||||
parent::__construct( $config );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the function to be called when fetching an access token results in an exception.
|
||||
*
|
||||
* @since 1.2.0
|
||||
*
|
||||
* @param callable $exception_callback Function accepting an exception as single parameter.
|
||||
*/
|
||||
public function setTokenExceptionCallback( callable $exception_callback ) {
|
||||
$this->token_exception_callback = $exception_callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not to return raw requests and returns a callback to reset to the previous value.
|
||||
*
|
||||
* @since 1.2.0
|
||||
*
|
||||
* @param bool $defer Whether or not to return raw requests.
|
||||
* @return callable Callback function that resets to the original $defer value.
|
||||
*/
|
||||
public function withDefer( $defer ) {
|
||||
$orig_defer = $this->shouldDefer();
|
||||
$this->setDefer( $defer );
|
||||
|
||||
// Return a function to restore the original refer value.
|
||||
return function () use ( $orig_defer ) {
|
||||
$this->setDefer( $orig_defer );
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds auth listeners to the HTTP client based on the credentials set in the Google API Client object.
|
||||
*
|
||||
* @since 1.2.0
|
||||
*
|
||||
* @param ClientInterface $http The HTTP client object.
|
||||
* @return ClientInterface The HTTP client object.
|
||||
*
|
||||
* @throws Exception Thrown when fetching a new access token via refresh token on-the-fly fails.
|
||||
*/
|
||||
public function authorize( ClientInterface $http = null ) {
|
||||
if ( $this->isUsingApplicationDefaultCredentials() ) {
|
||||
return parent::authorize( $http );
|
||||
}
|
||||
|
||||
$token = $this->getAccessToken();
|
||||
if ( isset( $token['refresh_token'] ) && $this->isAccessTokenExpired() ) {
|
||||
$callback = $this->getConfig( 'token_callback' );
|
||||
|
||||
try {
|
||||
$token_response = $this->fetchAccessTokenWithRefreshToken( $token['refresh_token'] );
|
||||
if ( $callback ) {
|
||||
// Due to original callback signature this can only accept the token itself.
|
||||
call_user_func( $callback, '', $token_response['access_token'] );
|
||||
}
|
||||
} catch ( Exception $e ) {
|
||||
// Pass exception to special callback if provided.
|
||||
if ( $this->token_exception_callback ) {
|
||||
call_user_func( $this->token_exception_callback, $e );
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
return parent::authorize( $http );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches an OAuth 2.0 access token by using a temporary code.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @since 1.2.0 Ported from Google_Site_Kit_Proxy_Client.
|
||||
*
|
||||
* @param string $code Temporary authorization code, or undelegated token code.
|
||||
* @return array Access token.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown when the passed code is empty.
|
||||
*/
|
||||
public function fetchAccessTokenWithAuthCode( $code ) {
|
||||
if ( strlen( $code ) === 0 ) {
|
||||
throw new InvalidArgumentException( 'Invalid code' );
|
||||
}
|
||||
|
||||
$auth = $this->getOAuth2Service();
|
||||
$auth->setCode( $code );
|
||||
$auth->setRedirectUri( $this->getRedirectUri() );
|
||||
|
||||
$http_handler = HttpHandlerFactory::build( $this->getHttpClient() );
|
||||
|
||||
$token_response = $this->fetchAuthToken( $auth, $http_handler );
|
||||
if ( $token_response && isset( $token_response['access_token'] ) ) {
|
||||
$token_response['created'] = time();
|
||||
$this->setAccessToken( $token_response );
|
||||
}
|
||||
|
||||
return $token_response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a fresh OAuth 2.0 access token by using a refresh token.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @since 1.2.0 Ported from Google_Site_Kit_Proxy_Client.
|
||||
*
|
||||
* @param string $refresh_token Optional. Refresh token. Unused here.
|
||||
* @param array $extra_params Optional. Array of extra parameters to fetch with.
|
||||
* @return array Access token.
|
||||
*
|
||||
* @throws LogicException Thrown when no refresh token is available.
|
||||
*/
|
||||
public function fetchAccessTokenWithRefreshToken( $refresh_token = null, $extra_params = array() ) {
|
||||
if ( null === $refresh_token ) {
|
||||
$refresh_token = $this->getRefreshToken();
|
||||
if ( ! $refresh_token ) {
|
||||
throw new LogicException( 'refresh token must be passed in or set as part of setAccessToken' );
|
||||
}
|
||||
}
|
||||
|
||||
$this->getLogger()->info( 'OAuth2 access token refresh' );
|
||||
$auth = $this->getOAuth2Service();
|
||||
$auth->setRefreshToken( $refresh_token );
|
||||
|
||||
$http_handler = HttpHandlerFactory::build( $this->getHttpClient() );
|
||||
|
||||
$token_response = $this->fetchAuthToken( $auth, $http_handler, $extra_params );
|
||||
if ( $token_response && isset( $token_response['access_token'] ) ) {
|
||||
$token_response['created'] = time();
|
||||
if ( ! isset( $token_response['refresh_token'] ) ) {
|
||||
$token_response['refresh_token'] = $refresh_token;
|
||||
}
|
||||
$this->setAccessToken( $token_response );
|
||||
|
||||
/**
|
||||
* Fires when the current user has just been reauthorized to access Google APIs with a refreshed access token.
|
||||
*
|
||||
* In other words, this action fires whenever Site Kit has just obtained a new access token based on
|
||||
* the refresh token for the current user, which typically happens once every hour when using Site Kit,
|
||||
* since that is the lifetime of every access token.
|
||||
*
|
||||
* @since 1.25.0
|
||||
*
|
||||
* @param array $token_response Token response data.
|
||||
*/
|
||||
do_action( 'googlesitekit_reauthorize_user', $token_response );
|
||||
}
|
||||
|
||||
return $token_response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes deferred HTTP requests.
|
||||
*
|
||||
* @since 1.38.0
|
||||
*
|
||||
* @param RequestInterface $request Request object to execute.
|
||||
* @param string $expected_class Expected class to return.
|
||||
* @return object An object of the type of the expected class or Psr\Http\Message\ResponseInterface.
|
||||
*/
|
||||
public function execute( RequestInterface $request, $expected_class = null ) {
|
||||
$request = $request->withHeader( 'X-Goog-Quota-User', self::getQuotaUser() );
|
||||
|
||||
return parent::execute( $request, $expected_class );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string that uniquely identifies a user of the application.
|
||||
*
|
||||
* @since 1.38.0
|
||||
*
|
||||
* @return string Unique user identifier.
|
||||
*/
|
||||
public static function getQuotaUser() {
|
||||
$user_id = get_current_user_id();
|
||||
$url = get_home_url();
|
||||
|
||||
$scheme = URL::parse( $url, PHP_URL_SCHEME );
|
||||
$host = URL::parse( $url, PHP_URL_HOST );
|
||||
$path = URL::parse( $url, PHP_URL_PATH );
|
||||
|
||||
return "{$scheme}://{$user_id}@{$host}{$path}";
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @since 1.2.0 Ported from Google_Site_Kit_Proxy_Client.
|
||||
*
|
||||
* @param OAuth2 $auth OAuth2 instance.
|
||||
* @param callable|null $http_handler Optional. HTTP handler callback. Default null.
|
||||
* @param array $extra_params Optional. Array of extra parameters to fetch with.
|
||||
* @return array Access token.
|
||||
*/
|
||||
protected function fetchAuthToken( OAuth2 $auth, callable $http_handler = null, $extra_params = array() ) {
|
||||
if ( is_null( $http_handler ) ) {
|
||||
$http_handler = HttpHandlerFactory::build( HttpClientCache::getHttpClient() );
|
||||
}
|
||||
|
||||
$request = $auth->generateCredentialsRequest( $extra_params );
|
||||
$response = $http_handler( $request );
|
||||
$credentials = $auth->parseTokenResponse( $response );
|
||||
if ( ! empty( $credentials['error'] ) ) {
|
||||
$this->handleAuthTokenErrorResponse( $credentials['error'], $credentials );
|
||||
}
|
||||
|
||||
$auth->updateToken( $credentials );
|
||||
|
||||
return $credentials;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an erroneous response from a request to fetch an auth token.
|
||||
*
|
||||
* @since 1.2.0
|
||||
*
|
||||
* @param string $error Error code / error message.
|
||||
* @param array $data Associative array of full response data.
|
||||
*
|
||||
* @throws Google_OAuth_Exception Thrown with the given $error as message.
|
||||
*/
|
||||
protected function handleAuthTokenErrorResponse( $error, array $data ) {
|
||||
throw new Google_OAuth_Exception( $error );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a default Google OAuth2 object.
|
||||
*
|
||||
* @return OAuth2 Created OAuth2 instance.
|
||||
*/
|
||||
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->getConfig( 'client_id' ),
|
||||
'signingKey' => $this->getConfig( 'signing_key' ),
|
||||
'signingAlgorithm' => $this->getConfig( 'signing_algorithm' ),
|
||||
)
|
||||
);
|
||||
|
||||
return $auth;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Authentication\Clients\Google_Site_Kit_Proxy_Client
|
||||
*
|
||||
* @package Google\Site_Kit
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Authentication\Clients;
|
||||
|
||||
use Google\Site_Kit\Core\Authentication\Google_Proxy;
|
||||
use Google\Site_Kit\Core\Authentication\Clients\OAuth2;
|
||||
use Google\Site_Kit\Core\Authentication\Exception\Google_Proxy_Code_Exception;
|
||||
use Google\Site_Kit_Dependencies\Google\Auth\HttpHandler\HttpHandlerFactory;
|
||||
use Google\Site_Kit_Dependencies\GuzzleHttp\Psr7;
|
||||
use Google\Site_Kit_Dependencies\GuzzleHttp\Psr7\Request;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Modified Google Site Kit API client relying on the authentication proxy.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @since 1.2.0 Renamed to Google_Site_Kit_Proxy_Client.
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class Google_Site_Kit_Proxy_Client extends Google_Site_Kit_Client {
|
||||
|
||||
/**
|
||||
* Base URL to the proxy.
|
||||
*
|
||||
* @since 1.1.2
|
||||
* @var string
|
||||
*/
|
||||
protected $proxy_base_path = Google_Proxy::PRODUCTION_BASE_URL;
|
||||
|
||||
/**
|
||||
* Construct the Google client.
|
||||
*
|
||||
* @since 1.1.2
|
||||
*
|
||||
* @param array $config Proxy client configuration.
|
||||
*/
|
||||
public function __construct( array $config = array() ) {
|
||||
if ( ! empty( $config['proxy_base_path'] ) ) {
|
||||
$this->setProxyBasePath( $config['proxy_base_path'] );
|
||||
}
|
||||
|
||||
unset( $config['proxy_base_path'] );
|
||||
|
||||
parent::__construct( $config );
|
||||
|
||||
$this->setApplicationName( Google_Proxy::get_application_name() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the base URL to the proxy.
|
||||
*
|
||||
* @since 1.2.0
|
||||
*
|
||||
* @param string $base_path Proxy base URL.
|
||||
*/
|
||||
public function setProxyBasePath( $base_path ) {
|
||||
$this->proxy_base_path = untrailingslashit( $base_path );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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',
|
||||
$this->proxy_base_path . Google_Proxy::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() {
|
||||
return new OAuth2(
|
||||
array(
|
||||
'clientId' => $this->getClientId(),
|
||||
'clientSecret' => $this->getClientSecret(),
|
||||
'authorizationUri' => $this->proxy_base_path . Google_Proxy::OAUTH2_AUTH_URI,
|
||||
'tokenCredentialUri' => $this->proxy_base_path . Google_Proxy::OAUTH2_TOKEN_URI,
|
||||
'redirectUri' => $this->getRedirectUri(),
|
||||
'issuer' => $this->getClientId(),
|
||||
'signingKey' => null,
|
||||
'signingAlgorithm' => null,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Handles an erroneous response from a request to fetch an auth token.
|
||||
*
|
||||
* @since 1.2.0
|
||||
*
|
||||
* @param string $error Error code / error message.
|
||||
* @param array $data Associative array of full response data.
|
||||
*
|
||||
* @throws Google_Proxy_Code_Exception Thrown when proxy returns an error accompanied by a temporary access code.
|
||||
*/
|
||||
protected function handleAuthTokenErrorResponse( $error, array $data ) {
|
||||
if ( ! empty( $data['code'] ) ) {
|
||||
throw new Google_Proxy_Code_Exception( $error, 0, $data['code'] );
|
||||
}
|
||||
parent::handleAuthTokenErrorResponse( $error, $data );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Authentication\Clients\OAuth2
|
||||
*
|
||||
* @package Google\Site_Kit
|
||||
* @copyright 2022 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Authentication\Clients;
|
||||
|
||||
use Google\Site_Kit_Dependencies\Google\Auth\OAuth2 as Google_Service_OAuth2;
|
||||
use Google\Site_Kit_Dependencies\GuzzleHttp\Psr7\Utils;
|
||||
use Google\Site_Kit_Dependencies\GuzzleHttp\Psr7\Query;
|
||||
use Google\Site_Kit_Dependencies\Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* Class for connecting to Google APIs via OAuth2.
|
||||
*
|
||||
* @since 1.87.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class OAuth2 extends Google_Service_OAuth2 {
|
||||
|
||||
/**
|
||||
* Overrides generateCredentialsRequest with additional parameters.
|
||||
*
|
||||
* @since 1.87.0
|
||||
*
|
||||
* @param array $extra_params Optional. Array of extra parameters to fetch with.
|
||||
* @return RequestInterface Token credentials request.
|
||||
*/
|
||||
public function generateCredentialsRequest( $extra_params = array() ) {
|
||||
$request = parent::generateCredentialsRequest();
|
||||
$grant_type = $this->getGrantType();
|
||||
|
||||
if ( empty( $extra_params ) || 'refresh_token' !== $grant_type ) {
|
||||
return $request;
|
||||
}
|
||||
|
||||
$params = array(
|
||||
'body' => Query::build(
|
||||
array_merge(
|
||||
Query::parse( Utils::copyToString( $request->getBody() ) ),
|
||||
$extra_params
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
return Utils::modifyRequest( $request, $params );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,667 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Authentication\Clients\OAuth_Client
|
||||
*
|
||||
* @package Google\Site_Kit
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Authentication\Clients;
|
||||
|
||||
use Exception;
|
||||
use Google\Site_Kit\Context;
|
||||
use Google\Site_Kit\Core\Authentication\Credentials;
|
||||
use Google\Site_Kit\Core\Authentication\Exception\Google_Proxy_Code_Exception;
|
||||
use Google\Site_Kit\Core\Authentication\Google_Proxy;
|
||||
use Google\Site_Kit\Core\Authentication\Owner_ID;
|
||||
use Google\Site_Kit\Core\Authentication\Profile;
|
||||
use Google\Site_Kit\Core\Authentication\Token;
|
||||
use Google\Site_Kit\Core\Dashboard_Sharing\Activity_Metrics\Activity_Metrics;
|
||||
use Google\Site_Kit\Core\Dashboard_Sharing\Activity_Metrics\Active_Consumers;
|
||||
use Google\Site_Kit\Core\Permissions\Permissions;
|
||||
use Google\Site_Kit\Core\Storage\Options;
|
||||
use Google\Site_Kit\Core\Storage\User_Options;
|
||||
use Google\Site_Kit\Core\Util\Scopes;
|
||||
use Google\Site_Kit\Core\Util\URL;
|
||||
use Google\Site_Kit_Dependencies\Google\Service\PeopleService as Google_Service_PeopleService;
|
||||
use WP_User;
|
||||
|
||||
/**
|
||||
* Class for connecting to Google APIs via OAuth.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @since 1.39.0 Now extends `OAuth_Client_Base`.
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
final class OAuth_Client extends OAuth_Client_Base {
|
||||
|
||||
const OPTION_ADDITIONAL_AUTH_SCOPES = 'googlesitekit_additional_auth_scopes';
|
||||
const OPTION_REDIRECT_URL = 'googlesitekit_redirect_url';
|
||||
const CRON_REFRESH_PROFILE_DATA = 'googlesitekit_cron_refresh_profile_data';
|
||||
|
||||
/**
|
||||
* Owner_ID instance.
|
||||
*
|
||||
* @since 1.16.0
|
||||
* @var Owner_ID
|
||||
*/
|
||||
private $owner_id;
|
||||
|
||||
/**
|
||||
* Activity_Metrics instance.
|
||||
*
|
||||
* @since 1.87.0
|
||||
* @var Activity_Metrics
|
||||
*/
|
||||
private $activity_metrics;
|
||||
|
||||
/**
|
||||
* Active_Consumers instance.
|
||||
*
|
||||
* @since 1.87.0
|
||||
* @var Active_Consumers
|
||||
*/
|
||||
private $active_consumers;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param Context $context Plugin context.
|
||||
* @param Options $options Optional. Option API instance. Default is a new instance.
|
||||
* @param User_Options $user_options Optional. User Option API instance. Default is a new instance.
|
||||
* @param Credentials $credentials Optional. Credentials instance. Default is a new instance from $options.
|
||||
* @param Google_Proxy $google_proxy Optional. Google proxy instance. Default is a new instance.
|
||||
* @param Profile $profile Optional. Profile instance. Default is a new instance.
|
||||
* @param Token $token Optional. Token instance. Default is a new instance.
|
||||
*/
|
||||
public function __construct(
|
||||
Context $context,
|
||||
Options $options = null,
|
||||
User_Options $user_options = null,
|
||||
Credentials $credentials = null,
|
||||
Google_Proxy $google_proxy = null,
|
||||
Profile $profile = null,
|
||||
Token $token = null
|
||||
) {
|
||||
parent::__construct(
|
||||
$context,
|
||||
$options,
|
||||
$user_options,
|
||||
$credentials,
|
||||
$google_proxy,
|
||||
$profile,
|
||||
$token
|
||||
);
|
||||
|
||||
$this->owner_id = new Owner_ID( $this->options );
|
||||
$this->activity_metrics = new Activity_Metrics( $this->context, $this->user_options );
|
||||
$this->active_consumers = new Active_Consumers( $this->user_options );
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the access token.
|
||||
*
|
||||
* While this method can be used to explicitly refresh the current access token, the preferred way
|
||||
* should be to rely on the Google_Site_Kit_Client to do that automatically whenever the current access token
|
||||
* has expired.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function refresh_token() {
|
||||
$token = $this->get_token();
|
||||
if ( empty( $token['refresh_token'] ) ) {
|
||||
$this->delete_token();
|
||||
$this->user_options->set( self::OPTION_ERROR_CODE, 'refresh_token_not_exist' );
|
||||
return;
|
||||
}
|
||||
|
||||
$active_consumers = $this->activity_metrics->get_for_refresh_token();
|
||||
|
||||
try {
|
||||
$token_response = $this->get_client()->fetchAccessTokenWithRefreshToken( $token['refresh_token'], $active_consumers );
|
||||
} catch ( \Exception $e ) {
|
||||
$this->handle_fetch_token_exception( $e );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $token_response['access_token'] ) ) {
|
||||
$this->user_options->set( self::OPTION_ERROR_CODE, 'access_token_not_received' );
|
||||
return;
|
||||
}
|
||||
|
||||
$this->active_consumers->delete();
|
||||
$this->set_token( $token_response );
|
||||
}
|
||||
|
||||
/**
|
||||
* Revokes the access token.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function revoke_token() {
|
||||
try {
|
||||
$this->get_client()->revokeToken();
|
||||
} catch ( \Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement
|
||||
// No special handling, we just need to make sure this goes through.
|
||||
}
|
||||
|
||||
$this->delete_token();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of currently granted Google OAuth scopes for the current user.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @see https://developers.google.com/identity/protocols/googlescopes
|
||||
*
|
||||
* @return string[] List of Google OAuth scopes.
|
||||
*/
|
||||
public function get_granted_scopes() {
|
||||
$base_scopes = parent::get_granted_scopes();
|
||||
$extra_scopes = $this->get_granted_additional_scopes();
|
||||
|
||||
return array_unique(
|
||||
array_merge( $base_scopes, $extra_scopes )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of currently granted additional Google OAuth scopes for the current user.
|
||||
*
|
||||
* Scopes are considered "additional scopes" if they were granted to perform a specific action,
|
||||
* rather than being granted as an overall required scope.
|
||||
*
|
||||
* @since 1.9.0
|
||||
* @see https://developers.google.com/identity/protocols/googlescopes
|
||||
*
|
||||
* @return string[] List of Google OAuth scopes.
|
||||
*/
|
||||
public function get_granted_additional_scopes() {
|
||||
return array_values( $this->user_options->get( self::OPTION_ADDITIONAL_AUTH_SCOPES ) ?: array() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if new scopes are required that are not yet granted for the current user.
|
||||
*
|
||||
* @since 1.9.0
|
||||
*
|
||||
* @return bool true if any required scopes are not satisfied, otherwise false.
|
||||
*/
|
||||
public function needs_reauthentication() {
|
||||
if ( ! $this->token->has() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ! $this->has_sufficient_scopes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of scopes which are not satisfied by the currently granted scopes.
|
||||
*
|
||||
* @since 1.9.0
|
||||
*
|
||||
* @param string[] $scopes Optional. List of scopes to test against granted scopes.
|
||||
* Default is the list of required scopes.
|
||||
* @return string[] Filtered $scopes list, only including scopes that are not satisfied.
|
||||
*/
|
||||
public function get_unsatisfied_scopes( array $scopes = null ) {
|
||||
if ( null === $scopes ) {
|
||||
$scopes = $this->get_required_scopes();
|
||||
}
|
||||
|
||||
$granted_scopes = $this->get_granted_scopes();
|
||||
$unsatisfied_scopes = array_filter(
|
||||
$scopes,
|
||||
function( $scope ) use ( $granted_scopes ) {
|
||||
return ! Scopes::is_satisfied_by( $scope, $granted_scopes );
|
||||
}
|
||||
);
|
||||
|
||||
return array_values( $unsatisfied_scopes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not currently granted scopes are sufficient for the given list.
|
||||
*
|
||||
* @since 1.9.0
|
||||
*
|
||||
* @param string[] $scopes Optional. List of scopes to test against granted scopes.
|
||||
* Default is the list of required scopes.
|
||||
* @return bool True if all $scopes are satisfied, false otherwise.
|
||||
*/
|
||||
public function has_sufficient_scopes( array $scopes = null ) {
|
||||
if ( null === $scopes ) {
|
||||
$scopes = $this->get_required_scopes();
|
||||
}
|
||||
return Scopes::are_satisfied_by( $scopes, $this->get_granted_scopes() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of currently granted Google OAuth scopes for the current user.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @see https://developers.google.com/identity/protocols/googlescopes
|
||||
*
|
||||
* @param string[] $scopes List of Google OAuth scopes.
|
||||
*/
|
||||
public function set_granted_scopes( $scopes ) {
|
||||
$required_scopes = $this->get_required_scopes();
|
||||
$base_scopes = array();
|
||||
$extra_scopes = array();
|
||||
|
||||
foreach ( $scopes as $scope ) {
|
||||
if ( in_array( $scope, $required_scopes, true ) ) {
|
||||
$base_scopes[] = $scope;
|
||||
} else {
|
||||
$extra_scopes[] = $scope;
|
||||
}
|
||||
}
|
||||
|
||||
parent::set_granted_scopes( $base_scopes );
|
||||
$this->user_options->set( self::OPTION_ADDITIONAL_AUTH_SCOPES, $extra_scopes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current user's OAuth access token.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return string|bool Access token if it exists, false otherwise.
|
||||
*/
|
||||
public function get_access_token() {
|
||||
$token = $this->get_token();
|
||||
if ( empty( $token['access_token'] ) ) {
|
||||
return false;
|
||||
}
|
||||
return $token['access_token'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current user's OAuth access token.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @deprecated 1.39.0 Use `OAuth_Client::set_token` instead.
|
||||
*
|
||||
* @param string $access_token New access token.
|
||||
* @param int $expires_in TTL of the access token in seconds.
|
||||
* @param int $created Optional. Timestamp when the token was created, in GMT. Default is the current time.
|
||||
* @return bool True on success, false on failure.
|
||||
*/
|
||||
public function set_access_token( $access_token, $expires_in, $created = 0 ) {
|
||||
_deprecated_function( __METHOD__, '1.39.0', self::class . '::set_token' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
|
||||
return $this->set_token(
|
||||
array(
|
||||
'access_token' => $access_token,
|
||||
'expires_in' => $expires_in,
|
||||
'created' => $created,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current user's OAuth refresh token.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @deprecated 1.39.0 Use `OAuth_Client::get_token` instead.
|
||||
*
|
||||
* @return string|bool Refresh token if it exists, false otherwise.
|
||||
*/
|
||||
public function get_refresh_token() {
|
||||
_deprecated_function( __METHOD__, '1.39.0', self::class . '::get_token' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
|
||||
$token = $this->get_token();
|
||||
if ( empty( $token['refresh_token'] ) ) {
|
||||
return false;
|
||||
}
|
||||
return $token['refresh_token'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current user's OAuth refresh token.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @deprecated 1.39.0 Use `OAuth_Client::set_token` instead.
|
||||
*
|
||||
* @param string $refresh_token New refresh token.
|
||||
* @return bool True on success, false on failure.
|
||||
*/
|
||||
public function set_refresh_token( $refresh_token ) {
|
||||
_deprecated_function( __METHOD__, '1.39.0', self::class . '::set_token' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
|
||||
$token = $this->get_token();
|
||||
$token['refresh_token'] = $refresh_token;
|
||||
return $this->set_token( $token );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the authentication URL.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @since 1.9.0 Added $additional_scopes parameter.
|
||||
* @since 1.34.1 Updated handling of $additional_scopes to restore rewritten scope.
|
||||
*
|
||||
* @param string $redirect_url Redirect URL after authentication.
|
||||
* @param string[] $additional_scopes List of additional scopes to request.
|
||||
* @return string Authentication URL.
|
||||
*/
|
||||
public function get_authentication_url( $redirect_url = '', $additional_scopes = array() ) {
|
||||
if ( empty( $redirect_url ) ) {
|
||||
$redirect_url = $this->context->admin_url( 'splash' );
|
||||
}
|
||||
if ( is_array( $additional_scopes ) ) {
|
||||
// Rewrite each scope to convert `gttp` -> `http`, if it starts with this placeholder scheme.
|
||||
// This restores the original scope rewritten by getConnectURL.
|
||||
$additional_scopes = array_map(
|
||||
function ( $scope ) {
|
||||
return preg_replace( '/^gttp(s)?:/', 'http$1:', $scope );
|
||||
},
|
||||
$additional_scopes
|
||||
);
|
||||
} else {
|
||||
$additional_scopes = array();
|
||||
}
|
||||
|
||||
$url_query = URL::parse( $redirect_url, PHP_URL_QUERY );
|
||||
|
||||
if ( $url_query ) {
|
||||
parse_str( $url_query, $query_args );
|
||||
}
|
||||
|
||||
if ( empty( $query_args['notification'] ) ) {
|
||||
$redirect_url = add_query_arg( array( 'notification' => 'authentication_success' ), $redirect_url );
|
||||
}
|
||||
// Ensure we remove error query string.
|
||||
$redirect_url = remove_query_arg( 'error', $redirect_url );
|
||||
|
||||
$this->user_options->set( self::OPTION_REDIRECT_URL, $redirect_url );
|
||||
|
||||
// Ensure the latest required scopes are requested.
|
||||
$scopes = array_merge( $this->get_required_scopes(), $additional_scopes );
|
||||
$this->get_client()->setScopes( array_unique( $scopes ) );
|
||||
|
||||
return add_query_arg(
|
||||
$this->google_proxy->get_metadata_fields(),
|
||||
$this->get_client()->createAuthUrl()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects the current user to the Google OAuth consent screen, or processes a response from that consent
|
||||
* screen if present.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @since 1.49.0 Uses the new `Google_Proxy::setup_url_v2` method when the `serviceSetupV2` feature flag is enabled.
|
||||
*/
|
||||
public function authorize_user() {
|
||||
$code = htmlspecialchars( $this->context->input()->filter( INPUT_GET, 'code' ) );
|
||||
$error_code = htmlspecialchars( $this->context->input()->filter( INPUT_GET, 'error' ) );
|
||||
// If the OAuth redirects with an error code, handle it.
|
||||
if ( ! empty( $error_code ) ) {
|
||||
$this->user_options->set( self::OPTION_ERROR_CODE, $error_code );
|
||||
wp_safe_redirect( $this->authorize_user_redirect_url() );
|
||||
exit();
|
||||
}
|
||||
|
||||
if ( ! $this->credentials->has() ) {
|
||||
$this->user_options->set( self::OPTION_ERROR_CODE, 'oauth_credentials_not_exist' );
|
||||
wp_safe_redirect( $this->authorize_user_redirect_url() );
|
||||
exit();
|
||||
}
|
||||
|
||||
try {
|
||||
$token_response = $this->get_client()->fetchAccessTokenWithAuthCode( $code );
|
||||
} catch ( Google_Proxy_Code_Exception $e ) {
|
||||
// Redirect back to proxy immediately with the access code.
|
||||
$credentials = $this->credentials->get();
|
||||
$params = array(
|
||||
'code' => $e->getAccessCode(),
|
||||
'site_id' => ! empty( $credentials['oauth2_client_id'] ) ? $credentials['oauth2_client_id'] : '',
|
||||
);
|
||||
$params = $this->google_proxy->add_setup_step_from_error_code( $params, $e->getMessage() );
|
||||
$url = $this->google_proxy->setup_url( $params );
|
||||
|
||||
wp_safe_redirect( $url );
|
||||
exit();
|
||||
} catch ( Exception $e ) {
|
||||
$this->handle_fetch_token_exception( $e );
|
||||
wp_safe_redirect( $this->authorize_user_redirect_url() );
|
||||
exit();
|
||||
}
|
||||
|
||||
if ( ! isset( $token_response['access_token'] ) ) {
|
||||
$this->user_options->set( self::OPTION_ERROR_CODE, 'access_token_not_received' );
|
||||
wp_safe_redirect( $this->authorize_user_redirect_url() );
|
||||
exit();
|
||||
}
|
||||
|
||||
// Update the access token and refresh token.
|
||||
$this->set_token( $token_response );
|
||||
|
||||
// Store the previously granted scopes for use in the action below before they're updated.
|
||||
$previous_scopes = $this->get_granted_scopes();
|
||||
|
||||
// Update granted scopes.
|
||||
if ( isset( $token_response['scope'] ) ) {
|
||||
$scopes = explode( ' ', sanitize_text_field( $token_response['scope'] ) );
|
||||
} elseif ( $this->context->input()->filter( INPUT_GET, 'scope' ) ) {
|
||||
$scope = htmlspecialchars( $this->context->input()->filter( INPUT_GET, 'scope' ) );
|
||||
$scopes = explode( ' ', $scope );
|
||||
} else {
|
||||
$scopes = $this->get_required_scopes();
|
||||
}
|
||||
$scopes = array_filter(
|
||||
$scopes,
|
||||
function( $scope ) {
|
||||
if ( ! is_string( $scope ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( in_array( $scope, array( 'openid', 'profile', 'email' ), true ) ) {
|
||||
return true;
|
||||
}
|
||||
return 0 === strpos( $scope, 'https://www.googleapis.com/auth/' );
|
||||
}
|
||||
);
|
||||
$this->set_granted_scopes( $scopes );
|
||||
|
||||
$this->refresh_profile_data( 2 * MINUTE_IN_SECONDS );
|
||||
|
||||
/**
|
||||
* Fires when the current user has just been authorized to access Google APIs.
|
||||
*
|
||||
* In other words, this action fires whenever Site Kit has just obtained a new set of access token and
|
||||
* refresh token for the current user, which may happen to set up the initial connection or to request
|
||||
* access to further scopes.
|
||||
*
|
||||
* @since 1.3.0
|
||||
* @since 1.6.0 The $token_response parameter was added.
|
||||
* @since 1.30.0 The $scopes and $previous_scopes parameters were added.
|
||||
*
|
||||
* @param array $token_response Token response data.
|
||||
* @param string[] $scopes List of scopes.
|
||||
* @param string[] $previous_scopes List of previous scopes.
|
||||
*/
|
||||
do_action( 'googlesitekit_authorize_user', $token_response, $scopes, $previous_scopes );
|
||||
|
||||
// This must happen after googlesitekit_authorize_user as the permissions checks depend on
|
||||
// values set which affect the meta capability mapping.
|
||||
$current_user_id = get_current_user_id();
|
||||
if ( $this->should_update_owner_id( $current_user_id ) ) {
|
||||
$this->owner_id->set( $current_user_id );
|
||||
}
|
||||
|
||||
$redirect_url = $this->user_options->get( self::OPTION_REDIRECT_URL );
|
||||
|
||||
if ( $redirect_url ) {
|
||||
$url_query = URL::parse( $redirect_url, PHP_URL_QUERY );
|
||||
|
||||
if ( $url_query ) {
|
||||
parse_str( $url_query, $query_args );
|
||||
}
|
||||
|
||||
$reauth = isset( $query_args['reAuth'] ) && 'true' === $query_args['reAuth'];
|
||||
|
||||
if ( false === $reauth && empty( $query_args['notification'] ) ) {
|
||||
$redirect_url = add_query_arg( array( 'notification' => 'authentication_success' ), $redirect_url );
|
||||
}
|
||||
$this->user_options->delete( self::OPTION_REDIRECT_URL );
|
||||
} else {
|
||||
// No redirect_url is set, use default page.
|
||||
$redirect_url = $this->context->admin_url( 'splash', array( 'notification' => 'authentication_success' ) );
|
||||
}
|
||||
|
||||
wp_safe_redirect( $redirect_url );
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and updates the user profile data for the currently authenticated Google account.
|
||||
*
|
||||
* @since 1.1.4
|
||||
* @since 1.13.0 Added $retry_after param, also made public.
|
||||
*
|
||||
* @param int $retry_after Optional. Number of seconds to retry data fetch if unsuccessful.
|
||||
*/
|
||||
public function refresh_profile_data( $retry_after = 0 ) {
|
||||
$client = $this->get_client();
|
||||
$restore_defer = $client->withDefer( false );
|
||||
|
||||
try {
|
||||
$people_service = new Google_Service_PeopleService( $client );
|
||||
$response = $people_service->people->get( 'people/me', array( 'personFields' => 'emailAddresses,photos,names' ) );
|
||||
|
||||
if ( isset( $response['emailAddresses'][0]['value'], $response['photos'][0]['url'], $response['names'][0]['displayName'] ) ) {
|
||||
$this->profile->set(
|
||||
array(
|
||||
'email' => $response['emailAddresses'][0]['value'],
|
||||
'photo' => $response['photos'][0]['url'],
|
||||
'full_name' => $response['names'][0]['displayName'],
|
||||
'last_updated' => time(),
|
||||
)
|
||||
);
|
||||
}
|
||||
// Clear any scheduled job to refresh this data later, if any.
|
||||
wp_clear_scheduled_hook(
|
||||
self::CRON_REFRESH_PROFILE_DATA,
|
||||
array( $this->user_options->get_user_id() )
|
||||
);
|
||||
} catch ( Exception $e ) {
|
||||
$retry_after = absint( $retry_after );
|
||||
if ( $retry_after < 1 ) {
|
||||
return;
|
||||
}
|
||||
wp_schedule_single_event(
|
||||
time() + $retry_after,
|
||||
self::CRON_REFRESH_PROFILE_DATA,
|
||||
array( $this->user_options->get_user_id() )
|
||||
);
|
||||
} finally {
|
||||
$restore_defer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @deprecated 1.9.0
|
||||
*
|
||||
* @return bool True if proxy authentication is used, false otherwise.
|
||||
*/
|
||||
public function using_proxy() {
|
||||
_deprecated_function( __METHOD__, '1.9.0', Credentials::class . '::using_proxy' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
|
||||
return $this->credentials->using_proxy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the current owner ID must be changed or not.
|
||||
*
|
||||
* @since 1.16.0
|
||||
*
|
||||
* @param int $user_id Current user ID.
|
||||
* @return bool TRUE if owner needs to be changed, otherwise FALSE.
|
||||
*/
|
||||
private function should_update_owner_id( $user_id ) {
|
||||
$current_owner_id = $this->owner_id->get();
|
||||
if ( $current_owner_id === $user_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! empty( $current_owner_id ) && user_can( $current_owner_id, Permissions::MANAGE_OPTIONS ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! user_can( $user_id, Permissions::MANAGE_OPTIONS ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 '';
|
||||
}
|
||||
|
||||
return $this->google_proxy->permissions_url(
|
||||
$this->credentials,
|
||||
array( 'token' => $access_token )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the current user's token and all associated data.
|
||||
*
|
||||
* @since 1.0.3
|
||||
*/
|
||||
protected function delete_token() {
|
||||
parent::delete_token();
|
||||
|
||||
$this->user_options->delete( self::OPTION_REDIRECT_URL );
|
||||
$this->user_options->delete( self::OPTION_ADDITIONAL_AUTH_SCOPES );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the URL for the user to view the dashboard/splash
|
||||
* page based on their permissions.
|
||||
*
|
||||
* @since 1.77.0
|
||||
*/
|
||||
private function authorize_user_redirect_url() {
|
||||
return current_user_can( Permissions::VIEW_DASHBOARD )
|
||||
? $this->context->admin_url( 'dashboard' )
|
||||
: $this->context->admin_url( 'splash' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a user to the active consumers list.
|
||||
*
|
||||
* @since 1.87.0
|
||||
*
|
||||
* @param WP_User $user User object.
|
||||
*/
|
||||
public function add_active_consumer( WP_User $user ) {
|
||||
$this->active_consumers->add( $user->ID, $user->roles );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Authentication\Clients\OAuth_Client_Base
|
||||
*
|
||||
* @package Google\Site_Kit
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Authentication\Clients;
|
||||
|
||||
use Exception;
|
||||
use Google\Site_Kit\Context;
|
||||
use Google\Site_Kit\Core\Authentication\Credentials;
|
||||
use Google\Site_Kit\Core\Authentication\Exception\Google_Proxy_Code_Exception;
|
||||
use Google\Site_Kit\Core\Authentication\Google_Proxy;
|
||||
use Google\Site_Kit\Core\Authentication\Profile;
|
||||
use Google\Site_Kit\Core\Authentication\Token;
|
||||
use Google\Site_Kit\Core\Permissions\Permissions;
|
||||
use Google\Site_Kit\Core\Storage\Encrypted_Options;
|
||||
use Google\Site_Kit\Core\Storage\Options;
|
||||
use Google\Site_Kit\Core\Storage\User_Options;
|
||||
|
||||
/**
|
||||
* Base class for connecting to Google APIs via OAuth.
|
||||
*
|
||||
* @since 1.39.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
abstract class OAuth_Client_Base {
|
||||
|
||||
const OPTION_ACCESS_TOKEN = 'googlesitekit_access_token';
|
||||
const OPTION_ACCESS_TOKEN_EXPIRES_IN = 'googlesitekit_access_token_expires_in';
|
||||
const OPTION_ACCESS_TOKEN_CREATED = 'googlesitekit_access_token_created_at';
|
||||
const OPTION_REFRESH_TOKEN = 'googlesitekit_refresh_token';
|
||||
const OPTION_AUTH_SCOPES = 'googlesitekit_auth_scopes';
|
||||
const OPTION_ERROR_CODE = 'googlesitekit_error_code';
|
||||
const OPTION_PROXY_ACCESS_CODE = 'googlesitekit_proxy_access_code';
|
||||
|
||||
/**
|
||||
* Plugin context.
|
||||
*
|
||||
* @since 1.39.0
|
||||
* @var Context
|
||||
*/
|
||||
protected $context;
|
||||
|
||||
/**
|
||||
* Options instance
|
||||
*
|
||||
* @since 1.39.0
|
||||
* @var Options
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* User_Options instance
|
||||
*
|
||||
* @since 1.39.0
|
||||
* @var User_Options
|
||||
*/
|
||||
protected $user_options;
|
||||
|
||||
/**
|
||||
* OAuth credentials instance.
|
||||
*
|
||||
* @since 1.39.0
|
||||
* @var Credentials
|
||||
*/
|
||||
protected $credentials;
|
||||
|
||||
/**
|
||||
* Google_Proxy instance.
|
||||
*
|
||||
* @since 1.39.0
|
||||
* @var Google_Proxy
|
||||
*/
|
||||
protected $google_proxy;
|
||||
|
||||
/**
|
||||
* Google Client object.
|
||||
*
|
||||
* @since 1.39.0
|
||||
* @var Google_Site_Kit_Client
|
||||
*/
|
||||
protected $google_client;
|
||||
|
||||
/**
|
||||
* Profile instance.
|
||||
*
|
||||
* @since 1.39.0
|
||||
* @var Profile
|
||||
*/
|
||||
protected $profile;
|
||||
|
||||
/**
|
||||
* Token instance.
|
||||
*
|
||||
* @since 1.39.0
|
||||
* @var Token
|
||||
*/
|
||||
protected $token;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 1.39.0
|
||||
*
|
||||
* @param Context $context Plugin context.
|
||||
* @param Options $options Optional. Option API instance. Default is a new instance.
|
||||
* @param User_Options $user_options Optional. User Option API instance. Default is a new instance.
|
||||
* @param Credentials $credentials Optional. Credentials instance. Default is a new instance from $options.
|
||||
* @param Google_Proxy $google_proxy Optional. Google proxy instance. Default is a new instance.
|
||||
* @param Profile $profile Optional. Profile instance. Default is a new instance.
|
||||
* @param Token $token Optional. Token instance. Default is a new instance.
|
||||
*/
|
||||
public function __construct(
|
||||
Context $context,
|
||||
Options $options = null,
|
||||
User_Options $user_options = null,
|
||||
Credentials $credentials = null,
|
||||
Google_Proxy $google_proxy = null,
|
||||
Profile $profile = null,
|
||||
Token $token = null
|
||||
) {
|
||||
$this->context = $context;
|
||||
$this->options = $options ?: new Options( $this->context );
|
||||
$this->user_options = $user_options ?: new User_Options( $this->context );
|
||||
$this->credentials = $credentials ?: new Credentials( new Encrypted_Options( $this->options ) );
|
||||
$this->google_proxy = $google_proxy ?: new Google_Proxy( $this->context );
|
||||
$this->profile = $profile ?: new Profile( $this->user_options );
|
||||
$this->token = $token ?: new Token( $this->user_options );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Google client object.
|
||||
*
|
||||
* @since 1.39.0
|
||||
* @since 1.2.0 Now always returns a Google_Site_Kit_Client.
|
||||
*
|
||||
* @return Google_Site_Kit_Client Google client object.
|
||||
*/
|
||||
public function get_client() {
|
||||
if ( ! $this->google_client instanceof Google_Site_Kit_Client ) {
|
||||
$credentials = $this->credentials->get();
|
||||
|
||||
$this->google_client = Client_Factory::create_client(
|
||||
array(
|
||||
'client_id' => $credentials['oauth2_client_id'],
|
||||
'client_secret' => $credentials['oauth2_client_secret'],
|
||||
'redirect_uri' => $this->get_redirect_uri(),
|
||||
'token' => $this->get_token(),
|
||||
'token_callback' => array( $this, 'set_token' ),
|
||||
'token_exception_callback' => function( Exception $e ) {
|
||||
$this->handle_fetch_token_exception( $e );
|
||||
},
|
||||
'required_scopes' => $this->get_required_scopes(),
|
||||
'login_hint_email' => $this->profile->has() ? $this->profile->get()['email'] : '',
|
||||
'using_proxy' => $this->credentials->using_proxy(),
|
||||
'proxy_url' => $this->google_proxy->url(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $this->google_client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of currently required Google OAuth scopes.
|
||||
*
|
||||
* @since 1.39.0
|
||||
* @see https://developers.google.com/identity/protocols/googlescopes
|
||||
*
|
||||
* @return array List of Google OAuth scopes.
|
||||
*/
|
||||
public function get_required_scopes() {
|
||||
/**
|
||||
* Filters the list of required Google OAuth scopes.
|
||||
*
|
||||
* See all Google oauth scopes here: https://developers.google.com/identity/protocols/googlescopes
|
||||
*
|
||||
* @since 1.39.0
|
||||
*
|
||||
* @param array $scopes List of scopes.
|
||||
*/
|
||||
$scopes = (array) apply_filters( 'googlesitekit_auth_scopes', array() );
|
||||
|
||||
return array_unique(
|
||||
array_merge(
|
||||
// Default scopes that are always required.
|
||||
array(
|
||||
'openid',
|
||||
'https://www.googleapis.com/auth/userinfo.profile',
|
||||
'https://www.googleapis.com/auth/userinfo.email',
|
||||
),
|
||||
$scopes
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of currently granted Google OAuth scopes for the current user.
|
||||
*
|
||||
* @since 1.39.0
|
||||
* @see https://developers.google.com/identity/protocols/googlescopes
|
||||
*
|
||||
* @return string[] List of Google OAuth scopes.
|
||||
*/
|
||||
public function get_granted_scopes() {
|
||||
return $this->user_options->get( self::OPTION_AUTH_SCOPES ) ?: array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of currently granted Google OAuth scopes for the current user.
|
||||
*
|
||||
* @since 1.39.0
|
||||
* @see https://developers.google.com/identity/protocols/googlescopes
|
||||
*
|
||||
* @param string[] $scopes List of Google OAuth scopes.
|
||||
*/
|
||||
public function set_granted_scopes( $scopes ) {
|
||||
$required_scopes = $this->get_required_scopes();
|
||||
$scopes = array_values( array_unique( array_intersect( $scopes, $required_scopes ) ) );
|
||||
|
||||
$this->user_options->set( self::OPTION_AUTH_SCOPES, $scopes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current user's full OAuth token data, including access token and optional refresh token.
|
||||
*
|
||||
* @since 1.39.0
|
||||
*
|
||||
* @return array Associative array with 'access_token', 'expires_in', 'created', and 'refresh_token' keys, or empty
|
||||
* array if no token available.
|
||||
*/
|
||||
public function get_token() {
|
||||
return $this->token->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current user's full OAuth token data, including access token and optional refresh token.
|
||||
*
|
||||
* @since 1.39.0
|
||||
*
|
||||
* @param array $token {
|
||||
* Full token data, optionally including the refresh token.
|
||||
*
|
||||
* @type string $access_token Required. The access token.
|
||||
* @type int $expires_in Number of seconds in which the token expires. Default 3600 (1 hour).
|
||||
* @type int $created Timestamp in seconds when the token was created. Default is the current time.
|
||||
* @type string $refresh_token The refresh token, if relevant. If passed, it is set as well.
|
||||
* }
|
||||
* @return bool True on success, false on failure.
|
||||
*/
|
||||
public function set_token( array $token ) {
|
||||
// Remove the error code from the user options so it doesn't
|
||||
// appear again.
|
||||
$this->user_options->delete( OAuth_Client::OPTION_ERROR_CODE );
|
||||
|
||||
return $this->token->set( $token );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the current user's token and all associated data.
|
||||
*
|
||||
* @since 1.0.3
|
||||
*/
|
||||
protected function delete_token() {
|
||||
$this->token->delete();
|
||||
|
||||
$this->user_options->delete( self::OPTION_AUTH_SCOPES );
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given error code to a user-facing message.
|
||||
*
|
||||
* @since 1.39.0
|
||||
*
|
||||
* @param string $error_code Error code.
|
||||
* @return string Error message.
|
||||
*/
|
||||
public function get_error_message( $error_code ) {
|
||||
switch ( $error_code ) {
|
||||
case 'access_denied':
|
||||
return __( 'Setup was interrupted because you did not grant the necessary permissions.', 'google-site-kit' );
|
||||
case 'access_token_not_received':
|
||||
return __( 'Unable to receive access token because of an unknown error.', 'google-site-kit' );
|
||||
case 'cannot_log_in':
|
||||
return __( 'Internal error that the Google login redirect failed.', 'google-site-kit' );
|
||||
case 'invalid_client':
|
||||
return __( 'Unable to receive access token because of an invalid client.', 'google-site-kit' );
|
||||
case 'invalid_code':
|
||||
return __( 'Unable to receive access token because of an empty authorization code.', 'google-site-kit' );
|
||||
case 'invalid_grant':
|
||||
return __( 'Unable to receive access token because of an invalid authorization code or refresh token.', 'google-site-kit' );
|
||||
case 'invalid_request':
|
||||
return __( 'Unable to receive access token because of an invalid OAuth request.', 'google-site-kit' );
|
||||
case 'missing_delegation_consent':
|
||||
return __( 'Looks like your site is not allowed access to Google account data and can’t display stats in the dashboard.', 'google-site-kit' );
|
||||
case 'missing_search_console_property':
|
||||
return __( 'Looks like there is no Search Console property for your site.', 'google-site-kit' );
|
||||
case 'missing_verification':
|
||||
return __( 'Looks like the verification token for your site is missing.', 'google-site-kit' );
|
||||
case 'oauth_credentials_not_exist':
|
||||
return __( 'Unable to authenticate Site Kit, as no client credentials exist.', 'google-site-kit' );
|
||||
case 'refresh_token_not_exist':
|
||||
return __( 'Unable to refresh access token, as no refresh token exists.', 'google-site-kit' );
|
||||
case 'unauthorized_client':
|
||||
return __( 'Unable to receive access token because of an unauthorized client.', 'google-site-kit' );
|
||||
case 'unsupported_grant_type':
|
||||
return __( 'Unable to receive access token because of an unsupported grant type.', 'google-site-kit' );
|
||||
default:
|
||||
/* translators: %s: error code from API */
|
||||
return sprintf( __( 'Unknown Error (code: %s).', 'google-site-kit' ), $error_code );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an exception thrown when fetching an access token.
|
||||
*
|
||||
* @since 1.2.0
|
||||
*
|
||||
* @param Exception $e Exception thrown.
|
||||
*/
|
||||
protected function handle_fetch_token_exception( Exception $e ) {
|
||||
$error_code = $e->getMessage();
|
||||
|
||||
// Revoke and delete user connection data on 'invalid_grant'.
|
||||
// This typically happens during refresh if the refresh token is invalid or expired.
|
||||
if ( 'invalid_grant' === $error_code ) {
|
||||
$this->delete_token();
|
||||
}
|
||||
|
||||
$this->user_options->set( self::OPTION_ERROR_CODE, $error_code );
|
||||
if ( $e instanceof Google_Proxy_Code_Exception ) {
|
||||
$this->user_options->set( self::OPTION_PROXY_ACCESS_CODE, $e->getAccessCode() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the OAuth redirect URI that listens to the callback request.
|
||||
*
|
||||
* @since 1.39.0
|
||||
*
|
||||
* @return string OAuth redirect URI.
|
||||
*/
|
||||
protected function get_redirect_uri() {
|
||||
return add_query_arg( 'oauth2callback', '1', admin_url( 'index.php' ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Authentication\Connected_Proxy_URL
|
||||
*
|
||||
* @package Google\Site_Kit\Core\Authentication
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Authentication;
|
||||
|
||||
use Google\Site_Kit\Core\Storage\Setting;
|
||||
|
||||
/**
|
||||
* Connected_Proxy_URL class.
|
||||
*
|
||||
* @since 1.17.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class Connected_Proxy_URL extends Setting {
|
||||
|
||||
/**
|
||||
* The option_name for this setting.
|
||||
*/
|
||||
const OPTION = 'googlesitekit_connected_proxy_url';
|
||||
|
||||
/**
|
||||
* Matches provided URL with the current proxy URL in the settings.
|
||||
*
|
||||
* @since 1.17.0
|
||||
*
|
||||
* @param string $url URL to match against the current one in the settings.
|
||||
* @return bool TRUE if URL matches the current one, otherwise FALSE.
|
||||
*/
|
||||
public function matches_url( $url ) {
|
||||
$sanitize = $this->get_sanitize_callback();
|
||||
$normalized = $sanitize( $url );
|
||||
return $normalized === $this->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the callback for sanitizing the setting's value before saving.
|
||||
*
|
||||
* @since 1.17.0
|
||||
*
|
||||
* @return callable A sanitizing function.
|
||||
*/
|
||||
protected function get_sanitize_callback() {
|
||||
return 'trailingslashit';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Authentication\Credentials
|
||||
*
|
||||
* @package Google\Site_Kit
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Authentication;
|
||||
|
||||
use Google\Site_Kit\Core\Storage\Setting;
|
||||
|
||||
/**
|
||||
* Class representing the OAuth client ID and secret credentials.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
final class Credentials extends Setting {
|
||||
|
||||
/**
|
||||
* Option key in options table.
|
||||
*/
|
||||
const OPTION = 'googlesitekit_credentials';
|
||||
|
||||
/**
|
||||
* Retrieves Site Kit credentials.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @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://<domainname>?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->options->get( self::OPTION )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether Site Kit has been setup with client ID and secret.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return bool True if credentials are set, false otherwise.
|
||||
*/
|
||||
public function has() {
|
||||
$credentials = (array) $this->get();
|
||||
if ( ! empty( $credentials ) && ! empty( $credentials['oauth2_client_id'] ) && ! empty( $credentials['oauth2_client_secret'] ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses Credentials data and merges with its defaults.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param mixed $data Credentials data.
|
||||
* @return array Parsed $data.
|
||||
*/
|
||||
private function parse_defaults( $data ) {
|
||||
$defaults = $this->get_default();
|
||||
|
||||
if ( ! is_array( $data ) ) {
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
return wp_parse_args( $data, $defaults );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default value.
|
||||
*
|
||||
* @since 1.2.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_default() {
|
||||
return array(
|
||||
'oauth2_client_id' => '',
|
||||
'oauth2_client_secret' => '',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.9.0
|
||||
*
|
||||
* @return bool True if proxy authentication is used, false otherwise.
|
||||
*/
|
||||
public function using_proxy() {
|
||||
$creds = $this->get();
|
||||
|
||||
if ( ! $this->has() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (bool) preg_match( '/\.apps\.sitekit\.withgoogle\.com$/', $creds['oauth2_client_id'] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Authentication\Disconnected_Reason
|
||||
*
|
||||
* @package Google\Site_Kit\Core\Authentication
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Authentication;
|
||||
|
||||
use Google\Site_Kit\Core\Storage\User_Setting;
|
||||
|
||||
/**
|
||||
* Disconnected_Reason class.
|
||||
*
|
||||
* @since 1.17.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class Disconnected_Reason extends User_Setting {
|
||||
|
||||
/**
|
||||
* The option_name for this setting.
|
||||
*/
|
||||
const OPTION = 'googlesitekit_disconnected_reason';
|
||||
|
||||
/**
|
||||
* Available reasons.
|
||||
*/
|
||||
const REASON_CONNECTED_URL_MISMATCH = 'connected_url_mismatch';
|
||||
|
||||
/**
|
||||
* Registers the setting in WordPress.
|
||||
*
|
||||
* @since 1.17.0
|
||||
*/
|
||||
public function register() {
|
||||
parent::register();
|
||||
add_action( 'googlesitekit_authorize_user', array( $this, 'delete' ) );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Authentication\Exception\Exchange_Site_Code_Exception
|
||||
*
|
||||
* @package Google\Site_Kit
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Authentication\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when exchanging the site code fails.
|
||||
*
|
||||
* @since 1.48.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class Exchange_Site_Code_Exception extends Exception {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Authentication\Exception\Google_OAuth_Exception
|
||||
*
|
||||
* @package Google\Site_Kit
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Authentication\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when a Google OAuth response contains an OAuth error.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class Google_OAuth_Exception extends Exception {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Authentication\Exception\Google_Proxy_Code_Exception
|
||||
*
|
||||
* @package Google\Site_Kit
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Authentication\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when Google proxy returns an error accompanied with a temporary access code.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @since 1.2.0 Renamed to Google_Proxy_Code_Exception.
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class Google_Proxy_Code_Exception extends Exception {
|
||||
|
||||
/**
|
||||
* Temporary code for an undelegated proxy token.
|
||||
*
|
||||
* @since 1.109.0 Explicitly declared; previously, it was dynamically declared.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $access_code;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param string $message Optional. The exception message. Default empty string.
|
||||
* @param integer $code Optional. The numeric exception code. Default 0.
|
||||
* @param string $access_code Optional. Temporary code for an undelegated proxy token. Default empty string.
|
||||
*/
|
||||
public function __construct( $message = '', $code = 0, $access_code = '' ) {
|
||||
parent::__construct( $message, $code );
|
||||
|
||||
$this->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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Authentication\Exception\Insufficient_Scopes_Exception
|
||||
*
|
||||
* @package Google\Site_Kit\Core\Authentication\Exception
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Authentication\Exception;
|
||||
|
||||
use Exception;
|
||||
use Google\Site_Kit\Core\Contracts\WP_Errorable;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Exception thrown when authentication scopes are insufficient for a request.
|
||||
*
|
||||
* @since 1.9.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class Insufficient_Scopes_Exception extends Exception implements WP_Errorable {
|
||||
|
||||
const WP_ERROR_CODE = 'missing_required_scopes';
|
||||
|
||||
/**
|
||||
* OAuth scopes that are required but not yet granted.
|
||||
*
|
||||
* @since 1.9.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $scopes = array();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 1.9.0
|
||||
*
|
||||
* @param string $message Optional. Exception message.
|
||||
* @param int $code Optional. Exception code.
|
||||
* @param Throwable $previous Optional. Previous exception used for chaining.
|
||||
* @param array $scopes Optional. Scopes that are missing.
|
||||
*/
|
||||
public function __construct( $message = '', $code = 0, $previous = null, $scopes = array() ) {
|
||||
parent::__construct( $message, $code, $previous );
|
||||
$this->set_scopes( $scopes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the missing scopes that raised this exception.
|
||||
*
|
||||
* @since 1.9.0
|
||||
*
|
||||
* @param array $scopes OAuth scopes that are required but not yet granted.
|
||||
*/
|
||||
public function set_scopes( array $scopes ) {
|
||||
$this->scopes = $scopes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the missing scopes that raised this exception.
|
||||
*
|
||||
* @since 1.9.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_scopes() {
|
||||
return $this->scopes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the WP_Error representation of this exception.
|
||||
*
|
||||
* @since 1.9.0
|
||||
*
|
||||
* @return WP_Error
|
||||
*/
|
||||
public function to_wp_error() {
|
||||
return new WP_Error(
|
||||
static::WP_ERROR_CODE,
|
||||
$this->getMessage(),
|
||||
array(
|
||||
'status' => 403, // Forbidden.
|
||||
'scopes' => $this->scopes,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Authentication\Exception\Missing_Verification_Exception
|
||||
*
|
||||
* @package Google\Site_Kit
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Authentication\Exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when the a missing verification error is encountered when exchanging the site code.
|
||||
*
|
||||
* @since 1.48.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class Missing_Verification_Exception extends Exchange_Site_Code_Exception {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,642 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Authentication\Google_Proxy
|
||||
*
|
||||
* @package Google\Site_Kit\Core\Authentication
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Authentication;
|
||||
|
||||
use Google\Site_Kit\Context;
|
||||
use Google\Site_Kit\Core\Util\Feature_Flags;
|
||||
use Exception;
|
||||
use Google\Site_Kit\Core\Authentication\Clients\OAuth_Client;
|
||||
use Google\Site_Kit\Core\Storage\User_Options;
|
||||
use Google\Site_Kit\Core\Util\URL;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Class for authentication service.
|
||||
*
|
||||
* @since 1.1.2
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class Google_Proxy {
|
||||
|
||||
const PRODUCTION_BASE_URL = 'https://sitekit.withgoogle.com';
|
||||
const STAGING_BASE_URL = 'https://site-kit-dev.appspot.com';
|
||||
const DEVELOPMENT_BASE_URL = 'https://site-kit-local.appspot.com';
|
||||
const OAUTH2_SITE_URI = '/o/oauth2/site/';
|
||||
const OAUTH2_REVOKE_URI = '/o/oauth2/revoke/';
|
||||
const OAUTH2_TOKEN_URI = '/o/oauth2/token/';
|
||||
const OAUTH2_AUTH_URI = '/o/oauth2/auth/';
|
||||
const OAUTH2_DELETE_SITE_URI = '/o/oauth2/delete-site/';
|
||||
const SETUP_URI = '/v2/site-management/setup/';
|
||||
const PERMISSIONS_URI = '/site-management/permissions/';
|
||||
const FEATURES_URI = '/site-management/features/';
|
||||
const SURVEY_TRIGGER_URI = '/survey/trigger/';
|
||||
const SURVEY_EVENT_URI = '/survey/event/';
|
||||
const SUPPORT_LINK_URI = '/support';
|
||||
const ACTION_EXCHANGE_SITE_CODE = 'googlesitekit_proxy_exchange_site_code';
|
||||
const ACTION_SETUP = 'googlesitekit_proxy_setup';
|
||||
const ACTION_SETUP_START = 'googlesitekit_proxy_setup_start';
|
||||
const ACTION_PERMISSIONS = 'googlesitekit_proxy_permissions';
|
||||
const ACTION_VERIFY = 'googlesitekit_proxy_verify';
|
||||
const NONCE_ACTION = 'googlesitekit_proxy_nonce';
|
||||
const HEADER_REDIRECT_TO = 'Redirect-To';
|
||||
|
||||
/**
|
||||
* Plugin context.
|
||||
*
|
||||
* @since 1.1.2
|
||||
* @var Context
|
||||
*/
|
||||
private $context;
|
||||
|
||||
/**
|
||||
* Required scopes list.
|
||||
*
|
||||
* @since 1.68.0
|
||||
* @var array
|
||||
*/
|
||||
private $required_scopes = array();
|
||||
|
||||
/**
|
||||
* Google_Proxy constructor.
|
||||
*
|
||||
* @since 1.1.2
|
||||
*
|
||||
* @param Context $context Plugin context.
|
||||
*/
|
||||
public function __construct( Context $context ) {
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets required scopes to use when the site is registering at proxy.
|
||||
*
|
||||
* @since 1.68.0
|
||||
*
|
||||
* @param array $scopes List of scopes.
|
||||
*/
|
||||
public function with_scopes( array $scopes ) {
|
||||
$this->required_scopes = $scopes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the application name: a combination of the namespace and version.
|
||||
*
|
||||
* @since 1.27.0
|
||||
*
|
||||
* @return string The application name.
|
||||
*/
|
||||
public static function get_application_name() {
|
||||
$platform = self::get_platform();
|
||||
return $platform . '/google-site-kit/' . GOOGLESITEKIT_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of features to declare support for when setting up with the proxy.
|
||||
*
|
||||
* @since 1.27.0
|
||||
*
|
||||
* @return array Array of supported features.
|
||||
*/
|
||||
private function get_supports() {
|
||||
$supports = array(
|
||||
'credentials_retrieval',
|
||||
'short_verification_token',
|
||||
);
|
||||
|
||||
$home_path = URL::parse( $this->context->get_canonical_home_url(), PHP_URL_PATH );
|
||||
if ( ! $home_path || '/' === $home_path ) {
|
||||
$supports[] = 'file_verification';
|
||||
}
|
||||
|
||||
return $supports;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the setup URL to the authentication proxy.
|
||||
*
|
||||
* @since 1.49.0
|
||||
* @since 1.71.0 Uses the V2 setup flow by default.
|
||||
*
|
||||
* @param array $query_params Query parameters to include in the URL.
|
||||
* @return string URL to the setup page on the authentication proxy.
|
||||
*
|
||||
* @throws Exception Thrown if called without the required query parameters.
|
||||
*/
|
||||
public function setup_url( array $query_params = array() ) {
|
||||
if ( empty( $query_params['code'] ) ) {
|
||||
throw new Exception( __( 'Missing code parameter for setup URL.', 'google-site-kit' ) );
|
||||
}
|
||||
if ( empty( $query_params['site_id'] ) && empty( $query_params['site_code'] ) ) {
|
||||
throw new Exception( __( 'Missing site_id or site_code parameter for setup URL.', 'google-site-kit' ) );
|
||||
}
|
||||
|
||||
return add_query_arg( $query_params, $this->url( self::SETUP_URI ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Conditionally adds the `step` parameter to the passed query parameters, depending on the given error code.
|
||||
*
|
||||
* @since 1.49.0
|
||||
*
|
||||
* @param array $query_params Query parameters.
|
||||
* @param string $error_code Error code.
|
||||
* @return array Query parameters with `step` included, depending on the error code.
|
||||
*/
|
||||
public function add_setup_step_from_error_code( $query_params, $error_code ) {
|
||||
switch ( $error_code ) {
|
||||
case 'missing_verification':
|
||||
$query_params['step'] = 'verification';
|
||||
break;
|
||||
case 'missing_delegation_consent':
|
||||
$query_params['step'] = 'delegation_consent';
|
||||
break;
|
||||
case 'missing_search_console_property':
|
||||
$query_params['step'] = 'search_console_property';
|
||||
break;
|
||||
}
|
||||
return $query_params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the permissions URL to the authentication proxy.
|
||||
*
|
||||
* This only returns a URL if the user already has an access token set.
|
||||
*
|
||||
* @since 1.27.0
|
||||
*
|
||||
* @param Credentials $credentials Credentials instance.
|
||||
* @param array $query_args Optional. Additional query parameters.
|
||||
* @return string URL to the permissions page on the authentication proxy on success, or an empty string on failure.
|
||||
*/
|
||||
public function permissions_url( Credentials $credentials, array $query_args = array() ) {
|
||||
if ( $credentials->has() ) {
|
||||
$creds = $credentials->get();
|
||||
$query_args['site_id'] = $creds['oauth2_client_id'];
|
||||
}
|
||||
|
||||
$query_args['application_name'] = rawurlencode( self::get_application_name() );
|
||||
$query_args['hl'] = $this->context->get_locale( 'user' );
|
||||
|
||||
return add_query_arg( $query_args, $this->url( self::PERMISSIONS_URI ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a URL to the proxy with optional path.
|
||||
*
|
||||
* @since 1.1.2
|
||||
*
|
||||
* @param string $path Optional. Path to append to the base URL.
|
||||
* @return string Complete proxy URL.
|
||||
*/
|
||||
public function url( $path = '' ) {
|
||||
$url = self::PRODUCTION_BASE_URL;
|
||||
$allowed_urls = array(
|
||||
self::PRODUCTION_BASE_URL,
|
||||
self::STAGING_BASE_URL,
|
||||
self::DEVELOPMENT_BASE_URL,
|
||||
);
|
||||
|
||||
if ( defined( 'GOOGLESITEKIT_PROXY_URL' ) && in_array( GOOGLESITEKIT_PROXY_URL, $allowed_urls, true ) ) {
|
||||
$url = GOOGLESITEKIT_PROXY_URL;
|
||||
}
|
||||
|
||||
$url = untrailingslashit( $url );
|
||||
if ( $path && is_string( $path ) ) {
|
||||
$url .= '/' . ltrim( $path, '/' );
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a POST request to the Google Proxy server.
|
||||
*
|
||||
* @since 1.27.0
|
||||
*
|
||||
* @param string $uri Endpoint to send the request to.
|
||||
* @param Credentials $credentials Credentials instance.
|
||||
* @param array $args Array of request arguments.
|
||||
* @return array|WP_Error The response as an associative array or WP_Error on failure.
|
||||
*/
|
||||
private function request( $uri, $credentials, array $args = array() ) {
|
||||
$request_args = array(
|
||||
'headers' => ! empty( $args['headers'] ) && is_array( $args['headers'] ) ? $args['headers'] : array(),
|
||||
'body' => ! empty( $args['body'] ) && is_array( $args['body'] ) ? $args['body'] : array(),
|
||||
'timeout' => isset( $args['timeout'] ) ? $args['timeout'] : 15,
|
||||
);
|
||||
|
||||
if ( $credentials && $credentials instanceof Credentials ) {
|
||||
if ( ! $credentials->has() ) {
|
||||
return new WP_Error(
|
||||
'oauth_credentials_not_exist',
|
||||
__( 'OAuth credentials haven\'t been found.', 'google-site-kit' ),
|
||||
array( 'status' => 401 )
|
||||
);
|
||||
}
|
||||
|
||||
$creds = $credentials->get();
|
||||
$request_args['body']['site_id'] = $creds['oauth2_client_id'];
|
||||
$request_args['body']['site_secret'] = $creds['oauth2_client_secret'];
|
||||
}
|
||||
|
||||
if ( ! empty( $args['access_token'] ) && is_string( $args['access_token'] ) ) {
|
||||
$request_args['headers']['Authorization'] = 'Bearer ' . $args['access_token'];
|
||||
}
|
||||
|
||||
if ( isset( $args['mode'] ) && 'async' === $args['mode'] ) {
|
||||
$request_args['timeout'] = 0.01;
|
||||
$request_args['blocking'] = false;
|
||||
}
|
||||
|
||||
if ( ! empty( $args['json_request'] ) ) {
|
||||
$request_args['headers']['Content-Type'] = 'application/json';
|
||||
$request_args['body'] = wp_json_encode( $request_args['body'] );
|
||||
}
|
||||
|
||||
$url = $this->url( $uri );
|
||||
$response = wp_remote_post( $url, $request_args );
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$code = wp_remote_retrieve_response_code( $response );
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
$body = json_decode( $body, true );
|
||||
if ( $code < 200 || 299 < $code ) {
|
||||
$message = is_array( $body ) && ! empty( $body['error'] ) ? $body['error'] : '';
|
||||
return new WP_Error( 'request_failed', $message, array( 'status' => $code ) );
|
||||
}
|
||||
|
||||
if ( ! empty( $args['return'] ) && 'response' === $args['return'] ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
if ( is_null( $body ) ) {
|
||||
return new WP_Error(
|
||||
'failed_to_parse_response',
|
||||
__( 'Failed to parse response.', 'google-site-kit' ),
|
||||
array( 'status' => 500 )
|
||||
);
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets site fields.
|
||||
*
|
||||
* @since 1.5.0
|
||||
*
|
||||
* @return array Associative array of $query_arg => $value pairs.
|
||||
*/
|
||||
public function get_site_fields() {
|
||||
return array(
|
||||
'name' => wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ),
|
||||
'url' => $this->context->get_canonical_home_url(),
|
||||
'redirect_uri' => add_query_arg( 'oauth2callback', 1, admin_url( 'index.php' ) ),
|
||||
'action_uri' => admin_url( 'index.php' ),
|
||||
'return_uri' => $this->context->admin_url( 'splash' ),
|
||||
'analytics_redirect_uri' => add_query_arg( 'gatoscallback', 1, admin_url( 'index.php' ) ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets metadata fields.
|
||||
*
|
||||
* @since 1.68.0
|
||||
*
|
||||
* @return array Metadata fields array.
|
||||
*/
|
||||
public function get_metadata_fields() {
|
||||
$metadata = array(
|
||||
'supports' => implode( ' ', $this->get_supports() ),
|
||||
'nonce' => wp_create_nonce( self::NONCE_ACTION ),
|
||||
'mode' => '',
|
||||
'hl' => $this->context->get_locale( 'user' ),
|
||||
'application_name' => self::get_application_name(),
|
||||
'service_version' => 'v2',
|
||||
);
|
||||
|
||||
/**
|
||||
* Filters the setup mode.
|
||||
*
|
||||
* @since 1.68.0
|
||||
*
|
||||
* @param string $mode An initial setup mode.
|
||||
*/
|
||||
$metadata['mode'] = apply_filters( 'googlesitekit_proxy_setup_mode', $metadata['mode'] );
|
||||
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch site fields
|
||||
*
|
||||
* @since 1.22.0
|
||||
*
|
||||
* @param Credentials $credentials Credentials instance.
|
||||
* @return array|WP_Error The response as an associative array or WP_Error on failure.
|
||||
*/
|
||||
public function fetch_site_fields( Credentials $credentials ) {
|
||||
return $this->request( self::OAUTH2_SITE_URI, $credentials );
|
||||
}
|
||||
|
||||
/**
|
||||
* Are site fields synced
|
||||
*
|
||||
* @since 1.22.0
|
||||
*
|
||||
* @param Credentials $credentials Credentials instance.
|
||||
*
|
||||
* @return boolean|WP_Error Boolean do the site fields match or WP_Error on failure.
|
||||
*/
|
||||
public function are_site_fields_synced( Credentials $credentials ) {
|
||||
$site_fields = $this->fetch_site_fields( $credentials );
|
||||
if ( is_wp_error( $site_fields ) ) {
|
||||
return $site_fields;
|
||||
}
|
||||
|
||||
$get_site_fields = $this->get_site_fields();
|
||||
foreach ( $get_site_fields as $key => $site_field ) {
|
||||
if ( ! array_key_exists( $key, $site_fields ) || $site_fields[ $key ] !== $site_field ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets user fields.
|
||||
*
|
||||
* @since 1.10.0
|
||||
*
|
||||
* @return array Associative array of $query_arg => $value pairs.
|
||||
*/
|
||||
public function get_user_fields() {
|
||||
$user_roles = wp_get_current_user()->roles;
|
||||
// If multisite, also consider network administrators.
|
||||
if ( is_multisite() && current_user_can( 'manage_network' ) ) {
|
||||
$user_roles[] = 'network_administrator';
|
||||
}
|
||||
$user_roles = array_unique( $user_roles );
|
||||
|
||||
return array(
|
||||
'user_roles' => implode( ',', $user_roles ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the site on the proxy.
|
||||
*
|
||||
* @since 1.20.0
|
||||
*
|
||||
* @param Credentials $credentials Credentials instance.
|
||||
* @return array|WP_Error Response data on success, otherwise WP_Error object.
|
||||
*/
|
||||
public function unregister_site( Credentials $credentials ) {
|
||||
return $this->request( self::OAUTH2_DELETE_SITE_URI, $credentials );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the site on the proxy.
|
||||
*
|
||||
* @since 1.68.0
|
||||
*
|
||||
* @param string $mode Sync mode.
|
||||
* @return string|WP_Error Redirect URL on success, otherwise an error.
|
||||
*/
|
||||
public function register_site( $mode = 'async' ) {
|
||||
return $this->send_site_fields( null, $mode );
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizes site fields with the proxy.
|
||||
*
|
||||
* @since 1.5.0
|
||||
* @since 1.68.0 Updated the function to return redirect URL.
|
||||
*
|
||||
* @param Credentials $credentials Credentials instance.
|
||||
* @param string $mode Sync mode.
|
||||
* @return string|WP_Error Redirect URL on success, otherwise an error.
|
||||
*/
|
||||
public function sync_site_fields( Credentials $credentials, $mode = 'async' ) {
|
||||
return $this->send_site_fields( $credentials, $mode );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends site fields to the proxy.
|
||||
*
|
||||
* @since 1.68.0
|
||||
*
|
||||
* @param Credentials $credentials Credentials instance.
|
||||
* @param string $mode Sync mode.
|
||||
* @return string|WP_Error Redirect URL on success, otherwise an error.
|
||||
*/
|
||||
private function send_site_fields( Credentials $credentials = null, $mode = 'async' ) {
|
||||
$response = $this->request(
|
||||
self::OAUTH2_SITE_URI,
|
||||
$credentials,
|
||||
array(
|
||||
'return' => 'response',
|
||||
'mode' => $mode,
|
||||
'body' => array_merge(
|
||||
$this->get_site_fields(),
|
||||
$this->get_user_fields(),
|
||||
$this->get_metadata_fields(),
|
||||
array(
|
||||
'scope' => implode( ' ', $this->required_scopes ),
|
||||
)
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$redirect_to = wp_remote_retrieve_header( $response, self::HEADER_REDIRECT_TO );
|
||||
if ( empty( $redirect_to ) ) {
|
||||
return new WP_Error(
|
||||
'failed_to_retrive_redirect',
|
||||
__( 'Failed to retrieve redirect URL.', 'google-site-kit' ),
|
||||
array( 'status' => 500 )
|
||||
);
|
||||
}
|
||||
|
||||
return $redirect_to;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exchanges a site code for client credentials from the proxy.
|
||||
*
|
||||
* @since 1.1.2
|
||||
*
|
||||
* @param string $site_code Site code identifying the site.
|
||||
* @param string $undelegated_code Undelegated code identifying the undelegated token.
|
||||
* @return array|WP_Error Response data containing site_id and site_secret on success, WP_Error object on failure.
|
||||
*/
|
||||
public function exchange_site_code( $site_code, $undelegated_code ) {
|
||||
$response_data = $this->request(
|
||||
self::OAUTH2_SITE_URI,
|
||||
null,
|
||||
array(
|
||||
'body' => array(
|
||||
'code' => $undelegated_code,
|
||||
'site_code' => $site_code,
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response_data ) ) {
|
||||
return $response_data;
|
||||
}
|
||||
|
||||
if ( ! isset( $response_data['site_id'], $response_data['site_secret'] ) ) {
|
||||
return new WP_Error(
|
||||
'oauth_credentials_not_exist',
|
||||
__( 'OAuth credentials haven\'t been found.', 'google-site-kit' ),
|
||||
array( 'status' => 401 )
|
||||
);
|
||||
}
|
||||
|
||||
return $response_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets remote features.
|
||||
*
|
||||
* @since 1.27.0
|
||||
* @since 1.104.0 Added `php_version` to request.
|
||||
*
|
||||
* @param Credentials $credentials Credentials instance.
|
||||
* @return array|WP_Error Response of the wp_remote_post request.
|
||||
*/
|
||||
public function get_features( Credentials $credentials ) {
|
||||
global $wp_version;
|
||||
|
||||
$platform = self::get_platform();
|
||||
$user_count = count_users();
|
||||
$connectable_user_count = isset( $user_count['avail_roles']['administrator'] ) ? $user_count['avail_roles']['administrator'] : 0;
|
||||
|
||||
$body = array(
|
||||
'platform' => $platform . '/google-site-kit',
|
||||
'version' => GOOGLESITEKIT_VERSION,
|
||||
'platform_version' => $wp_version,
|
||||
'php_version' => phpversion(),
|
||||
'user_count' => $user_count['total_users'],
|
||||
'connectable_user_count' => $connectable_user_count,
|
||||
'connected_user_count' => $this->count_connected_users(),
|
||||
);
|
||||
|
||||
/**
|
||||
* Filters additional context data sent with the body of a remote-controlled features request.
|
||||
*
|
||||
* @since 1.71.0
|
||||
*
|
||||
* @param array $body Context data to be sent with the features request.
|
||||
*/
|
||||
$body = apply_filters( 'googlesitekit_features_request_data', $body );
|
||||
|
||||
return $this->request( self::FEATURES_URI, $credentials, array( 'body' => $body ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of users who are connected (i.e. authenticated /
|
||||
* have an access token).
|
||||
*
|
||||
* @since 1.71.0
|
||||
*
|
||||
* @return int Number of WordPress user accounts connected to SiteKit.
|
||||
*/
|
||||
public function count_connected_users() {
|
||||
$user_options = new User_Options( $this->context );
|
||||
$connected_users = get_users(
|
||||
array(
|
||||
'meta_key' => $user_options->get_meta_key( OAuth_Client::OPTION_ACCESS_TOKEN ), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
|
||||
'meta_compare' => 'EXISTS',
|
||||
'role' => 'administrator',
|
||||
'fields' => 'ID',
|
||||
)
|
||||
);
|
||||
|
||||
return count( $connected_users );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the platform.
|
||||
*
|
||||
* @since 1.37.0
|
||||
*
|
||||
* @return string WordPress multisite or WordPress.
|
||||
*/
|
||||
public static function get_platform() {
|
||||
if ( is_multisite() ) {
|
||||
return 'wordpress-multisite';
|
||||
}
|
||||
return 'wordpress'; // phpcs:ignore WordPress.WP.CapitalPDangit.Misspelled
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends survey trigger ID to the proxy.
|
||||
*
|
||||
* @since 1.35.0
|
||||
*
|
||||
* @param Credentials $credentials Credentials instance.
|
||||
* @param string $access_token Access token.
|
||||
* @param string $trigger_id Token ID.
|
||||
* @return array|WP_Error Response of the wp_remote_post request.
|
||||
*/
|
||||
public function send_survey_trigger( Credentials $credentials, $access_token, $trigger_id ) {
|
||||
return $this->request(
|
||||
self::SURVEY_TRIGGER_URI,
|
||||
$credentials,
|
||||
array(
|
||||
'access_token' => $access_token,
|
||||
'json_request' => true,
|
||||
'body' => array(
|
||||
'trigger_context' => array(
|
||||
'trigger_id' => $trigger_id,
|
||||
'language' => get_user_locale(),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends survey event to the proxy.
|
||||
*
|
||||
* @since 1.35.0
|
||||
*
|
||||
* @param Credentials $credentials Credentials instance.
|
||||
* @param string $access_token Access token.
|
||||
* @param array|\stdClass $session Session object.
|
||||
* @param array|\stdClass $event Event object.
|
||||
* @return array|WP_Error Response of the wp_remote_post request.
|
||||
*/
|
||||
public function send_survey_event( Credentials $credentials, $access_token, $session, $event ) {
|
||||
return $this->request(
|
||||
self::SURVEY_EVENT_URI,
|
||||
$credentials,
|
||||
array(
|
||||
'access_token' => $access_token,
|
||||
'json_request' => true,
|
||||
'body' => array(
|
||||
'session' => $session,
|
||||
'event' => $event,
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Authentication\Has_Connected_Admins
|
||||
*
|
||||
* @package Google\Site_Kit\Core\Authentication
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Authentication;
|
||||
|
||||
use Google\Site_Kit\Core\Authentication\Clients\OAuth_Client;
|
||||
use Google\Site_Kit\Core\Storage\Options_Interface;
|
||||
use Google\Site_Kit\Core\Storage\Setting;
|
||||
use Google\Site_Kit\Core\Storage\User_Options_Interface;
|
||||
use WP_User;
|
||||
|
||||
/**
|
||||
* Has_Connected_Admins class.
|
||||
*
|
||||
* @since 1.14.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class Has_Connected_Admins extends Setting {
|
||||
|
||||
/**
|
||||
* The option_name for this setting.
|
||||
*/
|
||||
const OPTION = 'googlesitekit_has_connected_admins';
|
||||
|
||||
/**
|
||||
* User options instance implementing User_Options_Interface.
|
||||
*
|
||||
* @since 1.14.0
|
||||
* @var User_Options_Interface
|
||||
*/
|
||||
protected $user_options;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 1.14.0
|
||||
*
|
||||
* @param Options_Interface $options Options instance.
|
||||
* @param User_Options_Interface $user_options User options instance.
|
||||
*/
|
||||
public function __construct( Options_Interface $options, User_Options_Interface $user_options ) {
|
||||
parent::__construct( $options );
|
||||
$this->user_options = $user_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the setting in WordPress.
|
||||
*
|
||||
* @since 1.14.0
|
||||
*/
|
||||
public function register() {
|
||||
parent::register();
|
||||
|
||||
$access_token_meta_key = $this->user_options->get_meta_key( OAuth_Client::OPTION_ACCESS_TOKEN );
|
||||
|
||||
add_action(
|
||||
'added_user_meta',
|
||||
function ( $mid, $uid, $meta_key ) use ( $access_token_meta_key ) {
|
||||
if ( $meta_key === $access_token_meta_key && user_can( $uid, 'administrator' ) ) {
|
||||
$this->set( true );
|
||||
}
|
||||
},
|
||||
10,
|
||||
3
|
||||
);
|
||||
|
||||
add_action(
|
||||
'deleted_user_meta',
|
||||
function ( $mid, $uid, $meta_key ) use ( $access_token_meta_key ) {
|
||||
if ( $meta_key === $access_token_meta_key ) {
|
||||
$this->delete();
|
||||
}
|
||||
},
|
||||
10,
|
||||
3
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the setting. If the option is not set yet, it pulls connected
|
||||
* admins from the database and sets the option.
|
||||
*
|
||||
* @since 1.14.0
|
||||
*
|
||||
* @return boolean TRUE if the site kit already has connected admins, otherwise FALSE.
|
||||
*/
|
||||
public function get() {
|
||||
// If the option doesn't exist, query the fresh value, set it and return it.
|
||||
if ( ! $this->has() ) {
|
||||
$users = $this->query_connected_admins();
|
||||
$has_connected_admins = count( $users ) > 0;
|
||||
|
||||
$this->set( (int) $has_connected_admins );
|
||||
|
||||
return $has_connected_admins;
|
||||
}
|
||||
|
||||
return (bool) parent::get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries connected admins and returns an array of connected admin IDs.
|
||||
*
|
||||
* @since 1.14.0
|
||||
*
|
||||
* @return array The array of connected admin IDs.
|
||||
*/
|
||||
protected function query_connected_admins() {
|
||||
return get_users(
|
||||
array(
|
||||
'meta_key' => $this->user_options->get_meta_key( OAuth_Client::OPTION_ACCESS_TOKEN ), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
|
||||
'meta_compare' => 'EXISTS',
|
||||
'role' => 'administrator',
|
||||
'number' => 1,
|
||||
'fields' => 'ID',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the expected value type.
|
||||
*
|
||||
* @since 1.14.0
|
||||
*
|
||||
* @return string The type name.
|
||||
*/
|
||||
protected function get_type() {
|
||||
return 'boolean';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Authentication\Has_Multiple_Admins
|
||||
*
|
||||
* @package Google\Site_Kit\Core\Authentication
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Authentication;
|
||||
|
||||
use Google\Site_Kit\Core\Storage\Transients;
|
||||
use WP_User_Query;
|
||||
|
||||
/**
|
||||
* Has_Multiple_Admins class.
|
||||
*
|
||||
* @since 1.29.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class Has_Multiple_Admins {
|
||||
|
||||
/**
|
||||
* The option_name for this transient.
|
||||
*/
|
||||
const OPTION = 'googlesitekit_has_multiple_admins';
|
||||
|
||||
/**
|
||||
* Transients instance.
|
||||
*
|
||||
* @since 1.29.0
|
||||
* @var Transients
|
||||
*/
|
||||
protected $transients;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 1.29.0
|
||||
*
|
||||
* @param Transients $transients Transients instance.
|
||||
*/
|
||||
public function __construct( Transients $transients ) {
|
||||
$this->transients = $transients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a flag indicating whether the current site has multiple users.
|
||||
*
|
||||
* @since 1.29.0
|
||||
*
|
||||
* @return boolean TRUE if the site kit has multiple admins, otherwise FALSE.
|
||||
*/
|
||||
public function get() {
|
||||
$admins_count = $this->transients->get( self::OPTION );
|
||||
if ( false === $admins_count ) {
|
||||
$user_query_args = array(
|
||||
'number' => 1,
|
||||
'role__in' => array( 'Administrator' ),
|
||||
'count_total' => true,
|
||||
);
|
||||
|
||||
$user_query = new WP_User_Query( $user_query_args );
|
||||
$admins_count = $user_query->get_total();
|
||||
|
||||
$this->transients->get( self::OPTION, $admins_count, HOUR_IN_SECONDS );
|
||||
}
|
||||
|
||||
return $admins_count > 1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Authentication\Initial_Version
|
||||
*
|
||||
* @package Google\Site_Kit
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Authentication;
|
||||
|
||||
use Google\Site_Kit\Core\Storage\User_Setting;
|
||||
|
||||
/**
|
||||
* Class representing the initial Site Kit version the user started with.
|
||||
*
|
||||
* @since 1.25.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
final class Initial_Version extends User_Setting {
|
||||
|
||||
/**
|
||||
* User option key.
|
||||
*/
|
||||
const OPTION = 'googlesitekitpersistent_initial_version';
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Authentication\Owner_ID
|
||||
*
|
||||
* @package Google\Site_Kit\Core\Authentication
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Authentication;
|
||||
|
||||
use Google\Site_Kit\Core\Storage\Setting;
|
||||
|
||||
/**
|
||||
* Owner_ID class.
|
||||
*
|
||||
* @since 1.16.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class Owner_ID extends Setting {
|
||||
|
||||
/**
|
||||
* The option_name for this setting.
|
||||
*/
|
||||
const OPTION = 'googlesitekit_owner_id';
|
||||
|
||||
/**
|
||||
* Gets the value of the setting.
|
||||
*
|
||||
* @since 1.16.0
|
||||
*
|
||||
* @return mixed Value set for the option, or registered default if not set.
|
||||
*/
|
||||
public function get() {
|
||||
return (int) parent::get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the expected value type.
|
||||
*
|
||||
* @since 1.16.0
|
||||
*
|
||||
* @return string The type name.
|
||||
*/
|
||||
protected function get_type() {
|
||||
return 'integer';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default value.
|
||||
*
|
||||
* We use the old "googlesitekit_first_admin" option here as it used to store the ID
|
||||
* of the first admin user to use the plugin. If this option doesn't exist, it will return 0.
|
||||
*
|
||||
* @since 1.16.0
|
||||
*
|
||||
* @return int The default value.
|
||||
*/
|
||||
protected function get_default() {
|
||||
return (int) $this->options->get( 'googlesitekit_first_admin' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the callback for sanitizing the setting's value before saving.
|
||||
*
|
||||
* @since 1.16.0
|
||||
*
|
||||
* @return callable The callable sanitize callback.
|
||||
*/
|
||||
protected function get_sanitize_callback() {
|
||||
return 'intval';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Authentication\Profile
|
||||
*
|
||||
* @package Google\Site_Kit
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Authentication;
|
||||
|
||||
use Google\Site_Kit\Core\Storage\User_Options;
|
||||
|
||||
/**
|
||||
* Class controlling the user's Google profile.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
final class Profile {
|
||||
|
||||
/**
|
||||
* Option key in options table.
|
||||
*/
|
||||
const OPTION = 'googlesitekit_profile';
|
||||
|
||||
/**
|
||||
* User_Options instance.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @var User_Options
|
||||
*/
|
||||
private $user_options;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param User_Options $user_options User_Options instance.
|
||||
*/
|
||||
public function __construct( User_Options $user_options ) {
|
||||
$this->user_options = $user_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves user profile data.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return array|bool Value set for the profile, or false if not set.
|
||||
*/
|
||||
public function get() {
|
||||
return $this->user_options->get( self::OPTION );
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves user profile data.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param array $data User profile data: email and photo.
|
||||
* @return bool True on success, false on failure.
|
||||
*/
|
||||
public function set( $data ) {
|
||||
return $this->user_options->set( self::OPTION, $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if user has their profile information stored.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return bool True if profile is set, false otherwise.
|
||||
*/
|
||||
public function has() {
|
||||
$profile = (array) $this->get();
|
||||
|
||||
if ( ! empty( $profile['email'] ) && ! empty( $profile['photo'] ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Authentication\Setup
|
||||
*
|
||||
* @package Google\Site_Kit\Core\Authentication
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Authentication;
|
||||
|
||||
use Google\Site_Kit\Context;
|
||||
use Google\Site_Kit\Core\Authentication\Clients\OAuth_Client;
|
||||
use Google\Site_Kit\Core\Authentication\Exception\Exchange_Site_Code_Exception;
|
||||
use Google\Site_Kit\Core\Authentication\Exception\Missing_Verification_Exception;
|
||||
use Google\Site_Kit\Core\Permissions\Permissions;
|
||||
use Google\Site_Kit\Core\Storage\User_Options;
|
||||
use Google\Site_Kit\Core\Util\Remote_Features;
|
||||
|
||||
/**
|
||||
* Base class for authentication setup.
|
||||
*
|
||||
* @since 1.48.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
class Setup {
|
||||
|
||||
/**
|
||||
* Context instance.
|
||||
*
|
||||
* @since 1.48.0
|
||||
*
|
||||
* @var Context
|
||||
*/
|
||||
protected $context;
|
||||
|
||||
/**
|
||||
* User_Options instance.
|
||||
*
|
||||
* @since 1.48.0
|
||||
*
|
||||
* @var User_Options
|
||||
*/
|
||||
protected $user_options;
|
||||
|
||||
/**
|
||||
* Authentication instance.
|
||||
*
|
||||
* @since 1.48.0
|
||||
*
|
||||
* @var Authentication
|
||||
*/
|
||||
protected $authentication;
|
||||
|
||||
/**
|
||||
* Google_Proxy instance.
|
||||
*
|
||||
* @since 1.48.0
|
||||
*
|
||||
* @var Google_Proxy
|
||||
*/
|
||||
protected $google_proxy;
|
||||
|
||||
/**
|
||||
* Proxy support URL.
|
||||
*
|
||||
* @since 1.109.0 Explicitly declared; previously, it was dynamically declared.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $proxy_support_link_url;
|
||||
|
||||
/**
|
||||
* Credentials instance.
|
||||
*
|
||||
* @since 1.48.0
|
||||
*
|
||||
* @var Credentials
|
||||
*/
|
||||
protected $credentials;
|
||||
|
||||
/**
|
||||
* Remote_Features instance.
|
||||
*
|
||||
* @since 1.118.0
|
||||
*
|
||||
* @var Remote_Features
|
||||
*/
|
||||
protected $remote_features;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 1.48.0
|
||||
*
|
||||
* @param Context $context Context instance.
|
||||
* @param User_Options $user_options User_Options instance.
|
||||
* @param Authentication $authentication Authentication instance.
|
||||
* @param Remote_Features $remote_features Remote_Features instance.
|
||||
*/
|
||||
public function __construct(
|
||||
Context $context,
|
||||
User_Options $user_options,
|
||||
Authentication $authentication,
|
||||
Remote_Features $remote_features
|
||||
) {
|
||||
$this->context = $context;
|
||||
$this->user_options = $user_options;
|
||||
$this->authentication = $authentication;
|
||||
$this->remote_features = $remote_features;
|
||||
$this->credentials = $authentication->credentials();
|
||||
$this->google_proxy = $authentication->get_google_proxy();
|
||||
$this->proxy_support_link_url = $authentication->get_proxy_support_link_url();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers functionality through WordPress hooks.
|
||||
*
|
||||
* @since 1.48.0
|
||||
*/
|
||||
public function register() {
|
||||
add_action( 'admin_action_' . Google_Proxy::ACTION_SETUP_START, array( $this, 'handle_action_setup_start' ) );
|
||||
add_action( 'admin_action_' . Google_Proxy::ACTION_VERIFY, array( $this, 'handle_action_verify' ) );
|
||||
add_action( 'admin_action_' . Google_Proxy::ACTION_EXCHANGE_SITE_CODE, array( $this, 'handle_action_exchange_site_code' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Composes the oAuth proxy get help link.
|
||||
*
|
||||
* @since 1.81.0
|
||||
*
|
||||
* @return string The get help link.
|
||||
*/
|
||||
private function get_oauth_proxy_failed_help_link() {
|
||||
return sprintf(
|
||||
/* translators: 1: Support link URL. 2: Get help string. */
|
||||
__( '<a href="%1$s" target="_blank">%2$s</a>', 'google-site-kit' ),
|
||||
esc_url( add_query_arg( 'error_id', 'request_to_auth_proxy_failed', $this->proxy_support_link_url ) ),
|
||||
esc_html__( 'Get help', 'google-site-kit' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the setup start action, taking the user to the proxy setup screen.
|
||||
*
|
||||
* @since 1.48.0
|
||||
*/
|
||||
public function handle_action_setup_start() {
|
||||
$nonce = htmlspecialchars( $this->context->input()->filter( INPUT_GET, 'nonce' ) );
|
||||
$redirect_url = $this->context->input()->filter( INPUT_GET, 'redirect', FILTER_DEFAULT );
|
||||
|
||||
$this->verify_nonce( $nonce, Google_Proxy::ACTION_SETUP_START );
|
||||
|
||||
if ( ! current_user_can( Permissions::SETUP ) ) {
|
||||
wp_die( esc_html__( 'You have insufficient permissions to connect Site Kit.', 'google-site-kit' ) );
|
||||
}
|
||||
|
||||
if ( ! $this->credentials->using_proxy() ) {
|
||||
wp_die( esc_html__( 'Site Kit is not configured to use the authentication proxy.', 'google-site-kit' ) );
|
||||
}
|
||||
|
||||
$required_scopes = $this->authentication->get_oauth_client()->get_required_scopes();
|
||||
$this->google_proxy->with_scopes( $required_scopes );
|
||||
|
||||
$oauth_setup_redirect = $this->credentials->has()
|
||||
? $this->google_proxy->sync_site_fields( $this->credentials, 'sync' )
|
||||
: $this->google_proxy->register_site( 'sync' );
|
||||
|
||||
$oauth_proxy_failed_help_link = $this->get_oauth_proxy_failed_help_link();
|
||||
|
||||
if ( is_wp_error( $oauth_setup_redirect ) ) {
|
||||
$error_message = $oauth_setup_redirect->get_error_message();
|
||||
if ( empty( $error_message ) ) {
|
||||
$error_message = $oauth_setup_redirect->get_error_code();
|
||||
}
|
||||
|
||||
wp_die(
|
||||
sprintf(
|
||||
/* translators: 1: Error message or error code. 2: Get help link. */
|
||||
esc_html__( 'The request to the authentication proxy has failed with an error: %1$s %2$s.', 'google-site-kit' ),
|
||||
esc_html( $error_message ),
|
||||
wp_kses(
|
||||
$oauth_proxy_failed_help_link,
|
||||
array(
|
||||
'a' => array(
|
||||
'href' => array(),
|
||||
'target' => array(),
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! filter_var( $oauth_setup_redirect, FILTER_VALIDATE_URL ) ) {
|
||||
wp_die(
|
||||
sprintf(
|
||||
/* translators: %s: Get help link. */
|
||||
esc_html__( 'The request to the authentication proxy has failed. Please, try again later. %s.', 'google-site-kit' ),
|
||||
wp_kses(
|
||||
$oauth_proxy_failed_help_link,
|
||||
array(
|
||||
'a' => array(
|
||||
'href' => array(),
|
||||
'target' => array(),
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( $redirect_url ) {
|
||||
$this->user_options->set( OAuth_Client::OPTION_REDIRECT_URL, $redirect_url );
|
||||
}
|
||||
|
||||
wp_safe_redirect( $oauth_setup_redirect );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the action for verifying site ownership.
|
||||
*
|
||||
* @since 1.48.0
|
||||
* @since 1.49.0 Sets the `verify` and `verification_method` and `site_id` query params.
|
||||
*/
|
||||
public function handle_action_verify() {
|
||||
$input = $this->context->input();
|
||||
$step = htmlspecialchars( $input->filter( INPUT_GET, 'step' ) );
|
||||
$nonce = htmlspecialchars( $input->filter( INPUT_GET, 'nonce' ) );
|
||||
$code = htmlspecialchars( $input->filter( INPUT_GET, 'googlesitekit_code' ) );
|
||||
$site_code = htmlspecialchars( $input->filter( INPUT_GET, 'googlesitekit_site_code' ) );
|
||||
$verification_token = htmlspecialchars( $input->filter( INPUT_GET, 'googlesitekit_verification_token' ) );
|
||||
$verification_method = htmlspecialchars( $input->filter( INPUT_GET, 'googlesitekit_verification_token_type' ) );
|
||||
|
||||
$this->verify_nonce( $nonce );
|
||||
|
||||
if ( ! current_user_can( Permissions::SETUP ) ) {
|
||||
wp_die( esc_html__( 'You don’t have permissions to set up Site Kit.', 'google-site-kit' ), 403 );
|
||||
}
|
||||
|
||||
if ( ! $code ) {
|
||||
wp_die( esc_html__( 'Invalid request.', 'google-site-kit' ), 400 );
|
||||
}
|
||||
|
||||
if ( ! $verification_token || ! $verification_method ) {
|
||||
wp_die( esc_html__( 'Verifying site ownership requires a token and verification method.', 'google-site-kit' ), 400 );
|
||||
}
|
||||
|
||||
$this->handle_verification( $verification_token, $verification_method );
|
||||
|
||||
$proxy_query_params = array(
|
||||
'step' => $step,
|
||||
'verify' => 'true',
|
||||
'verification_method' => $verification_method,
|
||||
);
|
||||
|
||||
// If the site does not have a site ID yet, a site code will be passed.
|
||||
// Handling the site code here will save the extra redirect from the proxy if successful.
|
||||
if ( $site_code ) {
|
||||
try {
|
||||
$this->handle_site_code( $code, $site_code );
|
||||
} catch ( Missing_Verification_Exception $exception ) {
|
||||
$proxy_query_params['site_code'] = $site_code;
|
||||
|
||||
$this->redirect_to_proxy( $code, $proxy_query_params );
|
||||
} catch ( Exchange_Site_Code_Exception $exception ) {
|
||||
$this->redirect_to_splash();
|
||||
}
|
||||
}
|
||||
|
||||
$credentials = $this->credentials->get();
|
||||
$proxy_query_params['site_id'] = ! empty( $credentials['oauth2_client_id'] ) ? $credentials['oauth2_client_id'] : '';
|
||||
|
||||
$this->redirect_to_proxy( $code, $proxy_query_params );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the action for exchanging the site code for site credentials.
|
||||
*
|
||||
* This action will only be called if the site code failed to be handled
|
||||
* during the verification step.
|
||||
*
|
||||
* @since 1.48.0
|
||||
*/
|
||||
public function handle_action_exchange_site_code() {
|
||||
$input = $this->context->input();
|
||||
$step = htmlspecialchars( $input->filter( INPUT_GET, 'step' ) );
|
||||
$nonce = htmlspecialchars( $input->filter( INPUT_GET, 'nonce' ) );
|
||||
$code = htmlspecialchars( $input->filter( INPUT_GET, 'googlesitekit_code' ) );
|
||||
$site_code = htmlspecialchars( $input->filter( INPUT_GET, 'googlesitekit_site_code' ) );
|
||||
|
||||
$this->verify_nonce( $nonce );
|
||||
|
||||
if ( ! current_user_can( Permissions::SETUP ) ) {
|
||||
wp_die( esc_html__( 'You don’t have permissions to set up Site Kit.', 'google-site-kit' ), 403 );
|
||||
}
|
||||
|
||||
if ( ! $code || ! $site_code ) {
|
||||
wp_die( esc_html__( 'Invalid request.', 'google-site-kit' ), 400 );
|
||||
}
|
||||
|
||||
try {
|
||||
$this->handle_site_code( $code, $site_code );
|
||||
} catch ( Missing_Verification_Exception $exception ) {
|
||||
$this->redirect_to_proxy( $code, compact( 'site_code', 'step' ) );
|
||||
} catch ( Exchange_Site_Code_Exception $exception ) {
|
||||
$this->redirect_to_splash();
|
||||
}
|
||||
|
||||
$credentials = $this->credentials->get();
|
||||
$site_id = ! empty( $credentials['oauth2_client_id'] ) ? $credentials['oauth2_client_id'] : '';
|
||||
|
||||
$this->redirect_to_proxy( $code, compact( 'site_id', 'step' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the given nonce for a setup action.
|
||||
*
|
||||
* The nonce passed from the proxy will always be the one initially provided to it.
|
||||
* {@see Google_Proxy::setup_url()}
|
||||
*
|
||||
* @since 1.48.0
|
||||
*
|
||||
* @param string $nonce Action nonce.
|
||||
* @param string $action Action name. Optional. Defaults to the action for the nonce given to the proxy.
|
||||
*/
|
||||
protected function verify_nonce( $nonce, $action = Google_Proxy::NONCE_ACTION ) {
|
||||
if ( ! wp_verify_nonce( $nonce, $action ) ) {
|
||||
$this->authentication->invalid_nonce_error( $action );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles site verification.
|
||||
*
|
||||
* @since 1.48.0
|
||||
*
|
||||
* @param string $token Verification token.
|
||||
* @param string $method Verification method.
|
||||
*/
|
||||
protected function handle_verification( $token, $method ) {
|
||||
/**
|
||||
* Verifies site ownership using the given token and verification method.
|
||||
*
|
||||
* @since 1.48.0
|
||||
*
|
||||
* @param string $token Verification token.
|
||||
* @param string $method Verification method.
|
||||
*/
|
||||
do_action( 'googlesitekit_verify_site_ownership', $token, $method );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the exchange of a code and site code for client credentials from the proxy.
|
||||
*
|
||||
* @since 1.48.0
|
||||
*
|
||||
* @param string $code Code ('googlesitekit_code') provided by proxy.
|
||||
* @param string $site_code Site code ('googlesitekit_site_code') provided by proxy.
|
||||
*
|
||||
* @throws Missing_Verification_Exception Thrown if exchanging the site code fails due to missing site verification.
|
||||
* @throws Exchange_Site_Code_Exception Thrown if exchanging the site code fails for any other reason.
|
||||
*/
|
||||
protected function handle_site_code( $code, $site_code ) {
|
||||
$data = $this->google_proxy->exchange_site_code( $site_code, $code );
|
||||
|
||||
if ( is_wp_error( $data ) ) {
|
||||
$error_code = $data->get_error_message() ?: $data->get_error_code();
|
||||
$error_code = $error_code ?: 'unknown_error';
|
||||
|
||||
if ( 'missing_verification' === $error_code ) {
|
||||
throw new Missing_Verification_Exception();
|
||||
}
|
||||
|
||||
$this->user_options->set( OAuth_Client::OPTION_ERROR_CODE, $error_code );
|
||||
|
||||
throw new Exchange_Site_Code_Exception( $error_code );
|
||||
}
|
||||
|
||||
$this->credentials->set(
|
||||
array(
|
||||
'oauth2_client_id' => $data['site_id'],
|
||||
'oauth2_client_secret' => $data['site_secret'],
|
||||
)
|
||||
);
|
||||
|
||||
$this->remote_features->fetch_remote_features();
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects back to the authentication service with any added parameters.
|
||||
*
|
||||
* For v2 of the proxy, this method now has to ensure that the user is redirected back to the correct step on the
|
||||
* proxy, based on which action was received.
|
||||
*
|
||||
* @since 1.48.0
|
||||
* @since 1.49.0 Uses the new `Google_Proxy::setup_url_v2` method when the `serviceSetupV2` feature flag is enabled.
|
||||
*
|
||||
* @param string $code Code ('googlesitekit_code') provided by proxy.
|
||||
* @param array $params Additional query parameters to include in the proxy redirect URL.
|
||||
*/
|
||||
protected function redirect_to_proxy( $code = '', $params = array() ) {
|
||||
$params['code'] = $code;
|
||||
$url = $this->authentication->get_google_proxy()->setup_url( $params );
|
||||
|
||||
wp_safe_redirect( $url );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects to the Site Kit splash page.
|
||||
*
|
||||
* @since 1.48.0
|
||||
*/
|
||||
protected function redirect_to_splash() {
|
||||
wp_safe_redirect( $this->context->admin_url( 'splash' ) );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Authentication\Token
|
||||
*
|
||||
* @package Google\Site_Kit
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Authentication;
|
||||
|
||||
use Google\Site_Kit\Core\Storage\User_Options;
|
||||
use Google\Site_Kit\Core\Storage\Encrypted_User_Options;
|
||||
use Google\Site_Kit\Core\Authentication\Clients\OAuth_Client;
|
||||
|
||||
/**
|
||||
* Class representing the OAuth token for a user.
|
||||
*
|
||||
* This includes the access token, its creation and expiration data, and the refresh token.
|
||||
* This class is compatible with `Google\Site_Kit\Core\Storage\User_Setting`, as it should in the future be adjusted
|
||||
* so that the four pieces of data become a single user setting.
|
||||
*
|
||||
* @since 1.39.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
final class Token {
|
||||
|
||||
/**
|
||||
* User_Options instance.
|
||||
*
|
||||
* @since 1.39.0
|
||||
* @var User_Options
|
||||
*/
|
||||
protected $user_options;
|
||||
|
||||
/**
|
||||
* Encrypted_User_Options instance.
|
||||
*
|
||||
* @since 1.39.0
|
||||
* @var Encrypted_User_Options
|
||||
*/
|
||||
private $encrypted_user_options;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 1.39.0
|
||||
*
|
||||
* @param User_Options $user_options User_Options instance.
|
||||
*/
|
||||
public function __construct( User_Options $user_options ) {
|
||||
$this->user_options = $user_options;
|
||||
$this->encrypted_user_options = new Encrypted_User_Options( $this->user_options );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not the setting exists.
|
||||
*
|
||||
* @since 1.39.0
|
||||
*
|
||||
* @return bool True on success, false on failure.
|
||||
*/
|
||||
public function has() {
|
||||
if ( ! $this->get() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the setting.
|
||||
*
|
||||
* @since 1.39.0
|
||||
*
|
||||
* @return mixed Value set for the option, or default if not set.
|
||||
*/
|
||||
public function get() {
|
||||
$access_token = $this->encrypted_user_options->get( OAuth_Client::OPTION_ACCESS_TOKEN );
|
||||
if ( empty( $access_token ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$token = array(
|
||||
'access_token' => $access_token,
|
||||
'expires_in' => (int) $this->user_options->get( OAuth_Client::OPTION_ACCESS_TOKEN_EXPIRES_IN ),
|
||||
'created' => (int) $this->user_options->get( OAuth_Client::OPTION_ACCESS_TOKEN_CREATED ),
|
||||
);
|
||||
|
||||
$refresh_token = $this->encrypted_user_options->get( OAuth_Client::OPTION_REFRESH_TOKEN );
|
||||
if ( ! empty( $refresh_token ) ) {
|
||||
$token['refresh_token'] = $refresh_token;
|
||||
}
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the setting with the given value.
|
||||
*
|
||||
* @since 1.39.0
|
||||
*
|
||||
* @param mixed $value Setting value. Must be serializable if non-scalar.
|
||||
*
|
||||
* @return bool True on success, false on failure.
|
||||
*/
|
||||
public function set( $value ) {
|
||||
if ( empty( $value['access_token'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use reasonable defaults for these fields.
|
||||
if ( empty( $value['expires_in'] ) ) {
|
||||
$value['expires_in'] = HOUR_IN_SECONDS;
|
||||
}
|
||||
if ( empty( $value['created'] ) ) {
|
||||
$value['created'] = time();
|
||||
}
|
||||
|
||||
$this->encrypted_user_options->set( OAuth_Client::OPTION_ACCESS_TOKEN, $value['access_token'] );
|
||||
$this->user_options->set( OAuth_Client::OPTION_ACCESS_TOKEN_EXPIRES_IN, $value['expires_in'] );
|
||||
$this->user_options->set( OAuth_Client::OPTION_ACCESS_TOKEN_CREATED, $value['created'] );
|
||||
|
||||
if ( ! empty( $value['refresh_token'] ) ) {
|
||||
$this->encrypted_user_options->set( OAuth_Client::OPTION_REFRESH_TOKEN, $value['refresh_token'] );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the setting.
|
||||
*
|
||||
* @since 1.39.0
|
||||
*
|
||||
* @return bool True on success, false on failure.
|
||||
*/
|
||||
public function delete() {
|
||||
$this->user_options->delete( OAuth_Client::OPTION_ACCESS_TOKEN );
|
||||
$this->user_options->delete( OAuth_Client::OPTION_ACCESS_TOKEN_EXPIRES_IN );
|
||||
$this->user_options->delete( OAuth_Client::OPTION_ACCESS_TOKEN_CREATED );
|
||||
$this->user_options->delete( OAuth_Client::OPTION_REFRESH_TOKEN );
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Authentication\Verification
|
||||
*
|
||||
* @package Google\Site_Kit
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Authentication;
|
||||
|
||||
use Google\Site_Kit\Core\Storage\User_Setting;
|
||||
|
||||
/**
|
||||
* Class representing the status of whether a user is verified as an owner of the site.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
final class Verification extends User_Setting {
|
||||
|
||||
/**
|
||||
* User option key.
|
||||
*/
|
||||
const OPTION = 'googlesitekit_site_verified_meta';
|
||||
|
||||
/**
|
||||
* Gets the value of the setting.
|
||||
*
|
||||
* @since 1.4.0
|
||||
*
|
||||
* @return mixed Value set for the option, or default if not set.
|
||||
*/
|
||||
public function get() {
|
||||
return (bool) parent::get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flags the user as verified or unverified.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param bool $verified Whether to flag the user as verified or unverified.
|
||||
* @return bool True on success, false on failure.
|
||||
*/
|
||||
public function set( $verified ) {
|
||||
if ( ! $verified ) {
|
||||
return $this->delete();
|
||||
}
|
||||
|
||||
return parent::set( '1' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the expected value type.
|
||||
*
|
||||
* @since 1.4.0
|
||||
*
|
||||
* @return string The type name.
|
||||
*/
|
||||
protected function get_type() {
|
||||
return 'boolean';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default value.
|
||||
*
|
||||
* Returns an empty string by default for consistency with get_user_meta.
|
||||
*
|
||||
* @since 1.4.0
|
||||
*
|
||||
* @return mixed The default value.
|
||||
*/
|
||||
protected function get_default() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Authentication\Verification_File
|
||||
*
|
||||
* @package Google\Site_Kit
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Authentication;
|
||||
|
||||
use Google\Site_Kit\Core\Storage\User_Setting;
|
||||
|
||||
/**
|
||||
* Class representing the site verification file token for a user.
|
||||
*
|
||||
* @since 1.1.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
final class Verification_File extends User_Setting {
|
||||
|
||||
/**
|
||||
* User option key.
|
||||
*/
|
||||
const OPTION = 'googlesitekit_site_verification_file';
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google\Site_Kit\Core\Authentication\Verification_Meta
|
||||
*
|
||||
* @package Google\Site_Kit
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
* @link https://sitekit.withgoogle.com
|
||||
*/
|
||||
|
||||
namespace Google\Site_Kit\Core\Authentication;
|
||||
|
||||
use Google\Site_Kit\Core\Storage\User_Setting;
|
||||
|
||||
/**
|
||||
* Class representing the site verification meta tag for a user.
|
||||
*
|
||||
* @since 1.1.0
|
||||
* @access private
|
||||
* @ignore
|
||||
*/
|
||||
final class Verification_Meta extends User_Setting {
|
||||
|
||||
/**
|
||||
* User option key.
|
||||
*/
|
||||
const OPTION = 'googlesitekit_site_verification_meta';
|
||||
}
|
||||
Reference in New Issue
Block a user