-
Notifications
You must be signed in to change notification settings - Fork 800
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add/protect blocked send email #8117
Merged
Merged
Changes from 4 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
7f9582c
Jetpack Protect: blocked login page
roccotripaldi cbb1204
Removes unused block.
roccotripaldi a73c968
Filtering login url
roccotripaldi 9606864
Adding filters to reset-password urls
roccotripaldi 19aa31f
Make it look nicer
enejb 31b7fdd
Ensure folks can log out, even if their IP is blocked.
roccotripaldi 20851c1
Only allow logouts if the user the nonce passes and they are currentl…
enejb ec38955
Slightly nicer screen when shown to in the iframe
enejb c1b843c
Fix spacing, remove debugging code.
roccotripaldi a0caeb0
Fixes as suggested by @gititon
enejb 8ec53a9
Add constant that makes testing easier
enejb c50af89
Remove empty comment line
enejb 42b5a98
Improve comment.
roccotripaldi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -58,7 +58,7 @@ private function __construct() { | |
add_action( 'admin_init', array ( $this, 'maybe_display_security_warning' ) ); | ||
|
||
// This is a backup in case $pagenow fails for some reason | ||
add_action( 'login_head', array ( $this, 'check_login_ability' ) ); | ||
add_action( 'login_head', array ( $this, 'check_login_ability' ), 100, 3 ); | ||
|
||
// Runs a script every day to clean up expired transients so they don't | ||
// clog up our users' databases | ||
|
@@ -541,7 +541,7 @@ function block_with_math() { | |
* Kill a login attempt | ||
*/ | ||
function kill_login() { | ||
$ip = jetpack_protect_get_ip(); | ||
$ip = jetpack_protect_get_ip(); | ||
/** | ||
* Fires before every killed login. | ||
* | ||
|
@@ -552,19 +552,24 @@ function kill_login() { | |
* @param string $ip IP flagged by Protect. | ||
*/ | ||
do_action( 'jpp_kill_login', $ip ); | ||
$help_url = 'https://jetpack.com/support/security-features/#unblock'; | ||
|
||
$die_string = sprintf( __( 'Your IP (%1$s) has been flagged for potential security violations. <a href="%2$s">Find out more...</a>', 'jetpack' ), str_replace( 'http://', '', esc_url( 'http://' . $ip ) ), esc_url( $help_url ) ); | ||
|
||
if( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) { | ||
$die_string = sprintf( __( 'Your IP (%1$s) has been flagged for potential security violations.', 'jetpack' ), str_replace( 'http://', '', esc_url( 'http://' . $ip ) ) ); | ||
wp_die( | ||
$die_string, | ||
__( 'Login Blocked by Jetpack', 'jetpack' ), | ||
array ( 'response' => 403 ) | ||
); | ||
} | ||
|
||
wp_die( | ||
$die_string, | ||
__( 'Login Blocked by Jetpack', 'jetpack' ), | ||
array ( 'response' => 403 ) | ||
); | ||
require_once dirname( __FILE__ ) . '/protect/blocked-login-page.php'; | ||
$blocked_login_page = Jetpack_Protect_Blocked_Login_Page::instance( $ip ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Spacing appears off. |
||
|
||
if ( $blocked_login_page->is_blocked_user_valid() ) { | ||
return; | ||
} | ||
|
||
$blocked_login_page->render_and_die(); | ||
} | ||
|
||
/* | ||
|
@@ -862,8 +867,9 @@ function get_local_host() { | |
|
||
} | ||
|
||
Jetpack_Protect_Module::instance(); | ||
$jetpack_protect = Jetpack_Protect_Module::instance(); | ||
|
||
global $pagenow; | ||
if ( isset( $pagenow ) && 'wp-login.php' == $pagenow ) { | ||
Jetpack_Protect_Module::check_login_ability(); | ||
$jetpack_protect->check_login_ability(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,299 @@ | ||
<?php | ||
|
||
|
||
/** | ||
* Class Jetpack_Protect_Blocked_Login_Page | ||
* | ||
* Instanciated on the wp-login page when Jetpack modules are loaded and $pagenow | ||
* is available, or during the login_head hook. | ||
* | ||
* Class will only be instanciated if Protect has detected a hard blocked IP address. | ||
* | ||
* | ||
*/ | ||
class Jetpack_Protect_Blocked_Login_Page { | ||
|
||
private static $__instance = null; | ||
public $can_send_recovery_emails; | ||
public $ip_address; | ||
public $valid_blocked_user_id; | ||
public $page_title; | ||
public $email_address; | ||
public $help_url = 'https://jetpack.com/support/security-features/#unblock'; | ||
|
||
/** | ||
* Singleton implementation | ||
* | ||
* @return object | ||
*/ | ||
public static function instance( $ip_address ) { | ||
if ( ! is_a( self::$__instance, 'Jetpack_Protect_Blocked_Login_Page' ) ) { | ||
self::$__instance = new Jetpack_Protect_Blocked_Login_Page( $ip_address ); | ||
} | ||
|
||
return self::$__instance; | ||
} | ||
|
||
|
||
function __construct( $ip_address ) { | ||
/** | ||
* Filter controls if an email recovery form is shown to blocked IPs. | ||
* | ||
* A recovery form allows folks to re-gain access to the login form | ||
* via an email link if their IP was mistakenly blocked. | ||
* | ||
* @module protect | ||
* | ||
* @since 5.6 | ||
* | ||
* @param bool $can_send_recovery_emails Defaults to true. | ||
*/ | ||
$this->can_send_recovery_emails = apply_filters( 'jetpack_protect_can_send_recovery_emails', true ); | ||
$this->ip_address = $ip_address; | ||
|
||
add_filter( 'wp_authenticate_user', array( $this, 'check_valid_blocked_user' ), 10, 1 ); | ||
add_filter( 'site_url', array( $this, 'add_args_to_login_post_url' ), 10, 3 ); | ||
add_filter( 'network_site_url', array( $this, 'add_args_to_login_post_url' ), 10, 3 ); | ||
add_filter( 'lostpassword_url', array( $this, 'add_args_to_lostpassword_url' ), 10, 2 ); | ||
add_filter( 'login_url', array( $this, 'add_args_to_login_url' ), 10,3 ); | ||
add_filter( 'lostpassword_redirect', array( $this, 'add_args_to_lostpassword_redirect_url' ), 10, 1 ); | ||
} | ||
|
||
public function add_args_to_lostpassword_redirect_url( $url ) { | ||
if ( $this->valid_blocked_user_id ) { | ||
$url = ( empty( $url ) ) ? wp_login_url() : $url; | ||
$url = add_query_arg( | ||
array( | ||
'validate_jetpack_protect_recovery' => $_GET['validate_jetpack_protect_recovery'], | ||
'user_id' => $_GET['user_id'], | ||
'checkemail' => 'confirm', | ||
), | ||
$url | ||
); | ||
} | ||
return $url; | ||
} | ||
|
||
public function add_args_to_lostpassword_url( $url, $redirect ) { | ||
if ( $this->valid_blocked_user_id ) { | ||
$args = array( | ||
'validate_jetpack_protect_recovery' => $_GET['validate_jetpack_protect_recovery'], | ||
'user_id' => $_GET['user_id'], | ||
'action' => 'lostpassword', | ||
); | ||
if ( ! empty( $redirect ) ) { | ||
$args['redirect_to'] = $redirect; | ||
} | ||
$url = add_query_arg( $args, $url ); | ||
} | ||
return $url; | ||
} | ||
|
||
public function add_args_to_login_post_url( $url, $path, $scheme ) { | ||
if ( $this->valid_blocked_user_id && ( 'login_post' === $scheme || 'login' === $scheme ) ) { | ||
$url = add_query_arg( | ||
array( | ||
'validate_jetpack_protect_recovery' => $_GET['validate_jetpack_protect_recovery'], | ||
'user_id' => $_GET['user_id'], | ||
), | ||
$url | ||
); | ||
|
||
} | ||
return $url; | ||
} | ||
|
||
public function add_args_to_login_url( $url, $redirect, $force_reauth ) { | ||
if ( $this->valid_blocked_user_id ) { | ||
$args = array( | ||
'validate_jetpack_protect_recovery' => $_GET['validate_jetpack_protect_recovery'], | ||
'user_id' => $_GET['user_id'], | ||
); | ||
|
||
if ( ! empty( $redirect ) ) { | ||
$args['redirect_to'] = $redirect; | ||
} | ||
|
||
if ( ! empty( $force_reauth ) ) { | ||
$args['reauth'] = '1'; | ||
} | ||
$url = add_query_arg( $args, $url ); | ||
} | ||
return $url; | ||
} | ||
|
||
public function check_valid_blocked_user( $user ) { | ||
if ( $this->valid_blocked_user_id && $this->valid_blocked_user_id != $user->ID ) { | ||
return new WP_Error( 'invalid_recovery_token', __( 'The recovery token is not valid for this user.', 'jetpack' ) ); | ||
} | ||
return $user; | ||
} | ||
|
||
public function is_blocked_user_valid() { | ||
if ( ! $this->can_send_recovery_emails ) { | ||
return false; | ||
} | ||
|
||
if ( $this->valid_blocked_user_id ) { | ||
return true; | ||
} | ||
|
||
if ( ! isset( $_GET['validate_jetpack_protect_recovery'], $_GET['user_id'] ) ) { | ||
return false; | ||
} | ||
|
||
if ( ! $this->is_valid_protect_recovery_key( $_GET['validate_jetpack_protect_recovery'], $_GET['user_id'] ) ) { | ||
return false; | ||
} | ||
|
||
$this->valid_blocked_user_id = (int) $_GET['user_id']; | ||
return true; | ||
} | ||
|
||
public function is_valid_protect_recovery_key( $key, $user_id ) { | ||
|
||
$path = sprintf( '/sites/%d/protect/recovery/confirm', Jetpack::get_option( 'id' ) ); | ||
$response = Jetpack_Client::wpcom_json_api_request_as_blog( | ||
$path, | ||
'1.1', | ||
array( | ||
'method' => 'post' | ||
), | ||
array( | ||
'token' => $key, | ||
'user_id' => $user_id, | ||
'ip' => $this->ip_address, | ||
) | ||
); | ||
|
||
$result = json_decode( wp_remote_retrieve_body( $response ) ); | ||
|
||
if ( is_wp_error( $result ) || empty( $result ) || isset( $result->error ) ) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
public function render_and_die() { | ||
$this->page_title = __( 'Login Blocked by Jetpack', 'jetpack' ); | ||
|
||
if ( ! $this->can_send_recovery_emails ) { | ||
$this->render_blocked_login_message(); | ||
return; | ||
} | ||
|
||
if ( isset( $_GET['validate_jetpack_protect_recovery'] ) && $_GET['user_id'] ) { | ||
$this->protect_die( __( 'Could not validate recovery token.', 'jetpack' ) ); | ||
return; | ||
} | ||
|
||
if ( | ||
isset( $_GET['jetpack-protect-recovery'] ) && | ||
isset( $_POST['_wpnonce'] ) && | ||
wp_verify_nonce( $_POST['_wpnonce'], 'bypass-protect' ) | ||
) { | ||
$this->process_recovery_email(); | ||
return; | ||
} | ||
|
||
$this->render_recovery_form(); | ||
} | ||
|
||
public function render_blocked_login_message() { | ||
$this->protect_die( $this->get_html_blocked_login_message() ); | ||
} | ||
|
||
function process_recovery_email() { | ||
$sent = $this->send_recovery_email(); | ||
|
||
if ( is_wp_error( $sent ) ) { | ||
$this->protect_die( $sent, true ); | ||
} else { | ||
$this->render_recovery_success(); | ||
} | ||
} | ||
|
||
function send_recovery_email() { | ||
$email = isset( $_POST['email'] ) ? $_POST['email'] : ''; | ||
if ( sanitize_email( $email ) !== $email || ! is_email( $email ) ) { | ||
return new WP_Error( 'invalid_email', __( "Oops, looks like that's not the right email address. Please try again!", 'jetpack' ) ); | ||
} | ||
$user = get_user_by('email', trim( $email ) ); | ||
|
||
if ( ! $user ) { | ||
return new WP_Error( 'invalid_user', __( 'Oops, could not find a user with that email address.', 'jetpack' ) ); | ||
} | ||
$this->email_address = $email; | ||
$path = sprintf( '/sites/%d/protect/recovery/request', Jetpack::get_option( 'id' ) ); | ||
|
||
|
||
$response = Jetpack_Client::wpcom_json_api_request_as_blog( | ||
$path, | ||
'1.1', | ||
array( | ||
'method' => 'post' | ||
), | ||
array( | ||
'user_id' => $user->ID, | ||
'ip' => $this->ip_address | ||
) | ||
); | ||
|
||
$code = wp_remote_retrieve_response_code( $response ); | ||
$result = json_decode( wp_remote_retrieve_body( $response ) ); | ||
|
||
if ( 429 === $code ) { | ||
return new WP_Error( 'email_already_sent', __( 'An email was already sent to this address.', 'jetpack' ) ); | ||
} else if ( is_wp_error( $result ) || empty( $result ) || isset( $result->error ) ) { | ||
return new WP_Error( 'email_send_error', __( 'There was an error sending your email.', 'jetpack' ) ); | ||
} | ||
return true; | ||
} | ||
|
||
function protect_die( $content, $back_link = false ) { | ||
$image = sprintf( | ||
'<img src="%s" width="180" style="display: block; margin: 0 auto;" />', | ||
plugins_url( 'modules/protect/jetpack-security.png', JETPACK__PLUGIN_FILE ) | ||
); | ||
|
||
if ( is_wp_error( $content ) ) { | ||
$content = $content->get_error_message(); | ||
} | ||
// hack to get around default wp_die_handler. https://core.trac.wordpress.org/browser/tags/4.8.1/src/wp-includes/functions.php#L2698 | ||
$content = $image . '</p> ' . $content . '<p>'; | ||
|
||
wp_die( $content, $this->page_title, array( 'back_link' => $back_link ) ); | ||
} | ||
|
||
function render_recovery_form() { | ||
$content = $this->get_html_blocked_login_message() . $this->get_html_recovery_form(); | ||
$this->protect_die( $content ); | ||
} | ||
|
||
function render_recovery_success() { | ||
$this->protect_die( sprintf( __( 'An email with recovery instructions was sent to %s.', 'jetpack' ), $this->email_address ) ); | ||
} | ||
|
||
function get_html_blocked_login_message() { | ||
return sprintf( | ||
__( '<p>Your IP (%1$s) has been flagged for potential security violations.</p>', 'jetpack' ), | ||
str_replace( 'http://', '', esc_url( 'http://' . $this->ip_address ) | ||
) ); | ||
} | ||
|
||
function get_html_recovery_form() { | ||
ob_start(); ?> | ||
<p><?php _e( 'Email yourself a special link to regain access the login form.', 'jetpack' ); ?></p> | ||
<form method="post" action="?jetpack-protect-recovery=true"> | ||
<?php echo wp_nonce_field( 'bypass-protect' ); ?> | ||
<label for="email">Email Address:</label> | ||
<input type="email" name="email" /> | ||
<input type="submit" value="<?php echo esc_attr( __( 'Send', 'jetpack' ) ); ?>" /> | ||
</form> | ||
|
||
<?php | ||
$contents = ob_get_contents(); | ||
ob_end_clean(); | ||
return $contents; | ||
} | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the spacing off here?