user748650
user748650

Reputation: 21

authorisation REST service in Python

I am in the design as well as at the deciding phase whether to choose python as a primary language to implement the software. The task is to:

Upvotes: 2

Views: 1402

Answers (3)

personal_cloud
personal_cloud

Reputation: 4524

I agree with Constantinius that BaseHTTPServer is a great way to do RESTful in Python. It is pre-installed with Python 2.7 and scales much better than gunicorn/flask/wsgi/gevent for that sort of thing. And supports streaming which you might want down the road.

Below is an example showing how a web browser does a remote POST call to a BaseHTTPServer and gets data back.

JS (put in static/hello.html to serve via Python):

<html><head><meta charset="utf-8"/></head><body>
Hello.

<script>

var xhr = new XMLHttpRequest();
xhr.open("POST", "/postman", true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify({
    value: 'value'
}));
xhr.onload = function() {
  console.log("HELLO")
  console.log(this.responseText);
  var data = JSON.parse(this.responseText);
  console.log(data);
}

</script></body></html>

Python server (for testing):

import time, threading, socket, SocketServer, BaseHTTPServer
import os, traceback, sys, json


log_lock           = threading.Lock()
log_next_thread_id = 0

# Local log functiondef


def Log(module, msg):
    with log_lock:
        thread = threading.current_thread().__name__
        msg    = "%s %s: %s" % (module, thread, msg)
        sys.stderr.write(msg + '\n')

def Log_Traceback():
    t   = traceback.format_exc().strip('\n').split('\n')
    if ', in ' in t[-3]:
        t[-3] = t[-3].replace(', in','\n***\n***  In') + '(...):'
        t[-2] += '\n***'
    err = '\n***  '.join(t[-3:]).replace('"','').replace(' File ', '')
    err = err.replace(', line',':')
    Log("Traceback", '\n'.join(t[:-3]) + '\n\n\n***\n*** ' + err + '\n***\n\n')

    os._exit(4)

def Set_Thread_Label(s):
    global log_next_thread_id
    with log_lock:
        threading.current_thread().__name__ = "%d%s" \
            % (log_next_thread_id, s)
        log_next_thread_id += 1


class Handler(BaseHTTPServer.BaseHTTPRequestHandler):

    def do_GET(self):
        Set_Thread_Label(self.path + "[get]")
        try:
            Log("HTTP", "PATH='%s'" % self.path)
            with open('static' + self.path) as f:
                data = f.read()
            Log("Static", "DATA='%s'" % data)
            self.send_response(200)
            self.send_header("Content-type", "text/html")
            self.end_headers()
            self.wfile.write(data)
        except:
            Log_Traceback()

    def do_POST(self):
        Set_Thread_Label(self.path + "[post]")
        try:
            length = int(self.headers.getheader('content-length'))
            req   = self.rfile.read(length)
            Log("HTTP", "PATH='%s'" % self.path)
            Log("URL", "request data = %s" % req)
            req = json.loads(req)
            response = {'req': req}
            response = json.dumps(response)
            Log("URL", "response data = %s" % response)
            self.send_response(200)
            self.send_header("Content-type", "application/json")
            self.send_header("content-length", str(len(response)))
            self.end_headers()
            self.wfile.write(response)
        except:
            Log_Traceback()


# Create ONE socket.
addr = ('', 8000)
sock = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(addr)
sock.listen(5)

# Launch 10 listener threads.
class Thread(threading.Thread):
    def __init__(self, i):
        threading.Thread.__init__(self)
        self.i = i
        self.daemon = True
        self.start()
    def run(self):
        httpd = BaseHTTPServer.HTTPServer(addr, Handler, False)

        # Prevent the HTTP server from re-binding every handler.
        # https://stackoverflow.com/questions/46210672/
        httpd.socket = sock
        httpd.server_bind = self.server_close = lambda self: None

        httpd.serve_forever()
[Thread(i) for i in range(10)]
time.sleep(9e9)

Console log (chrome):

HELLO
hello.html:14 {"req": {"value": "value"}}
hello.html:16 
{req: {…}}
req
:
{value: "value"}
__proto__
:
Object

Console log (firefox):

GET 
http://XXXXX:8000/hello.html [HTTP/1.0 200 OK 0ms]
POST 
XHR 
http://XXXXX:8000/postman [HTTP/1.0 200 OK 0ms]
HELLO hello.html:13:3
{"req": {"value": "value"}} hello.html:14:3
Object { req: Object }

Console log (Edge):

HTML1300: Navigation occurred.
hello.html
HTML1527: DOCTYPE expected. Consider adding a valid HTML5 doctype: "<!DOCTYPE html>".
hello.html (1,1)
Current window: XXXXX/hello.html
HELLO
hello.html (13,3)
{"req": {"value": "value"}}
hello.html (14,3)
[object Object]
hello.html (16,3)
   {
      [functions]: ,
      __proto__: { },
      req: {
         [functions]: ,
         __proto__: { },
         value: "value"
      }
   }

Python log:

HTTP 8/postman[post]: PATH='/postman'
URL 8/postman[post]: request data = {"value":"value"}
URL 8/postman[post]: response data = {"req": {"value": "value"}}

Also you can easily add SSL by wrapping the socket before passing it to BaseHTTPServer.

Upvotes: 0

David Brossard
David Brossard

Reputation: 13834

Using Python you would want to create a XACML request that would include your user's id (you would get that from the authentication phase that typically takes place before you authenticate) and add information about the WS the user is targetting. It could be the URI, the HTTP method too...

In the end you may get sthg like:

<?xml version="1.0" encoding="UTF-8"?><xacml-ctx:Request xmlns:xacml-ctx="urn:oasis:names:tc:xacml:2.0:context:schema:os">
   <xacml-ctx:Subject SubjectCategory="urn:oasis:names:tc:xacml:1.0:subject-category:access-subject">
      <xacml-ctx:Attribute AttributeId="urn:oasis:names:tc:xacml:1.0:subject:subject-id" DataType="http://www.w3.org/2001/XMLSchema#string">
         <xacml-ctx:AttributeValue>Alice</xacml-ctx:AttributeValue>
      </xacml-ctx:Attribute>
   </xacml-ctx:Subject>
   <xacml-ctx:Resource>
      <xacml-ctx:Attribute AttributeId="urn:oasis:names:tc:xacml:1.0:resource:resource-id" DataType="http://www.w3.org/2001/XMLSchema#string">
         <xacml-ctx:AttributeValue>/someuri/myapi/target.py</xacml-ctx:AttributeValue>
      </xacml-ctx:Attribute>
   </xacml-ctx:Resource>
   <xacml-ctx:Action>
      <xacml-ctx:Attribute AttributeId="urn:oasis:names:tc:xacml:1.0:action:action-id" DataType="http://www.w3.org/2001/XMLSchema#string">
         <xacml-ctx:AttributeValue>GET</xacml-ctx:AttributeValue>
      </xacml-ctx:Attribute>
   </xacml-ctx:Action>
   <xacml-ctx:Environment>
   </xacml-ctx:Environment>
</xacml-ctx:Request>

You need to construct the request using Python and lxml for instance.

The response will look like

<xacml-ctx:Response xmlns:xacml-ctx="urn:oasis:names:tc:xacml:2.0:context:schema:os">
  <xacml-ctx:Result>
    <xacml-ctx:Decision>Permit</xacml-ctx:Decision>
    <xacml-ctx:Status>
      <xacml-ctx:StatusCode Value="urn:oasis:names:tc:xacml:1.0:status:ok"/>
    </xacml-ctx:Status>
  </xacml-ctx:Result>
</xacml-ctx:Response>

So again you need to parse the XML to extract the decision e.g. Permit. I wrote a basic REST-like interface to a XACML PDP where all you have to do is send an HTTP GET to a URI passing variables as GET variables e.g. http://www.xacml.eu/AuthZ/?a=alice&b=/someuri/myapi/target.py&c=GET

Does that help?

Upvotes: 0

Constantinius
Constantinius

Reputation: 35069

If your question was what libraries you can use to implement these RESTful services, then have a look into the BaseHTTPServer module of the standard python library.

The following code shows how easy it is to implement a simple server accepting GET requests:

class MyHandler(BaseHTTPRequestHandler):

    def do_GET(self):
        try:
            f = open(curdir + sep + self.path) #self.path has /test.html
            self.send_response(200)
            self.send_header('Content-type',    'text/html')
            self.end_headers()
            self.wfile.write(f.read())
            f.close()
        except IOError:
            self.send_error(404,'File Not Found: %s' % self.path)

def main():
    try:
        server = HTTPServer(('', 80), MyHandler)
        print 'Welcome to the machine...'
        server.serve_forever()
    except KeyboardInterrupt:
        print '^C received, shutting down server'
        server.socket.close()

if __name__ == '__main__':
    main()

Of course, the code is not from myself, I found it here.

Upvotes: 2

Related Questions