├── .editorconfig ├── .gitignore ├── .travis.yml ├── AUTHORS.rst ├── CONTRIBUTING.rst ├── HISTORY.rst ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── django_auth0 ├── __init__.py ├── apps.py ├── auth_backend.py ├── auth_helpers.py ├── context_processors.py ├── models.py ├── urls.py ├── utils.py └── views.py ├── requirements-dev.txt ├── requirements-test.txt ├── requirements.txt ├── runtests.py ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── test_auth_backend.py ├── test_auth_helper.py ├── test_backend.py └── test_context_processor.py └── tox.ini /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.{py,rst,ini}] 12 | indent_style = space 13 | indent_size = 4 14 | 15 | [*.{html,css,scss,json,yml}] 16 | indent_style = space 17 | indent_size = 2 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | 22 | [Makefile] 23 | indent_style = tab 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | __pycache__ 3 | 4 | # C extensions 5 | *.so 6 | 7 | # Packages 8 | *.egg 9 | *.egg-info 10 | dist 11 | build 12 | eggs 13 | parts 14 | bin 15 | var 16 | sdist 17 | develop-eggs 18 | .installed.cfg 19 | lib 20 | lib64 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | htmlcov 30 | 31 | # Translations 32 | *.mo 33 | 34 | # Mr Developer 35 | .mr.developer.cfg 36 | .project 37 | .pydevproject 38 | 39 | # Pycharm/Intellij 40 | .idea 41 | 42 | # Complexity 43 | output/*.html 44 | output/*/index.html 45 | 46 | # Sphinx 47 | docs/_build 48 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Config file for automatic testing at travis-ci.org 2 | 3 | language: python 4 | 5 | python: 6 | - "3.5" 7 | - "2.7" 8 | 9 | before_install: 10 | - pip install codecov 11 | 12 | # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors 13 | install: pip install -r requirements-test.txt 14 | 15 | # command to run tests using coverage, e.g. python setup.py test 16 | script: coverage run --source django_auth0 runtests.py 17 | 18 | after_success: 19 | - codecov 20 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Credits 3 | ======= 4 | 5 | Development Lead 6 | ---------------- 7 | 8 | * Sultan Imanhodjaev 9 | 10 | Contributors 11 | ------------ 12 | 13 | None yet. Why not be the first? 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Contributing 3 | ============ 4 | 5 | Contributions are welcome, and they are greatly appreciated! Every 6 | little bit helps, and credit will always be given. 7 | 8 | You can contribute in many ways: 9 | 10 | Types of Contributions 11 | ---------------------- 12 | 13 | Report Bugs 14 | ~~~~~~~~~~~ 15 | 16 | Report bugs at https://github.com/imanhodjaev/django-auth0/issues. 17 | 18 | If you are reporting a bug, please include: 19 | 20 | * Your operating system name and version. 21 | * Any details about your local setup that might be helpful in troubleshooting. 22 | * Detailed steps to reproduce the bug. 23 | 24 | Fix Bugs 25 | ~~~~~~~~ 26 | 27 | Look through the GitHub issues for bugs. Anything tagged with "bug" 28 | is open to whoever wants to implement it. 29 | 30 | Implement Features 31 | ~~~~~~~~~~~~~~~~~~ 32 | 33 | Look through the GitHub issues for features. Anything tagged with "feature" 34 | is open to whoever wants to implement it. 35 | 36 | Write Documentation 37 | ~~~~~~~~~~~~~~~~~~~ 38 | 39 | django-auth0 could always use more documentation, whether as part of the 40 | official django-auth0 docs, in docstrings, or even on the web in blog posts, 41 | articles, and such. 42 | 43 | Submit Feedback 44 | ~~~~~~~~~~~~~~~ 45 | 46 | The best way to send feedback is to file an issue at https://github.com/imanhodjaev/django-auth0/issues. 47 | 48 | If you are proposing a feature: 49 | 50 | * Explain in detail how it would work. 51 | * Keep the scope as narrow as possible, to make it easier to implement. 52 | * Remember that this is a volunteer-driven project, and that contributions 53 | are welcome :) 54 | 55 | Get Started! 56 | ------------ 57 | 58 | Ready to contribute? Here's how to set up `django-auth0` for local development. 59 | 60 | 1. Fork the `django-auth0` repo on GitHub. 61 | 2. Clone your fork locally:: 62 | 63 | $ git clone git@github.com:your_name_here/django-auth0.git 64 | 65 | 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: 66 | 67 | $ mkvirtualenv django-auth0 68 | $ cd django-auth0/ 69 | $ python setup.py develop 70 | 71 | 4. Create a branch for local development:: 72 | 73 | $ git checkout -b name-of-your-bugfix-or-feature 74 | 75 | Now you can make your changes locally. 76 | 77 | 5. When you're done making changes, check that your changes pass flake8 and the 78 | tests, including testing other Python versions with tox:: 79 | 80 | $ flake8 django_auth0 tests 81 | $ python setup.py test 82 | $ tox 83 | 84 | To get flake8 and tox, just pip install them into your virtualenv. 85 | 86 | 6. Commit your changes and push your branch to GitHub:: 87 | 88 | $ git add . 89 | $ git commit -m "Your detailed description of your changes." 90 | $ git push origin name-of-your-bugfix-or-feature 91 | 92 | 7. Submit a pull request through the GitHub website. 93 | 94 | Pull Request Guidelines 95 | ----------------------- 96 | 97 | Before you submit a pull request, check that it meets these guidelines: 98 | 99 | 1. The pull request should include tests. 100 | 2. If the pull request adds functionality, the docs should be updated. Put 101 | your new functionality into a function with a docstring, and add the 102 | feature to the list in README.rst. 103 | 3. The pull request should work for Python 2.6, 2.7, and 3.3, and for PyPy. Check 104 | https://travis-ci.org/imanhodjaev/django-auth0/pull_requests 105 | and make sure that the tests pass for all supported Python versions. 106 | 107 | Tips 108 | ---- 109 | 110 | To run a subset of tests:: 111 | 112 | $ python -m unittest tests.test_django_auth0 113 | 114 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | .. :changelog: 2 | 3 | History 4 | ------- 5 | 6 | 0.0.2 (2016-02-01) 7 | ++++++++++++++++++ 8 | 9 | * Update documentation 10 | 11 | 12 | 0.0.1 (2016-02-01) 13 | ++++++++++++++++++ 14 | 15 | * First release on PyPI. 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Sultan Imanhodjaev 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 4 | to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 5 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS.rst 2 | include CONTRIBUTING.rst 3 | include HISTORY.rst 4 | include LICENSE 5 | include README.rst 6 | recursive-include django_auth0 *.html *.png *.gif *js *.css *jpg *jpeg *svg *py 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean-pyc clean-build 2 | 3 | help: 4 | @echo "clean-build - remove build artifacts" 5 | @echo "clean-pyc - remove Python file artifacts" 6 | @echo "lint - check style with flake8" 7 | @echo "test - run tests quickly with the default Python" 8 | @echo "test-all - run tests on every Python version with tox" 9 | @echo "coverage - check code coverage quickly with the default Python" 10 | @echo "release - package and upload a release" 11 | @echo "sdist - package" 12 | @echo "checkdocs - lint README.rst" 13 | 14 | clean: clean-build clean-pyc 15 | 16 | clean-build: 17 | rm -fr build/ 18 | rm -fr dist/ 19 | rm -fr *.egg-info 20 | 21 | clean-pyc: 22 | find . -name '*.pyc' -exec rm -f {} + 23 | find . -name '*.pyo' -exec rm -f {} + 24 | find . -name '*~' -exec rm -f {} + 25 | 26 | lint: 27 | flake8 django_auth0 tests 28 | 29 | test: 30 | python runtests.py tests 31 | 32 | test-all: 33 | tox 34 | 35 | coverage: 36 | coverage run --source django_auth0 --omit=django_auth0/apps.py runtests.py tests 37 | coverage report -m 38 | coverage html 39 | open htmlcov/index.html 40 | 41 | release: clean 42 | python setup.py sdist upload 43 | python setup.py bdist_wheel upload 44 | 45 | sdist: clean 46 | python setup.py sdist 47 | ls -l dist 48 | 49 | checkdocs: 50 | python setup.py checkdocs 51 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | django-auth0 3 | ============ 4 | 5 | .. image:: https://codecov.io/gh/imanhodjaev/django-auth0/branch/master/graph/badge.svg 6 | :target: https://codecov.io/gh/imanhodjaev/django-auth0 7 | 8 | .. image:: https://landscape.io/github/imanhodjaev/django-auth0/master/landscape.svg?style=flat 9 | :target: https://landscape.io/github/imanhodjaev/django-auth0/master 10 | :alt: Code Health 11 | 12 | .. image:: https://travis-ci.org/imanhodjaev/django-auth0.png?branch=master 13 | :target: https://travis-ci.org/imanhodjaev/django-auth0 14 | 15 | Django Auth0 authentication backend 16 | Backend implemented against Auth0 `Regular Python application`_ 17 | 18 | 19 | Please use official tutorial and libraries from Auth0 https://auth0.com/docs/quickstart/webapp/django/01-login 20 | 21 | Quickstart 22 | ---------- 23 | 24 | * Install django-auth0 ``$ pip install django-auth0`` 25 | 26 | * Add ``django_auth0`` to ``INSTALLED_APPS`` 27 | 28 | * Add ``django_auth0.auth_backend.Auth0Backend`` to ``AUTHENTICATION_BACKENDS`` 29 | 30 | .. code-block::python 31 | 32 | AUTHENTICATION_BACKENDS = [ 33 | "django_auth0.auth_backend.Auth0Backend", 34 | "django.contrib.auth.backends.ModelBackend" 35 | ] 36 | 37 | * Add ``django_auth0.context_processors.auth0`` to ``CONTEXT_PROCESSORS`` so necessary template context will be provided 38 | 39 | * Include callback urls 40 | 41 | .. code-block::python 42 | 43 | urlpatterns = [ 44 | ... 45 | url(r'^auth/', include('django_auth0.urls')), 46 | ) 47 | 48 | Update ``AUTH0_CALLBACK_URL`` in ``settings.py`` to the following if want to use default authentication handler 49 | 50 | .. code-block::python 51 | AUTH0_CALLBACK_URL = 'http://YOUR_DOMAIN/auth/auth_callback' 52 | 53 | 54 | * Add Auth0 client side JavaScript and initialize it 55 | 56 | .. code-block::python 57 | 58 | 59 | 73 | 74 | Options: 75 | 76 | 1. ``AUTH0_CLIENT_ID`` - Auth0 client app id, 77 | 2. ``AUTH0_SECRET`` - Auth0 app secret, 78 | 3. ``AUTH0_DOMAIN`` - Auth0 subdomain ``YOU_APP.auth0.com``. 79 | 4. ``AUTH0_CALLBACK_URL`` - Auth0 callback url is full url to your callback view like ``https://YOUR_DOMAIN/CALLBACK`` 80 | 5. ``AUTH0_SUCCESS_URL`` - Url to redirect once you login successfully 81 | 82 | Overriding callback view 83 | Default callback view looks like this so you can always write your own and 84 | set ``AUTH0_CALLBACK_URL`` to your custom view it should be url name. 85 | 86 | .. code-block::python 87 | 88 | def process_login(request): 89 | """ 90 | Default handler to login user 91 | :param request: HttpRequest 92 | """ 93 | code = request.GET.get('code', '') 94 | json_header = {'content-type': 'application/json'} 95 | token_url = 'https://%s/oauth/token' % settings.AUTH0_DOMAIN 96 | 97 | token_payload = { 98 | 'client_id': settings.AUTH0_CLIENT_ID, 99 | 'client_secret': settings.AUTH0_SECRET, 100 | 'redirect_uri': reverse(settings.AUTH0_CALLBACK_URL), 101 | 'code': code, 102 | 'grant_type': 'authorization_code' 103 | } 104 | 105 | token_info = requests.post(token_url, 106 | data=json.dumps(token_payload), 107 | headers=json_header).json() 108 | 109 | url = 'https://%s/userinfo?access_token=%s' 110 | user_url = url % (settings.AUTH0_DOMAIN, token_info['access_token']) 111 | user_info = requests.get(user_url).json() 112 | 113 | # We're saving all user information into the session 114 | request.session['profile'] = user_info 115 | user = authenticate(**user_info) 116 | 117 | if user: 118 | login(request, user) 119 | return redirect(settings.AUTH0_SUCCESS_URL) 120 | 121 | return HttpResponse(status=400) 122 | 123 | 124 | `Sample application`_ is at https://github.com/imanhodjaev/auth0-sample 125 | 126 | TODO 127 | -------- 128 | 129 | * Improve tests, 130 | * Add Auth0 user profile model, 131 | * Add support for settings from Auth0, 132 | * Move string literals to configuration file 133 | 134 | Running Tests 135 | -------------- 136 | 137 | Does the code actually work? 138 | 139 | :: 140 | 141 | source /bin/activate 142 | (myenv) $ pip install -r requirements-test.txt 143 | (myenv) $ python runtests.py 144 | 145 | Credits 146 | --------- 147 | 148 | Tools used in rendering this package: 149 | 150 | * Cookiecutter_ 151 | * `cookiecutter-djangopackage`_ 152 | 153 | .. _Cookiecutter: https://github.com/audreyr/cookiecutter 154 | .. _`cookiecutter-djangopackage`: https://github.com/pydanny/cookiecutter-djangopackage 155 | .. _`Sample application`: https://github.com/imanhodjaev/auth0-sample 156 | .. _`Regular Python application`: https://auth0.com/docs/quickstart/webapp/python/ 157 | -------------------------------------------------------------------------------- /django_auth0/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __version__ = '0.0.3' 3 | -------------------------------------------------------------------------------- /django_auth0/apps.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.apps import AppConfig 3 | 4 | 5 | class Config(AppConfig): 6 | name = 'django_auth0' 7 | -------------------------------------------------------------------------------- /django_auth0/auth_backend.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from django.contrib.auth import get_user_model 4 | from django.utils.translation import ugettext as _ 5 | 6 | 7 | UserModel = get_user_model() 8 | 9 | # user profile keys that are always present as specified by 10 | # https://auth0.com/docs/user-profile/normalized#normalized-user-profile-schema 11 | AUTH0_USER_INFO_KEYS = [ 12 | 'name', 13 | 'nickname', 14 | 'picture', 15 | 'user_id', 16 | ] 17 | 18 | 19 | class Auth0Backend(object): 20 | def authenticate(self, **kwargs): 21 | """ 22 | Auth0 return a dict which contains the following fields 23 | :param kwargs: user information provided by auth0 24 | :return: user 25 | """ 26 | # check that each auth0 key is present in kwargs 27 | for key in AUTH0_USER_INFO_KEYS: 28 | if key not in kwargs: 29 | # End the authentication attempt if this is not an Auth0 30 | # payload. 31 | return 32 | 33 | user_id = kwargs.get('user_id', None) 34 | 35 | if not user_id: 36 | raise ValueError(_('user_id can\'t be blank!')) 37 | 38 | # The format of user_id is 39 | # {identity provider id}|{unique id in the provider} 40 | # The pipe character is invalid for the django username field 41 | # The solution is to replace the pipe with a dash 42 | username = user_id.replace('|', '-') 43 | 44 | try: 45 | return UserModel.objects.get(username__iexact=username) 46 | except UserModel.DoesNotExist: 47 | return UserModel.objects.create(username=username) 48 | 49 | # noinspection PyProtectedMember 50 | def get_user(self, user_id): 51 | """ 52 | Primary key identifier 53 | It is better to raise UserModel.DoesNotExist 54 | :param user_id: 55 | :return: UserModel instance 56 | """ 57 | return UserModel._default_manager.get(pk=user_id) 58 | -------------------------------------------------------------------------------- /django_auth0/auth_helpers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import json 3 | import requests 4 | 5 | from django.contrib.auth import login, authenticate 6 | from django.http import HttpResponse 7 | from django.shortcuts import redirect 8 | from .utils import get_config 9 | 10 | 11 | def process_login(request): 12 | """ 13 | Default handler to login user 14 | :param request: HttpRequest 15 | """ 16 | code = request.GET.get('code', '') 17 | config = get_config() 18 | json_header = {'content-type': 'application/json'} 19 | token_url = 'https://%s/oauth/token' % config['AUTH0_DOMAIN'] 20 | 21 | token_payload = { 22 | 'client_id': config['AUTH0_CLIENT_ID'], 23 | 'client_secret': config['AUTH0_SECRET'], 24 | 'redirect_uri': config['AUTH0_CALLBACK_URL'], 25 | 'code': code, 26 | 'grant_type': 'authorization_code' 27 | } 28 | 29 | token_info = requests.post(token_url, 30 | data=json.dumps(token_payload), 31 | headers=json_header).json() 32 | 33 | url = 'https://%s/userinfo?access_token=%s' 34 | user_url = url % (config['AUTH0_DOMAIN'], 35 | token_info.get('access_token', '')) 36 | 37 | user_info = requests.get(user_url).json() 38 | 39 | # We're saving all user information into the session 40 | request.session['profile'] = user_info 41 | user = authenticate(**user_info) 42 | 43 | if user: 44 | login(request, user) 45 | return redirect(config['AUTH0_SUCCESS_URL']) 46 | 47 | return HttpResponse(status=400) 48 | -------------------------------------------------------------------------------- /django_auth0/context_processors.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from .utils import get_config 3 | 4 | 5 | def auth0(request): # noinspection PyUnusedLocal 6 | return get_config() 7 | -------------------------------------------------------------------------------- /django_auth0/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /django_auth0/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.conf.urls import url 3 | from .views import auth_callback 4 | 5 | 6 | urlpatterns = [ 7 | url(r'callback/?$', auth_callback, name='auth_callback'), 8 | ] 9 | -------------------------------------------------------------------------------- /django_auth0/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.conf import settings 3 | 4 | 5 | def get_config(): 6 | """ Collects AUTH0_* configurations """ 7 | return { 8 | 'AUTH0_CLIENT_ID': settings.AUTH0_CLIENT_ID, 9 | 'AUTH0_SECRET': settings.AUTH0_SECRET, 10 | 'AUTH0_DOMAIN': settings.AUTH0_DOMAIN, 11 | 'AUTH0_CALLBACK_URL': settings.AUTH0_CALLBACK_URL, 12 | 'AUTH0_SUCCESS_URL': settings.AUTH0_SUCCESS_URL, 13 | } 14 | -------------------------------------------------------------------------------- /django_auth0/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from .auth_helpers import process_login 3 | 4 | 5 | def auth_callback(request): 6 | return process_login(request) 7 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | bumpversion==0.5.3 2 | wheel==0.29.0 3 | collective.checkdocs 4 | -------------------------------------------------------------------------------- /requirements-test.txt: -------------------------------------------------------------------------------- 1 | django>=1.9.8 2 | coverage 3 | mock>=2.0.0 4 | flake8>=3.0.4 5 | tox>=2.3.1 6 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | django>=1.9.8 2 | requests>=2.11.1 3 | -------------------------------------------------------------------------------- /runtests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import sys 3 | 4 | try: 5 | from django.conf import settings 6 | from django.test.utils import get_runner 7 | from django.core.management import call_command 8 | 9 | settings.configure( 10 | DEBUG=True, 11 | USE_TZ=True, 12 | DATABASES={ 13 | 'default': { 14 | 'ENGINE': 'django.db.backends.sqlite3', 15 | 'NAME': ':memory:' 16 | } 17 | }, 18 | ROOT_URLCONF='django_auth0.urls', 19 | INSTALLED_APPS=[ 20 | 'django.contrib.auth', 21 | 'django.contrib.sessions', 22 | 'django.contrib.contenttypes', 23 | 'django.contrib.sites', 24 | 'django_auth0' 25 | ], 26 | SITE_ID=1, 27 | MIDDLEWARE_CLASSES=(), 28 | AUTH0_CLIENT_ID='client id', 29 | AUTH0_SECRET='secret', 30 | AUTH0_DOMAIN='domain', 31 | AUTH0_CALLBACK_URL='auth_callback', 32 | AUTH0_SUCCESS_URL='/' 33 | ) 34 | 35 | try: 36 | import django 37 | setup = django.setup 38 | except AttributeError: 39 | pass 40 | else: 41 | setup() 42 | 43 | call_command('migrate') 44 | 45 | except ImportError: 46 | import traceback 47 | traceback.print_exc() 48 | raise ImportError('To fix this error, run: pip install' 49 | ' -r requirements-test.txt') 50 | 51 | 52 | def run_tests(*test_args): 53 | if not test_args: 54 | test_args = ['tests'] 55 | 56 | # Run tests 57 | runner = get_runner(settings) 58 | test_runner = runner() 59 | 60 | failures = test_runner.run_tests(test_args) 61 | 62 | if failures: 63 | sys.exit(bool(failures)) 64 | 65 | 66 | if __name__ == '__main__': 67 | run_tests(*sys.argv[1:]) 68 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.0.3 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:setup.py] 7 | 8 | [bumpversion:file:django_auth0/__init__.py] 9 | 10 | [wheel] 11 | universal = 1 12 | 13 | [metadata] 14 | description-file = README.md 15 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import os 4 | import re 5 | import sys 6 | 7 | 8 | try: 9 | from setuptools import setup 10 | except ImportError: 11 | from distutils.core import setup 12 | 13 | 14 | def get_version(*file_paths): 15 | filename = os.path.join(os.path.dirname(__file__), *file_paths) 16 | version_file = open(filename).read() 17 | version_match = re.search(r'^__version__ = [\'"]([^\'"]*)[\'"]', 18 | version_file, re.M) 19 | 20 | if version_match: 21 | return version_match.group(1) 22 | 23 | raise RuntimeError('Unable to find version string.') 24 | 25 | 26 | version = get_version('django_auth0', '__init__.py') 27 | 28 | if sys.argv[-1] == 'publish': 29 | try: 30 | import wheel 31 | except ImportError: 32 | print('Wheel library missing. Please run pip install wheel') 33 | sys.exit() 34 | 35 | os.system('python setup.py sdist upload') 36 | os.system('python setup.py bdist_wheel upload') 37 | sys.exit() 38 | 39 | if sys.argv[-1] == 'tag': 40 | print('Tagging the version on github:') 41 | 42 | os.system('git tag -a %s -m \'version %s\'' % (version, version)) 43 | os.system('git push --tags') 44 | sys.exit() 45 | 46 | readme = open('README.rst').read() 47 | history = open('HISTORY.rst').read().replace('.. :changelog:', '') 48 | 49 | setup( 50 | name='django-auth0', 51 | version=version, 52 | description='''Django Auth0 authentication background''', 53 | long_description=readme + '\n\n' + history, 54 | author='Sultan Imanhodjaev', 55 | author_email='sultan.imanhodjaev@gmail.com', 56 | url='https://github.com/imanhodjaev/django-auth0', 57 | packages=['django_auth0'], 58 | include_package_data=True, 59 | install_requires=[ 60 | 'Django>=1.8', 61 | 'requests>=2.11.1' 62 | ], 63 | license='MIT', 64 | zip_safe=False, 65 | keywords='django-auth0 auth0', 66 | classifiers=[ 67 | 'Development Status :: 3 - Alpha', 68 | 'Framework :: Django', 69 | 'Framework :: Django :: 1.8', 70 | 'Framework :: Django :: 1.9', 71 | 'Intended Audience :: Developers', 72 | 'License :: OSI Approved :: BSD License', 73 | 'Natural Language :: English', 74 | 'Programming Language :: Python :: 2.7', 75 | 'Programming Language :: Python :: 3', 76 | 'Programming Language :: Python :: 3.5', 77 | ] 78 | ) 79 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sultaniman/django-auth0/2fa2413cbb36dd9a4644b9eeb93f0eb27873a4b4/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_auth_backend.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Tests for `django-auth0` models module. 6 | """ 7 | from django.test import TestCase 8 | from django.contrib.auth.models import User 9 | from django_auth0.auth_backend import Auth0Backend 10 | 11 | 12 | class TestDjangoAuth0(TestCase): 13 | """ Test Auth0Backend """ 14 | 15 | def setUp(self): 16 | self.backend = Auth0Backend() 17 | self.auth_data = { 18 | 'email': 'email@email.com', 19 | 'nickname': 'test_username', 20 | 'name': 'Test User', 21 | 'picture': 'http://localhost/test.png', 22 | 'user_id': 'auth0|1111111', 23 | } 24 | 25 | def test_authenticate_works(self): 26 | """ Authenticate works ok """ 27 | user = self.backend.authenticate(**self.auth_data) 28 | self.assertTrue(isinstance(user, User), msg='user is instance of User') 29 | 30 | def test_authenticate_creates_user(self): 31 | """ Authenticate works creates a user """ 32 | user = self.backend.authenticate(**self.auth_data) 33 | self.assertIsNotNone(user, msg='User exists') 34 | 35 | def test_authenticate_fires_exception(self): 36 | """ Authenticate fires exception when insufficient data supplied """ 37 | self.assertRaises(ValueError, self._value_error) 38 | 39 | def _value_error(self): 40 | self.auth_data['user_id'] = None 41 | return self.backend.authenticate(**self.auth_data) 42 | 43 | def test_authenticate_ignores_non_auth0(self): 44 | """ 45 | Auth0Backend.authenticate() will ignore 46 | attempts to authenticate that do not contain 47 | the user info fields that are always provided by auth0 48 | """ 49 | self.assertIsNone(self.backend.authenticate()) 50 | -------------------------------------------------------------------------------- /tests/test_auth_helper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Tests for `django-auth0` auth_helpers module. 6 | """ 7 | from mock import patch 8 | 9 | from django.http import HttpResponseRedirect 10 | from django.core.urlresolvers import reverse 11 | from django.test import TestCase, RequestFactory 12 | from django.contrib.auth.models import User 13 | from django.contrib.sessions.middleware import SessionMiddleware 14 | 15 | from django_auth0 import auth_helpers 16 | from django_auth0 import views 17 | 18 | 19 | mock_user = { 20 | 'username': 'username', 21 | 'email': 'email@email.com', 22 | 'access_token': 'access_token' 23 | } 24 | 25 | user = User.objects.create_user(username=mock_user['username'], 26 | email=mock_user['email']) 27 | 28 | 29 | class MockObject(object): 30 | def json(self): 31 | return mock_user 32 | 33 | 34 | def mock_auth(*args, **kwargs): 35 | """ Returns creates test user and assigns authentication backend """ 36 | setattr(user, 'backend', 'django_auth0.auth_backends.Auth0Backend') 37 | return user 38 | 39 | 40 | def mock_request(*args, **kwargs): 41 | """ Returns mocked object call result for requests.join """ 42 | return MockObject() 43 | 44 | 45 | def mock_response(*args, **kwargs): 46 | """ Returns HTTPResponse """ 47 | return HttpResponseRedirect(redirect_to='/') 48 | 49 | 50 | def make_request(): 51 | """ Creates request factory object with session and url params """ 52 | factory = RequestFactory() 53 | url = reverse('auth_callback') 54 | request = factory.get('%s/?code=code' % url) 55 | middleware = SessionMiddleware() 56 | middleware.process_request(request) 57 | request.session.save() 58 | 59 | return request 60 | 61 | 62 | class TestDjangoAuth0(TestCase): 63 | """ Test auth helper """ 64 | 65 | @patch('django_auth0.auth_helpers.requests.get', side_effect=mock_request) 66 | @patch('django_auth0.auth_helpers.requests.post', side_effect=mock_request) 67 | def test_login_process_returns_400(self, *args, **kwargs): 68 | """ It returns HTTP 400 when profile data from Auth0 is empty """ 69 | request = make_request() 70 | result = auth_helpers.process_login(request) 71 | self.assertEqual(result.status_code, 400, msg='Bad request returned') 72 | 73 | @patch('django_auth0.auth_helpers.requests.get', side_effect=mock_request) 74 | @patch('django_auth0.auth_helpers.requests.post', side_effect=mock_request) 75 | @patch('django_auth0.auth_helpers.authenticate', side_effect=mock_auth) 76 | def test_login_process_it_works(self, *args, **kwargs): 77 | """ It returns HTTPRedirect when everything is ok """ 78 | request = make_request() 79 | result = auth_helpers.process_login(request) 80 | 81 | self.assertEqual(result.status_code, 302, 82 | msg='Success redirect happens') 83 | 84 | self.assertIsInstance(result, HttpResponseRedirect, 85 | msg='Correct redirect class used') 86 | 87 | @patch('django_auth0.views.auth_callback', side_effect=mock_response) 88 | def test_callback_view(self, *args, **kwargs): 89 | """ View returns HTTPRedirect when everything is ok """ 90 | request = make_request() 91 | result = views.auth_callback(request) 92 | 93 | self.assertEqual(result.status_code, 302, 94 | msg='Success redirect happens') 95 | 96 | self.assertIsInstance(result, HttpResponseRedirect, 97 | msg='Correct redirect class used') 98 | -------------------------------------------------------------------------------- /tests/test_backend.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Tests for `django-auth0` authentication backend. 6 | """ 7 | from django.test import TestCase 8 | from django.contrib.auth.models import User 9 | from django_auth0.auth_backend import Auth0Backend 10 | 11 | 12 | def _get_user(backend): 13 | return backend.get_user(-1) 14 | 15 | 16 | class TestDjangoAuth0(TestCase): 17 | """ Test Auth0Backend """ 18 | 19 | def setUp(self): 20 | self.backend = Auth0Backend() 21 | self.user = User.objects.create_user(username='user', 22 | email='email@email.com') 23 | 24 | def test_existing_user_returned(self): 25 | """ Test if get user returns a user if it exists in database """ 26 | self.assertIsNotNone(self.backend.get_user(self.user.pk)) 27 | 28 | def test_exception_raised_when_user_not_found(self): 29 | """ Test if get user returns a user if it exists in database """ 30 | self.assertRaises(User.DoesNotExist, self._get_user) 31 | 32 | def _get_user(self): 33 | return _get_user(self.backend) 34 | -------------------------------------------------------------------------------- /tests/test_context_processor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Tests for `django-auth0` authentication backend. 6 | """ 7 | from django.test import TestCase 8 | from django_auth0.context_processors import auth0 9 | 10 | 11 | class TestDjangoAuth0ContextProcessor(TestCase): 12 | """ Test Auth0Backend ContextProcessor """ 13 | 14 | def test_context_processor(self): 15 | """ It returns dict with settings """ 16 | config = auth0(None) 17 | self.assertTrue(len(config.keys()) > 0) 18 | self.assertTrue(type(config) == dict) 19 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27, py35 3 | 4 | [testenv] 5 | setenv = 6 | PYTHONPATH = {toxinidir}:{toxinidir}/django_auth0 7 | 8 | commands = python runtests.py 9 | 10 | deps = 11 | -r{toxinidir}/requirements-test.txt 12 | --------------------------------------------------------------------------------