Skip to content

Commit

Permalink
Merge pull request #221 from tpazderka/allow_namespaced_urls
Browse files Browse the repository at this point in the history
Allow namespaced authentication_callback_url
  • Loading branch information
johngian committed Apr 19, 2018
2 parents 68a6d67 + f8f9f55 commit feab268
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 2 deletions.
2 changes: 2 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ History
0.7.0 (unreleased)
++++++++++++++++++

* Add OIDC_AUTHENTICATION_CALLBACK_URL as a new configuration parameter

Backwards-incompatible changes:

* ``OIDC_OP_LOGOUT_URL_METHOD`` takes a ``request`` parameter now.
Expand Down
11 changes: 11 additions & 0 deletions docs/settings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,14 @@ of ``mozilla-django-oidc``.

.. versionchanged:: 0.7.0
The function must now take a ``request`` parameter.

.. py:attribute:: OIDC_AUTHENTICATION_CALLBACK_URL
:default: ``oidc_authentication_callback``

URL pattern name for ``OIDCAuthenticationCallbackView``. Will be passed to ``reverse``.
The pattern can also include namespace in order to resolve included urls.

.. seealso::

https://docs.djangoproject.com/en/2.0/topics/http/urls/#url-namespaces
5 changes: 4 additions & 1 deletion mozilla_django_oidc/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,14 +159,17 @@ def authenticate(self, **kwargs):
if not code or not state:
raise SuspiciousOperation('Code or state not found.')

reverse_url = import_from_settings('OIDC_AUTHENTICATION_CALLBACK_URL',
'oidc_authentication_callback')

token_payload = {
'client_id': self.OIDC_RP_CLIENT_ID,
'client_secret': self.OIDC_RP_CLIENT_SECRET,
'grant_type': 'authorization_code',
'code': code,
'redirect_uri': absolutify(
self.request,
reverse('oidc_authentication_callback')
reverse(reverse_url)
),
}

Expand Down
4 changes: 3 additions & 1 deletion mozilla_django_oidc/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,14 +134,16 @@ def get(self, request):
"""OIDC client authentication initialization HTTP endpoint"""
state = get_random_string(import_from_settings('OIDC_STATE_SIZE', 32))
redirect_field_name = import_from_settings('OIDC_REDIRECT_FIELD_NAME', 'next')
reverse_url = import_from_settings('OIDC_AUTHENTICATION_CALLBACK_URL',
'oidc_authentication_callback')

params = {
'response_type': 'code',
'scope': import_from_settings('OIDC_RP_SCOPES', 'openid email'),
'client_id': self.OIDC_RP_CLIENT_ID,
'redirect_uri': absolutify(
request,
reverse('oidc_authentication_callback')
reverse(reverse_url)
),
'state': state,
}
Expand Down
8 changes: 8 additions & 0 deletions tests/namespaced_urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import django
from django.conf.urls import url, include


if django.VERSION < (1, 8, 99):
urlpatterns = [url(r'^namespace/', include('mozilla_django_oidc.urls', namespace='namespace'))]
else:
urlpatterns = [url(r'^namespace/', include(('mozilla_django_oidc.urls', 'namespace')))]
44 changes: 44 additions & 0 deletions tests/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,50 @@ def test_get_invalid_user(self):

self.assertEqual(self.backend.get_user(user_id=1), None)

@override_settings(ROOT_URLCONF='tests.namespaced_urls')
@override_settings(OIDC_AUTHENTICATION_CALLBACK_URL='namespace:oidc_authentication_callback')
@patch('mozilla_django_oidc.auth.requests')
@patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend.verify_token')
def test_successful_authentication_existing_user_namespaced(self, token_mock, request_mock):
"""Test successful authentication for existing user."""
auth_request = RequestFactory().get('/foo', {'code': 'foo',
'state': 'bar'})
auth_request.session = {}

user = User.objects.create_user(username='a_username',
email='email@example.com')
token_mock.return_value = True
get_json_mock = Mock()
get_json_mock.json.return_value = {
'nickname': 'a_username',
'email': 'email@example.com'
}
request_mock.get.return_value = get_json_mock
post_json_mock = Mock()
post_json_mock.json.return_value = {
'id_token': 'id_token',
'access_token': 'access_granted'
}
request_mock.post.return_value = post_json_mock

post_data = {
'client_id': 'example_id',
'client_secret': 'client_secret',
'grant_type': 'authorization_code',
'code': 'foo',
'redirect_uri': 'http://testserver/namespace/callback/'
}
self.assertEqual(self.backend.authenticate(request=auth_request), user)
token_mock.assert_called_once_with('id_token', nonce=None)
request_mock.post.assert_called_once_with('https://server.example.com/token',
data=post_data,
verify=True)
request_mock.get.assert_called_once_with(
'https://server.example.com/user',
headers={'Authorization': 'Bearer access_granted'},
verify=True
)

@patch('mozilla_django_oidc.auth.requests')
@patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend.verify_token')
def test_successful_authentication_existing_user(self, token_mock, request_mock):
Expand Down
28 changes: 28 additions & 0 deletions tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,34 @@ def test_get(self, mock_random_string):
self.assertEqual(o.hostname, 'server.example.com')
self.assertEqual(o.path, '/auth')

@override_settings(ROOT_URLCONF='tests.namespaced_urls')
@override_settings(OIDC_OP_AUTHORIZATION_ENDPOINT='https://server.example.com/auth')
@override_settings(OIDC_RP_CLIENT_ID='example_id')
@override_settings(OIDC_AUTHENTICATION_CALLBACK_URL='namespace:oidc_authentication_callback')
@patch('mozilla_django_oidc.views.get_random_string')
def test_get_namespaced(self, mock_random_string):
"""Test initiation of a successful OIDC attempt with namespaced redirect_uri."""
mock_random_string.return_value = 'examplestring'
url = reverse('namespace:oidc_authentication_init')
request = self.factory.get(url)
request.session = dict()
login_view = views.OIDCAuthenticationRequestView.as_view()
response = login_view(request)
self.assertEqual(response.status_code, 302)

o = urlparse(response.url)
expected_query = {
'response_type': ['code'],
'scope': ['openid email'],
'client_id': ['example_id'],
'redirect_uri': ['http://testserver/namespace/callback/'],
'state': ['examplestring'],
'nonce': ['examplestring']
}
self.assertDictEqual(parse_qs(o.query), expected_query)
self.assertEqual(o.hostname, 'server.example.com')
self.assertEqual(o.path, '/auth')

@override_settings(OIDC_OP_AUTHORIZATION_ENDPOINT='https://server.example.com/auth')
@override_settings(OIDC_RP_CLIENT_ID='example_id')
@override_settings(OIDC_AUTH_REQUEST_EXTRA_PARAMS={'audience': 'some-api.example.com'})
Expand Down

0 comments on commit feab268

Please sign in to comment.