idlesign / deluge-webapi

Plugin for Deluge WebUI providing sane JSON API
https://github.com/idlesign/deluge-webapi
BSD 3-Clause "New" or "Revised" License
120 stars 27 forks source link

No 'Access-Control-Allow-Origin' header is present on the requested resource. #4

Closed jamesmehorter closed 6 years ago

jamesmehorter commented 8 years ago

Hello,

When I use Postman to POST to the webapi everything works perfectly. I can send requests and receive responses. Now I'm attempting to write JavaScript to interact with my deluged server using AJAX while I'm browsing the web (using Chrome). However, when I POST to the server with JavaScript (in my browser) I receive the following error:

XMLHttpRequest cannot load http://192.168.0.132:8112/json. 
Response to preflight request doesn't pass access control check: 
No 'Access-Control-Allow-Origin' header is present on the requested resource. 
Origin 'null' is therefore not allowed access. The response had HTTP status code 405.

For reference, the JavaScript I'm using is below.

This error seems to be very common when performing CORS requests like this in a web browser. And the simple workaround is for the server to send the following header with each response: Access-Control-Allow-Origin: *

It also appears that the web-ui uses the Twisted HTTP server, and I was able to locate this post about Twisted and CORS: https://msoulier.wordpress.com/2010/06/05/cross-origin-requests-in-twisted/

Is there any possibility that you could add this header to the webapi responses? Or do you have any suggestions? I'd love to query this webapi from within a browser. Thanks!!

JavaScript I'm using..

// Create the XMLHttp object
xmlhttp = new XMLHttpRequest();

// Handle the response we'll receive from the server
xmlhttp.onload = function() {
    if ( 200 === xmlhttp.status ) {

        // Here we'll receive the auth cookie

        // Now that we're authenticated let's make some more requests.. 
    }
};

// Open the URL to the server
xmlhttp.open( 'POST', 'http://192.168.0.132:8112/json', true );
xmlhttp.setRequestHeader( 'Content-Type', 'application/json' );
xmlhttp.setRequestHeader( 'Accept', 'application/json' );

// Send the POST authentication request 
xmlhttp.send( JSON.stringify( {
    'id': 1,
    'method': 'auth.login',
    'params': ['my-password']
} ) );
idlesign commented 8 years ago

Hi,

Thank you for this report. The thing is webapi doesn't deal with Twisted response objects directly. The other thing is that Allow-Origin set to * sounds to me like a security issue.

So we probably need to work out a possible solution the with that in mind. Nowadays I have but a little time to attend to the project, and so can't say for sure when this one will be resolved.

jamesmehorter commented 8 years ago

Hi Igor, thanks for your response!

Hmmm, is webapi (and deluge plugins in general) capable of adding/adjusting the Twisted headers?

I do agree, in many cases adding these headers could allow cross site forgery requests. Though, because webapi implements sessions/cookie authentication I think we'd be safe. Maybe it could be enabled via a setting, and set to disabled by default? That way folks like myself could enable CORS for the webapi. I also feel like as a rule of thumb any api should allow requests from any source—i.e. as it stands I can only POST to webapi (from within a browser) while on localhost, which is quite restrictive. Also, because the webui is accessible from any external browser (visiting http://my-deluge-server:8112 [if port forwarding is enabled on your router]) would you agree that it seems logical for webapi to allow that same level of access?

A little background.. I'm running deluge on a server in my home local network, and plan to use this JavaScript to add torrents via a bookmarklet on other computers also in my home network. My deluge server is behind my router (I am not using port forwarding either). In my case the CORS error is preventing machines with the same 192.168.0.* ip range from POSTing to the webapi.

Lastly, I've read a little more on this and believe the 'Access-Control-Allow-Credentials: true' would also need to be set in order to send/receive cookies from CORS requests. (And likewise XHR requests from JavaScript like I'm doing would need to include the 'withCredentials' header.)

Thanks Igor!!!

furiousbal commented 8 years ago

I too would like to be able to query the webapi from within a browser. Very much looking forward to a solution.

Thanks!

idlesign commented 8 years ago

Implementation hint: could possibly be achieved with subclassing deluge.ui.web.json_api.JSON and overriding _send_response with request.setHeader('Access-Control-Allow-Origin', '*'), and using a subclass of WebPluginBase where in __init__ subclassed JSON is used. Probably some option is required to fine tune that * so access is restricted to trusted hosts.

BrandonGillis commented 6 years ago

I needed that feature for a project related to deluge json rpc API. Here's a fast implementation of it, without any config editor inside deluge at the moment.

webapi/core.py

import logging

from deluge import component
from deluge.plugins.pluginbase import CorePluginBase
from deluge.ui.web.json_api import JSON
from twisted.web import http, server

LOGGER = logging.getLogger(__name__)

class Core(CorePluginBase):

    def enable(self):
        """"""
        LOGGER.info('Enabling WebAPI plugin CORE ...')
        self.patch_web_ui()

    def disable(self):
        """"""
        LOGGER.info('Disabling WebAPI plugin CORE ...')

    def update(self):
        """"""

    def patch_web_ui(self):
        LOGGER.info('Patching webui for CORS...')
        JSON_instance = component.get('JSON')
        self.old_render = JSON_instance.render
        self.old_send_request = JSON_instance._send_response
        JSON_instance.render = self.render_patch
        JSON_instance._send_response = self._send_response_patch

    def render_patch(self, request):
        LOGGER.debug("json-CORS PROXY")
        if request.method == "OPTIONS":
            request.setResponseCode(http.OK)
             # should use config for allowed origin
            request.setHeader('Access-Control-Allow-Origin', 'http://lvh.me:3000')
            request.setHeader('Access-Control-Allow-Headers', 'content-type')
            request.setHeader('Access-Control-Allow-Methods', 'POST')
            request.setHeader('Access-Control-Allow-Credentials', 'true')
            request.write('')
            request.finish()
            return server.NOT_DONE_YET
        return self.old_render(request)

    def _send_response_patch(self, request, response):
        if request._disconnected:
            return ''
        # should use config for allowed origin
        request.setHeader('Access-Control-Allow-Origin', 'http://lvh.me:3000')
        request.setHeader('Access-Control-Allow-Credentials', 'true')
        return self.old_send_request(request, response)

By the way this is a monkey-patch that override the existing render method of JSON class from webui plugins. webui method patched : def render(self, request):

idlesign commented 6 years ago

This seems to be resolved in 0.3.0 by Brandon in #12 This version is available on GitHub, I'll upload PyPI dist eventually.