Skip to content
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

Receiving Vouches #181

Merged
merged 8 commits into from
Sep 4, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 75 additions & 13 deletions includes/class-webmention-receiver.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public static function init() {

// Webmention whitelist
add_filter( 'webmention_comment_data', array( 'Webmention_Receiver', 'auto_approve' ), 13, 1 );
add_filter( 'comment_row_actions', array( 'Webmention_Receiver', 'comment_row_actions' ), 13, 2 );
add_filter( 'comment_unapproved_to_approved', array( 'Webmention_Receiver', 'transition_to_whitelist' ), 10 );

// Webmention data handler
add_filter( 'webmention_comment_data', array( 'Webmention_Receiver', 'default_title_filter' ), 21, 1 );
Expand All @@ -50,6 +52,52 @@ public static function init() {
self::register_meta();
}

public static function comment_row_actions( $actions, $comment ) {
$approve_nonce = esc_html( '_wpnonce=' . wp_create_nonce( "approve-comment_$comment->comment_ID" ) );
$approve_url = esc_url( "comment.php?action=approvecomment&$approve_nonce" );
$unapprove_url = esc_url( "comment.php?action=unapprovecomment&$approve_nonce" );
$status = wp_get_comment_status( $comment );
if ( 'unapproved' === $status ) {
$actions['domainwhitelist'] = "<a href='{$approve_url}&domain=true&c={$comment->comment_ID}' aria-label='" . esc_attr__( 'Approve & Whitelist', 'webmention' ) . "'>" . __( 'Approve & Whitelist', 'webmention' ) . '</a>';
}
return $actions;
}

public static function get_webmention_approve_domains() {
$whitelist = get_option( 'webmention_approve_domains' );
$whitelist = trim( $whitelist );
$whitelist = explode( "\n", $whitelist );
return $whitelist;
}

public static function extract_domain( $url ) {
$host = wp_parse_url( $url, PHP_URL_HOST );
// strip leading www, if any
return preg_replace( '/^www\./', '', $host );
}

public static function add_webmention_approve_domain( $host ) {
$whitelist = self::get_webmention_approve_domains();
$whitelist[] = $host;
$whitelist = array_unique( $whitelist );
$whitelist = implode( "\n", $whitelist );
update_option( 'webmention_approve_domains', $whitelist );
}

public static function transition_to_whitelist( $comment ) {
if ( ! current_user_can( 'moderate_comments' ) ) {
return;
}
if ( isset( $_REQUEST['domain'] ) ) {
$url = get_comment_meta( $comment->comment_ID, 'webmention_source_url', true );
if ( ! $url ) {
return;
}
$host = self::extract_domain( $url );
self::add_webmention_approve_domain( $host );
}
}

/**
* This is more to lay out the data structure than anything else.
*/
Expand Down Expand Up @@ -96,6 +144,15 @@ public static function register_meta() {
'show_in_rest' => true,
);
register_meta( 'comment', 'webmention_response_code', $args );

// Purpose of this is to store a vouch URL
Copy link
Owner

Choose a reason for hiding this comment

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

Can you move this to the vouch class, otherwise it will load, if vouch is enabled or not...

Copy link
Collaborator Author

@dshanske dshanske Aug 5, 2018

Choose a reason for hiding this comment

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

I'd like to store the vouch URL whether or not vouch is enabled or not, in the event it becomes stable in future. Is that fine, or do you feel strongly it shouldn't store it unless enabled?

$args = array(
'type' => 'string',
'description' => __( 'Webmention Vouch URL', 'webmention' ),
'single' => true,
'show_in_rest' => true,
);
register_meta( 'comment', 'webmention_vouch_url', $args );
}

/**
Expand Down Expand Up @@ -261,13 +318,20 @@ public static function post( $request ) {
$comment_date_gmt = current_time( 'mysql', 1 );
$comment_meta['webmention_created_at'] = $comment_date_gmt;

if ( isset( $params['vouch'] ) ) {
// If there is a vouch pass it along
$vouch = urldecode( $params['vouch'] );
// Safely store a version of the data
$comment_meta['webmention_vouch_url'] = esc_url_raw( $vouch );
}

// change this if your theme can't handle the Webmentions comment type
$comment_type = WEBMENTION_COMMENT_TYPE;

// change this if you want to auto approve your Webmentions
$comment_approved = WEBMENTION_COMMENT_APPROVE;

$commentdata = compact( 'comment_type', 'comment_approved', 'comment_agent', 'comment_date', 'comment_date_gmt', 'comment_meta', 'source', 'target' );
$commentdata = compact( 'comment_type', 'comment_approved', 'comment_agent', 'comment_date', 'comment_date_gmt', 'comment_meta', 'source', 'target', 'vouch' );

$commentdata['comment_post_ID'] = $comment_post_id;
$commentdata['comment_author_IP'] = $comment_author_ip;
Expand Down Expand Up @@ -689,10 +753,7 @@ public static function default_content_filter( $commentdata ) {
$post_format = $post_formatstrings[ $post_format ];
}

$host = wp_parse_url( $commentdata['comment_author_url'], PHP_URL_HOST );

// strip leading www, if any
$host = preg_replace( '/^www\./', '', $host );
$host = self::extract_domain( $commentdata['comment_author_url'] );

// generate default text
// translators: This post format was mentioned on this URL with this domain name
Expand Down Expand Up @@ -727,7 +788,11 @@ public static function delete( $error ) {
)
);

if ( ! in_array( $error->get_error_code(), $error_codes ) ) {
if ( ! is_wp_error( $error ) ) {
return;
}

if ( ! in_array( $error->get_error_code(), $error_codes, true ) ) {
return;
}

Expand Down Expand Up @@ -764,15 +829,12 @@ public static function auto_approve( $commentdata ) {
* @return boolean
*/
public static function is_source_whitelisted( $url ) {
$whitelist = get_option( 'webmention_approve_domains' );
$whitelist = trim( $whitelist );
$host = wp_parse_url( $url, PHP_URL_HOST );
// strip leading www, if any
$host = preg_replace( '/^www\./', '', $host );
if ( '' === $whitelist ) {
$whitelist = self::get_webmention_approve_domains();
$host = self::extract_domain( $url );
if ( empty( $whitelist ) ) {
return false;
}
$domains = explode( '\n', $whitelist );

foreach ( (array) $domains as $domain ) {
$domain = trim( $domain );
if ( empty( $domain ) ) {
Expand Down
135 changes: 135 additions & 0 deletions includes/class-webmention-vouch.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<?php
/**
* Webmention Vouch Class
*
* @author Matthias Pfefferle
*/
class Webmention_Vouch {
/**
* Initialize, registering WordPress hooks
*/
public static function init() {
// Webmention helper
add_filter( 'webmention_comment_data', array( 'Webmention_Vouch', 'verify_vouch' ), 10, 1 );

self::register_meta();
}

/**
* This is more to lay out the data structure than anything else.
*/
public static function register_meta() {
// Purpose of this is to store whether something has been vouched for
$args = array(
'type' => 'int',
'description' => __( 'Has this Webmention Been Vouched for', 'webmention' ),
'single' => true,
'show_in_rest' => true,
);
register_meta( 'comment', 'webmention_vouched', $args );
}

/**
* Verifies vouch is valid and either return an error if not verified or return the array with retrieved
* data. For right now, it just sets a parameter whether or not it would be vouched
*
* @param array $data {
* $comment_type
* $comment_author_url
* $comment_author_IP
* $target
* $vouch
* }
*
* @return array|WP_Error $data Return Error Object or array with added fields {
* $remote_source
* $remote_source_original
* $content_type
* }
*
* @uses apply_filters calls "http_headers_useragent" on the user agent
*/
public static function verify_vouch( $data ) {
if ( ! $data || is_wp_error( $data ) ) {
return $data;
}

if ( ! is_array( $data ) || empty( $data ) ) {
return new WP_Error( 'invalid_data', __( 'Invalid data passed', 'webmention' ), array( 'status' => 500 ) );
}
// The remaining instructions only apply if there is a vouch parameter
if ( ! isset( $data['vouch'] ) ) {
return $data;
}
$data['comment_meta']['webmention_vouch_url'] = esc_url_raw( $data['vouch'] );

// Is the person vouching for the relationship using a page on your own site?
$vouch_id = url_to_postid( $data['vouch'] );
if ( $vouch_id ) {
$data['comment_meta']['webmention_vouched'] = '1';
return $data;
}

$wp_version = get_bloginfo( 'version' );

$user_agent = apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . get_bloginfo( 'url' ) );
$args = array(
'timeout' => 100,
'limit_response_size' => 153600,
'redirection' => 20,
'user-agent' => "$user_agent; verifying Vouch from " . $data['comment_author_IP'],
);

$response = wp_safe_remote_head( $data['vouch'], $args );

// check if vouch is accessible if not reject
if ( is_wp_error( $response ) ) {
return new WP_Error(
'vouch_not_found', __( 'Vouch Not Found', 'webmention' ), array(
'status' => 400,
'data' => $data,
)
);
}

// A valid response code from the other server would not be considered an error.
$response_code = wp_remote_retrieve_response_code( $response );
// not an (x)html, sgml, or xml page, so asking the sender to try again
if ( preg_match( '#(image|audio|video|model)/#is', wp_remote_retrieve_header( $response, 'content-type' ) ) ) {
return new WP_Error(
'vouch_not_found', __( 'Vouch Not Found', 'webmention' ), array(
'status' => 449,
'data' => $data,
)
);
}
if ( 200 !== $response_code ) {
return new WP_Error(
'vouch_error', array(
'status' => 400,
'data' => array( $data, $response_code ),
)
);
}
// If this is not
if ( ! Webmention_Receiver::is_source_whitelisted( $data['vouch'] ) ) {
$data['comment_meta']['webmention_vouched'] = '0';
return $data;
}

$response = wp_safe_remote_get( $data['vouch'], $args );
$urls = wp_extract_urls( wp_remote_retrieve_body( $response ) );
foreach ( $urls as $url ) {
if ( wp_parse_url( $url, PHP_URL_HOST ) === wp_parse_url( $data['source'] ) ) {
$data['comment_meta']['webmention_vouched'] = '1';
return $data;
}
}
return new WP_Error(
'vouch_error', __( 'Vouch Not Found', 'webmention' ), array(
'status' => 400,
'data' => $data,
)
);
}
}
Loading