Skip to content

A partially functional OAuth2 adventure with Python

Last updated on 2019-12-23

Python’s functional aspects

Python [is] multi-paradigm; you can write programs or libraries that are largely procedural, object-oriented, or functional.

Source: https://docs.python.org/howto/functional.html#introduction

Simplicity, ease-of-use and flexibility are hallmarks of the Python programming language. Its functional constructs, such as iterators and generators, list comprehension, built-in functions like map, and functools, are among its most useful features.

While this post is only about functools.partial(), I encourage you to read through the Functional Programming HOWTO document, as it contains a treasure trove of highly useful tools that Python provides.

The function of functools.partial() ;D

Consider a Python function f(a, b, c); you may wish to create a new function g(b, c) that’s equivalent to f(1, b, c); you’re filling in a value for one of f()’s parameters. This is called “partial function application”.

Source: https://docs.python.org/howto/functional.html#the-functools-module

And this is exactly what functools.partial() does. It takes a function as its first argument, and then any number of positional and keyword arguments which you can use to configure the partial function application.

Let’s take the explanation above, of f(a, b, c), and put it in terms of Python code.

Now, using functools.partial(), let’s construct a function, g(b, c), that essentially calls f(1, b, c):

That’s it! Now, for example, calling g(3, 2) is equivalent to calling f(1, 3, 2):

More examples

What if you wanted to set the value of a as well as b?

What if you wanted to set the value of c alone? Two options come readily to mind. Either:

  • Move c to the first positional argument:
  • Or, make c a keyword argument:

Application: single-reply web server

This seems weird; a web server that spins up, replies to a single request, and shuts down?

That’s right. It’s a contrived scenario, too, so bear with me while we glance over:

  1. I wanted to build a CLI application that would make use of an OAuth2-enabled API.
  2. The Authorization Server in question did not support what would’ve been the appropriate mechanism: device flow.
  3. Now I’m forced to use the authorization code flow, which is meant for actual web servers with multiple users.
  4. Since this is a CLI program with just me using it, I only need to listen for the authorization code delivery one time (see where this is going?). I’m referring to step 5 in this diagram.

Handling requests

In order to handle the authorization code delivery (it will come to me as a GET request from the Authorization Server), I subclassed BaseHTTPRequestHandler as such:

callback_handler.py

1
from http         import HTTPStatus
2
from http.server  import BaseHTTPRequestHandler
3
from urllib.parse import urlparse, parse_qs
4
5
6
class CallbackHandler(BaseHTTPRequestHandler):
7
    def __init__(self, result, *args, **kwargs):
8
        # This dictionary will be used to extract the authorization code
9
        # from the request parameters (query string).
10
        self.result = result if type(result) == dict else dict()
11
        super().__init__(*args, **kwargs)
12
13
    def do_GET(self):
14
        self.handle_callback()
15
        self.send_response_only(HTTPStatus.ACCEPTED)
16
        self.end_headers()
17
        self.wfile.write(HTTPStatus.ACCEPTED.description.encode())
18
19
    def handle_callback(self):
20
        query = urlparse(self.path).query
21
        self.result.update(parse_qs(query))

Why subclass?

Firstly, because that’s how you use BaseHTTPRequestHandler. By itself, it doesn’t know how to handle requests. You must implement your own do_*() methods for each HTTP method you need to support. In my case, I just need do_GET(), for GET requests.

Secondly, because I need to extract information from the request parameters; however, I won’t be able to use the class instance to do that, and so I’ve added a positional argument, result, which is a dictionary because if I pass in my own dictionary, its values will be updated in-place, effectively extracting the information I need into a variable that the program can use to finish the OAuth2 transaction.

In case you missed it above, handle_callback() is what updates the dictionary with the contents of the request’s query string:

Why not just access class variables?

An HTTPServer is built with a handler class (not instance). For example:

HTTPServer((host, port), CallbackHandler)

As you can see, I don’t instantiate the handler class myself; furthermore, after the handler class gets instantiated during the construction of HTTPServer, the HTTPServer instance doesn’t expose easy access to its own instance of that handler class.

So, by passing in my own dictionary into my handler class, CallbackHandler, I can update that dictionary with the necessary information and later access it without directly accessing the CallbackHandler instance.

Enter functools.partial

I just said I won’t be instantiating the handler class myself, right? So how can I pass any arguments to CallbackHandler?

Turns out that functools.partial accepts not just functions, but callables! I can give it my handler class with predefined arguments!

Let’s see it at work (note that callback_handler below is this file):

In a real OAuth2 authorization code flow, the query string parameters will contain the authorization code needed to obtain, from the Authorization Server, an access token with which to finally contact the API.

Moral of the story: please add device flow support to your Authorization Servers. =)

Published inUncategorized