Ocultar puntos finales de WordPress REST API v2 de la visualización pública

14

Me gustaría comenzar a usar WordPress REST API v2 para consultar información de mi sitio. Me di cuenta de que cuando visito una URL de punto final directamente, puedo ver todos los datos públicamente. También he visto que muchos tutoriales mencionan el uso de servidores de prueba o locales en lugar de sitios en vivo.

Mis preguntas son:

  • ¿Está destinado a ser utilizado en sitios en producción?
  • ¿Existe un riesgo de seguridad al permitir que cualquier persona pueda ver los puntos finales, como lo /wp-json/wp/v2/users/que muestra todos los usuarios registrados en el sitio?
  • ¿Es posible permitir que solo los usuarios autorizados accedan a un punto final?

Quiero asegurarme de seguir las mejores prácticas en materia de seguridad, por lo que cualquier consejo sería útil. Los documentos de la API mencionan la autenticación, pero no estoy seguro de cómo evitar que se acceda directamente a la URL. ¿Cómo suelen otros configurar esta información para que accedan las aplicaciones externas sin exponer demasiada información?

Morgan
fuente
1
La verdadera pregunta es, ¿está utilizando los puntos finales del lado del cliente (es decir, en llamadas AJAX) o del lado del servidor (quizás desde otra aplicación)?
TheDeadMedic
1
Nota: La versión más reciente del complemento de WordFence tiene una opción para "Prevenir el descubrimiento de nombres de usuario a través de escaneos '/? Author = N', la API oEmbed y la API REST de WordPress"
squarecandy

Respuestas:

18

¿Está destinado a ser utilizado en sitios en producción?

Si. Muchos sitios ya lo han estado usando .

¿Existe un riesgo de seguridad al permitir que cualquier persona pueda ver los puntos finales, como / wp-json / wp / v2 / users / que muestra a todos los usuarios registrados en el sitio?

No. Las respuestas del servidor no tienen nada que ver con la seguridad, ¿qué puede hacer con una pantalla en blanco / acceso de solo lectura? ¡Nada!

Sin embargo, si sus sitios permiten contraseñas débiles, hay algunos problemas . Pero es la política de sus sitios, REST API no sabe nada al respecto.

¿Es posible permitir que solo los usuarios autorizados accedan a un punto final?

Si. Puede hacerlo utilizando la devolución de llamada de permiso .

Por ejemplo:

if ( 'edit' === $request['context'] && ! current_user_can( 'list_users' ) ) {
    return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you cannot view this resource with edit context.' ), array( 'status' => rest_authorization_required_code() ) );
}

¿Cómo suelen otros configurar esta información para que accedan las aplicaciones externas sin exponer demasiada información?

Esta pregunta es difícil de responder porque no sabemos qué / cuándo es demasiada información . Pero todos estamos usando referencias y cheatsheets .

MinhTri
fuente
1
Importante tener en cuenta: "La exposición se limita a los usuarios que han creado tipos de publicación que están configurados para ser expuestos a través de la API REST". - Entonces, si tiene una tienda en línea donde cada cliente tiene un usuario, estos usuarios no están expuestos a través de /wp-json/wp/v2/users/. (Referencia wordpress.stackexchange.com/q/252328/41488 @JHoffmann comment)
squarecandy
Cabe señalar que debe tener un nonce basado en REST wp_create_nonce ('wp_rest') en el encabezado 'X-WP-Nonce', o ninguno de estos elementos funcionará en absoluto, y siempre devolverá un 403.
Andrew Killen
5

¿Es posible permitir que solo los usuarios autorizados accedan a un punto final?

Es posible agregar una devolución de llamada de permiso personalizada a su punto final de API que requiere autenticación para ver el contenido. Los usuarios no autorizados recibirán una respuesta de error."code": "rest_forbidden"

La forma más sencilla de hacer esto es extender el WP_REST_Posts_Controller. Aquí hay un ejemplo muy simple de eso:

class My_Private_Posts_Controller extends WP_REST_Posts_Controller {

   /**
   * The namespace.
   *
   * @var string
   */
   protected $namespace;

   /**
   * The post type for the current object.
   *
   * @var string
   */
   protected $post_type;

   /**
   * Rest base for the current object.
   *
   * @var string
   */
   protected $rest_base;

  /**
   * Register the routes for the objects of the controller.
   * Nearly the same as WP_REST_Posts_Controller::register_routes(), but with a 
   * custom permission callback.
   */
  public function register_routes() {
    register_rest_route( $this->namespace, '/' . $this->rest_base, array(
        array(
            'methods'             => WP_REST_Server::READABLE,
            'callback'            => array( $this, 'get_items' ),
            'permission_callback' => array( $this, 'get_items_permissions_check' ),
            'args'                => $this->get_collection_params(),
            'show_in_index'       => true,
        ),
        array(
            'methods'             => WP_REST_Server::CREATABLE,
            'callback'            => array( $this, 'create_item' ),
            'permission_callback' => array( $this, 'create_item_permissions_check' ),
            'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
            'show_in_index'       => true,
        ),
        'schema' => array( $this, 'get_public_item_schema' ),
    ) );

    register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
        array(
            'methods'             => WP_REST_Server::READABLE,
            'callback'            => array( $this, 'get_item' ),
            'permission_callback' => array( $this, 'get_item_permissions_check' ),
            'args'                => array(
                'context' => $this->get_context_param( array( 'default' => 'view' ) ),
            ),
            'show_in_index'       => true,
        ),
        array(
            'methods'             => WP_REST_Server::EDITABLE,
            'callback'            => array( $this, 'update_item' ),
            'permission_callback' => array( $this, 'update_item_permissions_check' ),
            'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
            'show_in_index'       => true,
        ),
        array(
            'methods'             => WP_REST_Server::DELETABLE,
            'callback'            => array( $this, 'delete_item' ),
            'permission_callback' => array( $this, 'delete_item_permissions_check' ),
            'args'                => array(
                'force' => array(
                    'default'     => true,
                    'description' => __( 'Whether to bypass trash and force deletion.' ),
                ),
            ),
            'show_in_index'       => false,
        ),
        'schema' => array( $this, 'get_public_item_schema' ),
    ) );     
  }

  /**
   * Check if a given request has access to get items
   *
   * @param WP_REST_Request $request Full data about the request.
   * @return WP_Error|bool
   */
  public function get_items_permissions_check( $request ) {
    return current_user_can( 'edit_posts' );
  }

}

Notará que la devolución de llamada de permisos se function get_items_permissions_checkutiliza current_user_canpara determinar si se debe permitir el acceso. Dependiendo de cómo esté utilizando la API, es posible que deba aprender más sobre la autenticación del cliente.

A continuación, puede registrar su tipo de publicación personalizada con soporte de API REST agregando los siguientes argumentos en register_post_type

  /**
   * Register a book post type, with REST API support
   *
   * Based on example at: http://codex.wordpress.org/Function_Reference/register_post_type
   */
  add_action( 'init', 'my_book_cpt' );
  function my_book_cpt() {
    $labels = array(
        'name'               => _x( 'Books', 'post type general name', 'your-plugin-textdomain' ),
        'singular_name'      => _x( 'Book', 'post type singular name', 'your-plugin-textdomain' ),
        'menu_name'          => _x( 'Books', 'admin menu', 'your-plugin-textdomain' ),
        'name_admin_bar'     => _x( 'Book', 'add new on admin bar', 'your-plugin-textdomain' ),
        'add_new'            => _x( 'Add New', 'book', 'your-plugin-textdomain' ),
        'add_new_item'       => __( 'Add New Book', 'your-plugin-textdomain' ),
        'new_item'           => __( 'New Book', 'your-plugin-textdomain' ),
        'edit_item'          => __( 'Edit Book', 'your-plugin-textdomain' ),
        'view_item'          => __( 'View Book', 'your-plugin-textdomain' ),
        'all_items'          => __( 'All Books', 'your-plugin-textdomain' ),
        'search_items'       => __( 'Search Books', 'your-plugin-textdomain' ),
        'parent_item_colon'  => __( 'Parent Books:', 'your-plugin-textdomain' ),
        'not_found'          => __( 'No books found.', 'your-plugin-textdomain' ),
        'not_found_in_trash' => __( 'No books found in Trash.', 'your-plugin-textdomain' )
    );

    $args = array(
        'labels'             => $labels,
        'description'        => __( 'Description.', 'your-plugin-textdomain' ),
        'public'             => true,
        'publicly_queryable' => true,
        'show_ui'            => true,
        'show_in_menu'       => true,
        'query_var'          => true,
        'rewrite'            => array( 'slug' => 'book' ),
        'capability_type'    => 'post',
        'has_archive'        => true,
        'hierarchical'       => false,
        'menu_position'      => null,
        'show_in_rest'       => true,
        'rest_base'          => 'books-api',
        'rest_controller_class' => 'My_Private_Posts_Controller',
        'supports'           => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'comments' )
    );

    register_post_type( 'book', $args );
}

Verá rest_controller_classusos en My_Private_Posts_Controllerlugar del controlador predeterminado.

Me ha resultado difícil encontrar buenos ejemplos y explicaciones para usar la API REST fuera de la documentación . Encontré esta gran explicación de extender el controlador predeterminado , y aquí hay una guía muy completa para agregar puntos finales .

Dalton
fuente
2

Esto es lo que he usado para bloquear a todos los usuarios que no han iniciado sesión para que no utilicen la API REST:

add_filter( 'rest_api_init', 'rest_only_for_authorized_users', 99 );
function rest_only_for_authorized_users($wp_rest_server){
    if ( !is_user_logged_in() ) {
        wp_die('sorry you are not allowed to access this data','cheatin eh?',403);
    }
}
Squarecandy
fuente
A medida que el uso del punto final del resto se va a expandir, este tipo de estrategia se volverá problemático. Al final, el punto final wp-json reemplazará al administrador-ajax, lo que significa que también habrá todo tipo de solicitudes legítimas de front-end. De todos modos, es mejor morir con un 403 que algo que pueda interpretarse como contenido.
Mark Kaplun
@ MarkKaplun: sí, tienes razón en eso. Estoy usando esto en el contexto de un sitio que no ofrece esencialmente ningún tipo de datos públicos y los datos que estamos almacenando, incluidos los usuarios, meta de usuario, datos de tipo de publicación personalizada, etc., son datos de propiedad a los que el público nunca debería acceder. . Es una mierda cuando haces un montón de trabajo dentro de la estructura clásica de la plantilla WP para asegurarte de que ciertos datos son privados y luego te das cuenta de repente de que todo es accesible públicamente a través de la API REST. De todos modos, buen punto sobre servir un 403 ...
squarecandy
0
add_filter( 'rest_api_init', 'rest_only_for_authorized_users', 99 );
function rest_only_for_authorized_users($wp_rest_server)
{
if( !is_user_logged_in() ) 

    wp_die('sorry you are not allowed to access this data','Require Authentication',403);
} } 
function json_authenticate_handler( $user ) {

global $wp_json_basic_auth_error;

$wp_json_basic_auth_error = null;

// Don't authenticate twice
if ( ! empty( $user ) ) {
    return $user;
}

if ( !isset( $_SERVER['PHP_AUTH_USER'] ) ) {
    return $user;
}

$username = $_SERVER['PHP_AUTH_USER'];
$password = $_SERVER['PHP_AUTH_PW'];


remove_filter( 'determine_current_user', 'json_authenticate_handler', 20 );

$user = wp_authenticate( $username, $password );

add_filter( 'determine_current_user', 'json_authenticate_handler', 20 );

if ( is_wp_error( $user ) ) {
    $wp_json_basic_auth_error = $user;
    return null;
}

$wp_json_basic_auth_error = true;

return $user->ID;}add_filter( 'determine_current_user', 'json_authenticate_handler', 20 );
dipen patel
fuente
1
¿Podría elaborar en el texto por qué y cómo esto responde las preguntas del OP?
kero
Esta no es la respuesta de OP y me dio sólo el código para mostrar cómo el trabajo en la práctica y he tratado de que usted consiga fácilmente comprensible en programación Si entendiste
dipen Patel