Skip to content

Commit

Permalink
Merge pull request #14 from johngian/oidc-step-1
Browse files Browse the repository at this point in the history
OIDC authorization request and callback handler
  • Loading branch information
johngian committed Oct 26, 2016
2 parents ae4a38a + b2eda03 commit 77b3445
Show file tree
Hide file tree
Showing 5 changed files with 271 additions and 4 deletions.
21 changes: 18 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,30 @@ python:
- "3.3"
- "2.7"

env:
- DJANGO="Django<1.9"
- DJANGO="Django<1.10"
- DJANGO="Django<1.11"
- DJANGO="Django<1.12"

matrix:
exclude:
- python: "3.3"
env: DJANGO="Django<1.10"
- python: "3.3"
env: DJANGO="Django<1.11"
- python: "3.3"
env: DJANGO="Django<1.12"

before_install:
- pip install codecov
- pip install flake8
- flake8 .

# command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors
install: pip install -r requirements/requirements_test.txt
install:
- pip install -r requirements/requirements_test.txt
- pip install --upgrade "$DJANGO"

# command to run tests using coverage, e.g. python setup.py test
script: coverage run --source mozilla_django_oidc runtests.py

after_success:
Expand Down
10 changes: 10 additions & 0 deletions mozilla_django_oidc/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django.conf.urls import url

from mozilla_django_oidc import views

urlpatterns = [
url(r'^oidc/authorization_callback/$', views.OIDCAuthorizationCallbackView.as_view(),
name='oidc_authorization_callback'),
url(r'^oidc/authorization_init/$', views.OIDCAuthorizationRequestView.as_view(),
name='oidc_authorization_init'),
]
71 changes: 71 additions & 0 deletions mozilla_django_oidc/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
try:
from urllib import urlencode
except ImportError:
from urllib.parse import urlencode

from django.core.urlresolvers import reverse
from django.contrib import auth
from django.http import HttpResponseRedirect
from django.views.generic import View

from mozilla_django_oidc.utils import import_from_settings


class OIDCAuthorizationCallbackView(View):
"""OIDC client authentication callback HTTP endpoint"""

http_method_names = ['post']

@property
def failure_url(self):
return import_from_settings('LOGIN_REDIRECT_URL_FAILURE', '/')

@property
def success_url(self):
return import_from_settings('LOGIN_REDIRECT_URL', '/')

def login_failure(self):
return HttpResponseRedirect(self.failure_url)

def login_success(self):
auth.login(self.request, self.user)
return HttpResponseRedirect(self.success_url)

def post(self, request):
"""Callback handler for OIDC authorization code flow"""

if 'code' in request.POST and 'state' in request.POST:
kwargs = {
'code': request.POST['code'],
'state': request.POST['state']
}
self.user = auth.authenticate(**kwargs)

if self.user and self.user.is_active:
return self.login_success()
return self.login_failure()


class OIDCAuthorizationRequestView(View):
"""OIDC client authentication HTTP endpoint"""

http_method_names = ['get']

def __init__(self, *args, **kwargs):
super(OIDCAuthorizationRequestView, self).__init__(*args, **kwargs)

self.OIDC_OP_AUTH_ENDPOINT = import_from_settings('OIDC_OP_AUTHORIZATION_ENDPOINT')
self.OIDC_OP_CLIENT_ID = import_from_settings('OIDC_OP_CLIENT_ID')

def get(self, request):
"""OIDC client authentication initialization HTTP endpoint"""
params = {
'response_type': 'code',
'scope': 'openid',
'client_id': self.OIDC_OP_CLIENT_ID,
'redirect_uri': reverse('oidc_authorization_callback')
}

query = urlencode(params)
redirect_url = '{url}?{query}'.format(url=self.OIDC_OP_AUTH_ENDPOINT, query=query)
return HttpResponseRedirect(redirect_url)
46 changes: 45 additions & 1 deletion runtests.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,52 @@
import sys

try:
from django.conf import settings
from django.test.utils import get_runner

settings.configure(
DEBUG=True,
USE_TZ=True,
DATABASES={
'default': {
'ENGINE': 'django.db.backends.sqlite3',
}
},
ROOT_URLCONF='mozilla_django_oidc.urls',
INSTALLED_APPS=[
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sites',
'mozilla_django_oidc',
],
SITE_ID=1,
MIDDLEWARE_CLASSES=(),
)

try:
import django
setup = django.setup
except AttributeError:
pass
else:
setup()

except ImportError:
import traceback
traceback.print_exc()
msg = 'To fix this error, run: pip install -r requirements/requirements_test.txt'
raise ImportError(msg)


def run_tests(*test_args):
pass
if not test_args:
test_args = ['tests']

test_runner = get_runner(settings)()
failures = test_runner.run_tests(test_args)

if failures:
sys.exit(bool(failures))


if __name__ == '__main__':
Expand Down
127 changes: 127 additions & 0 deletions tests/test_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
try:
from urlparse import parse_qs, urlparse
except ImportError:
from urllib.parse import parse_qs, urlparse

from mock import patch

from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse
from django.test import RequestFactory, TestCase, override_settings

from mozilla_django_oidc import views


User = get_user_model()


class OIDCAuthorizationCallbackViewTestCase(TestCase):
def setUp(self):
self.factory = RequestFactory()

@override_settings(LOGIN_REDIRECT_URL='/success')
def test_post_auth_success(self):
user = User.objects.create_user('example_username')
user.is_active = True
user.save()

post_data = {
'code': 'example_code',
'state': 'example_state'
}
url = reverse('oidc_authorization_callback')
request = self.factory.post(url, post_data)
callback_view = views.OIDCAuthorizationCallbackView.as_view()

with patch('mozilla_django_oidc.views.auth.authenticate') as mock_auth:
with patch('mozilla_django_oidc.views.auth.login') as mock_login:
mock_auth.return_value = user
response = callback_view(request)

mock_auth.assert_called_once_with(code='example_code', state='example_state')
mock_login.assert_called_once_with(request, user)

self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, '/success')

@override_settings(LOGIN_REDIRECT_URL_FAILURE='/failure')
def test_post_auth_failure_nonexisting_user(self):
post_data = {
'code': 'example_code',
'state': 'example_state'
}

url = reverse('oidc_authorization_callback')
request = self.factory.post(url, post_data)
callback_view = views.OIDCAuthorizationCallbackView.as_view()

with patch('mozilla_django_oidc.views.auth.authenticate') as mock_auth:
mock_auth.return_value = None
response = callback_view(request)

mock_auth.assert_called_once_with(code='example_code', state='example_state')

self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, '/failure')

@override_settings(LOGIN_REDIRECT_URL_FAILURE='/failure')
def test_post_auth_failure_inactive_user(self):
user = User.objects.create_user('example_username')
user.is_active = False
user.save()

post_data = {
'code': 'example_code',
'state': 'example_state'
}

url = reverse('oidc_authorization_callback')
request = self.factory.post(url, post_data)
callback_view = views.OIDCAuthorizationCallbackView.as_view()

with patch('mozilla_django_oidc.views.auth.authenticate') as mock_auth:
mock_auth.return_value = user
response = callback_view(request)

mock_auth.assert_called_once_with(code='example_code', state='example_state')

self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, '/failure')

@override_settings(LOGIN_REDIRECT_URL_FAILURE='/failure')
def test_post_auth_dirty_data(self):
post_data = {
'foo': 'bar',
}

url = reverse('oidc_authorization_callback')
request = self.factory.post(url, post_data)
callback_view = views.OIDCAuthorizationCallbackView.as_view()
response = callback_view(request)
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, '/failure')


class OIDCAuthorizationRequestViewTestCase(TestCase):
def setUp(self):
self.factory = RequestFactory()

@override_settings(OIDC_OP_AUTHORIZATION_ENDPOINT='https://server.example.com/auth')
@override_settings(OIDC_OP_CLIENT_ID='example_id')
def test_get(self):
url = reverse('oidc_authorization_init')
request = self.factory.get(url)
login_view = views.OIDCAuthorizationRequestView.as_view()
response = login_view(request)
self.assertEqual(response.status_code, 302)

o = urlparse(response.url)
expected_query = {
'response_type': ['code'],
'scope': ['openid'],
'client_id': ['example_id'],
'redirect_uri': ['/oidc/authorization_callback/']
}
self.assertDictEqual(parse_qs(o.query), expected_query)
self.assertEqual(o.hostname, 'server.example.com')
self.assertEqual(o.path, '/auth')

0 comments on commit 77b3445

Please sign in to comment.