REST API: Add Authentication method based on Jetpack Signatures#5418
REST API: Add Authentication method based on Jetpack Signatures#5418samhotchkiss merged 11 commits intomasterfrom
Conversation
roccotripaldi
left a comment
There was a problem hiding this comment.
I haven't actually tested this yet. But it seems like a great start. I left a few comments.
class.jetpack.php
Outdated
| Jetpack_Heartbeat::init(); | ||
| } | ||
|
|
||
| if ( Jetpack::is_active() ) { |
There was a problem hiding this comment.
Perhaps we should do
if ( Jetpack::is_active() && defined( 'REST_REQUEST' ) ) {
to limit these filters to rest request.
class.jetpack.php
Outdated
| * Authenticates REST API via user_tokens passed as GET query parameter | ||
| */ | ||
| function authenticate_user_tokens( $user_id ) { | ||
| $get_token = isset( $_GET[ 'user_token' ] ) ? $_GET[ 'user_token' ] : false; |
There was a problem hiding this comment.
Does it have to be a $_GET param?
Could it instead be an actual authorization header? Should we support both?
There was a problem hiding this comment.
I'd vote for the header alternative
|
It looks like this sends user tokens unencrypted, which seems problematic for non-HTTPS sites. Does the existing XMLRPC-based authentication do this too? |
|
Thanks for the recommendations. I've just updated this PR description to take in account latest comments. Note This PR was created as a kick-off for an implementation of securing the REST API with a mechanism that is currently not supported, so if you reach here please take in account those observations under Remarks about this implementation as they represent things that would need to be addressed for this implementation to be solid. |
Using a header, the token would still be sent in plaintext over HTTP, which is not a great idea. I assume that our existing XMLRPC auth strategy has some form of encryption and/or request signing for this reason. Unfortunately I'm not the person to ask about how that works, but this possible lack of transport-level encryption is why the OAuth1 flow and request signing mechanism look the way they do. |
|
We should definitely try and re-use as much as possible from the XML-RPC authentication. @mdawaffe might be able to help out with some specifics. |
|
These Jetpack tokens can't be included "raw" in requests, for the reasons others have outlined above. This is why Jetpack uses the (The full background: Though we mostly only use A few things that will need to happen:
|
The method to use for this is If there are further changes needed there for different environments, let's get these changes into WP core as well. |
class.jetpack.php
Outdated
| Jetpack_Heartbeat::init(); | ||
| } | ||
|
|
||
| if ( Jetpack::is_active() && defined( 'REST_REQUEST' ) ) { |
There was a problem hiding this comment.
I was using WordPress 4.5.4 and REST_REQUEST was not defined. Same thing happened in WordPress 4.6.1.
I've put some var_dump() here and there, and what is happening is that this code is being executed, and after that, wp-includes/rest-api.php#rest_api_loaded() is executed (and the REST_REQUEST constant is set).
Which version of WordPress did you use to test this?
There was a problem hiding this comment.
I was using 4.5 But I didn't update the code here to move the hooking to a safe place yet. I've just labeled this PR to "In Progress" as there's a lot to be changed.
|
I added a commit to address @DanReyLop 's feedback. I get this when trying to run the But i was also getting that prior to my commit. I think if we start addressing the feedback from @nb and @mdawaffe we will be on the right track. i'm happy to help out this week. :) |
|
Testing instructions have been updated. Please ping me on Slack for how to run tests from the Jetpack server. |
class.jetpack-signature.php
Outdated
There was a problem hiding this comment.
! empty() will include a check for null values, so the ! is_null() check is now obsolete.
|
This needs unit tests for requests with both valid and invalid signatures. |
563460e to
0307a06
Compare
7ddacda to
03ac039
Compare
|
I thought of a couple more small improvements to make, see above. Also, sorry for the noise, but I'm switching this back to In Progress since I think there are some remaining issues still. It looks like we need to pay more attention to this earlier comment:
If it's not set, we'll end up calling But, from the
So I think this is probably a bit broken in, say, PHP 5.5 with This also made me think of where else we might have problems with handling of request bodies. Basically we need to make sure that the signature verification code in |
|
Since this PR is getting rather large and unwieldy, I propose we create issues for the following improvements:
We can merge this PR, and work on improvements in separate PRs. Keeping this unmerged is slowing down work on Jetpack server enhancements. |
|
The first issue is fine for a separate improvement. The second issue needs to be handled here before anything is merged. We need to make sure it is not possible to send a request parameter that is not checked as part of request signing. |
|
@roccotripaldi @nylen I've just squashed this branch to have a single commit (plus a debugging one from last night by Rocco) so we can eventually simplify the merging task when wrapping up the release that will include this PR. By doing that, I totally owned your 30+ commits :( . I left the relevant commit messages though, but I forgot to add references to the authors. |
class.jetpack.php
Outdated
There was a problem hiding this comment.
This feels like an awkward way to log errors -- wouldn't it be simpler to concatenate the print_r( $body, true ) onto the string, rather than making a bigger array?
There was a problem hiding this comment.
This isn't sticking around in the final version.
class.jetpack.php
Outdated
There was a problem hiding this comment.
Would it be more readable to do empty() instead of ! isset() ? Or do we want to accept falsey empty strings?
There was a problem hiding this comment.
Doesn't matter - this can only ever be blog or user. See $token_type in verify_xml_rpc_signature.
class.jetpack.php
Outdated
There was a problem hiding this comment.
Should this have translation __() calls around it?
There was a problem hiding this comment.
Probably - will go through and add these.
class.jetpack.php
Outdated
There was a problem hiding this comment.
Is this the best place to handle authentication? I had thought it was better to use determine_current_user for that?
There was a problem hiding this comment.
Good catch - I think you're right. This would cause auth here to override the user set by a cookie, for example. WP_REST_OAuth1 only uses rest_authentication_errors to report errors that were stored previously.
* Check if we're on a REST API request before enabling the token-based auth method for the rest API * Do some Jetpack specific stuff on action `wp_json_init` instead of checking for `REST_REQUEST` constant. * Use the existing XMLRPC signature verification to validate WP API requests coming from the Jetpack server. Modifies the algorithm to handle a request containing an empty string body. * Ensure that the authorization object is not a WP_Error before attempting to use it. * To prove that we can use this mechanism to post complex objects, I've modified the bulk module activation endpoint to support JSON in POST body. * The endpoint should still work with multi-part form data as it did before. * In core's phpunit suites, `WP_Test_REST_Controller_Testcase` is already defined. Jetpack should define it if the class already exists. * Updating auth logic so that we do not overwrite any work from other plugins. props @nylen * improve coverage of Jetpack::wp_rest_authenticate to cover generic errors as well as errors from verify_xml_rpc_signature * REST Authentication Logic - WP API returns simply `true` for generic auth errors - Jetpack's authorization will account for this by also returning `true` in these cases - Jetpack's authorizaiton will also account for other plugins that may be authenticating, by giving their errors priority * Update modules list endpoint parameters for WP 4.7 * Run new API tests under multisite too * Improve error message when signature verification fails * Once we decide to handle authentication, either succeed or fail: Eliminate undecided states caused by successful signature verification without a token that we know how to deal with. It looks like this could happen if we successfully authenticated with a token of type 'blog' - let's make this (and any other such conditions) unsupported (and return an error) instead. * When receiving a POST request with no body, authentication fails with body-hash errors. This is because our signature verification is looking explicitly for `null` in regards to body. If, after all of Jetpack's sniffing, we still find an empty body, we should explicitly set it to `null`. Not all POST requests will require a body.
PHP versions prior to 5.6 cannot handle multiple reads from php://input
This also means we need to change up the logic to parse the request body, because we are now running *before* the WP REST API body-parsing code rather than *after* it.
|
With the latest changes this is ready for review again. |
|
High fives @nylen and @georgestephanis ! This looks good and works well. I think all concerns have been addressed. If George would give it a thumbs up, I am completely confident to merge. |
|
@nylen @roccotripaldi -- Last thing -- one thing I had to do with Application Passwords was this -- WordPress/application-passwords@4226502 -- because I was only allowing my override of edit: the code in the changeset linked above eventually changed to https://github.com/georgestephanis/application-passwords/blob/c975c312b4f448d6dab0d016e152ab75354d38cd/class.application-passwords.php#L40-L67 Apart from that, 💯 |
|
@vortfu is going to be reviewing shortly, once he signs off, let's go ahead and merge to master and |
|
Discussed #5418 (comment) a bit with George via DM. This comment highlights the fact that we are currently running our auth logic on every request, but we should probably only be running it for a As George points out, this is a bit tricky, because the @samhotchkiss Let me know if y'all would like to fix this here or in a separate PR, either one should be fine. |
|
@nylen -- if you can fix here in the next couple of hours, that's 👍 |
This is the simplest way to make sure that we only apply this authentication method when intended (i.e. for requests from the WP.com servers).
|
So, I am a little bit uncomfortable with adding the Where we might have run into problems is if someone did another API request (unrelated to Jetpack) that happened to contain a In 1937dcd I addressed this by following the existing convention of a Note - this new parameter will require changes to existing WP.com server code. See D3926-code. I still think this is good to ship. Possible future enhancements:
|
@nylen : noted. Thanks. |
Part of Automattic/wp-calypso#9171.
Changes proposed in this Pull Request:
Adds a token based authentication mechanism for the REST API.
We already generate tokens for the users when they connect thir self-hosted account to a WordPress.com account by means of the Jetpack connection process.
Remarks about this implementation
Testing instructions:
Running Unit tests
In a proper WordPress testing environment, try this command:
phpunit --filter=test_jetpack_rest_api_authentication --debugWhy
We're going to implement Jetpack module settings in Calypso and this communication mechanism ensures that we can update the Jetpack site in real time without the need of going through the XML-RPC API.
Proposed changelog entry for your changes:
Adds a token based authentication mechanism to the REST API in order for the site to be able to receive authenticated requests from WordPress.com