updated plugin Jetpack Protect
version 4.0.0
This commit is contained in:
@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
extends: [ require.resolve( 'jetpack-js-tools/eslintrc/react' ) ],
|
||||
};
|
@ -5,6 +5,189 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [6.8.1] - 2025-03-24
|
||||
### Changed
|
||||
- Update dependencies.
|
||||
|
||||
## [6.8.0] - 2025-03-24
|
||||
### Added
|
||||
- Add support for provider-specific authentication. [#42602]
|
||||
|
||||
## [6.7.7] - 2025-03-21
|
||||
### Changed
|
||||
- Internal updates.
|
||||
|
||||
## [6.7.6] - 2025-03-18
|
||||
### Changed
|
||||
- Update package dependencies. [#42511]
|
||||
|
||||
## [6.7.5] - 2025-03-17
|
||||
### Changed
|
||||
- Internal updates.
|
||||
|
||||
## [6.7.4] - 2025-03-12
|
||||
### Changed
|
||||
- Update package dependencies. [#42384]
|
||||
|
||||
## [6.7.3] - 2025-03-10
|
||||
### Changed
|
||||
- Internal updates.
|
||||
|
||||
## [6.7.2] - 2025-03-06
|
||||
### Changed
|
||||
- Update dependencies.
|
||||
|
||||
## [6.7.1] - 2025-03-05
|
||||
### Changed
|
||||
- Internal updates.
|
||||
|
||||
## [6.7.0] - 2025-03-03
|
||||
### Added
|
||||
- Add 'isRegistered' flag to connection data endpoint. [#42123]
|
||||
- Add the 'is_signed_with_user_token()' method for REST authentication. [#39432]
|
||||
- Allow cookie auth for user provisioning. [#42086]
|
||||
|
||||
### Changed
|
||||
- Update package dependencies. [#42163]
|
||||
|
||||
### Removed
|
||||
- Remove excessive check in fetching current user ID in user provisioning. [#42106]
|
||||
- Remove register_nonce from site connection. [#42076]
|
||||
|
||||
## [6.6.0] - 2025-02-24
|
||||
### Added
|
||||
- Warn that disconnecting owner account will disconnect all other users first. [#41923]
|
||||
|
||||
### Changed
|
||||
- Move the API endpoint for unlinking the user to the automattic/jetpack-connection package. [#41398]
|
||||
|
||||
## [6.5.0] - 2025-02-17
|
||||
### Changed
|
||||
- Display connection status on Users page independent of the SSO module. [#41794]
|
||||
|
||||
### Fixed
|
||||
- Make sure wpcom_id is a string before passing it over as _ui. [#41787]
|
||||
|
||||
## [6.4.1] - 2025-02-11
|
||||
### Changed
|
||||
- Update dependencies.
|
||||
|
||||
## [6.4.0] - 2025-02-10
|
||||
### Changed
|
||||
- Tracks: Add site type to events [#41307]
|
||||
- Updated package dependencies. [#41491]
|
||||
|
||||
## [6.3.2] - 2025-02-03
|
||||
### Changed
|
||||
- Updated package dependencies. [#41286]
|
||||
|
||||
### Fixed
|
||||
- Code: Remove extra params on function calls. [#41263]
|
||||
|
||||
## [6.3.1] - 2025-01-27
|
||||
### Changed
|
||||
- Internal updates.
|
||||
|
||||
## [6.3.0] - 2025-01-20
|
||||
### Changed
|
||||
- Code: Use function-style exit() and die() with a default status code of 0. [#41167]
|
||||
- Move WPCOM_REST_API_Proxy_Request trait to the connection package. [#41023]
|
||||
- Updated package dependencies. [#41099]
|
||||
|
||||
### Fixed
|
||||
- Add heartbeat deactivation on site disconnection. [#41117]
|
||||
|
||||
## [6.2.2] - 2025-01-06
|
||||
### Added
|
||||
- Added tests to increase code coverage. [#39963]
|
||||
|
||||
### Changed
|
||||
- Updated package dependencies. [#40831]
|
||||
|
||||
## [6.2.1] - 2024-12-16
|
||||
### Changed
|
||||
- Updated package dependencies. [#40564]
|
||||
|
||||
## [6.2.0] - 2024-12-09
|
||||
### Added
|
||||
- Added a mechanism to use callbacks for package options. [#40474]
|
||||
- REST user provisioning with an app password. [#40447]
|
||||
|
||||
## [6.1.1] - 2024-12-04
|
||||
### Changed
|
||||
- Updated package dependencies. [#40363]
|
||||
|
||||
## [6.1.0] - 2024-11-25
|
||||
### Added
|
||||
- Allow using application password for site registration. [#40233]
|
||||
|
||||
### Changed
|
||||
- Updated dependencies. [#40286]
|
||||
- Updated package dependencies. [#40258] [#40288]
|
||||
|
||||
## [6.0.1] - 2024-11-18
|
||||
### Fixed
|
||||
- Work around a WP user caching bug (https://core.trac.wordpress.org/ticket/62003). [#40188]
|
||||
|
||||
## [6.0.0] - 2024-11-14
|
||||
### Removed
|
||||
- General: Update minimum PHP version to 7.2. [#40147]
|
||||
|
||||
## [5.1.7] - 2024-11-11
|
||||
### Changed
|
||||
- Updated package dependencies. [#39999] [#40060]
|
||||
|
||||
## [5.1.6] - 2024-11-04
|
||||
### Added
|
||||
- Enable test coverage. [#39961]
|
||||
|
||||
## [5.1.5] - 2024-10-25
|
||||
### Changed
|
||||
- Internal updates.
|
||||
|
||||
## [5.1.4] - 2024-10-21
|
||||
### Changed
|
||||
- SSO: optimize 'admin_notices' action callback. [#39811]
|
||||
|
||||
## [5.1.3] - 2024-10-10
|
||||
### Changed
|
||||
- Updated package dependencies.
|
||||
|
||||
## [5.1.2] - 2024-10-07
|
||||
### Changed
|
||||
- Adjust conditions to optimize admin notices callback. [#39650]
|
||||
- Initialize assets in wp-admin only. [#39604]
|
||||
- Updated package dependencies. [#39594]
|
||||
|
||||
## [5.1.1] - 2024-09-30
|
||||
### Changed
|
||||
- In 'connect_url_redirect' hook, redirect to 'redirect_after_auth` url if already connected (for connect_after_checkout flow). [#39573]
|
||||
- My Jetpack Welcome Flow: Display default recommendations upfront first, then offer optional survey for customized recommendations. [#39485]
|
||||
|
||||
## [5.1.0] - 2024-09-25
|
||||
### Changed
|
||||
- Jetpack Connection - REST API: Allow site-level authentication on POST requests to 'jetpack/v4/connection' [#39503]
|
||||
|
||||
## [5.0.0] - 2024-09-23
|
||||
### Removed
|
||||
- Connection: Removed deprecated features_enabled method [#39475]
|
||||
- Connection: Removed deprecated method features_available [#39442]
|
||||
|
||||
## [4.0.4] - 2024-09-18
|
||||
### Changed
|
||||
- SSO tooltip: Use anchor element's document instead of the global `document`. [#39364]
|
||||
|
||||
## [4.0.3] - 2024-09-16
|
||||
### Removed
|
||||
- Remove deprecated code from connected plugins component. [#39375]
|
||||
|
||||
### Fixed
|
||||
- Fix the connected plugins option on multisites. [#39355]
|
||||
|
||||
## [4.0.2] - 2024-09-10
|
||||
### Changed
|
||||
- Updated package dependencies. [#39302]
|
||||
|
||||
## [4.0.1] - 2024-09-06
|
||||
### Removed
|
||||
- Removed throwing of warning if a given Jetpack options does not exist [#39270]
|
||||
@ -1187,6 +1370,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
- Separate the connection library into its own package.
|
||||
|
||||
[6.8.1]: https://github.com/Automattic/jetpack-connection/compare/v6.8.0...v6.8.1
|
||||
[6.8.0]: https://github.com/Automattic/jetpack-connection/compare/v6.7.7...v6.8.0
|
||||
[6.7.7]: https://github.com/Automattic/jetpack-connection/compare/v6.7.6...v6.7.7
|
||||
[6.7.6]: https://github.com/Automattic/jetpack-connection/compare/v6.7.5...v6.7.6
|
||||
[6.7.5]: https://github.com/Automattic/jetpack-connection/compare/v6.7.4...v6.7.5
|
||||
[6.7.4]: https://github.com/Automattic/jetpack-connection/compare/v6.7.3...v6.7.4
|
||||
[6.7.3]: https://github.com/Automattic/jetpack-connection/compare/v6.7.2...v6.7.3
|
||||
[6.7.2]: https://github.com/Automattic/jetpack-connection/compare/v6.7.1...v6.7.2
|
||||
[6.7.1]: https://github.com/Automattic/jetpack-connection/compare/v6.7.0...v6.7.1
|
||||
[6.7.0]: https://github.com/Automattic/jetpack-connection/compare/v6.6.0...v6.7.0
|
||||
[6.6.0]: https://github.com/Automattic/jetpack-connection/compare/v6.5.0...v6.6.0
|
||||
[6.5.0]: https://github.com/Automattic/jetpack-connection/compare/v6.4.1...v6.5.0
|
||||
[6.4.1]: https://github.com/Automattic/jetpack-connection/compare/v6.4.0...v6.4.1
|
||||
[6.4.0]: https://github.com/Automattic/jetpack-connection/compare/v6.3.2...v6.4.0
|
||||
[6.3.2]: https://github.com/Automattic/jetpack-connection/compare/v6.3.1...v6.3.2
|
||||
[6.3.1]: https://github.com/Automattic/jetpack-connection/compare/v6.3.0...v6.3.1
|
||||
[6.3.0]: https://github.com/Automattic/jetpack-connection/compare/v6.2.2...v6.3.0
|
||||
[6.2.2]: https://github.com/Automattic/jetpack-connection/compare/v6.2.1...v6.2.2
|
||||
[6.2.1]: https://github.com/Automattic/jetpack-connection/compare/v6.2.0...v6.2.1
|
||||
[6.2.0]: https://github.com/Automattic/jetpack-connection/compare/v6.1.1...v6.2.0
|
||||
[6.1.1]: https://github.com/Automattic/jetpack-connection/compare/v6.1.0...v6.1.1
|
||||
[6.1.0]: https://github.com/Automattic/jetpack-connection/compare/v6.0.1...v6.1.0
|
||||
[6.0.1]: https://github.com/Automattic/jetpack-connection/compare/v6.0.0...v6.0.1
|
||||
[6.0.0]: https://github.com/Automattic/jetpack-connection/compare/v5.1.7...v6.0.0
|
||||
[5.1.7]: https://github.com/Automattic/jetpack-connection/compare/v5.1.6...v5.1.7
|
||||
[5.1.6]: https://github.com/Automattic/jetpack-connection/compare/v5.1.5...v5.1.6
|
||||
[5.1.5]: https://github.com/Automattic/jetpack-connection/compare/v5.1.4...v5.1.5
|
||||
[5.1.4]: https://github.com/Automattic/jetpack-connection/compare/v5.1.3...v5.1.4
|
||||
[5.1.3]: https://github.com/Automattic/jetpack-connection/compare/v5.1.2...v5.1.3
|
||||
[5.1.2]: https://github.com/Automattic/jetpack-connection/compare/v5.1.1...v5.1.2
|
||||
[5.1.1]: https://github.com/Automattic/jetpack-connection/compare/v5.1.0...v5.1.1
|
||||
[5.1.0]: https://github.com/Automattic/jetpack-connection/compare/v5.0.0...v5.1.0
|
||||
[5.0.0]: https://github.com/Automattic/jetpack-connection/compare/v4.0.4...v5.0.0
|
||||
[4.0.4]: https://github.com/Automattic/jetpack-connection/compare/v4.0.3...v4.0.4
|
||||
[4.0.3]: https://github.com/Automattic/jetpack-connection/compare/v4.0.2...v4.0.3
|
||||
[4.0.2]: https://github.com/Automattic/jetpack-connection/compare/v4.0.1...v4.0.2
|
||||
[4.0.1]: https://github.com/Automattic/jetpack-connection/compare/v4.0.0...v4.0.1
|
||||
[4.0.0]: https://github.com/Automattic/jetpack-connection/compare/v3.0.0...v4.0.0
|
||||
[3.0.0]: https://github.com/Automattic/jetpack-connection/compare/v2.12.5...v3.0.0
|
||||
|
@ -5,6 +5,11 @@
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
if ( function_exists( 'is_admin' ) && ! is_admin() && ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM ) ) {
|
||||
// Don't initialize the assets in the frontend on self-hosted and WoA.
|
||||
return;
|
||||
}
|
||||
|
||||
// If WordPress's plugin API is available already, use it. If not,
|
||||
// drop data into `$wp_filter` for `WP_Hook::build_preinitialized_hooks()`.
|
||||
if ( function_exists( 'add_action' ) ) {
|
||||
|
@ -4,20 +4,21 @@
|
||||
"type": "jetpack-library",
|
||||
"license": "GPL-2.0-or-later",
|
||||
"require": {
|
||||
"php": ">=7.0",
|
||||
"automattic/jetpack-a8c-mc-stats": "^2.0.2",
|
||||
"automattic/jetpack-admin-ui": "^0.4.5",
|
||||
"automattic/jetpack-assets": "^2.3.7",
|
||||
"automattic/jetpack-constants": "^2.0.4",
|
||||
"automattic/jetpack-roles": "^2.0.3",
|
||||
"automattic/jetpack-status": "^4.0.1",
|
||||
"automattic/jetpack-redirect": "^2.0.4"
|
||||
"php": ">=7.2",
|
||||
"automattic/jetpack-a8c-mc-stats": "^3.0.4",
|
||||
"automattic/jetpack-admin-ui": "^0.5.7",
|
||||
"automattic/jetpack-assets": "^4.0.14",
|
||||
"automattic/jetpack-constants": "^3.0.5",
|
||||
"automattic/jetpack-roles": "^3.0.5",
|
||||
"automattic/jetpack-status": "^5.0.10",
|
||||
"automattic/jetpack-redirect": "^3.0.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"automattic/wordbless": "@dev",
|
||||
"yoast/phpunit-polyfills": "^1.1.1",
|
||||
"brain/monkey": "2.6.1",
|
||||
"automattic/jetpack-changelogger": "^4.2.6"
|
||||
"automattic/jetpack-test-environment": "@dev",
|
||||
"yoast/phpunit-polyfills": "^3.0.0",
|
||||
"brain/monkey": "^2.6.2",
|
||||
"automattic/jetpack-changelogger": "^6.0.2",
|
||||
"automattic/phpunit-select-config": "^1.0.1"
|
||||
},
|
||||
"suggest": {
|
||||
"automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package."
|
||||
@ -41,10 +42,11 @@
|
||||
"pnpm run build"
|
||||
],
|
||||
"phpunit": [
|
||||
"./vendor/phpunit/phpunit/phpunit --colors=always"
|
||||
"phpunit-select-config phpunit.#.xml.dist --colors=always"
|
||||
],
|
||||
"test-coverage": [
|
||||
"php -dpcov.directory=. ./vendor/bin/phpunit-select-config phpunit.#.xml.dist --coverage-php \"$COVERAGE_DIR/php.cov\""
|
||||
],
|
||||
"post-install-cmd": "WorDBless\\Composer\\InstallDropin::copy",
|
||||
"post-update-cmd": "WorDBless\\Composer\\InstallDropin::copy",
|
||||
"test-php": [
|
||||
"@composer phpunit"
|
||||
]
|
||||
@ -62,7 +64,7 @@
|
||||
"link-template": "https://github.com/Automattic/jetpack-connection/compare/v${old}...v${new}"
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-trunk": "4.0.x-dev"
|
||||
"dev-trunk": "6.8.x-dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"test-only": [
|
||||
|
@ -1 +1 @@
|
||||
<?php return array('dependencies' => array('react', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n', 'wp-url'), 'version' => 'f6bce0e6b8e0527839ee');
|
||||
<?php return array('dependencies' => array('react', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-url'), 'version' => '286b93b23b84729b30b9');
|
||||
|
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
<?php return array('dependencies' => array('jetpack-script-data', 'react', 'react-jsx-runtime', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives', 'wp-url'), 'version' => '633f5b84c0735e749fc1');
|
||||
<?php return array('dependencies' => array('jetpack-script-data', 'react', 'react-jsx-runtime', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-primitives', 'wp-url'), 'version' => 'c64bcd54b1a8ca23f813');
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
<?php return array('dependencies' => array(), 'version' => '04d208524c748ec232f3');
|
||||
<?php return array('dependencies' => array('wp-polyfill'), 'version' => '15315caa8ea669cf4372');
|
||||
|
@ -1 +1 @@
|
||||
document.addEventListener("DOMContentLoaded",(function(){function t(){this.querySelector(".jetpack-sso-invitation-tooltip").style.display="block"}function e(t){document.activeElement!==t.target&&(this.querySelector(".jetpack-sso-invitation-tooltip").style.display="none")}document.querySelectorAll(".jetpack-sso-invitation-tooltip-icon:not(.sso-disconnected-user)").forEach((function(t){t.innerHTML+=" [?]";const e=document.createElement("span");e.classList.add("jetpack-sso-invitation-tooltip","jetpack-sso-th-tooltip");const n=window.Jetpack_SSOTooltip.tooltipString;function o(){t.appendChild(e),e.style.display="block"}function i(){document.activeElement!==t&&t.removeChild(e)}e.innerHTML+=n,t.addEventListener("mouseenter",o),t.addEventListener("focus",o),t.addEventListener("mouseleave",i),t.addEventListener("blur",i)})),document.querySelectorAll(".jetpack-sso-invitation-tooltip-icon:not(.jetpack-sso-status-column)").forEach((function(n){n.addEventListener("mouseenter",t),n.addEventListener("focus",t),n.addEventListener("mouseleave",e),n.addEventListener("blur",e)}))}));
|
||||
document.addEventListener("DOMContentLoaded",(function(){function t(){this.querySelector(".jetpack-sso-invitation-tooltip").style.display="block"}function e(t){t.target.ownerDocument.activeElement!==t.target&&(this.querySelector(".jetpack-sso-invitation-tooltip").style.display="none")}document.querySelectorAll(".jetpack-sso-invitation-tooltip-icon:not(.sso-disconnected-user)").forEach((function(t){t.innerHTML+=" [?]";const e=document.createElement("span");e.classList.add("jetpack-sso-invitation-tooltip");const n=window.Jetpack_SSOTooltip.tooltipString;function o(){t.appendChild(e),e.style.display="block"}function i(){t.ownerDocument.activeElement!==t&&t.removeChild(e)}e.innerHTML+=n,t.addEventListener("mouseenter",o),t.addEventListener("focus",o),t.addEventListener("mouseleave",i),t.addEventListener("blur",i)})),document.querySelectorAll(".jetpack-sso-invitation-tooltip-icon:not(.jetpack-sso-status-column)").forEach((function(n){n.addEventListener("mouseenter",t),n.addEventListener("focus",t),n.addEventListener("mouseleave",e),n.addEventListener("blur",e)}))}));
|
@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array('wp-polyfill'), 'version' => '53f1c19b5a564105c882');
|
@ -0,0 +1 @@
|
||||
document.addEventListener("DOMContentLoaded",(function(){document.querySelectorAll(".jetpack-connection-tooltip").forEach((function(o){o.textContent=window.jetpackConnectionTooltips.columnTooltip}))}));
|
@ -16,7 +16,7 @@ use Automattic\Jetpack\Connection\Manager;
|
||||
* Disable direct access.
|
||||
*/
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
if ( ! class_exists( IXR_Client::class ) ) {
|
||||
|
@ -127,6 +127,7 @@ class Jetpack_Options {
|
||||
'dismissed_welcome_banner', // (bool) Determines if the welcome banner has been dismissed or not.
|
||||
'recommendations_evaluation', // (object) Catalog of recommended modules with corresponding score following successful site evaluation in Welcome Banner.
|
||||
'dismissed_recommendations', // (bool) Determines if the recommendations have been dismissed or not.
|
||||
'recommendations_first_run', // (bool) Determines if the current recommendations are the initial default auto-loaded ones (without user input).
|
||||
'historically_active_modules', // (array) List of installed plugins/enabled modules that have at one point in time been active and working
|
||||
);
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ class Jetpack_Tracks_Client {
|
||||
return $event;
|
||||
}
|
||||
|
||||
$pixel = $event->build_pixel_url( $event );
|
||||
$pixel = $event->build_pixel_url();
|
||||
|
||||
if ( ! $pixel ) {
|
||||
return new WP_Error( 'invalid_pixel', 'cannot generate tracks pixel for given input', 400 );
|
||||
|
@ -823,42 +823,6 @@ class Jetpack_XMLRPC_Server {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated: This method is no longer part of the Connection package and now lives on the Jetpack plugin.
|
||||
*
|
||||
* Returns what features are available. Uses the slug of the module files.
|
||||
*
|
||||
* @deprecated since 1.25.0
|
||||
* @see Jetpack_XMLRPC_Methods::features_available() in the Jetpack plugin
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function features_available() {
|
||||
_deprecated_function( __METHOD__, '1.25.0', 'Jetpack_XMLRPC_Methods::features_available()' );
|
||||
if ( class_exists( 'Jetpack_XMLRPC_Methods' ) ) {
|
||||
return Jetpack_XMLRPC_Methods::features_available();
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated: This method is no longer part of the Connection package and now lives on the Jetpack plugin.
|
||||
*
|
||||
* Returns what features are enabled. Uses the slug of the modules files.
|
||||
*
|
||||
* @deprecated since 1.25.0
|
||||
* @see Jetpack_XMLRPC_Methods::features_enabled() in the Jetpack plugin
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function features_enabled() {
|
||||
_deprecated_function( __METHOD__, '1.25.0', 'Jetpack_XMLRPC_Methods::features_enabled()' );
|
||||
if ( class_exists( 'Jetpack_XMLRPC_Methods' ) ) {
|
||||
return Jetpack_XMLRPC_Methods::features_enabled();
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated: This method is no longer part of the Connection package and now lives on the Jetpack plugin.
|
||||
*
|
||||
|
@ -25,6 +25,9 @@ class Connection_Assets {
|
||||
|
||||
/**
|
||||
* Register assets.
|
||||
*
|
||||
* NOTICE: Please think twice before including Connection scripts in the frontend.
|
||||
* Those scripts are intended to be used in WP admin area.
|
||||
*/
|
||||
public static function register_assets() {
|
||||
|
||||
|
@ -40,7 +40,7 @@ class Connection_Notice {
|
||||
* @return void
|
||||
*/
|
||||
public function initialize_notices( $screen ) {
|
||||
if ( ! in_array(
|
||||
if ( in_array(
|
||||
$screen->id,
|
||||
array(
|
||||
'jetpack_page_akismet-key-config',
|
||||
@ -48,6 +48,19 @@ class Connection_Notice {
|
||||
),
|
||||
true
|
||||
) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
*
|
||||
* This function is firing within wp-admin and checks (below) if it is in the midst of a deletion on the users
|
||||
* page. Nonce will be already checked by WordPress, so we do not need to check ourselves.
|
||||
*/
|
||||
|
||||
if ( isset( $screen->base ) && 'users' === $screen->base
|
||||
&& isset( $_REQUEST['action'] ) && 'delete' === $_REQUEST['action']
|
||||
) {
|
||||
add_action( 'admin_notices', array( $this, 'delete_user_update_connection_owner_notice' ) );
|
||||
}
|
||||
}
|
||||
@ -57,23 +70,6 @@ class Connection_Notice {
|
||||
* the connection owner.
|
||||
*/
|
||||
public function delete_user_update_connection_owner_notice() {
|
||||
global $current_screen;
|
||||
|
||||
/*
|
||||
* phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
*
|
||||
* This function is firing within wp-admin and checks (below) if it is in the midst of a deletion on the users
|
||||
* page. Nonce will be already checked by WordPress, so we do not need to check ourselves.
|
||||
*/
|
||||
|
||||
if ( ! isset( $current_screen->base ) || 'users' !== $current_screen->base ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $_REQUEST['action'] ) || 'delete' !== $_REQUEST['action'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get connection owner or bail.
|
||||
$connection_manager = new Manager();
|
||||
$connection_owner_id = $connection_manager->get_connection_owner_id();
|
||||
|
@ -691,7 +691,7 @@ class Error_Handler {
|
||||
/**
|
||||
* Fires inside the admin_notices hook just before displaying the error message for a broken connection.
|
||||
*
|
||||
* If you want to disable the default message from being displayed, return an emtpy value in the jetpack_connection_error_notice_message filter.
|
||||
* If you want to disable the default message from being displayed, return an empty value in the jetpack_connection_error_notice_message filter.
|
||||
*
|
||||
* @since 8.9.0
|
||||
*
|
||||
|
@ -80,6 +80,20 @@ class Manager {
|
||||
*/
|
||||
private static $disconnected_users = array();
|
||||
|
||||
/**
|
||||
* Cached connection status.
|
||||
*
|
||||
* @var bool|null True if the site is connected, false if not, null if not determined yet.
|
||||
*/
|
||||
private static $is_connected = null;
|
||||
|
||||
/**
|
||||
* Tracks whether connection status invalidation hooks have been added.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private static $connection_invalidators_added = false;
|
||||
|
||||
/**
|
||||
* Initialize the object.
|
||||
* Make sure to call the "Configure" first.
|
||||
@ -123,7 +137,9 @@ class Manager {
|
||||
add_filter( 'shutdown', array( new Package_Version_Tracker(), 'maybe_update_package_versions' ) );
|
||||
}
|
||||
|
||||
add_action( 'rest_api_init', array( $manager, 'initialize_rest_api_registration_connector' ) );
|
||||
// This runs on priority 11 - at least one api method in the connection package is set to override a previously
|
||||
// existing method from the Jetpack plugin. Running later than Jetpack's api init ensures the override is successful.
|
||||
add_action( 'rest_api_init', array( $manager, 'initialize_rest_api_registration_connector' ), 11 );
|
||||
|
||||
( new Nonce_Handler() )->init_schedule();
|
||||
|
||||
@ -140,6 +156,8 @@ class Manager {
|
||||
add_action( 'deleted_user', array( $manager, 'disconnect_user_force' ), 9, 1 );
|
||||
add_action( 'remove_user_from_blog', array( $manager, 'disconnect_user_force' ), 9, 1 );
|
||||
|
||||
$manager->add_connection_status_invalidation_hooks();
|
||||
|
||||
// Set up package version hook.
|
||||
add_filter( 'jetpack_package_versions', __NAMESPACE__ . '\Package_Version::send_package_version_to_tracker' );
|
||||
|
||||
@ -157,6 +175,28 @@ class Manager {
|
||||
Partner::init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds hooks to invalidate the memoized connection status.
|
||||
*/
|
||||
private function add_connection_status_invalidation_hooks() {
|
||||
if ( self::$connection_invalidators_added ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Force is_connected() to recompute after important actions.
|
||||
add_action( 'jetpack_site_registered', array( $this, 'reset_connection_status' ) );
|
||||
add_action( 'jetpack_site_disconnected', array( $this, 'reset_connection_status' ) );
|
||||
add_action( 'jetpack_sync_register_user', array( $this, 'reset_connection_status' ) );
|
||||
add_action( 'pre_update_jetpack_option_id', array( $this, 'reset_connection_status' ) );
|
||||
add_action( 'pre_update_jetpack_option_blog_token', array( $this, 'reset_connection_status' ) );
|
||||
add_action( 'pre_update_jetpack_option_user_token', array( $this, 'reset_connection_status' ) );
|
||||
add_action( 'pre_update_jetpack_option_user_tokens', array( $this, 'reset_connection_status' ) );
|
||||
// phpcs:ignore WPCUT.SwitchBlog.SwitchBlog -- wpcom flags **every** use of switch_blog, apparently expecting valid instances to ignore or suppress the sniff.
|
||||
add_action( 'switch_blog', array( $this, 'reset_connection_status' ) );
|
||||
|
||||
self::$connection_invalidators_added = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the XMLRPC request handlers.
|
||||
*
|
||||
@ -172,7 +212,7 @@ class Manager {
|
||||
$deprecated,
|
||||
$has_connected_owner,
|
||||
$is_signed,
|
||||
Jetpack_XMLRPC_Server $xmlrpc_server = null
|
||||
?Jetpack_XMLRPC_Server $xmlrpc_server = null
|
||||
) {
|
||||
add_filter( 'xmlrpc_blog_options', array( $this, 'xmlrpc_options' ), 1000, 2 );
|
||||
if ( $deprecated !== null ) {
|
||||
@ -280,7 +320,7 @@ class Manager {
|
||||
nocache_headers();
|
||||
$wp_xmlrpc_server->serve_request();
|
||||
|
||||
exit;
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -415,8 +455,9 @@ class Manager {
|
||||
|
||||
if (
|
||||
empty( $token_key )
|
||||
||
|
||||
empty( $version ) || (string) $jetpack_api_version !== $version ) {
|
||||
|| empty( $version )
|
||||
|| (string) $jetpack_api_version !== $version
|
||||
) {
|
||||
return new \WP_Error( 'malformed_token', 'Malformed token in request', compact( 'signature_details', 'error_type' ) );
|
||||
}
|
||||
|
||||
@ -596,9 +637,31 @@ class Manager {
|
||||
* @return bool
|
||||
*/
|
||||
public function is_connected() {
|
||||
$has_blog_id = (bool) \Jetpack_Options::get_option( 'id' );
|
||||
$has_blog_token = (bool) $this->get_tokens()->get_access_token();
|
||||
return $has_blog_id && $has_blog_token;
|
||||
if ( self::$is_connected === null ) {
|
||||
if ( ! self::$connection_invalidators_added ) {
|
||||
$this->add_connection_status_invalidation_hooks();
|
||||
}
|
||||
|
||||
$has_blog_id = (bool) \Jetpack_Options::get_option( 'id' );
|
||||
if ( $has_blog_id ) {
|
||||
$has_blog_token = (bool) $this->get_tokens()->get_access_token();
|
||||
self::$is_connected = ( $has_blog_id && $has_blog_token );
|
||||
} else {
|
||||
// Short-circuit, no need to check for tokens if there's no blog ID.
|
||||
self::$is_connected = false;
|
||||
}
|
||||
}
|
||||
return self::$is_connected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the memoized connection status.
|
||||
* This will force the connection status to be recomputed on the next check.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function reset_connection_status() {
|
||||
self::$is_connected = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -875,25 +938,54 @@ class Manager {
|
||||
|
||||
// Using wp_redirect intentionally because we're redirecting outside.
|
||||
wp_redirect( $this->get_authorization_url( $user, $redirect_url ) ); // phpcs:ignore WordPress.Security.SafeRedirect
|
||||
exit();
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Force user disconnect.
|
||||
*
|
||||
* @param int $user_id Local (external) user ID.
|
||||
* @param int $user_id Local (external) user ID.
|
||||
* @param bool $disconnect_all_users Whether to disconnect all users before disconnecting the primary user.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function disconnect_user_force( $user_id ) {
|
||||
public function disconnect_user_force( $user_id, $disconnect_all_users = false ) {
|
||||
if ( ! (int) $user_id ) {
|
||||
// Missing user ID.
|
||||
return false;
|
||||
}
|
||||
// If we are disconnecting the primary user we may need to disconnect all other users first
|
||||
if ( $user_id === $this->get_connection_owner_id() && $disconnect_all_users && ! $this->disconnect_all_users_except_primary() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->disconnect_user( $user_id, true, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects all users except the primary user.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function disconnect_all_users_except_primary() {
|
||||
|
||||
$all_connected_users = $this->get_connected_users();
|
||||
|
||||
foreach ( $all_connected_users as $user ) {
|
||||
// Skip the primary.
|
||||
if ( $user->ID === $this->get_connection_owner_id() ) {
|
||||
continue;
|
||||
}
|
||||
$disconnected = $this->disconnect_user( $user->ID, false, true );
|
||||
// If we fail to disconnect any user, we should not proceed with disconnecting the primary user.
|
||||
if ( ! $disconnected ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlinks the current user from the linked WordPress.com user.
|
||||
*
|
||||
@ -1505,6 +1597,16 @@ class Manager {
|
||||
// With site connections in mind, non-admin users can connect their account only if a connection owner exists.
|
||||
$caps = $this->has_connected_owner() ? array( 'read' ) : array( 'manage_options' );
|
||||
break;
|
||||
case 'jetpack_unlink_user':
|
||||
$is_offline_mode = ( new Status() )->is_offline_mode();
|
||||
if ( $is_offline_mode ) {
|
||||
$caps = array( 'do_not_allow' );
|
||||
break;
|
||||
}
|
||||
|
||||
// Non-admins can always disconnect
|
||||
$caps = array( 'read' );
|
||||
break;
|
||||
}
|
||||
return $caps;
|
||||
}
|
||||
@ -1561,12 +1663,17 @@ class Manager {
|
||||
return $cached_date;
|
||||
}
|
||||
|
||||
/**
|
||||
* We don't use the 'ID' field, but need it to overcome a WP caching bug: https://core.trac.wordpress.org/ticket/62003
|
||||
*
|
||||
* @todo Remote the 'ID' field from users fetching when the issue is fixed and Jetpack-supported WP versions move beyond it.
|
||||
*/
|
||||
$earliest_registered_users = get_users(
|
||||
array(
|
||||
'role' => 'administrator',
|
||||
'orderby' => 'user_registered',
|
||||
'order' => 'ASC',
|
||||
'fields' => array( 'user_registered' ),
|
||||
'fields' => array( 'ID', 'user_registered' ),
|
||||
'number' => 1,
|
||||
)
|
||||
);
|
||||
@ -2125,6 +2232,8 @@ class Manager {
|
||||
|
||||
( new Nonce_Handler() )->clean_all();
|
||||
|
||||
Heartbeat::init()->deactivate();
|
||||
|
||||
/**
|
||||
* Fires before a site is disconnected.
|
||||
*
|
||||
|
@ -12,7 +12,7 @@ namespace Automattic\Jetpack\Connection;
|
||||
*/
|
||||
class Package_Version {
|
||||
|
||||
const PACKAGE_VERSION = '4.0.1';
|
||||
const PACKAGE_VERSION = '6.8.1';
|
||||
|
||||
const PACKAGE_SLUG = 'connection';
|
||||
|
||||
|
@ -15,7 +15,7 @@ use Jetpack_Options;
|
||||
* Disable direct access.
|
||||
*/
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -17,14 +17,6 @@ class Plugin_Storage {
|
||||
|
||||
const ACTIVE_PLUGINS_OPTION_NAME = 'jetpack_connection_active_plugins';
|
||||
|
||||
/**
|
||||
* Options where disabled plugins were stored
|
||||
*
|
||||
* @deprecated since 1.39.0.
|
||||
* @var string
|
||||
*/
|
||||
const PLUGINS_DISABLED_OPTION_NAME = 'jetpack_connection_disabled_plugins';
|
||||
|
||||
/**
|
||||
* Transient name used as flag to indicate that the active connected plugins list needs refreshing.
|
||||
*/
|
||||
@ -93,13 +85,9 @@ class Plugin_Storage {
|
||||
* Even if you don't use Jetpack Config, it may be introduced later by other plugins,
|
||||
* so please make sure not to run the method too early in the code.
|
||||
*
|
||||
* @since 1.39.0 deprecated the $connected_only argument.
|
||||
*
|
||||
* @param null $deprecated null plugins that were explicitly disconnected. Deprecated, there's no such a thing as disconnecting only specific plugins anymore.
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public static function get_all( $deprecated = null ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
public static function get_all() {
|
||||
$maybe_error = self::ensure_configured();
|
||||
|
||||
if ( $maybe_error instanceof WP_Error ) {
|
||||
@ -144,7 +132,10 @@ class Plugin_Storage {
|
||||
}
|
||||
|
||||
if ( is_multisite() && get_current_blog_id() !== self::$current_blog_id ) {
|
||||
self::$plugins = (array) get_option( self::ACTIVE_PLUGINS_OPTION_NAME, array() );
|
||||
if ( self::$current_blog_id ) {
|
||||
// If blog ID got changed, pull the list of active plugins for that blog from the database.
|
||||
self::$plugins = (array) get_option( self::ACTIVE_PLUGINS_OPTION_NAME, array() );
|
||||
}
|
||||
self::$current_blog_id = get_current_blog_id();
|
||||
}
|
||||
|
||||
@ -234,43 +225,6 @@ class Plugin_Storage {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the plugin to the set of disconnected ones.
|
||||
*
|
||||
* @deprecated since 1.39.0.
|
||||
*
|
||||
* @param string $slug Plugin slug.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function disable_plugin( $slug ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the plugin from the set of disconnected ones.
|
||||
*
|
||||
* @deprecated since 1.39.0.
|
||||
*
|
||||
* @param string $slug Plugin slug.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function enable_plugin( $slug ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all plugins that were disconnected by user.
|
||||
*
|
||||
* @deprecated since 1.39.0.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_all_disabled_plugins() { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update active plugins option with current list of active plugins on WPCOM.
|
||||
* This is a fallback to ensure this option is always up to date on WPCOM in case
|
||||
|
@ -31,6 +31,13 @@ class Plugin {
|
||||
*/
|
||||
private $slug;
|
||||
|
||||
/**
|
||||
* Users Connection Admin instance.
|
||||
*
|
||||
* @var Users_Connection_Admin
|
||||
*/
|
||||
private $users_connection_admin;
|
||||
|
||||
/**
|
||||
* Initialize the plugin manager.
|
||||
*
|
||||
@ -38,6 +45,9 @@ class Plugin {
|
||||
*/
|
||||
public function __construct( $slug ) {
|
||||
$this->slug = $slug;
|
||||
|
||||
// Initialize Users_Connection_Admin
|
||||
$this->users_connection_admin = new Users_Connection_Admin();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -87,36 +97,4 @@ class Plugin {
|
||||
|
||||
return ! $plugins || ( array_key_exists( $this->slug, $plugins ) && 1 === count( $plugins ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the plugin to the set of disconnected ones.
|
||||
*
|
||||
* @deprecated since 1.39.0.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function disable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the plugin from the set of disconnected ones.
|
||||
*
|
||||
* @deprecated since 1.39.0.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function enable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this plugin is allowed to use the connection.
|
||||
*
|
||||
* @deprecated since 11.0
|
||||
* @return bool
|
||||
*/
|
||||
public function is_enabled() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -219,4 +219,17 @@ class Rest_Authentication {
|
||||
|
||||
return true === $instance->rest_authentication_status && 'blog' === $instance->rest_authentication_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the request was signed with a user token.
|
||||
*
|
||||
* @since 6.7.0
|
||||
*
|
||||
* @return bool True if the request was signed with a valid user token, false otherwise.
|
||||
*/
|
||||
public static function is_signed_with_user_token() {
|
||||
$instance = self::init();
|
||||
|
||||
return true === $instance->rest_authentication_status && 'user' === $instance->rest_authentication_type;
|
||||
}
|
||||
}
|
||||
|
@ -167,6 +167,20 @@ class REST_Connector {
|
||||
)
|
||||
);
|
||||
|
||||
// Disconnect/unlink user from WordPress.com servers.
|
||||
// this endpoint is set to override the older endpoint that was previously in the Jetpack plugin
|
||||
// Override is here in case an older version of the Jetpack plugin is installed alongside an updated standalone
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/connection/user',
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => __CLASS__ . '::unlink_user',
|
||||
'permission_callback' => __CLASS__ . '::unlink_user_permission_callback',
|
||||
),
|
||||
true // override other implementations
|
||||
);
|
||||
|
||||
// We are only registering this route if Jetpack-the-plugin is not active or it's version is ge 10.0-alpha.
|
||||
// The reason for doing so is to avoid conflicts between the Connection package and
|
||||
// older versions of Jetpack, registering the same route twice.
|
||||
@ -214,20 +228,15 @@ class REST_Connector {
|
||||
'callback' => array( $this, 'connection_register' ),
|
||||
'permission_callback' => __CLASS__ . '::jetpack_register_permission_check',
|
||||
'args' => array(
|
||||
'from' => array(
|
||||
'from' => array(
|
||||
'description' => __( 'Indicates where the registration action was triggered for tracking/segmentation purposes', 'jetpack-connection' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'registration_nonce' => array(
|
||||
'description' => __( 'The registration nonce', 'jetpack-connection' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
'redirect_uri' => array(
|
||||
'redirect_uri' => array(
|
||||
'description' => __( 'URI of the admin page where the user should be redirected after connection flow', 'jetpack-connection' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'plugin_slug' => array(
|
||||
'plugin_slug' => array(
|
||||
'description' => __( 'Indicates from what plugin the request is coming from', 'jetpack-connection' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
@ -252,6 +261,34 @@ class REST_Connector {
|
||||
)
|
||||
);
|
||||
|
||||
// Provider-specific authorization URL endpoint
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/connection/authorize_url/(?P<provider>[a-zA-Z]+)',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'connection_authorize_url_provider' ),
|
||||
'permission_callback' => __CLASS__ . '::user_connection_data_permission_check',
|
||||
'args' => array(
|
||||
'provider' => array(
|
||||
'description' => __( 'Authentication provider (google, github, apple, link)', 'jetpack-connection' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'enum' => array( 'google', 'github', 'apple', 'link' ),
|
||||
),
|
||||
'redirect_uri' => array(
|
||||
'description' => __( 'URI of the admin page where the user should be redirected after connection flow', 'jetpack-connection' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'email_address' => array(
|
||||
'description' => __( 'Email address for magic link authentication', 'jetpack-connection' ),
|
||||
'type' => 'string',
|
||||
'format' => 'email',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/user-token',
|
||||
@ -340,9 +377,15 @@ class REST_Connector {
|
||||
*
|
||||
* @return WP_Error|array
|
||||
*/
|
||||
public static function remote_provision( WP_REST_Request $request ) {
|
||||
public function remote_provision( WP_REST_Request $request ) {
|
||||
$request_data = $request->get_params();
|
||||
|
||||
if ( current_user_can( 'jetpack_connect_user' ) ) {
|
||||
$request_data['local_user'] = get_current_user_id();
|
||||
}
|
||||
|
||||
$xmlrpc_server = new Jetpack_XMLRPC_Server();
|
||||
$result = $xmlrpc_server->remote_provision( $request );
|
||||
$result = $xmlrpc_server->remote_provision( $request_data );
|
||||
|
||||
if ( is_a( $result, 'IXR_Error' ) ) {
|
||||
$result = new WP_Error( $result->code, $result->message );
|
||||
@ -394,9 +437,15 @@ class REST_Connector {
|
||||
/**
|
||||
* Remote provision endpoint permission check.
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public function remote_provision_permission_check() {
|
||||
public function remote_provision_permission_check( WP_REST_Request $request ) {
|
||||
if ( empty( $request['local_user'] ) && current_user_can( 'jetpack_connect_user' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Rest_Authentication::is_signed_with_blog_token()
|
||||
? true
|
||||
: new WP_Error( 'invalid_permission_remote_provision', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
|
||||
@ -545,15 +594,36 @@ class REST_Connector {
|
||||
*
|
||||
* @since 1.30.1
|
||||
*
|
||||
* @return bool|WP_Error True if user is able to disconnect the site.
|
||||
* @since 5.1.0 Modified the permission check to accept requests signed with blog tokens.
|
||||
*
|
||||
* @return bool|WP_Error True if user is able to disconnect the site or the request is signed with a blog token (aka a direct request from WPCOM).
|
||||
*/
|
||||
public static function disconnect_site_permission_check() {
|
||||
if ( current_user_can( 'jetpack_disconnect' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Rest_Authentication::is_signed_with_blog_token()
|
||||
? true
|
||||
: new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a user can use the /connection/user endpoint. Has to be a registered user and be currently linked.
|
||||
*
|
||||
* @since 6.3.3
|
||||
*
|
||||
* @return bool|WP_Error True if user is able to unlink.
|
||||
*/
|
||||
public static function unlink_user_permission_callback() {
|
||||
// This is a mapped capability
|
||||
// phpcs:ignore WordPress.WP.Capabilities.Unknown
|
||||
if ( current_user_can( 'jetpack_unlink_user' ) && ( new Manager() )->is_user_connected( get_current_user_id() ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
'invalid_user_permission_jetpack_disconnect',
|
||||
'invalid_user_permission_unlink_user',
|
||||
self::get_user_permissions_error_msg(),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
@ -607,11 +677,15 @@ class REST_Connector {
|
||||
'id' => $current_user->ID,
|
||||
'blogId' => $blog_id,
|
||||
'wpcomUser' => $wpcom_user_data,
|
||||
'gravatar' => get_avatar_url( $current_user->ID, 64, 'mm', '', array( 'force_display' => true ) ),
|
||||
'gravatar' => get_avatar_url( $current_user->ID ),
|
||||
'permissions' => array(
|
||||
'connect' => current_user_can( 'jetpack_connect' ),
|
||||
'connect_user' => current_user_can( 'jetpack_connect_user' ),
|
||||
'disconnect' => current_user_can( 'jetpack_disconnect' ),
|
||||
'connect' => current_user_can( 'jetpack_connect' ),
|
||||
'connect_user' => current_user_can( 'jetpack_connect_user' ),
|
||||
// This is a mapped capability
|
||||
// phpcs:ignore WordPress.WP.Capabilities.Unknown
|
||||
'unlink_user' => current_user_can( 'jetpack_unlink_user' ),
|
||||
'disconnect' => current_user_can( 'jetpack_disconnect' ),
|
||||
'manage_options' => current_user_can( 'manage_options' ),
|
||||
),
|
||||
);
|
||||
|
||||
@ -627,6 +701,7 @@ class REST_Connector {
|
||||
$response = array(
|
||||
'currentUser' => $current_user_connection_data,
|
||||
'connectionOwner' => $owner_display_name,
|
||||
'isRegistered' => $connection->is_connected(),
|
||||
);
|
||||
|
||||
if ( $rest_response ) {
|
||||
@ -781,9 +856,10 @@ class REST_Connector {
|
||||
}
|
||||
|
||||
/**
|
||||
* The endpoint tried to partially or fully reconnect the website to WP.com.
|
||||
* The endpoint tried to connect Jetpack site to WPCOM.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @since 6.7.0 No longer needs `registration_nonce`.
|
||||
* @since-jetpack 7.7.0
|
||||
*
|
||||
* @param \WP_REST_Request $request The request sent to the WP REST API.
|
||||
@ -791,10 +867,6 @@ class REST_Connector {
|
||||
* @return \WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function connection_register( $request ) {
|
||||
if ( ! wp_verify_nonce( $request->get_param( 'registration_nonce' ), 'jetpack-registration-nonce' ) ) {
|
||||
return new WP_Error( 'invalid_nonce', __( 'Unable to verify your request.', 'jetpack-connection' ), array( 'status' => 403 ) );
|
||||
}
|
||||
|
||||
if ( isset( $request['from'] ) ) {
|
||||
$this->connection->add_register_request_param( 'from', (string) $request['from'] );
|
||||
}
|
||||
@ -930,6 +1002,51 @@ class REST_Connector {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlinks current user from the WordPress.com Servers.
|
||||
*
|
||||
* @since 6.3.3
|
||||
*
|
||||
* @param WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return bool|WP_Error True if user successfully unlinked.
|
||||
*/
|
||||
public static function unlink_user( $request ) {
|
||||
|
||||
if ( ! isset( $request['linked'] ) || false !== $request['linked'] ) {
|
||||
return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack-connection' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
// If the user is also connection owner, we need to disconnect all users. Since disconnecting all users is a destructive action, we need to pass a parameter to confirm the action.
|
||||
$disconnect_all_users = false;
|
||||
|
||||
if ( ( new Manager() )->get_connection_owner_id() === get_current_user_id() ) {
|
||||
if ( isset( $request['disconnect-all-users'] ) && false !== $request['disconnect-all-users'] ) {
|
||||
$disconnect_all_users = true;
|
||||
} else {
|
||||
return new WP_Error( 'unlink_user_failed', esc_html__( 'Unable to unlink the connection owner.', 'jetpack-connection' ), array( 'status' => 400 ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Allow admins to force a disconnect by passing the "force" parameter
|
||||
// This allows an admin to disconnect themselves
|
||||
if ( isset( $request['force'] ) && false !== $request['force'] && current_user_can( 'manage_options' ) && ( new Manager( 'jetpack' ) )->disconnect_user_force( get_current_user_id(), $disconnect_all_users ) ) {
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'code' => 'success',
|
||||
)
|
||||
);
|
||||
} elseif ( ( new Manager( 'jetpack' ) )->disconnect_user() ) {
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'code' => 'success',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return new WP_Error( 'unlink_user_failed', esc_html__( 'Was not able to unlink the user. Please try again.', 'jetpack-connection' ), array( 'status' => 400 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the API client is allowed to replace user token.
|
||||
*
|
||||
@ -1021,4 +1138,53 @@ class REST_Connector {
|
||||
? true
|
||||
: new WP_Error( 'invalid_permission_connection_check', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider-specific authorization URL endpoint
|
||||
*
|
||||
* @param WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return \WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function connection_authorize_url_provider( $request ) {
|
||||
$provider = $request['provider'];
|
||||
$redirect_uri = $request['redirect_uri'] ?? '';
|
||||
|
||||
// Validate magic link parameters if provider is 'link'
|
||||
if ( 'link' === $provider ) {
|
||||
if ( empty( $request['email_address'] ) ) {
|
||||
return new WP_Error(
|
||||
'missing_email',
|
||||
__( 'Email address is required for magic link authentication.', 'jetpack-connection' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
// Sanitize email address
|
||||
$email = sanitize_email( $request['email_address'] );
|
||||
if ( ! is_email( $email ) ) {
|
||||
return new WP_Error(
|
||||
'invalid_email',
|
||||
__( 'Invalid email address format.', 'jetpack-connection' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$authorize_url = ( new Authorize_Redirect( $this->connection ) )->build_authorize_url(
|
||||
$redirect_uri,
|
||||
false,
|
||||
false,
|
||||
$provider,
|
||||
array(
|
||||
'email_address' => $email ?? '',
|
||||
)
|
||||
);
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'authorizeUrl' => $authorize_url,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -280,24 +280,28 @@ class Tracking {
|
||||
*/
|
||||
public function tracks_get_identity( $user_id ) {
|
||||
|
||||
// Meta is set, and user is still connected. Use WPCOM ID.
|
||||
// Meta is set, and user is still connected. Use WPCOM ID.
|
||||
$wpcom_id = get_user_meta( $user_id, 'jetpack_tracks_wpcom_id', true );
|
||||
if ( $wpcom_id && $this->connection->is_user_connected( $user_id ) ) {
|
||||
if ( $wpcom_id && is_string( $wpcom_id ) && $this->connection->is_user_connected( $user_id ) ) {
|
||||
return array(
|
||||
'_ut' => 'wpcom:user_id',
|
||||
'_ui' => $wpcom_id,
|
||||
);
|
||||
}
|
||||
|
||||
// User is connected, but no meta is set yet. Use WPCOM ID and set meta.
|
||||
// User is connected, but no meta is set yet. Use WPCOM ID and set meta.
|
||||
if ( $this->connection->is_user_connected( $user_id ) ) {
|
||||
$wpcom_user_data = $this->connection->get_connected_user_data( $user_id );
|
||||
update_user_meta( $user_id, 'jetpack_tracks_wpcom_id', $wpcom_user_data['ID'] );
|
||||
$wpcom_id = $wpcom_user_data['ID'] ?? null;
|
||||
|
||||
return array(
|
||||
'_ut' => 'wpcom:user_id',
|
||||
'_ui' => $wpcom_user_data['ID'],
|
||||
);
|
||||
if ( is_string( $wpcom_id ) ) {
|
||||
update_user_meta( $user_id, 'jetpack_tracks_wpcom_id', $wpcom_id );
|
||||
|
||||
return array(
|
||||
'_ut' => 'wpcom:user_id',
|
||||
'_ui' => $wpcom_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// User isn't linked at all. Fall back to anonymous ID.
|
||||
|
@ -0,0 +1,175 @@
|
||||
<?php
|
||||
/**
|
||||
* Handles the WordPress.com account column in the users list table.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection;
|
||||
|
||||
use Automattic\Jetpack\Assets;
|
||||
use Automattic\Jetpack\Status\Host;
|
||||
|
||||
/**
|
||||
* Class Users_Connection_Admin
|
||||
*/
|
||||
class Users_Connection_Admin {
|
||||
/**
|
||||
* The column ID used for the WordPress.com account column.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const COLUMN_ID = 'user_jetpack';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
// Only set up hooks if we're in the admin area and user has proper permissions
|
||||
add_action( 'init', array( $this, 'init' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the admin functionality if conditions are met.
|
||||
*/
|
||||
public function init() {
|
||||
if ( ! is_admin() || ! current_user_can( 'manage_options' ) || ( new Host() )->is_wpcom_simple() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_filter( 'manage_users_columns', array( $this, 'add_connection_column' ) );
|
||||
add_filter( 'manage_users_custom_column', array( $this, 'render_connection_column' ), 9, 3 ); // Priority 9 to run before SSO
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
|
||||
add_action( 'admin_print_styles-users.php', array( $this, 'add_connection_column_styles' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the connection column to the users list table.
|
||||
*
|
||||
* @param array $columns The current columns.
|
||||
* @return array Modified columns.
|
||||
*/
|
||||
public function add_connection_column( $columns ) {
|
||||
$columns[ self::COLUMN_ID ] = sprintf(
|
||||
'<span class="jetpack-connection-tooltip-icon" role="tooltip" tabindex="0" aria-label="%2$s: %1$s">
|
||||
%1$s
|
||||
<span class="jetpack-connection-tooltip"></span>
|
||||
</span>',
|
||||
esc_html__( 'WordPress.com account', 'jetpack-connection' ),
|
||||
esc_attr__( 'Tooltip', 'jetpack-connection' )
|
||||
);
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the connection column content.
|
||||
*
|
||||
* @param string $output Custom column output.
|
||||
* @param string $column_name Column name.
|
||||
* @param int $user_id ID of the currently-listed user.
|
||||
* @return string
|
||||
*/
|
||||
public function render_connection_column( $output, $column_name, $user_id ) {
|
||||
if ( self::COLUMN_ID !== $column_name ) {
|
||||
return $output;
|
||||
}
|
||||
|
||||
if ( ( new Manager() )->is_user_connected( $user_id ) ) {
|
||||
return sprintf(
|
||||
'<span title="%1$s" class="jetpack-connection-status">%2$s</span>',
|
||||
esc_attr__( 'This user has connected their WordPress.com account.', 'jetpack-connection' ),
|
||||
esc_html__( 'Connected', 'jetpack-connection' )
|
||||
);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue scripts and styles.
|
||||
*
|
||||
* @param string $hook The current admin page.
|
||||
*/
|
||||
public function enqueue_scripts( $hook ) {
|
||||
if ( 'users.php' !== $hook ) {
|
||||
return;
|
||||
}
|
||||
|
||||
Assets::register_script(
|
||||
'jetpack-users-connection',
|
||||
'../dist/jetpack-users-connection.js',
|
||||
__FILE__,
|
||||
array(
|
||||
'strategy' => 'defer',
|
||||
'in_footer' => true,
|
||||
'enqueue' => true,
|
||||
'version' => Package_Version::PACKAGE_VERSION,
|
||||
'deps' => array( 'wp-i18n' ),
|
||||
|
||||
)
|
||||
);
|
||||
|
||||
wp_localize_script(
|
||||
'jetpack-users-connection',
|
||||
'jetpackConnectionTooltips',
|
||||
array(
|
||||
'columnTooltip' => esc_html__( 'Connecting a WordPress.com account unlocks Jetpack’s full suite of features including secure logins.', 'jetpack-connection' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add styles for the connection column.
|
||||
*/
|
||||
public function add_connection_column_styles() {
|
||||
?>
|
||||
<style>
|
||||
.jetpack-connection-tooltip-icon {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
/* Add [?] icon using pseudo-element, only in column header */
|
||||
th.manage-column .jetpack-connection-tooltip-icon::after {
|
||||
content: '[?]';
|
||||
color: #3c434a;
|
||||
font-size: 1em;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.jetpack-connection-tooltip {
|
||||
position: absolute;
|
||||
background: #f6f7f7;
|
||||
top: -85px;
|
||||
width: 250px;
|
||||
padding: 7px;
|
||||
color: #3c434a;
|
||||
font-size: .75rem;
|
||||
line-height: 17px;
|
||||
text-align: left;
|
||||
margin: 0;
|
||||
display: none;
|
||||
border-radius: 4px;
|
||||
font-family: sans-serif;
|
||||
box-shadow: 5px 10px 10px rgba(0, 0, 0, 0.1);
|
||||
left: -170px;
|
||||
}
|
||||
.column-user_jetpack {
|
||||
width: 140px;
|
||||
}
|
||||
/* Show tooltip on hover and focus */
|
||||
.jetpack-connection-tooltip-icon:hover .jetpack-connection-tooltip,
|
||||
.jetpack-connection-tooltip-icon:focus-within .jetpack-connection-tooltip {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the column ID. Allows other classes to reference the same column.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_column_id() {
|
||||
return self::COLUMN_ID;
|
||||
}
|
||||
}
|
@ -163,7 +163,7 @@ class Webhooks {
|
||||
* @return never
|
||||
*/
|
||||
protected function do_exit() {
|
||||
exit;
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -199,6 +199,10 @@ class Webhooks {
|
||||
wp_safe_redirect( $redirect );
|
||||
$this->do_exit();
|
||||
} else {
|
||||
if ( 'connect-after-checkout' === $from && $redirect ) {
|
||||
wp_safe_redirect( $redirect );
|
||||
$this->do_exit();
|
||||
}
|
||||
$connect_url = add_query_arg(
|
||||
array(
|
||||
'from' => $from,
|
||||
|
@ -162,17 +162,21 @@ class UI {
|
||||
|
||||
$consumer_chosen = null;
|
||||
$consumer_url_length = 0;
|
||||
|
||||
foreach ( $consumers as $consumer ) {
|
||||
foreach ( $consumers as &$consumer ) {
|
||||
if ( empty( $consumer['admin_page'] ) || ! is_string( $consumer['admin_page'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( isset( $consumer['customContent'] ) && is_callable( $consumer['customContent'] ) ) {
|
||||
$consumer['customContent'] = call_user_func( $consumer['customContent'] );
|
||||
}
|
||||
|
||||
if ( isset( $_SERVER['REQUEST_URI'] ) && str_starts_with( filter_var( wp_unslash( $_SERVER['REQUEST_URI'] ) ), $consumer['admin_page'] ) && strlen( $consumer['admin_page'] ) > $consumer_url_length ) {
|
||||
$consumer_chosen = $consumer;
|
||||
$consumer_url_length = strlen( $consumer['admin_page'] );
|
||||
}
|
||||
}
|
||||
unset( $consumer );
|
||||
|
||||
static::$consumers = $consumer_chosen ? $consumer_chosen : array_shift( $consumers );
|
||||
|
||||
|
@ -191,7 +191,7 @@ class SSO {
|
||||
Helpers::delete_connection_for_user( $current_user->ID );
|
||||
wp_logout();
|
||||
wp_safe_redirect( wp_login_url() );
|
||||
exit;
|
||||
exit( 0 );
|
||||
}
|
||||
}
|
||||
|
||||
@ -491,7 +491,7 @@ class SSO {
|
||||
|
||||
$tracking->record_user_event( 'sso_login_redirect_success' );
|
||||
wp_safe_redirect( $sso_url );
|
||||
exit;
|
||||
exit( 0 );
|
||||
}
|
||||
} elseif ( Helpers::display_sso_form_for_action( $action ) ) {
|
||||
|
||||
@ -509,7 +509,7 @@ class SSO {
|
||||
$sso_url = $this->get_sso_url_or_die( $reauth );
|
||||
$tracking->record_user_event( 'sso_login_redirect_bypass_success' );
|
||||
wp_safe_redirect( $sso_url );
|
||||
exit;
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
$this->display_sso_login_form();
|
||||
@ -622,7 +622,7 @@ class SSO {
|
||||
|
||||
<?php if ( $display_name && $gravatar ) : ?>
|
||||
<a rel="nofollow" class="jetpack-sso-wrap__reauth" href="<?php echo esc_url( $this->build_sso_button_url( array( 'force_reauth' => '1' ) ) ); ?>">
|
||||
<?php esc_html_e( 'Log in as a different WordPress.com user', 'jetpack-connection' ); ?>
|
||||
<?php esc_html_e( 'Log in with another WordPress.com account', 'jetpack-connection' ); ?>
|
||||
</a>
|
||||
<?php else : ?>
|
||||
<p>
|
||||
@ -969,7 +969,7 @@ class SSO {
|
||||
admin_url()
|
||||
)
|
||||
);
|
||||
exit;
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
add_filter( 'allowed_redirect_hosts', array( Helpers::class, 'allowed_redirect_hosts' ) );
|
||||
@ -977,7 +977,7 @@ class SSO {
|
||||
/** This filter is documented in core/src/wp-login.php */
|
||||
apply_filters( 'login_redirect', $redirect_to, $_request_redirect_to, $user )
|
||||
);
|
||||
exit;
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
add_filter( 'jetpack_sso_default_to_sso_login', '__return_false' );
|
||||
@ -1207,7 +1207,7 @@ class SSO {
|
||||
|
||||
add_filter( 'allowed_redirect_hosts', array( Helpers::class, 'allowed_redirect_hosts' ) );
|
||||
wp_safe_redirect( $connect_url );
|
||||
exit;
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -11,6 +11,7 @@ use Automattic\Jetpack\Assets;
|
||||
use Automattic\Jetpack\Connection\Client;
|
||||
use Automattic\Jetpack\Connection\Manager;
|
||||
use Automattic\Jetpack\Connection\Package_Version;
|
||||
use Automattic\Jetpack\Connection\Users_Connection_Admin as Base_Admin;
|
||||
use Automattic\Jetpack\Roles;
|
||||
use Automattic\Jetpack\Status\Host;
|
||||
use Automattic\Jetpack\Tracking;
|
||||
@ -21,7 +22,7 @@ use WP_User_Query;
|
||||
/**
|
||||
* Jetpack sso user admin class.
|
||||
*/
|
||||
class User_Admin {
|
||||
class User_Admin extends Base_Admin {
|
||||
/**
|
||||
* Instance of WP_User_Query.
|
||||
*
|
||||
@ -56,16 +57,20 @@ class User_Admin {
|
||||
add_action( 'user_new_form', array( $this, 'render_custom_email_message_form_field' ), 1 );
|
||||
add_action( 'delete_user_form', array( $this, 'render_invitations_notices_for_deleted_users' ) );
|
||||
add_action( 'delete_user', array( $this, 'revoke_user_invite' ) );
|
||||
add_filter( 'manage_users_columns', array( $this, 'jetpack_user_connected_th' ) );
|
||||
add_filter( 'manage_users_custom_column', array( $this, 'jetpack_show_connection_status' ), 10, 3 );
|
||||
add_action( 'user_row_actions', array( $this, 'jetpack_user_table_row_actions' ), 10, 2 );
|
||||
add_action( 'admin_notices', array( $this, 'handle_invitation_results' ) );
|
||||
|
||||
if ( isset( $_GET['jetpack-sso-invite-user'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
add_action( 'admin_notices', array( $this, 'handle_invitation_results' ) );
|
||||
}
|
||||
|
||||
add_action( 'admin_post_jetpack_invite_user_to_wpcom', array( $this, 'invite_user_to_wpcom' ) );
|
||||
add_action( 'admin_post_jetpack_revoke_invite_user_to_wpcom', array( $this, 'handle_request_revoke_invite' ) );
|
||||
add_action( 'admin_post_jetpack_resend_invite_user_to_wpcom', array( $this, 'handle_request_resend_invite' ) );
|
||||
add_action( 'admin_print_styles-users.php', array( $this, 'jetpack_user_table_styles' ) );
|
||||
add_filter( 'users_list_table_query_args', array( $this, 'set_user_query' ), 100, 1 );
|
||||
add_action( 'admin_print_styles-user-new.php', array( $this, 'jetpack_new_users_styles' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
|
||||
|
||||
self::$tracking = new Tracking();
|
||||
}
|
||||
@ -102,6 +107,7 @@ class User_Admin {
|
||||
* Revokes WordPress.com invitation.
|
||||
*
|
||||
* @param int $user_id The user ID.
|
||||
* @return mixed Response from the API call or false on failure.
|
||||
*/
|
||||
public function revoke_user_invite( $user_id ) {
|
||||
try {
|
||||
@ -970,39 +976,14 @@ class User_Admin {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a column in the user admin table to display user connection status and actions.
|
||||
* Deprecated method. Adds a column in the user admin table to display user connection status and actions.
|
||||
*
|
||||
* @param array $columns User list table columns.
|
||||
*
|
||||
* @return array
|
||||
* @deprecated 6.5.0
|
||||
*/
|
||||
public function jetpack_user_connected_th( $columns ) {
|
||||
Assets::register_script(
|
||||
'jetpack-sso-users',
|
||||
'../../dist/jetpack-sso-users.js',
|
||||
__FILE__,
|
||||
array(
|
||||
'strategy' => 'defer',
|
||||
'in_footer' => true,
|
||||
'enqueue' => true,
|
||||
'version' => Package_Version::PACKAGE_VERSION,
|
||||
)
|
||||
);
|
||||
|
||||
$tooltip_string = esc_attr__( 'Jetpack SSO allows a seamless and secure experience on WordPress.com. Join millions of WordPress users who trust us to keep their accounts safe.', 'jetpack-connection' );
|
||||
|
||||
wp_add_inline_script(
|
||||
'jetpack-sso-users',
|
||||
"var Jetpack_SSOTooltip = { 'tooltipString': '{$tooltip_string}' }",
|
||||
'before'
|
||||
);
|
||||
|
||||
$columns['user_jetpack'] = sprintf(
|
||||
'<span class="jetpack-sso-invitation-tooltip-icon jetpack-sso-status-column" role="tooltip" aria-label="%3$s: %1$s" tabindex="0">%2$s</span>',
|
||||
$tooltip_string,
|
||||
esc_html__( 'SSO Status', 'jetpack-connection' ),
|
||||
esc_attr__( 'Tooltip', 'jetpack-connection' )
|
||||
);
|
||||
_deprecated_function( __METHOD__, 'package-6.5.0' );
|
||||
return $columns;
|
||||
}
|
||||
|
||||
@ -1175,59 +1156,58 @@ class User_Admin {
|
||||
* @param string $val HTML for the column.
|
||||
* @param string $col User list table column.
|
||||
* @param int $user_id User ID.
|
||||
*
|
||||
* @return string
|
||||
* @return string Modified column content.
|
||||
*/
|
||||
public function jetpack_show_connection_status( $val, $col, $user_id ) {
|
||||
if ( 'user_jetpack' === $col ) {
|
||||
if ( ( new Manager() )->is_user_connected( $user_id ) ) {
|
||||
$connection_html = sprintf(
|
||||
'<span title="%1$s" class="jetpack-sso-invitation">%2$s</span>',
|
||||
esc_attr__( 'This user is connected and can log-in to this site.', 'jetpack-connection' ),
|
||||
esc_html__( 'Connected', 'jetpack-connection' )
|
||||
);
|
||||
return $connection_html;
|
||||
} else {
|
||||
$has_pending_invite = self::has_pending_wpcom_invite( $user_id );
|
||||
if ( $has_pending_invite ) {
|
||||
$connection_html = sprintf(
|
||||
'<span title="%1$s" class="jetpack-sso-invitation sso-pending-invite">%2$s</span>',
|
||||
esc_attr__( 'This user didn’t accept the invitation to join this site yet.', 'jetpack-connection' ),
|
||||
esc_html__( 'Pending invite', 'jetpack-connection' )
|
||||
);
|
||||
return $connection_html;
|
||||
}
|
||||
$nonce = wp_create_nonce( 'jetpack-sso-invite-user' );
|
||||
$connection_html = sprintf(
|
||||
// Using formmethod and formaction because we can't nest forms and have to submit using the main form.
|
||||
'<span tabindex="0" role="tooltip" aria-label="%4$s: %3$s" class="jetpack-sso-invitation-tooltip-icon sso-disconnected-user">
|
||||
<a href="%1$s" class="jetpack-sso-invitation sso-disconnected-user">%2$s</a>
|
||||
<span class="sso-disconnected-user-icon dashicons dashicons-warning">
|
||||
<span class="jetpack-sso-invitation-tooltip jetpack-sso-td-tooltip">%3$s</span>
|
||||
</span>
|
||||
</span>',
|
||||
add_query_arg(
|
||||
array(
|
||||
'user_id' => $user_id,
|
||||
'invite_nonce' => $nonce,
|
||||
'action' => 'jetpack_invite_user_to_wpcom',
|
||||
),
|
||||
admin_url( 'admin-post.php' )
|
||||
),
|
||||
esc_html__( 'Send invite', 'jetpack-connection' ),
|
||||
esc_attr__( 'This user doesn’t have an SSO connection to WordPress.com. Invite them to the site to increase security and improve their experience.', 'jetpack-connection' ),
|
||||
esc_attr__( 'Tooltip', 'jetpack-connection' )
|
||||
);
|
||||
return $connection_html;
|
||||
}
|
||||
if ( 'user_jetpack' !== $col ) {
|
||||
return $val;
|
||||
}
|
||||
return $val;
|
||||
|
||||
// Get base connection status from parent
|
||||
$connection_status = parent::render_connection_column( '', $col, $user_id );
|
||||
|
||||
// If user is not connected, check for pending invite
|
||||
if ( ! $connection_status ) {
|
||||
$has_pending_invite = self::has_pending_wpcom_invite( $user_id );
|
||||
if ( $has_pending_invite ) {
|
||||
return sprintf(
|
||||
'<span title="%1$s" class="jetpack-sso-invitation sso-pending-invite">%2$s</span>',
|
||||
esc_attr__( 'This user didn’t accept the invitation to join this site yet.', 'jetpack-connection' ),
|
||||
esc_html__( 'Pending invite', 'jetpack-connection' )
|
||||
);
|
||||
}
|
||||
|
||||
// Show invite button for non-connected users
|
||||
$nonce = wp_create_nonce( 'jetpack-sso-invite-user' );
|
||||
return sprintf(
|
||||
'<span tabindex="0" role="tooltip" aria-label="%4$s: %3$s" class="jetpack-sso-invitation-tooltip-icon sso-disconnected-user">
|
||||
<a href="%1$s" class="jetpack-sso-invitation sso-disconnected-user">%2$s</a>
|
||||
<span class="sso-disconnected-user-icon dashicons dashicons-warning">
|
||||
<span class="jetpack-sso-invitation-tooltip jetpack-sso-td-tooltip">%3$s</span>
|
||||
</span>
|
||||
</span>',
|
||||
add_query_arg(
|
||||
array(
|
||||
'user_id' => $user_id,
|
||||
'invite_nonce' => $nonce,
|
||||
'action' => 'jetpack_invite_user_to_wpcom',
|
||||
),
|
||||
admin_url( 'admin-post.php' )
|
||||
),
|
||||
esc_html__( 'Send invite', 'jetpack-connection' ),
|
||||
esc_attr__( 'This user doesn’t have a Jetpack SSO connection to WordPress.com. Invite them to the site to increase security and improve their experience.', 'jetpack-connection' ),
|
||||
esc_attr__( 'Tooltip', 'jetpack-connection' )
|
||||
);
|
||||
}
|
||||
|
||||
return $connection_status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates error notices and redirects the user to the previous page.
|
||||
*
|
||||
* @param array $query_params - query parameters added to redirection URL.
|
||||
* @phan-suppress PhanPluginNeverReturnMethod
|
||||
*/
|
||||
public function create_error_notice_and_redirect( $query_params ) {
|
||||
$ref = wp_get_referer();
|
||||
@ -1239,7 +1219,8 @@ class User_Admin {
|
||||
$query_params,
|
||||
$ref
|
||||
);
|
||||
return wp_safe_redirect( $url );
|
||||
wp_safe_redirect( $url );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1254,9 +1235,6 @@ class User_Admin {
|
||||
#the-list tr:has(.sso-pending-invite) {
|
||||
background: #E9F0F5;
|
||||
}
|
||||
.fixed .column-user_jetpack {
|
||||
width: 100px;
|
||||
}
|
||||
.jetpack-sso-invitation {
|
||||
background: none;
|
||||
border: none;
|
||||
@ -1293,9 +1271,6 @@ class User_Admin {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
.jetpack-sso-th-tooltip {
|
||||
left: -170px;
|
||||
}
|
||||
.jetpack-sso-td-tooltip {
|
||||
left: -256px;
|
||||
}
|
||||
@ -1319,4 +1294,29 @@ class User_Admin {
|
||||
</style>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue SSO-specific scripts.
|
||||
*
|
||||
* @param string $hook The current admin page.
|
||||
*/
|
||||
public function enqueue_scripts( $hook ) {
|
||||
if ( 'users.php' !== $hook ) {
|
||||
return;
|
||||
}
|
||||
|
||||
parent::enqueue_scripts( $hook );
|
||||
// Enqueue the SSO users script.
|
||||
Assets::register_script(
|
||||
'jetpack-sso-users',
|
||||
'../../dist/jetpack-sso-users.js',
|
||||
__FILE__,
|
||||
array(
|
||||
'strategy' => 'defer',
|
||||
'in_footer' => true,
|
||||
'enqueue' => true,
|
||||
'version' => Package_Version::PACKAGE_VERSION,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ document.addEventListener( 'DOMContentLoaded', function () {
|
||||
tooltip.innerHTML += ' [?]';
|
||||
|
||||
const tooltipTextbox = document.createElement( 'span' );
|
||||
tooltipTextbox.classList.add( 'jetpack-sso-invitation-tooltip', 'jetpack-sso-th-tooltip' );
|
||||
tooltipTextbox.classList.add( 'jetpack-sso-invitation-tooltip' );
|
||||
|
||||
const tooltipString = window.Jetpack_SSOTooltip.tooltipString;
|
||||
tooltipTextbox.innerHTML += tooltipString;
|
||||
@ -28,7 +28,7 @@ document.addEventListener( 'DOMContentLoaded', function () {
|
||||
*/
|
||||
function removeTooltip() {
|
||||
// Only remove tooltip if the element isn't currently active.
|
||||
if ( document.activeElement === tooltip ) {
|
||||
if ( tooltip.ownerDocument.activeElement === tooltip ) {
|
||||
return;
|
||||
}
|
||||
tooltip.removeChild( tooltipTextbox );
|
||||
@ -56,7 +56,7 @@ document.addEventListener( 'DOMContentLoaded', function () {
|
||||
* @param {Event} event - Triggering event.
|
||||
*/
|
||||
function removeSSOInvitationTooltip( event ) {
|
||||
if ( document.activeElement === event.target ) {
|
||||
if ( event.target.ownerDocument.activeElement === event.target ) {
|
||||
return;
|
||||
}
|
||||
this.querySelector( '.jetpack-sso-invitation-tooltip' ).style.display = 'none';
|
||||
|
@ -0,0 +1,151 @@
|
||||
<?php
|
||||
/**
|
||||
* Trait WPCOM_REST_API_Proxy_Request
|
||||
*
|
||||
* Used to proxy requests to wpcom servers.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection\Traits;
|
||||
|
||||
use Automattic\Jetpack\Connection\Client;
|
||||
use Automattic\Jetpack\Connection\Manager;
|
||||
use Automattic\Jetpack\Status\Visitor;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
|
||||
trait WPCOM_REST_API_Proxy_Request {
|
||||
|
||||
/**
|
||||
* Base path for the API.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $base_api_path;
|
||||
|
||||
/**
|
||||
* Version of the API.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $version;
|
||||
|
||||
/**
|
||||
* The base of the controller's route.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base;
|
||||
|
||||
/**
|
||||
* Proxy request to wpcom servers on behalf of a user or using the Site-level Connection (blog token).
|
||||
*
|
||||
* @param WP_REST_Request $request Request to proxy.
|
||||
* @param string $path Path to append to the rest base.
|
||||
* @param string $context Whether the request should be proxied on behalf of the current user or using the Site-level Connection, aka 'blog' token. Can be Either 'user' or 'blog'. Defaults to 'user'.
|
||||
* @param bool $allow_fallback_to_blog If the $context is 'user', whether we should fallback to using the Site-level Connection in case the current user is not connected.
|
||||
* @param array $request_options Request options to pass to wp_remote_request.
|
||||
*
|
||||
* @return mixed|WP_Error Response from wpcom servers or an error.
|
||||
*/
|
||||
public function proxy_request_to_wpcom( $request, $path = '', $context = 'user', $allow_fallback_to_blog = false, $request_options = array() ) {
|
||||
$blog_id = \Jetpack_Options::get_option( 'id' );
|
||||
$path = '/sites/' . rawurldecode( $blog_id ) . '/' . rawurldecode( ltrim( $this->rest_base, '/' ) ) . ( $path ? '/' . rawurldecode( ltrim( $path, '/' ) ) : '' );
|
||||
$query_params = $request->get_query_params();
|
||||
$manager = new Manager();
|
||||
|
||||
/*
|
||||
* A rest_route parameter can be added when using plain permalinks.
|
||||
* It is not necessary to pass them to WordPress.com,
|
||||
* and may even cause issues with some endpoints.
|
||||
* Let's remove it.
|
||||
*/
|
||||
if ( isset( $query_params['rest_route'] ) ) {
|
||||
unset( $query_params['rest_route'] );
|
||||
}
|
||||
$api_url = add_query_arg( $query_params, $path );
|
||||
|
||||
$request_options = array_replace_recursive(
|
||||
array(
|
||||
'headers' => array(
|
||||
'Content-Type' => 'application/json',
|
||||
'X-Forwarded-For' => ( new Visitor() )->get_ip( true ),
|
||||
),
|
||||
'method' => $request->get_method(),
|
||||
),
|
||||
$request_options
|
||||
);
|
||||
|
||||
// If no body is present, passing it as $request->get_body() will cause an error.
|
||||
$body = $request->get_body() ? $request->get_body() : null;
|
||||
|
||||
$response = new WP_Error(
|
||||
'rest_unauthorized',
|
||||
__( 'Please connect your user account to WordPress.com', 'jetpack-connection' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
|
||||
if ( 'user' === $context ) {
|
||||
if ( ! $manager->is_user_connected() ) {
|
||||
if ( false === $allow_fallback_to_blog ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$context = 'blog';
|
||||
} else {
|
||||
$response = Client::wpcom_json_api_request_as_user( $api_url, $this->version, $request_options, $body, $this->base_api_path );
|
||||
}
|
||||
}
|
||||
|
||||
if ( 'blog' === $context ) {
|
||||
if ( ! $manager->is_connected() ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response = Client::wpcom_json_api_request_as_blog( $api_url, $this->version, $request_options, $body, $this->base_api_path );
|
||||
}
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response_status = wp_remote_retrieve_response_code( $response );
|
||||
$response_body = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
|
||||
if ( $response_status >= 400 ) {
|
||||
$code = $response_body['code'] ?? 'unknown_error';
|
||||
$message = $response_body['message'] ?? __( 'An unknown error occurred.', 'jetpack-connection' );
|
||||
|
||||
return new WP_Error( $code, $message, array( 'status' => $response_status ) );
|
||||
}
|
||||
|
||||
return $response_body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy request to wpcom servers on behalf of a user.
|
||||
*
|
||||
* @param WP_REST_Request $request Request to proxy.
|
||||
* @param string $path Path to append to the rest base.
|
||||
* @param array $request_options Request options to pass to wp_remote_request.
|
||||
*
|
||||
* @return mixed|WP_Error Response from wpcom servers or an error.
|
||||
*/
|
||||
public function proxy_request_to_wpcom_as_user( $request, $path = '', $request_options = array() ) {
|
||||
return $this->proxy_request_to_wpcom( $request, $path, 'user', false, $request_options );
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy request to wpcom servers using the Site-level Connection (blog token).
|
||||
*
|
||||
* @param WP_REST_Request $request Request to proxy.
|
||||
* @param string $path Path to append to the rest base.
|
||||
* @param array $request_options Request options to pass to wp_remote_request.
|
||||
*
|
||||
* @return mixed|WP_Error Response from wpcom servers or an error.
|
||||
*/
|
||||
public function proxy_request_to_wpcom_as_blog( $request, $path = '', $request_options = array() ) {
|
||||
return $this->proxy_request_to_wpcom( $request, $path, 'blog', false, $request_options );
|
||||
}
|
||||
}
|
@ -63,7 +63,7 @@ class Authorize_Redirect {
|
||||
|
||||
if ( ! $dest_url || ( 0 === stripos( $dest_url, 'https://jetpack.com/' ) && 0 === stripos( $dest_url, 'https://wordpress.com/' ) ) ) {
|
||||
// The destination URL is missing or invalid, nothing to do here.
|
||||
exit;
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
// The user is either already connected, or finished the connection process.
|
||||
@ -73,12 +73,12 @@ class Authorize_Redirect {
|
||||
}
|
||||
|
||||
wp_safe_redirect( $dest_url );
|
||||
exit;
|
||||
exit( 0 );
|
||||
} elseif ( ! empty( $_GET['done'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
// The user decided not to proceed with setting up the connection.
|
||||
|
||||
wp_safe_redirect( Admin_Menu::get_top_level_menu_item_url() );
|
||||
exit;
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
$redirect_args = array(
|
||||
@ -94,29 +94,66 @@ class Authorize_Redirect {
|
||||
}
|
||||
|
||||
wp_safe_redirect( $this->build_authorize_url( add_query_arg( $redirect_args, admin_url( 'admin.php' ) ) ) );
|
||||
exit;
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Jetpack authorization URL.
|
||||
*
|
||||
* @since 2.7.6 Added optional $from and $raw parameters.
|
||||
* @since 6.8.0 Added optional $provider and $provider_args parameters.
|
||||
*
|
||||
* @param bool|string $redirect URL to redirect to.
|
||||
* @param bool|string $from If not false, adds 'from=$from' param to the connect URL.
|
||||
* @param bool $raw If true, URL will not be escaped.
|
||||
* @param string|null $provider The authentication provider (google, github, apple, link).
|
||||
* @param array|null $provider_args Additional provider-specific arguments.
|
||||
*
|
||||
* @todo Update default value for redirect since the called function expects a string.
|
||||
*
|
||||
* @return mixed|void
|
||||
*/
|
||||
public function build_authorize_url( $redirect = false, $from = false, $raw = false ) {
|
||||
public function build_authorize_url( $redirect = false, $from = false, $raw = false, $provider = null, $provider_args = null ) {
|
||||
|
||||
add_filter( 'jetpack_connect_request_body', array( __CLASS__, 'filter_connect_request_body' ) );
|
||||
add_filter( 'jetpack_connect_redirect_url', array( __CLASS__, 'filter_connect_redirect_url' ) );
|
||||
|
||||
$url = $this->connection->get_authorization_url( wp_get_current_user(), $redirect, $from, $raw );
|
||||
|
||||
// If a provider is specified, modify the URL to use the provider-specific endpoint
|
||||
if ( $provider && in_array( $provider, array( 'google', 'github', 'apple', 'link' ), true ) ) {
|
||||
// Parse the URL to modify it safely
|
||||
$url_parts = wp_parse_url( $url );
|
||||
|
||||
if ( ! empty( $url_parts['host'] ) && ! empty( $url_parts['path'] ) ) {
|
||||
// Build the new URL using wordpress.com as the host
|
||||
$url_parts['host'] = 'wordpress.com';
|
||||
$url_parts['path'] = '/log-in/jetpack/' . $provider;
|
||||
|
||||
// Preserve the query parameters
|
||||
$query_params = array();
|
||||
if ( ! empty( $url_parts['query'] ) ) {
|
||||
parse_str( $url_parts['query'], $query_params );
|
||||
}
|
||||
|
||||
// Add magic link specific parameters if provider is 'link'
|
||||
if ( 'link' === $provider && is_array( $provider_args ) && ! empty( $provider_args['email_address'] ) ) {
|
||||
$query_params['email_address'] = $provider_args['email_address'];
|
||||
// Add flag to trigger magic link flow
|
||||
$query_params['auto_trigger'] = '1';
|
||||
}
|
||||
|
||||
// URL encode all parameter values
|
||||
$query_params = array_map( 'rawurlencode', $query_params );
|
||||
|
||||
// Rebuild the URL
|
||||
$url = 'https://' . $url_parts['host'] . $url_parts['path'];
|
||||
if ( ! empty( $query_params ) ) {
|
||||
$url = add_query_arg( $query_params, $url );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
remove_filter( 'jetpack_connect_request_body', array( __CLASS__, 'filter_connect_request_body' ) );
|
||||
remove_filter( 'jetpack_connect_redirect_url', array( __CLASS__, 'filter_connect_redirect_url' ) );
|
||||
|
||||
@ -125,11 +162,14 @@ class Authorize_Redirect {
|
||||
*
|
||||
* @since jetpack-8.9.0
|
||||
* @since 2.7.6 Added $raw parameter.
|
||||
* @since 6.8.0 Added $provider and $provider_args parameters.
|
||||
*
|
||||
* @param string $url Connection URL.
|
||||
* @param bool $raw If true, URL will not be escaped.
|
||||
* @param string $url Connection URL.
|
||||
* @param bool $raw If true, URL will not be escaped.
|
||||
* @param string|null $provider The authentication provider if specified.
|
||||
* @param array|null $provider_args Additional provider-specific arguments.
|
||||
*/
|
||||
return apply_filters( 'jetpack_build_authorize_url', $url, $raw );
|
||||
return apply_filters( 'jetpack_build_authorize_url', $url, $raw, $provider, $provider_args );
|
||||
}
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user