Merge pull request #90 from nitmir/dev

Update to version 2.1.0

v2.1.0 - 2024-08-18
===================

Added
-----

* Support for Django 4.2
* Allow forms to be overridden from settings

Deprecated
----------

* Make the crypt module optional and deprecate it's usage.
  The python stdlid crypt module is deprecated since version 3.11 and
  will be removed in version 3.13. Check for the availability of the
  crypt module.
  All password checks using the crypt module will stop to work on
  python 3.13.
This commit is contained in:
Valentin Samir 2024-08-18 13:21:16 +02:00 committed by GitHub
commit 9d04a7bd74
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 238 additions and 132 deletions

80
.github/workflows/github-actions.yml vendored Normal file
View file

@ -0,0 +1,80 @@
name: django-cas-server
run-name: ${{ github.actor }} is running django-cas-server CI tests
on: [push]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
flake8:
runs-on: ubuntu-latest
container:
image: python:bookworm
steps:
- uses: actions/checkout@v3
- run: pip install tox
- run: tox -e flake8
check_rst:
runs-on: ubuntu-latest
container:
image: python:bookworm
steps:
- uses: actions/checkout@v3
- run: pip install tox
- run: tox -e check_rst
coverage:
runs-on: ubuntu-latest
container:
image: python:bookworm
steps:
- uses: actions/checkout@v3
- run: pip install tox
- run: tox -e coverage
env:
COVERAGE_TOKEN: ${{ secrets.COVERAGE_TOKEN }}
tests:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
arch: ["amd64", "ppc64le"]
tox:
# REHL 7 support and Ubuntu bionic
- python: "3.6"
env: "py36-django111"
# RHEL 8 support
- python: "3.6"
env: "py36-django22"
# Debian buster support
- python: "3.7"
env: "py37-django111"
# Ubuntu focal support
- python: "3.8"
env: "py38-django22"
# Debian bullseye
- python: "3.9"
env: py39-django22
# Ubuntu jammy
- python: "3.10"
env: "py310-django32"
# Debian bookworm
- python: "3.11"
env: py311-django32
# Django additional supported version
- python: "3.9"
env: py39-django42
- python: "3.10"
env: py310-django42
- python: "3.11"
env: py311-django42
steps:
- uses: actions/checkout@v3
- if: matrix.arch != 'amd64'
name: "Install docker multiarch support"
run: |
sudo apt-get update -y
sudo apt-get install -y qemu qemu-user-static
sudo docker run --rm --privileged multiarch/qemu-user-static --reset -p yes --credential yes
- name: "Check docker arch ${{ matrix.arch }} support"
run: sudo docker run --platform linux/${{ matrix.arch }} --rm ${{ matrix.arch }}/python:${{ matrix.tox.python }} /bin/bash -c "echo -n \"Running with arch \"; uname -m;"
- name: "Run tests on arch ${{ matrix.arch }}"
run: sudo docker run --platform linux/${{ matrix.arch }} --rm -v $(pwd):$(pwd) ${{ matrix.arch }}/python:${{ matrix.tox.python }} /bin/bash -c "cd $(pwd); uname -m; pip install tox; tox -e ${{ matrix.tox.env }}"

View file

@ -1,67 +0,0 @@
dist: focal
language: python
matrix:
include:
# generic checks
- python: "3.9"
env: TOX_ENV=flake8
- python: "3.9"
env: TOX_ENV=check_rst
- python: "3.9"
env: TOX_ENV=coverage
# REHL 7 support and Ubuntu bionic
- python: "3.6"
env: TOX_ENV=py36-django111
- python: "3.6"
env: TOX_ENV=py36-django111
arch: ppc64le
# RHEL 8 support
- python: "3.6"
env: TOX_ENV=py36-django22
- python: "3.6"
env: TOX_ENV=py36-django22
arch: ppc64le
# Debian buster support
- python: "3.7"
env: TOX_ENV=py37-django111
- python: "3.7"
env: TOX_ENV=py37-django111
arch: ppc64le
# Ubuntu focal and Ubuntu groovy support
- python: "3.8"
env: TOX_ENV=py38-django22
- python: "3.8"
env: TOX_ENV=py38-django22
arch: ppc64le
# Debian bullseye and Ubuntu hirsute and impish support
- python: "3.9"
env: TOX_ENV=py39-django22
- python: "3.9"
env: TOX_ENV=py39-django22
arch: ppc64le
# Ubuntu jammy and kinetic support
- python: "3.10"
env: TOX_ENV=py310-django32
- python: "3.10"
env: TOX_ENV=py310-django32
arch: ppc64le
# Django additional supported version
- python: "3.9"
env: TOX_ENV=py39-django32
- python: "3.10"
env: TOX_ENV=py310-django40
- python: "3.10"
env: TOX_ENV=py310-django41
cache:
directories:
- $HOME/.cache/pip/http/
- $HOME/build/nitmir/django-cas-server/.tox/$TOX_ENV/
install:
- travis_retry pip install setuptools --upgrade
- pip install tox $PKGS
- pip freeze
script:
- tox -e $TOX_ENV
after_script:
- cat tox_log/*.log

View file

@ -6,6 +6,26 @@ All notable changes to this project will be documented in this file.
.. contents:: Table of Contents
:depth: 2
v2.1.0 - 2024-08-18
===================
Added
-----
* Support for Django 4.2
* Allow forms to be overridden from settings
Deprecated
----------
* Make the crypt module optional and deprecate it's usage.
The python stdlid crypt module is deprecated since version 3.11 and
will be removed in version 3.13. Check for the availability of the
crypt module.
All password checks using the crypt module will stop to work on
python 3.13.
v2.0.0 - 2022-10-17
===================

View file

@ -1,6 +1,11 @@
.PHONY: build dist docs
VERSION=`python3 setup.py -V`
WHL_FILES := $(wildcard dist/*.whl)
WHL_ASC := $(WHL_FILES:=.asc)
DIST_FILE := $(wildcard dist/*.tar.gz)
DIST_ASC := $(DIST_FILE:=.asc)
build:
python3 setup.py build
@ -35,10 +40,11 @@ clean_all: clean clean_tox clean_test_venv clean_docs clean_eggs
dist:
python3 setup.py sdist
python3 setup.py bdist_wheel
test_venv/bin/python:
python3 -m venv test_venv
test_venv/bin/pip install -U --requirement requirements-dev.txt 'Django>=3.2,<3.3'
test_venv/bin/pip install -U --requirement requirements-dev.txt 'Django>=4.2,<4.3'
test_venv/cas/manage.py: test_venv
mkdir -p test_venv/cas
@ -46,8 +52,8 @@ test_venv/cas/manage.py: test_venv
ln -s ../../cas_server test_venv/cas/cas_server
sed -i "s/'django.contrib.staticfiles',/'django.contrib.staticfiles',\n 'cas_server',/" test_venv/cas/cas/settings.py
sed -i "s/'django.middleware.clickjacking.XFrameOptionsMiddleware',/'django.middleware.clickjacking.XFrameOptionsMiddleware',\n 'django.middleware.locale.LocaleMiddleware',/" test_venv/cas/cas/settings.py
sed -i 's/from django.conf.urls import url/from django.conf.urls import url, include/' test_venv/cas/cas/urls.py
sed -i "s@url(r'^admin/', admin.site.urls),@url(r'^admin/', admin.site.urls),\n url(r'^', include('cas_server.urls', namespace='cas_server')),@" test_venv/cas/cas/urls.py
sed -i 's/from django.urls import path/from django.urls import path, include/' test_venv/cas/cas/urls.py
sed -i "s@path('admin/', admin.site.urls),@path('admin/', admin.site.urls),\n path('', include('cas_server.urls', namespace='cas_server')),@" test_venv/cas/cas/urls.py
test_venv/bin/python test_venv/cas/manage.py migrate
test_venv/bin/python test_venv/cas/manage.py createsuperuser
@ -71,5 +77,13 @@ test_venv/bin/sphinx-build: test_venv
docs: test_venv/bin/sphinx-build
bash -c "source test_venv/bin/activate; cd docs; make html"
publish_pypi_release:
python3 setup.py sdist bdist_wheel upload --sign
sign_release: $(WHL_ASC) $(DIST_ASC)
dist/%.asc:
gpg --detach-sign -a $(@:.asc=)
test_venv/bin/twine: test_venv
test_venv/bin/pip install twine
publish_pypi_release: test_venv test_venv/bin/twine dist sign_release
test_venv/bin/twine upload --sign dist/*

View file

@ -21,7 +21,7 @@ Features
* Possibility to rename/rewrite attributes per service
* Possibility to require some attribute values per service
* Federated mode between multiple CAS
* Supports Django 1.11, 2.2, 3.2, 4.0 and 4.1
* Supports Django 1.11, 2.2, 3.2, 4.2
* Supports Python 3.6+
Dependencies
@ -29,7 +29,7 @@ Dependencies
``django-cas-server`` depends on the following python packages:
* Django >= 1.11 < 4.2
* Django >= 1.11 < 4.3
* requests >= 2.4
* requests_futures >= 0.9.5
* lxml >= 3.4
@ -146,12 +146,12 @@ Quick start
2. Include the cas_server URLconf in your project urls.py like this::
from django.conf.urls import url, include
from django.urls import path, include
urlpatterns = [
url(r'^admin/', admin.site.urls),
path('admin/', admin.site.urls),
...
url(r'^cas/', include('cas_server.urls', namespace="cas_server")),
path('cas/', include('cas_server.urls', namespace="cas_server")),
]
3. Run ``python manage.py migrate`` to create the cas_server models.
@ -352,6 +352,19 @@ Tickets miscellaneous settings
* ``CAS_PROXY_GRANTING_TICKET_PREFIX``: Prefix of proxy granting ticket. The default is ``"PGT"``.
* ``CAS_PROXY_GRANTING_TICKET_IOU_PREFIX``: Prefix of proxy granting ticket IOU. The default is ``"PGTIOU"``.
Forms settings
--------------
* ``CAS_USER_CREDENTIAL_FORM``: A dotted path to a form or a form used on the login page to retrieve
user credentials. The default is ``"cas_server.forms.UserCredential"``.
* ``CAS_WARN_FORM``: A dotted path to a form or a form used on warn page before emitting a ticket.
The default is ``"cas_server.forms.WarnForm"``.
* ``CAS_FEDERATE_SELECT_FORM``: A dotted path to a form or a form used on the login page to select
another CAS in federated mode. The default is ``"cas_server.forms.FederateSelect"``
* ``CAS_FEDERATE_USER_CREDENTIAL_FORM``: A dotted path to a form or a form used on the login page in
federated mode. The default is ``"cas_server.forms.FederateUserCredential"``
* ``CAS_TICKET_FORM``: A dotted path to a form or a form for Tickets in the admin interface.
The default is ``"cas_server.forms.TicketForm"``
Mysql backend settings
----------------------
@ -370,16 +383,17 @@ Only useful if you are using the mysql authentication backend:
* ``CAS_SQL_PASSWORD_CHECK``: The method used to check the user password. Must be one of the following:
* ``"crypt"`` (see <https://en.wikipedia.org/wiki/Crypt_(C)>), the password in the database
should begin with $
should begin with $. This method is deprecated and will stop to work in python 3.13.
* ``"ldap"`` (see https://tools.ietf.org/id/draft-stroeder-hashed-userpassword-values-01.html)
the password in the database must begin with one of {MD5}, {SMD5}, {SHA}, {SSHA}, {SHA256},
{SSHA256}, {SHA384}, {SSHA384}, {SHA512}, {SSHA512}, {CRYPT}.
{SSHA256}, {SHA384}, {SSHA384}, {SHA512}, {SSHA512}, {CRYPT}. {CRYPT} is deprecated
and will stop to work in python 3.13.
* ``"hex_HASH_NAME"`` with ``HASH_NAME`` in md5, sha1, sha224, sha256, sha384, sha512.
The hashed password in the database is compared to the hexadecimal digest of the clear
password hashed with the corresponding algorithm.
* ``"plain"``, the password in the database must be in clear.
The default is ``"crypt"``.
The default is ``"crypt"``. This default is deprecated and will stop to work in python 3.13.
Sql backend settings
@ -396,16 +410,18 @@ used by the sql backend.
* ``CAS_SQL_PASSWORD_CHECK``: The method used to check the user password. Must be one of the following:
* ``"crypt"`` (see <https://en.wikipedia.org/wiki/Crypt_(C)>), the password in the database
should begin with $
should begin with $. This method is deprecated and will stop to work in python 3.13.
* ``"ldap"`` (see https://tools.ietf.org/id/draft-stroeder-hashed-userpassword-values-01.html)
the password in the database must begin with one of {MD5}, {SMD5}, {SHA}, {SSHA}, {SHA256},
{SSHA256}, {SHA384}, {SSHA384}, {SHA512}, {SSHA512}, {CRYPT}.
{SSHA256}, {SHA384}, {SSHA384}, {SHA512}, {SSHA512}, {CRYPT}. {CRYPT} is deprecated
and will stop to work in python 3.13.
* ``"hex_HASH_NAME"`` with ``HASH_NAME`` in md5, sha1, sha224, sha256, sha384, sha512.
The hashed password in the database is compared to the hexadecimal digest of the clear
password hashed with the corresponding algorithm.
* ``"plain"``, the password in the database must be in clear.
The default is ``"crypt"``.
The default is ``"crypt"``. This default is deprecated and will stop to work in python 3.13.
* ``CAS_SQL_PASSWORD_CHARSET``: Charset the SQL users passwords was hash with. This is needed to
encode the user submitted password before hashing it for comparison. The default is ``"utf-8"``.
@ -426,10 +442,11 @@ Only useful if you are using the ldap authentication backend:
* ``CAS_LDAP_PASSWORD_CHECK``: The method used to check the user password. Must be one of the following:
* ``"crypt"`` (see <https://en.wikipedia.org/wiki/Crypt_(C)>), the password in the database
should begin with $
should begin with $. This method is deprecated and will stop to work in python 3.13.
* ``"ldap"`` (see https://tools.ietf.org/id/draft-stroeder-hashed-userpassword-values-01.html)
the password in the database must begin with one of {MD5}, {SMD5}, {SHA}, {SSHA}, {SHA256},
{SSHA256}, {SHA384}, {SSHA384}, {SHA512}, {SSHA512}, {CRYPT}.
{SSHA256}, {SHA384}, {SSHA384}, {SHA512}, {SSHA512}, {CRYPT}. {CRYPT} is deprecated and
will stop to work in python 3.13.
* ``"hex_HASH_NAME"`` with ``HASH_NAME`` in md5, sha1, sha224, sha256, sha384, sha512.
The hashed password in the database is compared to the hexadecimal digest of the clear
password hashed with the corresponding algorithm.

View file

@ -15,7 +15,7 @@ except ModuleNotFoundError:
django = None
#: version of the application
VERSION = '2.0.0'
VERSION = '2.1.0'
if django is None or django.VERSION < (3, 2):
#: path the the application configuration class

View file

@ -15,7 +15,7 @@ from django.contrib import admin
from .models import ServiceTicket, ProxyTicket, ProxyGrantingTicket, User, ServicePattern
from .models import Username, ReplaceAttributName, ReplaceAttributValue, FilterAttributValue
from .models import FederatedIendityProvider, FederatedUser, UserAttributes
from .forms import TicketForm
from .utils import import_attr
class BaseInlines(admin.TabularInline):
@ -36,7 +36,7 @@ class UserAdminInlines(BaseInlines):
Base class for inlines in :class:`UserAdmin` interface
"""
#: The form :class:`TicketForm<cas_server.forms.TicketForm>` used to display tickets.
form = TicketForm
form = import_attr(settings.CAS_TICKET_FORM)
#: Fields to display on a object that are read only (not editable).
readonly_fields = (
'validate', 'service', 'service_pattern',

View file

@ -246,6 +246,16 @@ CAS_REMOVE_DJANGO_CSRF_COOKIE_ON_LOGOUT = False
#: :class:`bool` If `True` Django language cookie will be removed on logout from CAS server
CAS_REMOVE_DJANGO_LANGUAGE_COOKIE_ON_LOGOUT = False
#: A dotted path to a form or a form used on the login page to retrieve user credentials
CAS_USER_CREDENTIAL_FORM = "cas_server.forms.UserCredential"
#: A dotted path to a form or a form used on warn page before emitting a ticket
CAS_WARN_FORM = "cas_server.forms.WarnForm"
#: A dotted path to a form or a form used on the login page to select another CAS in federated mode
CAS_FEDERATE_SELECT_FORM = "cas_server.forms.FederateSelect"
#: A dotted path to a form or a form used on the login page in federated mode
CAS_FEDERATE_USER_CREDENTIAL_FORM = "cas_server.forms.FederateUserCredential"
#: A dotted path to a form or a form for Tickets in the admin interface
CAS_TICKET_FORM = "cas_server.forms.TicketForm"
GLOBALS = globals().copy()
for name, default_value in GLOBALS.items():
@ -253,7 +263,7 @@ for name, default_value in GLOBALS.items():
if name.startswith("CAS_"):
# get the current setting value, falling back to default_value
value = getattr(settings, name, default_value)
# set the setting value to its value if defined, ellse to the default_value.
# set the setting value to its value if defined, else to the default_value.
setattr(settings, name, value)
# Allow the user defined CAS_COMPONENT_URLS to omit not changed values

View file

@ -63,6 +63,9 @@ class CheckPasswordCase(TestCase):
def test_crypt(self):
"""test the crypt auth method"""
# Only run test if crypt is available
if utils.crypt is None:
return
salts = ["$6$UVVAQvrMyXMF3FF3", "aa"]
hashed_password1 = []
for salt in salts:

View file

@ -30,13 +30,17 @@ import random
import string
import json
import hashlib
import crypt
import base64
import six
import requests
import time
import logging
import binascii
# The crypt module is deprecated and will be removed in version 3.13
try:
import crypt
except ImportError:
crypt = None
from importlib import import_module
from datetime import datetime, timedelta
@ -411,6 +415,8 @@ def crypt_salt_is_valid(salt):
:return: ``True`` if ``salt`` is a valid crypt salt on this system, ``False`` otherwise
:rtype: bool
"""
if crypt is None:
return False
if len(salt) < 2:
return False
else:
@ -567,6 +573,8 @@ class LdapHashUserPassword(object):
cls._schemes_to_hash[scheme](password + salt).digest() + salt
)
except KeyError:
if crypt is None:
raise cls.BadScheme("Crypt is not available on the system")
if six.PY3:
password = password.decode(charset)
salt = salt.decode(charset)
@ -646,6 +654,8 @@ def check_password(method, password, hashed_password, charset):
if method == "plain":
return password == hashed_password
elif method == "crypt":
if crypt is None:
raise ValueError("Crypt is not available on the system")
if hashed_password.startswith(b'$'):
salt = b'$'.join(hashed_password.split(b'$', 3)[:-1])
elif hashed_password.startswith(b'_'): # pragma: no cover old BSD format not supported

View file

@ -44,10 +44,9 @@ from lxml import etree
from datetime import timedelta
import cas_server.utils as utils
import cas_server.forms as forms
import cas_server.models as models
from .utils import json_response
from .utils import json_response, import_attr
from .models import Ticket, ServiceTicket, ProxyTicket, ProxyGrantingTicket
from .models import ServicePattern, FederatedIendityProvider, FederatedUser
from .federate import CASFederateValidateUser
@ -302,7 +301,7 @@ class FederateAuth(CsrfExemptView):
.process_view(request, None, (), {})
if reason is not None: # pragma: no cover (csrf checks are disabled during tests)
return reason # Failed the test, stop here.
form = forms.FederateSelect(request.POST)
form = import_attr(settings.CAS_FEDERATE_SELECT_FORM)(request.POST)
if form.is_valid():
params = utils.copy_params(
request.POST,
@ -677,14 +676,16 @@ class LoginView(View, LogoutMixin):
form_initial['username'] = self.username
form_initial['password'] = self.ticket
form_initial['ticket'] = self.ticket
self.form = forms.FederateUserCredential(
self.form = import_attr(settings.CAS_FEDERATE_USER_CREDENTIAL_FORM)(
values,
initial=form_initial
)
else:
self.form = forms.FederateSelect(values, initial=form_initial)
self.form = import_attr(
settings.CAS_FEDERATE_SELECT_FORM
)(values, initial=form_initial)
else:
self.form = forms.UserCredential(
self.form = import_attr(settings.CAS_USER_CREDENTIAL_FORM)(
values,
initial=form_initial
)
@ -720,7 +721,7 @@ class LoginView(View, LogoutMixin):
data = {"status": "error", "detail": "confirmation needed"}
return json_response(self.request, data)
else:
warn_form = forms.WarnForm(initial={
warn_form = import_attr(settings.CAS_WARN_FORM)(initial={
'service': self.service,
'renew': self.renew,
'gateway': self.gateway,
@ -999,7 +1000,7 @@ class Auth(CsrfExemptView):
return HttpResponse(u"no\n", content_type="text/plain; charset=utf-8")
if not username or not password or not service:
return HttpResponse(u"no\n", content_type="text/plain; charset=utf-8")
form = forms.UserCredential(
form = import_attr(settings.CAS_USER_CREDENTIAL_FORM)(
request.POST,
initial={
'service': service,

View file

@ -1,4 +1,4 @@
Django >= 1.11,<4.2
Django >= 1.11,<4.3
setuptools>=5.5
requests>=2.4
requests_futures>=0.9.5

View file

@ -33,20 +33,20 @@ if __name__ == '__main__':
'Framework :: Django',
'Framework :: Django :: 1.11',
'Framework :: Django :: 2.2',
'Framework :: Django :: 3.1',
'Framework :: Django :: 3.2',
'Framework :: Django :: 4.2',
'Intended Audience :: Developers',
'Intended Audience :: System Administrators',
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
@ -61,7 +61,7 @@ if __name__ == '__main__':
},
keywords=['django', 'cas', 'cas3', 'server', 'sso', 'single sign-on', 'authentication', 'auth'],
install_requires=[
'Django >= 1.11,<4.2', 'requests >= 2.4', 'requests_futures >= 0.9.5',
'Django >= 1.11,<4.3', 'requests >= 2.4', 'requests_futures >= 0.9.5',
'lxml >= 3.4', 'six >= 1'
],
url="https://github.com/nitmir/django-cas-server",

78
tox.ini
View file

@ -8,6 +8,7 @@ envlist=
py3-django32,
py3-django40,
py3-django41,
py3-django42,
##################
# generic config #
@ -16,6 +17,7 @@ envlist=
[flake8]
max-line-length=100
exclude=migrations
allowlist_externals={[post_cmd]allowlist_external}
[base]
deps =
@ -26,16 +28,18 @@ commands=
find {toxworkdir} -name '*.pyc' -delete
mkdir -p {toxinidir}/tox_logs/
bash -c "mv {toxworkdir}/{envname}/log/* {toxinidir}/tox_logs/"
whitelist_externals=
allowlist_externals=
find
bash
mkdir
[testenv]
setenv=
PYTHONWARNINGS=always
commands=
py.test -rw {posargs:cas_server/tests/}
{[post_cmd]commands}
whitelist_externals={[post_cmd]whitelist_externals}
allowlist_externals={[post_cmd]allowlist_externals}
###################
# genercic checks #
@ -48,7 +52,7 @@ skip_install=True
commands=
flake8 {toxinidir}/cas_server
{[post_cmd]commands}
whitelist_externals={[post_cmd]whitelist_externals}
allowlist_externals={[post_cmd]allowlist_externals}
[testenv:check_rst]
basepython=python3
@ -57,10 +61,10 @@ deps=
Pygments
skip_install=True
commands=
rst2html.py --strict {toxinidir}/README.rst /dev/null
rst2html.py --halt=warning {toxinidir}/CHANGELOG.rst /dev/null
rst2html --strict {toxinidir}/README.rst /dev/null
rst2html --halt=warning {toxinidir}/CHANGELOG.rst /dev/null
{[post_cmd]commands}
whitelist_externals={[post_cmd]whitelist_externals}
allowlist_externals={[post_cmd]allowlist_externals}
[testenv:coverage]
basepython=python3
@ -75,9 +79,13 @@ deps=
skip_install=True
commands=
py.test --cov=cas_server --cov-report term --cov-report html
git config --global --add safe.directory "{toxinidir}"
{toxinidir}/.update_coverage "{toxinidir}" "django-cas-server"
{[post_cmd]commands}
whitelist_externals={[post_cmd]whitelist_externals}
allowlist_externals=
{toxinidir}/.update_coverage
git
{[post_cmd]allowlist_externals}
####################
@ -130,6 +138,12 @@ deps =
Django>=4.1,<4.2
{[base]deps}
[testenv:py3-django42]
basepython=python3
deps =
Django>=4.2,<4.3
{[base]deps}
#########################
# Debian strech support #
#########################
@ -170,9 +184,9 @@ deps =
Django>=1.11,<1.12
{[base]deps}
##########################################
# Ubuntu focal and Ubuntu groovy support #
##########################################
########################
# Ubuntu focal support #
########################
[testenv:py38-django22]
basepython=python3.8
@ -180,9 +194,9 @@ deps =
Django>=2.2,<3.0
{[base]deps}
#########################################################
# Debian bullseye and Ubuntu hirsute and impish support #
#########################################################
###################
# Debian bullseye #
###################
[testenv:py39-django22]
basepython=python3.9
@ -190,9 +204,9 @@ deps =
Django>=2.2,<3.0
{[base]deps}
####################################
# Ubuntu jammy and kinetic support #
####################################
################
# Ubuntu jammy #
################
[testenv:py310-django32]
basepython=python3.10
@ -201,31 +215,35 @@ deps =
{[base]deps}
###########################
# Debian bookworm support #
###########################
[testenv:py311-django32]
basepython=python3.11
deps =
Django>=3.2,<3.3
{[base]deps}
#######################################
# Django additional supported version #
#######################################
[testenv:py39-django31]
[testenv:py39-django42]
basepython=python3.9
deps =
Django>=3.1,<3.2
Django>=4.2,<4.3
{[base]deps}
[testenv:py39-django32]
basepython=python3.9
deps =
Django>=3.2,<3.3
{[base]deps}
[testenv:py310-django40]
[testenv:py310-django42]
basepython=python3.10
deps =
Django>=4.0,<4.1
Django>=4.2,<4.3
{[base]deps}
[testenv:py310-django41]
basepython=python3.10
[testenv:py311-django42]
basepython=python3.11
deps =
Django>=4.1,<4.2
Django>=4.2,<4.3
{[base]deps}