Decorating Django views that require Google Authorization with python decorators

In a project that I am currently involved, we needed to authenticate with Google Analytics in order to query statistics. As you may know currently Google supports three authentication mechanisms to allow other applications to authenticate on someone’s behalf.

  • ClientLogin: You give your username, password and the application does the rest for you. I don’t think there are still people out there who can give away their passwords to applications to be used on their behalf.
  • AuthSub: Google’s proprietary authorization API, just available as an alternative to OAuth.
  • OAuth: Provides authorization for all Google APIs and Google also suggests to migrate application that uses to OAuth.

As can be seen from the list that there are not so many logical alternatives, the suggested way is to use OAuth which is also an open standard. After deciding which authorization API to use, I used some time to investigate the usage and find some practical examples. However I could not find anything that fits to my needs completely.

  • I found one but that was using gdata v1.
  • I found another one but it was not using Django.
  • I found one that uses Django but I did not like the way it is designed.

Anyway, I decided to write my own. I spent some time thinking the best way to implement something that is pluggable and does not do stupid redirects between views and I decided to write a decorator that can be applied to Django views. I mainly choose to do so because of the following reasons, please comment on it if you think my reasoning is not right or there is a better way to do it.

  • It is declarative, means; it is pluggable
  • It is clean, it does not pollutes the actual view code
  • In place configuration, you can also accompany the configuration while applying it

So what I did was;

  • Creating a decorator to do the authorization
  • Creating a helper containing functions to load/save received tokens
  • Applying the decorators wherever necessary

The following is the source of the decorator.

  • It checks if there is an access token matching the provided settings
    • If so return the original view function
    • If not check if there is a matching request token
      • If so upgrade the request token to an access token and return the original view function
      • If not get a request token, redirect to the generated authorization url while setting the callback url to the url of the about to be executed view

What is important is that, after a successful authorization the user will return to this view again since we are setting the callback url accordingly before redirecting the user to Google.

import logging
try:
    from functools import wraps
except ImportError:
    from django.utils.functional import wraps

from django.utils.decorators import available_attrs

import gdata

from helpers import *

logger = logging.getLogger(__name__)

def secure_with_gauth(view_func=None, token_prefix='default', scopes=['https://docs.google.com/feeds/'],
                      consumer_key='anonymous', consumer_secret='anonymous', consumer_source = 'www.foo.com',
                      error_callback=None):
    """
    Wraps the actual view method and makes authorization for google services according to the paremters provided.
    """
    def decorator(view_func):

        @wraps(view_func, assigned=available_attrs(view_func))

        def _wrapped_view(request, *args, **kwargs):

            from django.shortcuts import redirect
            access_token = load_token(token_prefix, True)
            saved_request_token = load_token(token_prefix, False)

            if not isinstance(access_token, gdata.gauth.OAuthHmacToken):
                logger.info("Access token does not exists.")

                if isinstance(saved_request_token, gdata.gauth.OAuthHmacToken):
                    client = gdata.client.GDClient(source=consumer_source)

                    try:
                        request_token = gdata.gauth.AuthorizeRequestToken(saved_request_token,
                                                                          request.build_absolute_uri())
                        access_token = client.GetAccessToken(request_token)
                        save_token(access_token, token_prefix, True)
                    except gdata.client.RequestError:
                        if error_callback:
                            remove_token(token_prefix, False)
                            return error_callback(request, "Error while upgrading request token to authorization \
                            token, please try again later.")

                    logger.info("Successfully retrieved access token, returning the view.")

                    return view_func(request, *args, **kwargs)
                else:
                    logger.info("Request token does not exist")

                    client = gdata.client.GDClient(source=consumer_source)

                    try:
                        request_token = client.GetOAuthToken(scopes, request.build_absolute_uri(), consumer_key,
                                                             consumer_secret)
                        save_token(request_token, token_prefix, False)
                    except gdata.client.RequestError:
                        if error_callback:
                            return error_callback(request, "Error while getting request token, please try again later.")

                    logger.info("Successfully retrieved request token, redirecting to authorization url.")

                    return redirect(request_token.generate_authorization_url().__str__())

            return view_func(request, *args, **kwargs)

        return _wrapped_view

    return decorator

Applying the decorator is also very straight forward. See the following usage within views.py.

@secure_with_gauth(token_prefix=TOKEN_PREFIX, scopes=['https://www.google.com/analytics/feeds'],
                   consumer_source=CONSUMER_SOURCE, consumer_key=CONSUMER_KEY, consumer_secret=CONSUMER_SECRET,
                   error_callback=generate_error)
def google_analytics(request, type):

I hope it helps.

Read More Post

STAY CONNECTED

Most Popular

Manifesting Your Dream Career: Finding Your Passion And Purpose
01 Aug

Manifesting Your Dream Career: Finding Your Passion And Purpose

Are you passionate about finding your dream career? Manifesting can help you align your career with your passions and purpose.

Revolutionizing Your Ride: Expert Auto Service At Carolina Auto Service
27 Feb

Revolutionizing Your Ride: Expert Auto Service At Carolina Auto Service

Finding a reliable auto service provider is paramount in the bustling world of vehicle maintenance and repair. That's where Carolina

Floratam: The Robust Grass Solution From Council Growers SOD
25 Jan

Floratam: The Robust Grass Solution From Council Growers SOD

Floratam grass, a popular St. Augustine variety, has become a go-to choice for homeowners and landscapers seeking a lush, durable

Carolina Mobile Autoservice: Your Go-To For Expert Brake Service Repair
25 Jan

Carolina Mobile Autoservice: Your Go-To For Expert Brake Service Repair

For vehicle owners, the braking system is among the most critical components for safety and performance. Carolina Mobile Autoservice, a

Safeguarding Your Property: Expert Water Damage Services By MBC Capital
24 Jan

Safeguarding Your Property: Expert Water Damage Services By MBC Capital

Water damage can significantly impact homes and businesses, often leaving a trail of destruction and disruption in its wake. Understanding

Revanesse Lip Filler: The Secret To A Perfect Pout – Discover America’s New Favorite At Beauty From Ashes Aesthetics
19 Jan

Revanesse Lip Filler: The Secret To A Perfect Pout – Discover America’s New Favorite At Beauty From Ashes Aesthetics

In the ever-evolving world of cosmetic enhancements, there's a constant quest for the next big thing. The latest darling in