Skip to main content

Wedging Basic Authentication support into python-jsonrpc

Problem: 

I want to send JSON-RPC commands to XBMC on my Raspberry Pi. The XBMC install on there will allow JSON-RPC commands from the local box or a remote box, but I've passworded it. The easiest way to talk to the RPC interface through python appears to be using the python-jsonrpc library from here. The problem is that there's no way to send a login and a password.

Investigation:

Here's some simple code that should work (if passwords weren't turned on):

from jsonrpc import ServiceProxy
s = ServiceProxy("http://127.0.0.1:8000/jsonrpc")
print s.GUI.ShowNotification("Hello!","This is a message")

if the library supported login/password combos in urls, I could do this:

from jsonrpc import ServiceProxy
s = ServiceProxy("http://user:password@127.0.0.1:8000/jsonrpc")
print s.GUI.ShowNotification("Hello!","This is a message")

..but it doesn't. If I try it that way the library fails to get the address correctly (probably it's trying to do a DNS search for the string "user:password@127.0.0.1" instead of realising that "127.0.0.1" is the machine to connect to). If I don't put in the login/password combo, it gives me a 401 authentication refused - which is fair enough.

Looking into the library I see that 1) it's a bit old (2007 was the last commit), and 2) it uses urllib in a fairly simple way. A little searching around shows me that urllib doesn't have any way of taking an authentication. Neither, critically, does it have any way of adding headers. The latest version of XBMC requires the header Content-Type: application/json in the request.

The solution appears to be to use urllib2, which has both basic authentication support and allows you to add headers. I can modify the file python-jsonrpc/jsonrpc/proxy.py in the python-jsonrpc download, run setup.py install again, and bob's your uncle!

...not so fast. For some reason the Basic Authentication in urllib2 isn't working for me. I have no idea why, but it isn't. The password managers aren't triggering, so that the request gets made without authentication and fails with a 401 still.

A little googling throws up this solution - ignore the basic auth support in urllib2, and just use the support for adding headers. Basic auth is just a header "Authorization: Basic <HASH>" ...where <HASH> is login:password base64-encoded. Modifications made, result success!

Solution:

Replacement PYTHON-JSONRPC/JSONRPC/PROXY.PY:



"""
  Copyright (c) 2007 Jan-Klaas Kollhof
  Modified 2013 Keith Lawrence

  This file is part of jsonrpc.

  jsonrpc is free software; you can redistribute it and/or modify
  it under the terms of the GNU Lesser General Public License as published by
  the Free Software Foundation; either version 2.1 of the License, or
  (at your option) any later version.

  This software is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public License
  along with this software; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
"""

import urllib2
import base64
from jsonrpc.json import dumps, loads

class JSONRPCException(Exception):
    def __init__(self, rpcError):
        Exception.__init__(self)
        self.error = rpcError
        
class ServiceProxy(object):
    def __init__(self, serviceURL, serviceName=None, serviceUser=None, servicePassword=None):
        self.__serviceURL = serviceURL
        self.__serviceName = serviceName
        self.__serviceUser = serviceUser
        self.__servicePassword = servicePassword
        if serviceUser != None:
                self.__serviceAuth = base64.standard_b64encode('%s:%s' % (serviceUser,servicePassword))

    def __getattr__(self, name):
        if self.__serviceName != None:
            name = "%s.%s" % (self.__serviceName, name)
        return ServiceProxy(self.__serviceURL, name, self.__serviceUser, self.__servicePassword)

    def __call__(self, *args):
         postdata = dumps({'jsonrpc': '2.0', 'method': self.__serviceName, 'params': args, 'id':'jsonrpc'})

# Comment in following two lines to add header tracing
#         opener=urllib2.build_opener(urllib2.HTTPHandler(debuglevel=1))
#         urllib2.install_opener(opener)

         request = urllib2.Request(url=self.__serviceURL)
         request.add_data(postdata)
         request.add_header('Content-Type', 'application/json')
         if self.__serviceAuth != None:
                request.add_header("Authorization", "Basic %s" % self.__serviceAuth)   

         respdata = urllib2.urlopen(request).read()
         resp = loads(respdata)
         if 'error' in resp:
             raise JSONRPCException(resp['error'])
         else:
             return resp['result']
         

Comments

Popular posts from this blog

4Store with Snorql on Raspberry Pi

Problem I need to access triple-store data for a work thing, but the data I have to test with isn't in their (sesame) triple store yet. There are RDF files, though. Solution Install 4Store on a pi (I had one with a default Raspbian running because it's the mumble server). sudo apt-get install 4store ...then I set up the 4store with instructions from here : sudo 4s-backend-setup saws sudo 4s-backend saws 4s-httpd saws then import the RDF files with a convoluted command: curl --verbose --header 'Content-type: application/rdf+xml' --upload-file MSH_Thales_Trans.rdf --url 'http://localhost:8080/data/http%3A%2F%2Fwww.purl.org%2Fsaws%2Fontology%23' (for each file - the url is the saws url encoded, the .rdf bit was done for each file). Then fix the RDF, because rapper rejects it all. To validate the RDF I used this: http://www.rdfabout.com/demo/validator/ Okay, now I can see things on the pi: http:// <pi ip address...

Chrome Extension: iPlayer to XMBC

There's a Chrome extension called Play To XBMC which adds a little button that will send a YouTube, Vimeo, or CollegeHumor video to XBMC - provided you have the YouTube plugin installed. This is a lot more convenient that using XBMC to search directly, if you don't have a keyboard plugged into the XBMC box. The XBMC iPlayer plugin suffers from the same problem that browsing/searching aren't easy without a keyboard, so I wondered if I could make a chrome extension that would do the same for iPlayer. Chrome extensions are packages of javascript, html, and image files that get unpacked by Chrome when they're installed. You make a Manifest file (which is a JSON file) that tells Chrome what icons to include, what sort of package it is, etc. The Play To XBMC extension is a browser one - the button is always there. I made mine page specific - it only appears on valid iPlayer episode pages. You do this by putting in a javascript page that runs in the background every time ...

Mumble and Murmur on Raspberry Pi

Problem:  Skype kind of sucks for games night things. Potential Solution: People have suggested ventrilo, etc., but murmur (the mumble server) will run on a raspberry pi. As I have a few of them, I reimaged an SD card with the newest raspbian, then followed the instructions here: http://www.raspberrypi.org/phpBB3/viewtopic.php?f=36&t=8615 If I put it in the DMZ, hopefully people from outside Nerdvana should be able to connect to it. It supports positional audio for games - I wonder if I could make a plugin that would just allow you to set your position, so that we could be around a virtual table with positional audio? ...Looks like there is: Mumble comes with a plugin for manually positioning audio. http://mumble.sourceforge.net/Games#Manual_Positional_Audio_Plugin