External request to Wordpress WP-API - Basic Authentication - wordpress

I'm trying to hit my Wordpress API using Basic Auth with Guzzle (http tool) from my middleware (Laravel).
$username = 'myAdminUserName';
$password = 'myAdminPassword';
$uri = 'https://example.com/wp-json/mysite-api/cleared-action';
$response = $this->guzzle->put(
$uri,
[
'headers' => [
'Authorization' => 'Basic ' . base64_encode( $username . ':' . $password )
],
'body' => [
'user_id' => $wordpressId //passed into this function
]
]
);
It then hits the route set up in my Wordpress API
$routes['/mysite-api/cleared-action'] = array(
array(array($this, 'automatedClearing'), WP_JSON_Server::ACCEPT_JSON
| WP_JSON_Server::CREATABLE
| WP_JSON_Server::EDITABLE)
);
However that is as far as it gets. It does not hit my automatedClearing endpoint which looks like this
public function automatedClearing() {
global $container;
\Groups_User_Group::create( array('user_id' => 2903, 'group_id' => 13));
$mySiteServices = $container['services'];
$this->$mySiteServices->sendClearedEmail(2903); //2903 = user_id
}
I've used hardcoded values for the users ID.
I keep getting a 200 response from my call, so it definitely hits the route, but does not execute the endpoint. The response is basically just an empty one.
My Wordpress access.log shows the route being hit, but my error.log doesn't show anything. By the way, this is a laravel Homestead (vagrant) box hitting a Wordpress vagrant box.
I'm wondering if this is because the WP-API requires a nonce? But I thought nonce was only needed within Wordpress, whereas this is an external app hitting Wordpress.
I'm pretty stuck on this. Any guidance is greatly appreciated

Try to test it using postman ... if this works via postman then you have the problem with laravel or guzzle

Related

Make an HTTP POST request to upload a file in Wordpress - HTTP POST request get converted to GET

I want to have a HTTP POST link in my Wordpress website that lets another server to post an xml file every hour into the Wordpress server and I save it.
I created an index.php file in folders that map with the route I want, let say I need example.com/jobs/uploadFile, so I created a php file inside the folders /jobs/uploadFile of the root Wordpress directory.
<?php
if( $_SERVER['REQUEST_METHOD'] !== 'POST' ) {
header($_SERVER["SERVER_PROTOCOL"]." Method Not Allowed", true, 405);
exit;
}
$postData = trim(file_get_contents('php://input'));
$xml = simplexml_load_string($postData);
if($xml === false) {
header($_SERVER["SERVER_PROTOCOL"]." Bad Request", true, 400);
exit;
}
$xml->asXml('jobs.xml');
http_response_code(200);
1- I send a HTTP POST request via postman, but somehow the server or Wordpress changes it a HTTP GET request, so always the first if condition is executed. I'm using Laravel forge server with Nginx.
2- Appreciate any security advice about this approach, CORS...?
Thanks for your help
Since it may help others, I answer my question. I was doing it the wrong way. The better way to do it is by using actions in a custom Wordpress plugin. Just create a custom plugin and use add_action inside it:
add_action( 'rest_api_init', function() {
register_rest_route(
'myapi/v1', 'myUploadURL',
[
'methods' => 'POST',
'callback' => 'my_upload_function',
'permission_callback' => '__return_true',
]
);
});
And then you can get the $_FILES of the POST request in the my_upload_function() and save it on your server.

How to enable https for wordpress and install certificate

I have been able to create a custom endpoint following the code below for wordpress
function handle_woocommerce_keys($request){
$user_id = $request[user_id];
$consumer_key=$request[consumer_key];
$consumer_secret=$request[consumer_secret];
$key_permissions=$request[key_permissions];
/* search user_id in db and store the keys as meta_data */
$response = new WP_REST_Response();
$response->set_status(200);
return $response;
}
add_action('rest_api_init', function () {
register_rest_route( 'village/v1', 'authkeys',array(
'methods' => 'POST',
'callback' => 'handle_woocommerce_keys'
));
});
Unfortunatly it's only working using HTTP. I use Postman and it behave has expected. However, I need HTTPS to be supported. The reason is that the endpoint URL is provide as param to a server and used by this server to send a POST to this https URL.
Any idea how to make HTTPS endpoint supported on Wordpress ?
Do I need to install a certificate, If yes how ?
Thanks

Get current user inside register_rest_route method

How to retrive wp_get_current_user() inside a register_rest_route callback (Wordpress site)?
I'm just trying to do a simple hello wp_get_current_user()->user_login on a php test page:
add_action('rest_api_init', 'helloTest');
function helloTest() {
register_rest_route('hello', 'hello/(?P<id>\d+)', array(
'methods' => WP_REST_SERVER::READABLE,
'callback' => 'showHello'
));
}
function showHello($someVariable) {
echo "Hello " . wp_get_current_user()->user_login . $someVariable;
}
But wp_get_current_user() is null and wp_get_current_user->ID is 0;
I dont want to authenticate the user again. I just want to retrieve his username. If he is not logged in, just show empty an empty string.
If I have to authenticate again, how to add a "nonce" to it? On internet I just have examples using javascript, but not directly on PHP methods.
Issues in your code
First off, you should understand properly how to add custom WP REST API endpoints:
An endpoint's namespace (the first parameter passed to register_rest_route()) should be in this format: your-plugin/v<version>. E.g. hello/v1 or hello-world/v1 and not just hello or hello-world.
$someVariable (the first parameter passed to your endpoint callback function) is not just any variable — it's an instance of the WP_REST_Request class — and shouldn't be echo-ed like what you did here:
function showHello($someVariable) {
echo "Hello " . wp_get_current_user()->user_login . $someVariable;
}
And normally, the $someVariable is better be changed to $request (i.e. rename it to "request").
And you should return a valid WP REST API response. For example, to return just the username:
return new WP_REST_Response( wp_get_current_user()->user_login, 200 );
And know your own API endpoint URL..
(based on your original namespace)
/wp-json/hello/hello/1 <- correct
/wp-json/hello/?John <- incorrect
because in your code, the parameter is a number and not string: (?P<id>\d+)
I hope those help you, and once again, do read the handbook for a more detailed guide.
The Corrected Code
add_action( 'rest_api_init', 'helloTest' );
function helloTest() {
register_rest_route( 'hello/v1', 'hello/(?P<id>\d+)', array(
'methods' => WP_REST_SERVER::READABLE,
'callback' => 'showHello'
) );
}
function showHello( $request ) {
return new WP_REST_Response( wp_get_current_user()->user_login, 200 );
}
Now about getting the user (from the API endpoint — showHello())
If I have to authenticate again, how to add a "nonce" to it?
Just because the user is logged-in/authenticated to the (WordPress) site, it doesn't mean the user is automatically logged-in to the WP REST API. So yes, you'd need to either provide a nonce along with your API request, or use one of the authentication plugins mentioned right here.
Now in most cases, GET (i.e. read-only) requests to the API do not need any authentication, but if you'd like to retrieve the data of the currently logged-in user on your site, then one way is via the _wpnonce data parameter (either POST data or in the query for GET requests).
Example for a GET request:
http://example.com/wp-json/wp/v2/posts?_wpnonce=<nonce>
So based on your comment and the corrected code (above):
Theres no "code" that make the request. Its is just an anchor that
calls my route: Hello
You can add the nonce as part of the URL query string like so: (the namespace is hello/v1 and the <id> is 1)
// Make request to /wp-json/hello/v1/hello/<id>
$nonce = wp_create_nonce( 'wp_rest' );
echo 'Hello';
So try that out along with the corrected code and let me know how it goes. :)
And once again, be sure to read the REST API authentication handbook.

HTTP client Cakephp 3 ignores json body

I'm currently writing a RESTful API in Cakephp 3 whereby I need to test a POST operation through http://host.com/api/pictures. The code for the test:
<?php
namespace App\Test\TestCase\Controller;
use App\Controller\Api\UsersController;
use Cake\TestSuite\IntegrationTestCase;
use Cake\Network\Http\Client;
use Cake\Network\Http\FormData;
class ApiPicturesControllerTest extends IntegrationTestCase{
public $fixtures = [
'app.users',
'app.comments',
'app.albums',
'app.users_albums'
];
public function testAdd(){
// $data = new FormData();
$accessToken ='eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjksImV4cCI6MTQ1NzYyOTU3NH0.NnjXWEQCno3PUiwHhnUCBjiknR-NlmT42oPLA5KhuYo';
$http = new Client([
'headers' => ['Authorization' => 'Bearer ' . $accessToken, 'Content-Type' => 'application/json']
]);
$data = [
"album_id" => 1,
"link" => "http://www.google.com",
"description" => "testtesttest",
"favorite" => true
];
$result = $http->post('http://vecto.app/api/pictures/add.json', $data, ['type'=>'json']);
// $this->assertResponseOk();
// debug($result);
}
}
When I try to debug the result I get a 'cannot add or update child row' while I'm sure the responding id does exists
(the fixtures does have the id's too). Additionally, the log indicates that it only tries to insert the create/update rows. Therefore, I'm pretty sure the data is ignored but however I can't find a solution. I already tried different combination of headers like only application/json for Accept, application/json for Content-Type etc. I'm using the CRUD plugin for Cakephp to pass the data to an add function.
Postman output
Furthermore, I tried the Postman Chrome plugin to save the data and that actually does work. Does anyone know what I'm doing wrong in the test?
That's not how the integration test case is ment to be used. You are dispatching an external, real request, which will leave the test environment, while you should use the request dispatching tools that the integration test case supplies, that is
IntegrationTestCase::get()
IntegrationTestCase::post()
IntegrationTestCase::put()
etc...
These methods will dispatch simulated requests that do not leave the test environment, which is crucial for things to work properly, as you want to use test connections, inspect possible exceptions, have access to the used session, etc...
ie, you should do something along the lines of
$accessToken = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjksImV4cCI6MTQ1NzYyOTU3NH0.NnjXWEQCno3PUiwHhnUCBjiknR-NlmT42oPLA5KhuYo';
$this->configRequest([
'headers' => [
'Authorization' => 'Bearer ' . $accessToken,
'Content-Type' => 'application/json'
]
]);
$data = [
"album_id" => 1,
"link" => "http://www.google.com",
"description" => "testtesttest",
"favorite" => true
];
$this->post('/api/pictures/add.json', json_encode($data));
Note that a content type of application/json will require you to send raw JSON data! If you don't actually need/want to test parsing of raw input, then you could skip that header, and pass the array as data instead.
See also
Cookbook > Testing > Controller Integration Testing
API > \Cake\TestSuite\IntegrationTestCase

Edit include/mail.inc, add my own SMTP setting there

The following function is contained in include/mail.inc of Drupal6, it uses the default SMTP settings buried in a file named "php.ini" to send mail.
function drupal_mail_send($message) {
// Allow for a custom mail backend.
if (variable_get('smtp_library', '') && file_exists(variable_get('smtp_library', ''))) {
include_once './'. variable_get('smtp_library', '');
return drupal_mail_wrapper($message);
}
else {
$mimeheaders = array();
foreach ($message['headers'] as $name => $value) {
$mimeheaders[] = $name .': '. mime_header_encode($value);
}
return mail(
$message['to'],
mime_header_encode($message['subject']),
// Note: e-mail uses CRLF for line-endings, but PHP's API requires LF.
// They will appear correctly in the actual e-mail that is sent.
str_replace("\r", '', $message['body']),
// For headers, PHP's API suggests that we use CRLF normally,
// but some MTAs incorrecly replace LF with CRLF. See #234403.
join("\n", $mimeheaders)
);
}
}
but I use shared host, therefore i can't edit php.ini, i want to edit the above function "drupal_mail_send", add the codes below into that function so that it can bypass the PHP mail() function and send email directly to my favorite SMTP server.
include('Mail.php');
$recipients = array( 'someone#example.com' ); # Can be one or more emails
$headers = array (
'From' => 'someone#example.com',
'To' => join(', ', $recipients),
'Subject' => 'Testing email from project web',
);
$body = "This was sent via php from project web!\n";
$mail_object =& Mail::factory('smtp',
array(
'host' => 'prwebmail',
'auth' => true,
'username' => 'YOUR_PROJECT_NAME',
'password' => 'PASSWORD', # As set on your project's config page
#'debug' => true, # uncomment to enable debugging
));
$mail_object->send($recipients, $headers, $body);
Could you write down the modified code for my reference?
The code in drupal_mail_send is part o the Drupal core functionality and should not be changed directly as your changes may be overwritten when you update Drupal.
Modifications of Drupal core files is often referred to by the Drupal community as "hacking core" and is largely discouraged.
Drupal already has a number of modules available which may help you. See:
http://drupal.org/project/phpmailer module:
Adds SMTP support for sending e-mails using the PHPMailer library.
Comes with detailed configuration instructions for how to use Google
Mail as mail server.
http://drupal.org/project/smtp module:
This module allows Drupal to bypass the PHP mail() function and send
email directly to an SMTP server.

Resources