From a4f9ae2600596ce19a5d9beff249c0c334984f8c Mon Sep 17 00:00:00 2001 From: johnedstone Date: Thu, 7 Sep 2017 17:28:42 -0400 Subject: [PATCH 1/4] User.objects.get_by_natural_key with User.objects.get_or_create, and normalized username to lower case to be consistent with django-auth-ldap --- README.md | 6 ++++++ oidc_auth/authentication.py | 18 +++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 979bef4..08e9ccd 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ +# 07-Sept-2017 +* Replaced `User.objects.get_by_natural_key` with `User.objects.get_or_create` +* Forced username to lower case to be consistent with django-auth-ldap +* Requesting pull to the original project. +* Otherwise, awesome project from which this was forked!! Thank you [ByteInternet](https://github.com/ByteInternet/drf-oidc-auth) + # OpenID Connect authentication for Django Rest Framework This package contains an authentication mechanism for authenticating diff --git a/oidc_auth/authentication.py b/oidc_auth/authentication.py index 9d76895..a9b31d5 100644 --- a/oidc_auth/authentication.py +++ b/oidc_auth/authentication.py @@ -17,9 +17,23 @@ def get_user_by_id(request, id_token): + """Force to lower case to be consistent with django-auth-ldap""" User = get_user_model() try: - user = User.objects.get_by_natural_key(id_token.get('sub')) + email = id_token.get('email', None) + first_name = id_token.get('given_name', '') + last_name = id_token.get('family_name', '') + if email: + user, created = User.objects.get_or_create(username=id_token.get('sub').lower(), + defaults={'first_name': first_name, + 'last_name': last_name, + 'email': email, + }) + else: + user, created = User.objects.get_or_create(username=id_token.get('sub'.lower()), + defaults={'first_name': first_name, + 'last_name': last_name, + }) except User.DoesNotExist: msg = _('Invalid Authorization header. User not found.') raise AuthenticationFailed(msg) @@ -162,3 +176,5 @@ def validate_claims(self, id_token): def authenticate_header(self, request): return 'JWT realm="{0}"'.format(self.www_authenticate_realm) + +# vim: ai et ts=4 sw=4 sts=4 ru nu From afd1a2752954d641b87f7b9b26b0f5f0891b5827 Mon Sep 17 00:00:00 2001 From: johnedstone Date: Tue, 26 Sep 2017 18:27:11 -0400 Subject: [PATCH 2/4] Updated to pass tox test --- README.md | 9 +++------ oidc_auth/authentication.py | 14 +++++++------- oidc_auth/settings.py | 3 +++ tests/settings.py | 3 +++ tests/test_authentication.py | 6 +++++- tox.ini | 4 +++- 6 files changed, 24 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 08e9ccd..8f6ab97 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,3 @@ -# 07-Sept-2017 -* Replaced `User.objects.get_by_natural_key` with `User.objects.get_or_create` -* Forced username to lower case to be consistent with django-auth-ldap -* Requesting pull to the original project. -* Otherwise, awesome project from which this was forked!! Thank you [ByteInternet](https://github.com/ByteInternet/drf-oidc-auth) - # OpenID Connect authentication for Django Rest Framework This package contains an authentication mechanism for authenticating @@ -70,6 +64,9 @@ OIDC_AUTH = { # (Optional) Token prefix in Bearer authorization header (default 'Bearer') 'BEARER_AUTH_HEADER_PREFIX': 'Bearer', + + # (Optional) Create user if user authenticates but is not in the local database (default False) + 'CREATE_USER': False, } ``` diff --git a/oidc_auth/authentication.py b/oidc_auth/authentication.py index a9b31d5..40e78ce 100644 --- a/oidc_auth/authentication.py +++ b/oidc_auth/authentication.py @@ -17,9 +17,8 @@ def get_user_by_id(request, id_token): - """Force to lower case to be consistent with django-auth-ldap""" User = get_user_model() - try: + if api_settings.CREATE_USER: email = id_token.get('email', None) first_name = id_token.get('given_name', '') last_name = id_token.get('family_name', '') @@ -34,9 +33,12 @@ def get_user_by_id(request, id_token): defaults={'first_name': first_name, 'last_name': last_name, }) - except User.DoesNotExist: - msg = _('Invalid Authorization header. User not found.') - raise AuthenticationFailed(msg) + else: + try: + user = User.objects.get_by_natural_key(id_token.get('sub')) + except User.DoesNotExist: + msg = _('Invalid Authorization header. User not found.') + raise AuthenticationFailed(msg) return user @@ -176,5 +178,3 @@ def validate_claims(self, id_token): def authenticate_header(self, request): return 'JWT realm="{0}"'.format(self.www_authenticate_realm) - -# vim: ai et ts=4 sw=4 sts=4 ru nu diff --git a/oidc_auth/settings.py b/oidc_auth/settings.py index 5cccf26..5301e77 100644 --- a/oidc_auth/settings.py +++ b/oidc_auth/settings.py @@ -23,6 +23,9 @@ 'JWT_AUTH_HEADER_PREFIX': 'JWT', 'BEARER_AUTH_HEADER_PREFIX': 'Bearer', + + # Create user if user authenticates but is not in the local database + 'CREATE_USER': False, } # List of settings that may be in string import notation. diff --git a/tests/settings.py b/tests/settings.py index 8f768da..7280fdf 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -1,3 +1,5 @@ +import os + SECRET_KEY='secret' DATABASES = { 'default': { @@ -13,4 +15,5 @@ OIDC_AUTH = { 'OIDC_ENDPOINT': 'http://example.com', 'OIDC_AUDIENCES': ('you',), + 'CREATE_USER': os.getenv('CREATE_USER', 'yes') == 'yes', } diff --git a/tests/test_authentication.py b/tests/test_authentication.py index 538ecde..19d5e9c 100644 --- a/tests/test_authentication.py +++ b/tests/test_authentication.py @@ -11,6 +11,7 @@ from rest_framework.views import APIView from requests import Response, HTTPError, ConnectionError from oidc_auth.authentication import JSONWebTokenAuthentication, BearerTokenAuthentication +from oidc_auth.settings import api_settings import sys if sys.version_info > (3,): long = int @@ -208,7 +209,10 @@ def test_with_too_new_jwt(self): def test_with_unknown_subject(self): auth = 'JWT ' + make_id_token(self.user.username + 'x') resp = self.client.get('/test/', HTTP_AUTHORIZATION=auth) - self.assertEqual(resp.status_code, 401) + if not api_settings.CREATE_USER: + self.assertEqual(resp.status_code, 401) + else: + self.assertEqual(resp.status_code, 200) def test_with_multiple_audiences(self): auth = 'JWT ' + make_id_token(self.user.username, aud=['you', 'me']) diff --git a/tox.ini b/tox.ini index 8e8a14e..6d8b392 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist = - {py27,py34}-django{18,19,110}-drf{32} + {py27,py34}-django{18,19,110}-drf{32}-createuser{yes,no} [testenv] commands = @@ -9,6 +9,8 @@ setenv = PYTHONDONTWRITEBYTECODE=1 DJANGO_SETTINGS_MODULE=tests.settings PYTHONPATH={toxinidir} + createuseryes: CREATE_USER=yes + createuserno: CREATE_USER=no deps = django18: Django==1.8.4 django19: Django==1.9.12 From 745e1a6d8bade7ed7ede0d782e862610a07c048a Mon Sep 17 00:00:00 2001 From: johnedstone Date: Wed, 27 Sep 2017 14:01:35 -0400 Subject: [PATCH 3/4] README update --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8f6ab97..754c112 100644 --- a/README.md +++ b/README.md @@ -82,3 +82,4 @@ tox * Requires [Django REST Framework](http://www.django-rest-framework.org/) * And of course [Django](https://www.djangoproject.com/) * Inspired on [REST framework JWT Auth](https://github.com/GetBlimp/django-rest-framework-jwt) + From 6202a308d32fcc12fe555259d565268f523175d8 Mon Sep 17 00:00:00 2001 From: johnedstone Date: Wed, 27 Sep 2017 15:29:27 -0400 Subject: [PATCH 4/4] README update --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 754c112..8f6ab97 100644 --- a/README.md +++ b/README.md @@ -82,4 +82,3 @@ tox * Requires [Django REST Framework](http://www.django-rest-framework.org/) * And of course [Django](https://www.djangoproject.com/) * Inspired on [REST framework JWT Auth](https://github.com/GetBlimp/django-rest-framework-jwt) -