+ All Categories
Home > Technology > WordPress REST API hacking

WordPress REST API hacking

Date post: 09-Jan-2017
Category:
Upload: jeroen-van-dijk
View: 353 times
Download: 1 times
Share this document with a friend
52
WORDPRESS REST API HACKING
Transcript

WORDPRESS REST API HACKING

WORDPRESS : IT’S A BLOG

LONG AGO

WORDPRESS : IT’S NOT JUST A BLOG

THE PAST

WORDPRESS : IT’S NOT JUST CMS

PRESENT

WORDPRESS : IT’S AN APPLICATION FRAMEWORK

FUTURE?

BECAUSE OF THE WP REST API

NO TODAY!

RESOURCE BASED STATELESS COMMUNICATION

REPRESENTATIONAL STATE TRANSFER

TO GAIN ACCESS TO (A COMPUTER) ILLEGALLY

TO ALTER (A COMPUTER PROGRAM)

/HĂK/·ING

/00 ALTER THE REST APIWHAT YOU NEED TO KNOW FIRST..

INFRASTRUCTURE IS IN CORE

IMPLEMENTATION IN THE REST-API PLUGIN

2 PARTS

IN CORE

├── rest-api │   ├── class-wp-rest-request.php │   ├── class-wp-rest-response.php │   └── class-wp-rest-server.php ├── rest-api.php

IN PLUGIN

├── class-wp-rest-attachments-controller.php ├── class-wp-rest-comments-controller.php ├── class-wp-rest-controller.php ├── class-wp-rest-post-statuses-controller.php ├── class-wp-rest-post-types-controller.php ├── class-wp-rest-posts-controller.php ├── class-wp-rest-revisions-controller.php ├── class-wp-rest-settings-controller.php ├── class-wp-rest-taxonomies-controller.php ├── class-wp-rest-terms-controller.php └── class-wp-rest-users-controller.php

YOU KNOW HOW TO CREATE A PLUGIN?

EXTENDING THE API

MY PLUGIN.PHP SETUP

<?php /* Plugin Name: My REST API extension Plugin URI: https://www.a-wp-site.com/ Description: WordPress REST API extension Version: 1.0.0 Author: Enrise Author URI: https://www.enrise.com */ require_once('src/Bootstrap.php');

new \Bootstrap::getInstance();

WHEN TO TRIGGER YOUR CODE

public static function getInstance() { if ( ! ( self::$instance instanceof self ) ) { self::$instance = new self(); }

return self::$instance; }

protected function __construct() { add_action( 'plugins_loaded', [ $this, 'initServer' ], 100 ); }

public function initServer() { }

/01 DISABLE THE API

LET’S START EASY!

public function initServer() { add_filter( 'rest_enabled', [ $this, 'disableApi' ] ); }

public function disableApi( $isEnabled ) { if ( $isEnabled == true ) { return false; } return $isEnabled; }

/02 CHANGE THE ROOT URL

CHANGE WP-JSON INTO …

public function initServer() { if ( ! defined( 'REST_API_VERSION' ) ) { // return early if WP API versions do not exist add_action( 'all_admin_notices', [ $this, 'showError' ] );

return; }

add_filter( 'rest_url_prefix', [ $this, 'changeApiBase' ] ); }

public function changeApiBase( $prefix ) { if ($prefix === 'wp-json') { return 'api'; } return $prefix; }

/03 CHANGE EXPOSURE

register_post_type( $post_type, $args );

register_taxonomy( $taxonomy, $object_type, $args );

ACCESS POST TYPE & TAXONOMY

public function initServer() { add_action( 'rest_api_init', [ $this, 'updateExposure' ], 12 ); }

public function updateExposure() { global $wp_post_types, $wp_taxonomies;

$wp_post_types['customposttype']->show_in_rest = true; $wp_taxonomies['customtaxonomy']->show_in_rest = true; }

/04 CHANGE CUSTOM POST TYPE HANDLING

EXTEND API CONTROLLERS

public function updateExposure() { global $wp_post_types, $wp_taxonomies;

$wp_post_types['customposttype']->show_in_rest = true; $wp_post_types['customposttype']->rest_base = 'customposttype'; $wp_post_types['customposttype']->rest_controller_class = 'Enrise\Api\Endpoint\CustomType';

$wp_taxonomies['customtaxonomy']->show_in_rest = true; $wp_taxonomies['customtaxonomy']->rest_base = 'customtaxonomy'; $wp_taxonomies['customtaxonomy']->rest_controller_class = 'Enrise\Api\Endpoint\CustomTaxonomy'; }

class CustomType extends \WP_REST_Posts_Controller {} class CustomTaxonomy extends \WP_REST_Terms_Controller {}

EXTEND THE INPUT / OUTPUT

register_rest_field( 'customposttype', 'custommetafield', [ 'schema' => [ 'type' => 'integer', 'context' => [ 'view', 'edit' ], ], 'get_callback' => [ $this, 'getMetaField' ], 'update_callback' => [ $this, 'saveMetaField' ] ] );

public function getMetaField( $post, $key, $request ) { return get_post_meta( $post['id'], $key, true ); }

public function saveMetaField( $value, $post, $key ) { return update_post_meta( $post->ID, $key, $value ); }

EXTEND THE INPUT / OUTPUT

register_rest_field( 'customposttype', 'custommetafield', [ 'schema' => [ 'type' => 'integer', 'context' => [ 'view', 'edit' ], ], 'get_callback' => [ $this, 'getMetaField' ], 'update_callback' => [ $this, 'saveMetaField' ] ] );

public function getMetaField( $post, $key, $request ) { return get_post_meta( $post['id'], $key, true ); }

public function saveMetaField( $value, $post, $key ) { return update_post_meta( $post->ID, $key, $value ); }

REST API 2.0 BETA 15

$args =[ 'sanitize_callback' => 'sanitize_my_meta_key', 'auth_callback' => 'authorize_my_meta_key', 'type' => 'string', 'description' => 'My registered meta key', 'single' => true, 'show_in_rest' => true, ];

register_meta( 'post', 'my_meta_key', $args );

/05 ROLL YOUR OWN ENDPOINT

WP_REST_Controller

WP_REST_Posts_Controller

STANDARD IMPLEMENTATION

WP_REST_Controller

Your_Extended_Controller

OPTION #1

register_rest_route( 'enrise', '/version/(?P<os>[a-z]{3,7})', [ [ 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'getVersion' ], 'permission_callback' => [ $this, 'checkAccess' ], 'args' => [ 'os' => [ 'validate_callback' => [ $this, 'isOS' ], 'sanitize_callback' => [ $this, 'filterOS' ], 'required' => true, ], ], ], ] );

OPTION #2

/06 OVERRIDE DEFAULT FUNCTIONALITY

ANNOTATE AND VALIDATE JSON DOCUMENTS

JSON-SCHEMA.ORG

public function get_item_schema() { $schema = [ '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => $this->post_type, 'type' => 'object', /* * Base properties for every Post. */ 'properties' => [ 'date' => [ 'description' => __( "The date the object was published, in the site's timezone." ), 'type' => 'string', 'format' => 'date-time', 'context' => [ 'view', 'edit', 'embed' ], ], ... ]; }

JSON SCHEMA DEFINITION

global $wp_rest_additional_fields;

GLOBAL AGAIN…

register_rest_field( 'user', 'first_name', [ 'schema' => [ 'description' => __( 'First name for the resource.' ), 'type' => 'string', 'context' => [ 'edit', 'embed', 'view' ], 'arg_options' => [ 'sanitize_callback' => 'sanitize_text_field', ], ], ] ); register_rest_field( 'user', 'last_name', [ 'schema' => [ 'description' => __( 'Last name for the resource.' ), 'type' => 'string', 'context' => [ 'edit', 'embed', 'view' ], 'arg_options' => [ 'sanitize_callback' => 'sanitize_text_field', ], ], ] );

OVERRIDE DEFAULT CONFIGURATION

/07 APPLY SOME FILTERING

public function initServer() { add_filter('rest_customposttype_query', [ $this, 'filterbyMetaField' ], 10, 2); }

/** * @param $args * @param $request \WP_REST_Request */ public function filterByMetaField($args, $request) { $filter = []; $filter['meta_key'] = 'custommetafield'; $filter['meta_value'] = 1476896350; $filter['meta_type'] = 'DECIMAL'; $filter['meta_compare'] = '>=';

return array_merge($args, $filter); }

CHANGE THE QUERY BEHAVIOUR

/08 FILE UPLOADS

xhr.onload = function() { var attachment = JSON.parse(this.responseText); $.current.set('featured_image', attachment.id); saveCustompost(); }; xhr.open('POST', '/wp-json/wp/v2/media'); xhr.setRequestHeader('Content-Type', 'image/jpeg'); xhr.setRequestHeader('Content-Disposition', 'attachment; filename=filename.jpg');

xhr.send(image.read());

UPLOAD A FILE

public function initServer() { add_filter('wp_handle_sideload_prefilter', [ $this, 'autoRotate' ]); add_filter('rest_insert_customposttype', [$this, 'saveAttachment'], 10, 3); }

/** * @param $post \WP_Post * @param $request \WP_REST_Request * @param $create bool */ public function saveAttachment($post, $request, $create) { if ($create === true && isset( $request['featured_image'] )) { set_post_thumbnail( $post->ID, $request['featured_image'] ); } }

HANDLE THE ATTACHMENT

/09 CACHE REQUESTS

add_filter( 'rest_pre_dispatch', [ $this, 'lastUpdate' ], 10, 3 );

public function lastUpdate( $response, $server, $request ) { $since = $request->get_header( 'if_modified_since' ); if ( $since === null ) { return $response; } if ( $response !== null || $response->get_route() !== '/wp/v2/customposttype' ) { return $response; }

$lastrequest = \DateTime::createFromFormat( \DateTime::RFC1123, $since ); $lastpost = \DateTime::createFromFormat( 'Y-m-d H:i:s', get_lastpostmodified( 'gmt', 'customposttype' ) );

if ( $lastrequest >= $lastpost ) { return new \WP_REST_Response( null, 304 ); } return $response; }

LET THE CLIENT CACHE…

/10 AUTHENTICATION

add_filter( 'determine_current_user', [ $this, 'authenticate' ] ); add_filter( 'rest_authentication_errors', [ $this, 'getErrors' ] );

public function authenticate( $user ) { | public function getErrors( $value ) { // method run/used multiple times | // already overrided if ($user instanceof WP_User) { | if ( $value !== null ) { return $user; | return $value; } | } // roll your own authentication here | // WP_Error|null|bool(!?) if (false) { | return $this->status; $this->status = new WP_Error(); | } return null; | } | $this->status = true; | return $user->ID; | } |

TRY TO DEBUG THIS…

IMPLEMENTATIONS

WORDPRESS.ORG/PLUGINS/OAUTH2-PROVIDER

WORDPRESS.ORG/PLUGINS/JWT-AUTHENTICATION-FOR-WP-REST-API

WORDPRESS.ORG/PLUGINS/REST-API-OAUTH1

/11 CHANGE ALL OUTPUT!

add_filter( 'rest_pre_serve_request', [ $this, 'changeResponse' ], 10, 4 );

public function changeResponse( $served, $response, $request, $server ) { $route = $response->get_matched_route(); if ( $served || $route !== '/wp/v2/customposttype' ) { return false; }

if ( 'HEAD' === $request->get_method() ) { return null; }

$result = $server->response_to_data( $response, true ); $transform = new Transformer( $result ); echo wp_json_encode( iterator_to_array( $transform ) );

return true; }

THIS SHOULDN’T BE NECESSARY…

class Transformer extends \ArrayIterator { public function current() { $item = parent::current(); $item = $this->modify( $item );

return $item; }

private function modify( array $data ) { // modify the data in any way return $data; } }

THIS SHOULDN’T BE NECESSARY…

add_filter('rest_prepare_customposttype', 'beforeOutput', 10, 3);

add_filter('rest_pre_insert_customposttype', 'beforeInsertInDb', 10, 2);

add_filter('rest_insert_customposttype', 'afterInsertInDb', 10, 3);

add_filter('rest_query_vars', 'filterQueryVars', 10, 1);

OTHER INTERESTING HOOKS

MORE HOOKS?

V2.WP-API.ORG/EXTENDING/HOOKS


Recommended