Ocultando pontos de extremidade da API REST do WordPress v2 da exibição pública

14

Gostaria de começar a usar a API REST do WordPress v2 para consultar informações do meu site. Percebi que, quando visito diretamente um URL de terminal, posso ver todos os dados publicamente. Também vi que muitos tutoriais mencionam o uso de servidores locais ou de teste em vez de sites ativos.

Minhas perguntas são:

  • Isso deve ser usado em sites em produção?
  • Existe um risco de segurança para permitir que os pontos de extremidade sejam visualizados por alguém, como o /wp-json/wp/v2/users/que mostra todos os usuários registrados no site?
  • É possível permitir que apenas usuários autorizados acessem um terminal?

Quero ter certeza de que estou seguindo as práticas recomendadas em relação à segurança, para que qualquer dica seja útil. Os documentos da API mencionam autenticação, mas não sei como impedir que o URL seja acessado diretamente. Como outras pessoas geralmente configuram esses dados para serem acessados ​​por aplicativos externos sem expor muita informação?

Morgan
fonte
1
A verdadeira questão é: você está usando o ponto de extremidade do cliente (ou seja, nas chamadas AJAX) ou o servidor (talvez de outro aplicativo)?
TheDeadMedic
1
Nota: A versão mais recente do plug-in do WordFence tem uma opção para "Impedir a descoberta de nomes de usuários através de verificações '/? Author = N', a API oEmbed e a API REST do WordPress"
squarecandy

Respostas:

18

Isso deve ser usado em sites em produção?

Sim. Muitos sites já o estão usando .

Existe um risco de segurança ao permitir que os pontos de extremidade sejam visualizados por qualquer pessoa, como / wp-json / wp / v2 / users / que mostra todos os usuários registrados no site?

Não. As respostas do servidor não têm nada a ver com segurança, o que você pode fazer com uma tela em branco / acesso somente leitura? Nada!

No entanto, se seus sites permitem senhas fracas, existem alguns problemas . Mas é a política dos seus sites, a API REST não sabe nada sobre isso.

É possível permitir que apenas usuários autorizados acessem um terminal?

Sim. Você pode fazer isso usando a permissão de retorno de chamada .

Por exemplo:

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() ) );
}

Como outras pessoas geralmente configuram esses dados para serem acessados ​​por aplicativos externos sem expor muita informação?

É difícil responder a essa pergunta porque não sabemos o que / quando é muita informação . Mas todos nós estamos usando referências e folhas de dicas .

MinhTri
fonte
1
Importante observar: "A exposição fica limitada a usuários que criaram tipos de postagem que devem ser expostos por meio da API REST". - então, se você diz, uma loja on-line em que cada cliente tem um usuário, esses usuários não são expostos via /wp-json/wp/v2/users/. (Referência wordpress.stackexchange.com/q/252328/41488 @JHoffmann comment)
squarecandy
Deve-se observar que você precisa ter um nonce baseado em REST wp_create_nonce ('wp_rest') no cabeçalho 'X-WP-Nonce', ou nada disso funcionará e retornará sempre um 403.
Andrew Killen
5

É possível permitir que apenas usuários autorizados acessem um terminal?

É possível adicionar um retorno de chamada de permissão personalizada ao terminal da API, o que requer autenticação para exibir o conteúdo. Usuários não autorizados receberão uma resposta de erro"code": "rest_forbidden"

A maneira mais simples de fazer isso é estender o WP_REST_Posts_Controller. Aqui está um exemplo muito simples disso:

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' );
  }

}

Você notará que o retorno de chamada das permissões function get_items_permissions_checkusa current_user_canpara determinar se deve permitir o acesso. Dependendo de como você estiver usando a API, talvez seja necessário aprender mais sobre a autenticação do cliente.

Em seguida, você pode registrar seu tipo de postagem personalizado com suporte à API REST adicionando os seguintes argumentos em 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 );
}

Você verá rest_controller_classusos em My_Private_Posts_Controllervez do controlador padrão.

Tenho tido dificuldade em encontrar bons exemplos e explicações para usar a API REST fora da documentação . Eu encontrei essa ótima explicação para estender o controlador padrão e aqui está um guia completo para adicionar pontos de extremidade .

Dalton
fonte
2

Aqui está o que eu usei para impedir que todos os usuários não conectados usem a 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
fonte
À medida que o uso do ponto final restante se expandir, esse tipo de estratégia se tornará problemático. No final, o ponto final wp-json substituirá o ponto admin-ajax, o que significa que também haverá todos os tipos de solicitações legíveis de front-end. Enfim, é melhor morrer com um 403 do que algo que possa ser interpretado como conteúdo.
Mark Kaplun
@ MarkKaplun - sim, você está correto sobre isso. Estou usando isso no contexto de um site que não oferece praticamente nenhum dado público, e os dados que estamos armazenando, incluindo usuários, meta do usuário, dados personalizados do tipo de postagem etc. são dados proprietários que nunca devem ser acessados ​​pelo público . É péssimo quando você faz um monte de trabalho na estrutura clássica de modelo do WP para garantir que certos dados sejam privados e, de repente, percebe que tudo está acessível publicamente através da API REST. De qualquer forma, bom ponto sobre servindo um 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
fonte
1
Você poderia elaborar no texto por que e como isso responde às perguntas do OP?
Kero
Esta não é a resposta de op e eu dei apenas o código para mostrar como o trabalho em prática e eu tentei-lo a obter facilmente entendida em programaticamente Se você entendeu
Dipen patel