Support from Acquia helps fund testing for Drupal Acquia logo

Comments

klausi’s picture

Should not be too hard, would just require a JSONP format controller class that bases on the JSON format and adds the function call wrapping. Patches welcome.

scor’s picture

Title: Add JSONP support » Add JSONP support or consider CORS instead

I'd recommend CORS instead, it's easier to add: just one line of code to output an additional header without having to change the JSON output. Most modern browsers understand it (see site above). There is an example in the JSON-LD module.

chandimac’s picture

CORS can be enabled by adding the

Header set Access-Control-Allow-Origin "*"

in the .htaccess file

chandimac’s picture

For the benefit of folks trying to get cross-browser POST calls working with $.ajax clients:

CORS Header params in .htaccess file

    Header set Access-Control-Allow-Origin "http://client.server.com"
    Header set Access-Control-Allow-Methods "GET, PUT, POST, OPTIONS"
    Header set Access-Control-Allow-Credentials "true"
    Header set Access-Control-Allow-Headers "Authorization, Origin, Content-Type, X-CSRF-Token"

Javascript to create a new article entity. User needs to have 'bypass node access' perm set.

var server = 'restws.server.com';

// get auth token
$.ajax ({
  type: 'GET',
  url: 'http://'+server+'/restws/session/token',
  async: false,
  beforeSend: function (xhr){ 
    // base64 encoded user:pass
  	xhr.setRequestHeader('Authorization', 'Basic cmVzdHdzX2VkaXRvcjplYXN5cGFzcw=='); 
  },
  xhrFields: {
    withCredentials: true
  },
  success: function (data){
    createArticle(data);
  }
});


// create article
function createArticle(authToken) {
    $.ajax ({
		  type: 'POST',
		  url: 'http://'+server+'/node',
		  contentType: 'application/json',
		  processData: false,
		  data: JSON.stringify({"title":"new article","type":"article"}),
		  beforeSend: function (xhr){ 
		  	xhr.setRequestHeader('X-CSRF-Token', authToken); 
		  },
		  xhrFields: {
		    withCredentials: true
		  },
		  success: function (data){
		    console.log(data);
		  },
		  error: function (xhr, ajaxOptions, thrownError){
		    console.log(xhr);
		    console.log(thrownError);
		  }
	});
}
justafish’s picture

I just whipped this up for anyone who wants to configure this within drupal http://drupal.org/project/cors

marcus_w’s picture

Version: 7.x-1.x-dev » 7.x-2.x-dev
Issue summary: View changes
Status: Active » Needs review
FileSize
1.1 KB

When using the cors module #5 mentions, the GET /restws/session/token doesn't work for modern browsers. In a cross domain request an OPTIONS request will be fired first. (see https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Ov...).

As the restws module returns a 403 on the url, the actual GET request is never fired.

I've created a patch which checks for the request method first, then either returns the normal behavior on GET, and a 200 on OPTIONS. You'd still need to use the cors module to set the actual headers. But I think normal CORS functionality is possible with this patch.

Status: Needs review » Needs work

The last submitted patch, 6: restws-support_options_request-1084144-6-7.patch, failed testing.

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 6: restws-support_options_request-1084144-6-7.patch, failed testing.

marcus_w’s picture

I don't know why the test fails, seems to be something wrong with the tests itself, as there are many errors and they don't use the OPTIONS stuff.

marcus_w’s picture

Added support for OPTIONS request on the Entities as well (simply returns)

Cosy’s picture

Just about to try this out..
Got stuck for 6 hours wondering what the hell chrome was doing sending this "OPTIONS" request !

Cosy’s picture

Whilst I'm now getting past the OPTIONS scenario with a 200 -
Im still getting a 403 POSTing to rest/session/token

Has anyone got any clear advice on how to login & grab the token via http in a headless scenario using Basic auth?

Cosy’s picture

Just a thought, but as documented before, marcus_w patch deals with the OPTIONS object being sent..

In my network tab I can see 2 requests being fired,
1) With the options object:

OPTIONS /drupal/restws/session/token HTTP/1.1
Host: blurgh.com
Connection: keep-alive
Access-Control-Request-Method: POST
Access-Control-Request-Headers: accept, authorization, content-type
Accept: */*
Referer: http://127.0.0.1:9000/

Response is a 202:

Cache-Control: no-cache, must-revalidate, post-check=0, pre-check=0
ETag: "1459683937"
Access-Control-Allow-Origin: http://evil.com/
Access-Control-Allow-Credentials: true, http://127.0.0.1:9000
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Content-Type,Authorization

AND THEN A FURTHER POST REQUEST:
which returns the 403
Cache-Control: no-cache, must-revalidate, post-check=0, pre-check=0
etcetc
Is it possible that the OPTIONS request is fired, a token generated & some sort of session; (returns)
then the POST request is fired, but the token is regenerated & the session no longer valid at this point. ?

(Baring in mind i dont fully understand the mechanisms, that implement restws. )

m.stenta’s picture

Great patch @marcus_w! It helped to solve CORS preflight issues for us when creating new entities via POST requests.

However, we are having the same problem with updating existing entities via PUT requests. This is a little trickier to solve, because of the way the restws module alters menu items for entities.

We're creating/updating Log entities, which is an entity type provided by this module: https://drupal.org/project/log - but as far as I can tell this issue would also affect nodes and other core types.

In the case of creating new entities, we send a POST request to /log. But when updating existing entities, we send a PUT request to /log/%. When these requests are sent via Javascript in browsers (testing in Firefox and Chrome), a preflight OPTIONS request is sent to those endpoints before the actual POST or PUT. This preflight request does NOT contain the Drupal session cookie, so it is essentially an anonymous request.

The restws module's hook_menu_alter() adds a new menu router item for /log, with access callback set to TRUE. So when a POST request is sent to /log it passes the access check (because it always passes) and goes directly to the page callback (restws_page_callback). With the patch from #11 above for detecting that it's an OPTIONS request, this works to respond to the preflight request, so the POST request works as expected.

However, in the case of a PUT request sent to /log/%, the preflight OPTIONS request doesn't have a cookie, so the normal entity access callback (log_access in this case) denies access.

I think the only way we could solve this in the restws module would be to ALSO alter the access callback of entity menu items so that it can be checked to see if its an OPTIONS request, and return a 200 response code.

This would be somewhat similar to the new function added in the patch from #11, restws_user_is_logged_in_or_options() which returns TRUE when a user is logged in OR if the request method is OPTIONS.

In this case, though, we would want to return TRUE if the request method is OPTIONS OR delegate to the access callback defined in the original menu item (along with any additional arguments that requires).

m.stenta’s picture

Status: Needs work » Needs review
FileSize
5.92 KB

Attached is a patch that adds a new restws_access_callback(), which is used to override access callbacks of certain menu items, in the same way that the restws module is already overriding page callbacks.

The access callback simply checks to see if it's an OPTIONS request, and grants access if that's the case. Otherwise, it delegates to the original access callback.

m.stenta’s picture

Status: Needs review » Needs work

The patch in #16 is broken, sorry... changing this back to Needs Work.

m.stenta’s picture

Status: Needs work » Needs review
FileSize
5.49 KB

Here is the patch, fixed.

In this version, I also took it one step further and removed restws_user_is_logged_in_or_options() in favor of the new restws_access_callback() function.

I also made a minor change to the way user_is_logged_in() is used within restws_session_token(), to ensure that it returns consistent responses even if the user is not logged in (empty response).

This is working for me right now - I'm able to do both POST and PUT requests.

Ready for review!