Skip to content

Commit

Permalink
Merge pull request #4 from mwarkentin/token-auth
Browse files Browse the repository at this point in the history
Token based authentication for endpoint
  • Loading branch information
mwarkentin committed Feb 9, 2014
2 parents b4dd2d6 + 52f200e commit 7502713
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 7 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ clean-pyc:
find . -name '*~' -exec rm -f {} +

lint:
flake8 watchman tests
flake8 watchman tests --ignore=E501

test:
python runtests.py test
Expand Down
24 changes: 19 additions & 5 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,39 @@ The full documentation is at http://django-watchman.rtfd.org.
Quickstart
----------

1. Install django-watchman::
1. Install ``django-watchman``::

pip install django-watchman

2. Add "watchman" to your INSTALLED_APPS setting like this::
2. Add ``watchman`` to your ``INSTALLED_APPS`` setting like this::

INSTALLED_APPS = (
...
'watchman',
)

3. Include the watchman URLconf in your project urls.py like this::
3. Include the watchman URLconf in your project ``urls.py`` like this::

url(r'^watchman/', include('watchman.urls')),

4. Start the development server and visit http://127.0.0.1:8000/watchman/ to
4. Start the development server and visit ``http://127.0.0.1:8000/watchman/`` to
get a JSON response of your backing service statuses

Features
--------

* TODO
Token based authentication
**************************

If you want to protect the status endpoint, you can add a ``WATCHMAN_TOKEN`` to
your settings. When this setting is added, you must pass that value in as the
``watchman-token`` **GET** parameter::

GET http://127.0.0.1:8000/watchman/?watchman-token=:token

If you want to change the token name, you can set the ``WATCHMAN_TOKEN_NAME``.
The value of this setting will be the **GET** parameter that you must pass in::

WATCHMAN_TOKEN_NAME = 'custom-token-name'

GET http://127.0.0.1:8000/watchman/?custom-token-name=:token
60 changes: 60 additions & 0 deletions tests/test_decorators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
test_django-watchman
------------
Tests for `django-watchman` decorators module.
"""

from __future__ import unicode_literals

import unittest

from watchman import settings as watchman_settings
from django.core.urlresolvers import reverse
from django.test.client import Client


class TestWatchman(unittest.TestCase):

def setUp(self):
self.client = Client()
watchman_settings.WATCHMAN_TOKEN = 'foo'

def test_200_ok_if_no_token_set(self):
watchman_settings.WATCHMAN_TOKEN = None
response = self.client.get(reverse('watchman.views.status'))
self.assertEqual(response.status_code, 200)
watchman_settings.WATCHMAN_TOKEN = 'foo'

def test_200_ok_if_tokens_match(self):
data = {
'watchman-token': 'foo',
}
response = self.client.get(reverse('watchman.views.status'), data)
self.assertEqual(response.status_code, 200)

def test_required_token_param_can_be_renamed(self):
watchman_settings.WATCHMAN_TOKEN_NAME = 'custom-token'
data = {
'custom-token': 'foo',
}
response = self.client.get(reverse('watchman.views.status'), data)
self.assertEqual(response.status_code, 200)
watchman_settings.WATCHMAN_TOKEN_NAME = 'watchman-token'

def test_403_raised_if_missing_token(self):
response = self.client.get(reverse('watchman.views.status'))
self.assertEqual(response.status_code, 403)

def test_403_raised_if_invalid_token(self):
data = {
'watchman-token': 'bar',
}
response = self.client.get(reverse('watchman.views.status'), data)
self.assertEqual(response.status_code, 403)

def tearDown(self):
watchman_settings.WATCHMAN_TOKEN = None
34 changes: 34 additions & 0 deletions watchman/decorators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from django.http import HttpResponseForbidden
from django.views.decorators.csrf import csrf_exempt

from functools import wraps

from watchman import settings


def token_required(view_func):
"""
Decorator which ensures that WATCHMAN_TOKEN is provided if set.
WATCHMAN_TOKEN_NAME can also be set if the token GET parameter must be
customized.
"""

def _validate_token(request):
watchman_token = settings.WATCHMAN_TOKEN
if watchman_token is None:
return True

watchman_token_name = settings.WATCHMAN_TOKEN_NAME
return watchman_token == request.GET.get(watchman_token_name)

@csrf_exempt
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
if _validate_token(request):
return view_func(request, *args, **kwargs)

return HttpResponseForbidden()

return _wrapped_view
5 changes: 5 additions & 0 deletions watchman/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.conf import settings


WATCHMAN_TOKEN = getattr(settings, 'WATCHMAN_TOKEN', None)
WATCHMAN_TOKEN_NAME = getattr(settings, 'WATCHMAN_TOKEN_NAME', 'watchman-token')
3 changes: 2 additions & 1 deletion watchman/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from django.conf.urls import patterns, url

urlpatterns = patterns('',
urlpatterns = patterns(
'',
url(r'^$', 'watchman.views.status', name="status"),
)
2 changes: 2 additions & 0 deletions watchman/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
from jsonview.decorators import json_view

from watchman.checks import check_caches, check_databases
from watchman.decorators import token_required


@token_required
@json_view
def status(request):
response = {
Expand Down

0 comments on commit 7502713

Please sign in to comment.