diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml deleted file mode 100644 index 2d172c5..0000000 --- a/.github/workflows/python-app.yml +++ /dev/null @@ -1,40 +0,0 @@ -# This workflow will install Python dependencies, run tests and lint with a single version of Python -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python - -name: Run Tests - -on: - push: - branches: ["master"] - pull_request: - branches: ["master"] - -permissions: - contents: read - -jobs: - runtests: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - name: Set up Python 3.7 - uses: actions/setup-python@v4 - with: - python-version: "3.7" - cache: "pip" - cache-dependency-path: "test-requirements.txt" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install flake8 pytest - if [ -f test-requirements.txt ]; then pip install -r test-requirements.txt; fi - - name: Lint with flake8 - run: | - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - - name: Run tests - run: | - ./runtests.sh diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..306420d --- /dev/null +++ b/.python-version @@ -0,0 +1,8 @@ +2.7 +3.6 +3.7 +3.8 +3.9 +3.10 +3.11 +3.12 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 36203c3..0000000 --- a/.travis.yml +++ /dev/null @@ -1,28 +0,0 @@ -language: python -env: - - DJANGO=1.6 - - DJANGO=1.7 - - DJANGO=1.8 - - DJANGO=1.9 - - DJANGO=1.10 -python: - - "2.7" - - "3.5" - - "pypy" -# command to install dependencies -install: - - pip install . - - pip install -r test-requirements.txt - - if [ $DJANGO = "1.6" ] ; then pip install https://github.com/django/django/archive/stable/1.6.x.zip#egg=django; fi - - if [ $DJANGO = "1.7" ] ; then pip install https://github.com/django/django/archive/stable/1.7.x.zip#egg=django; fi - - if [ $DJANGO = "1.8" ] ; then pip install https://github.com/django/django/archive/stable/1.8.x.zip#egg=django; fi - - if [ $DJANGO = "1.9" ] ; then pip install https://github.com/django/django/archive/stable/1.9.x.zip#egg=django; fi - - if [ $DJANGO = "1.10" ] ; then pip install https://github.com/django/django/archive/stable/1.10.x.zip#egg=django; fi -matrix: - exclude: - - env: DJANGO="1.6" - python: 3.5 - - env: DJANGO="1.7" - python: 3.5 -# command to run tests -script: ./runtests.sh diff --git a/README.md b/README.md index e23e743..df3a2e7 100644 --- a/README.md +++ b/README.md @@ -13,20 +13,27 @@ django-admin-oauth2 should support Python 2.7, pypy, and Django versions 1.6 thr Step 1: `pip install django-admin-oauth2` and include it in your project's requirements -Step 2: Include the django-admin-oauth2 urlconf in your project's urls.py: +Step 2: Include the django-admin-oauth2 urlconf in your project's urls.py: + +Django 1.x ```python url(r'/admin/oauth/', include('oauthadmin.urls')) ``` +Django >= 2.0 + +```python +re_path(r'/admin/oauth/', include('oauthadmin.urls')) +``` + Step 3: Include oauthadmin in your INSTALLED_APPS: ```python INSTALLED_APPS = ( 'oauthadmin' ) -```` - +``` Step 4: Install the middleware in your project's settings.py: @@ -36,7 +43,7 @@ MIDDLEWARE_CLASSES = ( ) ``` -*make sure that this comes AFTER 'django.contrib.sessions.middleware.SessionMiddleware'* +_make sure that this comes AFTER 'django.contrib.sessions.middleware.SessionMiddleware'_ Step 5: If you are on Django 1.5 or above, you'll need to set your session serializer to "django.contrib.sessions.serializers.PickleSerializer" since we are storing the @@ -51,52 +58,52 @@ Step 6: Set up all the correct options (see below for available options) ## Settings - * OAUTHADMIN_GET_USER: This is function that is given the oauth token and returns - a django.auth.models.User model corresponding to the currently logged-in user. - You can set permissions on this user object and stuff. - * OAUTHADMIN_CLIENT_ID: Your oAuth client ID - * OAUTHADMIN_CLIENT_SECRET: oAuth client secret - * OAUTHADMIN_BASE_URL: The landing point for all oAuth related queries. - * OAUTHADMIN_AUTH_URL: oAuth provider URL - * OAUTHADMIN_TOKEN_URL: oAuth bearer token provider URL - * OAUTHADMIN_PING_INTERVAL (optional, defaults to 300): Minimum number of seconds between ping requests - * OAUTHADMIN_PING: (optional, defaults to None) This optional function takes an oauth token and returns True if it's still valid and False if it's no longer valid (if they have logged out of the oauth server) - * OAUTHADMIN_DEFAULT_NEXT_URL: (optional, defaults to /admin). This optional value is the default page that a successful oauth login process will land you on. - * OAUTHADMIN_SCOPE: (optional, defaults to ['default']). This is a list of authorization scopes. +- OAUTHADMIN_GET_USER: This is function that is given the oauth token and returns + a django.auth.models.User model corresponding to the currently logged-in user. + You can set permissions on this user object and stuff. +- OAUTHADMIN_CLIENT_ID: Your oAuth client ID +- OAUTHADMIN_CLIENT_SECRET: oAuth client secret +- OAUTHADMIN_BASE_URL: The landing point for all oAuth related queries. +- OAUTHADMIN_AUTH_URL: oAuth provider URL +- OAUTHADMIN_TOKEN_URL: oAuth bearer token provider URL +- OAUTHADMIN_PING_INTERVAL (optional, defaults to 300): Minimum number of seconds between ping requests +- OAUTHADMIN_PING: (optional, defaults to None) This optional function takes an oauth token and returns True if it's still valid and False if it's no longer valid (if they have logged out of the oauth server) +- OAUTHADMIN_DEFAULT_NEXT_URL: (optional, defaults to /admin). This optional value is the default page that a successful oauth login process will land you on. +- OAUTHADMIN_SCOPE: (optional, defaults to ['default']). This is a list of authorization scopes. ## Testing If you want to test this app, install the requirements needed for testing: ``` -pip install -r test-requirements.txt +pip install -r requirements-test.txt ``` and then run the tests with the provided script: ``` -./runtests.sh - +tox ``` ## Notes -When the CSRF validation token doesn't match, django-admin-oauth2 will redirect back to the login url so it can retry the authorization step. Sometimes people will bookmark the oauth server with an out-of-date CSRF state string, this is better than showing them an error page. - +When the CSRF validation token doesn't match, django-admin-oauth2 will redirect back to the login url so it can retry the authorization step. Sometimes people will bookmark the oauth server with an out-of-date CSRF state string, this is better than showing them an error page. ## Changelog - * 1.2.0: Allow overriding oauth scope with new parameter, OAUTHADMIN_SCOP - * 1.1.3: Bugfix in adminsite (tabs vs spaces) - * 1.1.2: Add support for django 2 - * 1.1.1: Fix a bug where the new setting wasn't getting read - * 1.1.0: Add new setting: OAUTHADMIN_DEFAULT_NEXT_URL - * 1.0.2: Support python3 - * 1.0.1: Send redirect URI when exchanging grant code for auth token - * 1.0.0: Add support for django 1.8, 1.9, and 1.10. Drop support for python 2.6. Add support for python 3.5. Update test suite to run with tox. - * 0.2.6: Roundtrip original URL accessed through the oauth process so you can go to the URL you requested after the authorization process finishes. Thanks @igorsobreira. - * 0.2.5: Fix bug where failing ping was not invalidating session immediately, only on the second request. - * 0.2.4: Redirect to the login if the grant is invalid - * 0.2.3: Redirect to the login if the state is mismatching - * 0.2.2: Redirect to the login if the state goes missing (sometimes people bookmark the login url) - * 0.2.1: Added tests for the ping function and fixed a bug with the session variable name for the ping timestamp. - * 0.2.0: Added support for pinging the auth server to make sure the token is still valid + +- 1.2.1: Add support for django 4, retain backwards compat with Django 1.x +- 1.2.0: Allow overriding oauth scope with new parameter, OAUTHADMIN_SCOP +- 1.1.3: Bugfix in adminsite (tabs vs spaces) +- 1.1.2: Add support for django 2 +- 1.1.1: Fix a bug where the new setting wasn't getting read +- 1.1.0: Add new setting: OAUTHADMIN_DEFAULT_NEXT_URL +- 1.0.2: Support python3 +- 1.0.1: Send redirect URI when exchanging grant code for auth token +- 1.0.0: Add support for django 1.8, 1.9, and 1.10. Drop support for python 2.6. Add support for python 3.5. Update test suite to run with tox. +- 0.2.6: Roundtrip original URL accessed through the oauth process so you can go to the URL you requested after the authorization process finishes. Thanks @igorsobreira. +- 0.2.5: Fix bug where failing ping was not invalidating session immediately, only on the second request. +- 0.2.4: Redirect to the login if the grant is invalid +- 0.2.3: Redirect to the login if the state is mismatching +- 0.2.2: Redirect to the login if the state goes missing (sometimes people bookmark the login url) +- 0.2.1: Added tests for the ping function and fixed a bug with the session variable name for the ping timestamp. +- 0.2.0: Added support for pinging the auth server to make sure the token is still valid diff --git a/oauthadmin/urls.py b/oauthadmin/urls.py index 5188446..aba5513 100644 --- a/oauthadmin/urls.py +++ b/oauthadmin/urls.py @@ -1,9 +1,14 @@ -from django.conf.urls import url import oauthadmin.views +try: + from django.urls import re_path as url +except ImportError: + from django.conf.urls import url + + urlpatterns = [ - url(r'login/', oauthadmin.views.login), - url(r'callback/', oauthadmin.views.callback), - url(r'logout/', oauthadmin.views.logout), - url(r'logout_redirect/', oauthadmin.views.logout_redirect), + url(r"login/", oauthadmin.views.login), + url(r"callback/", oauthadmin.views.callback), + url(r"logout/", oauthadmin.views.logout), + url(r"logout_redirect/", oauthadmin.views.logout_redirect), ] diff --git a/test-requirements.txt b/requirements-test.in similarity index 80% rename from test-requirements.txt rename to requirements-test.in index 8b1d128..d9ee5d6 100644 --- a/test-requirements.txt +++ b/requirements-test.in @@ -1,7 +1,9 @@ Django==1.10.0 +freezegun==0.3.7 mock==2.0.0 oauthlib==1.1.2 pytest==7.4.3 -requests==2.11.0 requests-oauthlib==0.6.2 -freezegun==0.3.7 +requests==2.11.0 +tox==4.11.4 +virtualenv-pyenv diff --git a/requirements-test.txt b/requirements-test.txt new file mode 100644 index 0000000..8757706 --- /dev/null +++ b/requirements-test.txt @@ -0,0 +1,79 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --resolver=backtracking requirements-test.in +# +cachetools==5.3.2 + # via tox +chardet==5.2.0 + # via tox +colorama==0.4.6 + # via tox +distlib==0.3.7 + # via virtualenv +django==1.10 + # via -r requirements-test.in +exceptiongroup==1.2.0 + # via pytest +filelock==3.13.1 + # via + # tox + # virtualenv +freezegun==0.3.7 + # via -r requirements-test.in +iniconfig==2.0.0 + # via pytest +mock==2.0.0 + # via -r requirements-test.in +oauthlib==1.1.2 + # via + # -r requirements-test.in + # requests-oauthlib +packaging==23.2 + # via + # pyproject-api + # pytest + # tox +pbr==6.0.0 + # via mock +platformdirs==4.0.0 + # via + # tox + # virtualenv +pluggy==1.3.0 + # via + # pytest + # tox +pyenv-inspect==0.3.0 + # via virtualenv-pyenv +pyproject-api==1.6.1 + # via tox +pytest==7.4.3 + # via -r requirements-test.in +python-dateutil==2.8.2 + # via freezegun +requests==2.11.0 + # via + # -r requirements-test.in + # requests-oauthlib +requests-oauthlib==0.6.2 + # via -r requirements-test.in +six==1.16.0 + # via + # freezegun + # mock + # python-dateutil +tomli==2.0.1 + # via + # pyproject-api + # pytest + # tox +tox==4.11.4 + # via -r requirements-test.in +virtualenv==20.24.7 + # via + # tox + # virtualenv-pyenv +virtualenv-pyenv==0.4.0 + # via -r requirements-test.in diff --git a/setup.py b/setup.py index 4783c56..8a96ad8 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ setup( name='django-admin-oauth2', - version='1.2.0', + version='1.2.1', description='A django app that replaces the django admin authentication mechanism by deferring to an oauth2 provider', long_description=README, url='https://github.com/RealGeeks/django-admin-oauth2', diff --git a/test/test_middleware.py b/test/test_middleware.py index 757d9fb..0ea00ed 100644 --- a/test/test_middleware.py +++ b/test/test_middleware.py @@ -7,7 +7,7 @@ def setup_module(mod): global mw global request - mw = OauthAdminSessionMiddleware() + mw = OauthAdminSessionMiddleware(Mock()) request = Mock() request.session = {} diff --git a/tox.ini b/tox.ini index 45a7d71..afdcefa 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,10 @@ [pytest] django_find_project = false -[base] +[testenv] +set_env = + VIRTUALENV_DISCOVERY = pyenv + PYTHONPATH = {toxinidir} deps = mock oauthlib @@ -9,53 +12,110 @@ deps = requests requests-oauthlib freezegun - -[testenv] -setenv = - PYTHONPATH = {toxinidir} +allowlist_externals=./runtests.sh commands=./runtests.sh -[testenv:py27-django16] +; Django 1.10 +[testenv:django110-py27] basepython=python2.7 -deps = django>=1.5,<1.7 - {[base]deps} +deps = django>=1.10,<1.11 + {[testenv]deps} -[testenv:py27-django17] -basepython=python2.7 -deps = django>=1.6,<1.8 - {[base]deps} +[testenv:django110-py36] +basepython=python3.6 +deps = django>=1.10,<1.11 + {[testenv]deps} -[testenv:py27-django18] -basepython=python2.7 -deps = django>=1.7,<1.9 - {[base]deps} +[testenv:django110-py37] +basepython=python3.7 +deps = django>=1.10,<1.11 + {[testenv]deps} -[testenv:py27-django19] +; Django 1.11 +[testenv:django111-py27] basepython=python2.7 -deps = django>=1.8,<1.10 - {[base]deps} +deps = django>=1.11,<1.12 + {[testenv]deps} -[testenv:py27-django110] -basepython=python2.7 -deps = django>=1.9,<1.11 - {[base]deps} +[testenv:django111-py36] +basepython=python3.6 +deps = django>=1.11,<1.12 + {[testenv]deps} + +[testenv:django111-py37] +basepython=python3.7 +deps = django>=1.11,<1.12 + {[testenv]deps} -[testenv:py36-django18] +; Django 2.2 +[testenv:django22-py35] basepython=python3.6 -deps = django>=1.7,<1.9 - {[base]deps} +deps = django>=2.2,<3.0 + {[testenv]deps} -[testenv:py36-django19] +[testenv:django22-py36] basepython=python3.6 -deps = django>=1.8,<1.10 - {[base]deps} +deps = django>=2.2,<3.0 + {[testenv]deps} -[testenv:py36-django110] +[testenv:django22-py37] basepython=python3.6 -deps = django>=1.9,<1.11 - {[base]deps} +deps = django>=2.2,<3.0 + {[testenv]deps} -[testenv:py36-django201] +[testenv:django22-py38] basepython=python3.6 -deps = django>=1.9,<2.1 - {[base]deps} +deps = django>=2.2,<3.0 + {[testenv]deps} + +[testenv:django22-py39] +basepython=python3.6 +deps = django>=2.2,<3.0 + {[testenv]deps} + +; Django 3.2 +[testenv:django32-py36] +basepython=python3.6 +deps = django>=3.2,<4.0 + {[testenv]deps} + +[testenv:django32-py37] +basepython=python3.7 +deps = django>=3.2,<4.0 + {[testenv]deps} + +[testenv:django32-py38] +basepython=python3.8 +deps = django>=3.2,<4.0 + {[testenv]deps} + +[testenv:django32-py39] +basepython=python3.9 +deps = django>=3.2,<4.0 + {[testenv]deps} + +[testenv:django32-py310] +basepython=python3.10 +deps = django>=3.2,<4.0 + {[testenv]deps} + +; Django 4.2 +[testenv:django42-py38] +basepython=python3.8 +deps = django>=4.2,<5.0 + {[testenv]deps} + +[testenv:django42-py39] +basepython=python3.9 +deps = django>=4.2,<5.0 + {[testenv]deps} + +[testenv:django42-py310] +basepython=python3.10 +deps = django>=4.2,<5.0 + {[testenv]deps} + +[testenv:django42-py311] +basepython=python3.11 +deps = django>=4.2,<5.0 + {[testenv]deps}