├── cas_server ├── tests │ ├── __init__.py │ ├── urls.py │ ├── auth.py │ ├── test_templatetags.py │ ├── settings.py │ ├── mixin.py │ └── test_utils.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ ├── cas_clean_federate.py │ │ ├── cas_clean_sessions.py │ │ └── cas_clean_tickets.py ├── migrations │ └── __init__.py ├── templatetags │ ├── __init__.py │ └── cas_server.py ├── locale │ ├── pt_BR │ │ ├── django.mo │ │ └── django.po │ ├── fr │ │ └── LC_MESSAGES │ │ │ └── django.mo │ ├── nl │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ └── zh_Hans │ │ └── LC_MESSAGES │ │ ├── django.mo │ │ └── django.po ├── static │ └── cas_server │ │ ├── logo.png │ │ ├── favicon.ico │ │ ├── bs4 │ │ ├── fa-lock.svg │ │ ├── fa-user.svg │ │ └── styles.css │ │ ├── functions.js │ │ ├── cas.js │ │ ├── bs3 │ │ └── styles.css │ │ └── logo.svg ├── templates │ └── cas_server │ │ ├── serviceValidateError.xml │ │ ├── bs3 │ │ ├── logout.html │ │ ├── warn.html │ │ ├── form.html │ │ ├── logged.html │ │ ├── login.html │ │ └── base.html │ │ ├── bs4 │ │ ├── logout.html │ │ ├── warn.html │ │ ├── logged.html │ │ ├── form.html │ │ ├── login.html │ │ └── base.html │ │ ├── proxy.xml │ │ ├── samlValidateError.xml │ │ ├── serviceValidate.xml │ │ └── samlValidate.xml ├── __init__.py ├── apps.py ├── urls.py ├── federate.py ├── admin.py ├── forms.py └── default_settings.py ├── docs ├── README.rst ├── CHANGELOG.rst ├── requirements.txt ├── package │ ├── modules.rst │ ├── cas_server.forms.rst │ ├── cas_server.apps.rst │ ├── cas_server.admin.rst │ ├── cas_server.models.rst │ ├── cas_server.cas.rst │ ├── cas_server.auth.rst │ ├── cas_server.urls.rst │ ├── cas_server.utils.rst │ ├── cas_server.views.rst │ ├── cas_server.federate.rst │ ├── cas_server.default_settings.rst │ ├── cas_server.templatetags.cas_server.rst │ ├── cas_server.templatetags.rst │ └── cas_server.rst ├── index.rst ├── Makefile ├── make.bat └── conf.py ├── requirements.txt ├── pytest.ini ├── requirements-dev.txt ├── setup.cfg ├── .gitignore ├── .coveragerc ├── MANIFEST.in ├── .readthedocs.yaml ├── .update_coverage ├── .gitlab-ci.yml ├── Makefile ├── setup.py ├── .github └── workflows │ └── github-actions.yml └── tox.ini /cas_server/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cas_server/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cas_server/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cas_server/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cas_server/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/README.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | -------------------------------------------------------------------------------- /docs/CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CHANGELOG.rst 2 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | Sphinx 2 | sphinx_rtd_theme 3 | -r ../requirements.txt 4 | -------------------------------------------------------------------------------- /docs/package/modules.rst: -------------------------------------------------------------------------------- 1 | cas_server 2 | ========== 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | cas_server 8 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django >= 1.11,<5.3 2 | setuptools>=5.5 3 | requests>=2.4 4 | requests_futures>=0.9.5 5 | lxml>=3.4 6 | -------------------------------------------------------------------------------- /cas_server/locale/pt_BR/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitmir/django-cas-server/HEAD/cas_server/locale/pt_BR/django.mo -------------------------------------------------------------------------------- /cas_server/static/cas_server/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitmir/django-cas-server/HEAD/cas_server/static/cas_server/logo.png -------------------------------------------------------------------------------- /cas_server/static/cas_server/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitmir/django-cas-server/HEAD/cas_server/static/cas_server/favicon.ico -------------------------------------------------------------------------------- /cas_server/locale/fr/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitmir/django-cas-server/HEAD/cas_server/locale/fr/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /cas_server/locale/nl/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitmir/django-cas-server/HEAD/cas_server/locale/nl/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /docs/package/cas_server.forms.rst: -------------------------------------------------------------------------------- 1 | cas_server.forms module 2 | ======================= 3 | 4 | .. automodule:: cas_server.forms 5 | :members: 6 | -------------------------------------------------------------------------------- /cas_server/locale/zh_Hans/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitmir/django-cas-server/HEAD/cas_server/locale/zh_Hans/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /docs/package/cas_server.apps.rst: -------------------------------------------------------------------------------- 1 | cas_server.apps module 2 | ====================== 3 | 4 | .. automodule:: cas_server.apps 5 | :members: 6 | :undoc-members: 7 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | testpaths = cas_server/tests/ 3 | DJANGO_SETTINGS_MODULE = cas_server.tests.settings 4 | norecursedirs = .* build dist docs 5 | pythonpath = . 6 | -------------------------------------------------------------------------------- /docs/package/cas_server.admin.rst: -------------------------------------------------------------------------------- 1 | cas_server.admin module 2 | ======================= 3 | 4 | .. automodule:: cas_server.admin 5 | :members: 6 | :undoc-members: 7 | 8 | -------------------------------------------------------------------------------- /docs/package/cas_server.models.rst: -------------------------------------------------------------------------------- 1 | cas_server.models module 2 | ======================== 3 | 4 | .. automodule:: cas_server.models 5 | :members: 6 | :undoc-members: 7 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | setuptools>=5.5 2 | requests>=2.4 3 | requests_futures>=0.9.5 4 | lxml>=3.4 5 | tox>=1.8.1 6 | pytest>=7 7 | pytest-django>=2.8.0 8 | pytest-cov>=2.2.1 9 | -------------------------------------------------------------------------------- /docs/package/cas_server.cas.rst: -------------------------------------------------------------------------------- 1 | cas_server.cas module 2 | ===================== 3 | 4 | .. automodule:: cas_server.cas 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/package/cas_server.auth.rst: -------------------------------------------------------------------------------- 1 | cas_server.auth module 2 | ====================== 3 | 4 | .. automodule:: cas_server.auth 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/package/cas_server.urls.rst: -------------------------------------------------------------------------------- 1 | cas_server.urls module 2 | ====================== 3 | 4 | .. automodule:: cas_server.urls 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/package/cas_server.utils.rst: -------------------------------------------------------------------------------- 1 | cas_server.utils module 2 | ======================= 3 | 4 | .. automodule:: cas_server.utils 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/package/cas_server.views.rst: -------------------------------------------------------------------------------- 1 | cas_server.views module 2 | ======================= 3 | 4 | .. automodule:: cas_server.views 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/package/cas_server.federate.rst: -------------------------------------------------------------------------------- 1 | cas_server.federate module 2 | ========================== 3 | 4 | .. automodule:: cas_server.federate 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.rst 3 | 4 | [egg_info] 5 | tag_build = 6 | tag_date = 0 7 | tag_svn_revision = 0 8 | 9 | [aliases] 10 | test=pytest 11 | 12 | [bdist_wheel] 13 | universal = 1 14 | -------------------------------------------------------------------------------- /cas_server/templates/cas_server/serviceValidateError.xml: -------------------------------------------------------------------------------- 1 | 2 | {{msg}} 3 | 4 | -------------------------------------------------------------------------------- /docs/package/cas_server.default_settings.rst: -------------------------------------------------------------------------------- 1 | cas_server.default_settings module 2 | ================================== 3 | 4 | .. automodule:: cas_server.default_settings 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /cas_server/templates/cas_server/bs3/logout.html: -------------------------------------------------------------------------------- 1 | {% extends "cas_server/bs3/base.html" %} 2 | {% load static %} 3 | {% load i18n %} 4 | {% block content %} 5 | 6 | {% endblock %} 7 | 8 | -------------------------------------------------------------------------------- /cas_server/templates/cas_server/bs4/logout.html: -------------------------------------------------------------------------------- 1 | {% extends "cas_server/bs4/base.html" %} 2 | {% load static %} 3 | {% load i18n %} 4 | {% block content %} 5 | 6 | {% endblock %} 7 | 8 | -------------------------------------------------------------------------------- /cas_server/templates/cas_server/proxy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ticket}} 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/package/cas_server.templatetags.cas_server.rst: -------------------------------------------------------------------------------- 1 | cas_server.templatetags.cas_server module 2 | ========================================= 3 | 4 | .. automodule:: cas_server.templatetags.cas_server 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.egg-info 3 | *~ 4 | *.swp 5 | 6 | build/ 7 | cas/ 8 | dist/ 9 | db.sqlite3 10 | manage.py 11 | coverage.xml 12 | docs/_build/ 13 | docs/django.inv 14 | docs/python.inv 15 | 16 | .tox 17 | test_venv 18 | .coverage 19 | htmlcov/ 20 | tox_logs/ 21 | .cache/ 22 | .eggs/ 23 | -------------------------------------------------------------------------------- /docs/package/cas_server.templatetags.rst: -------------------------------------------------------------------------------- 1 | cas_server.templatetags package 2 | =============================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | .. toctree:: 8 | 9 | cas_server.templatetags.cas_server 10 | 11 | Module contents 12 | --------------- 13 | 14 | .. automodule:: cas_server.templatetags 15 | :members: 16 | :undoc-members: 17 | :show-inheritance: 18 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | source = cas_server 4 | omit = 5 | cas_server/migrations* 6 | cas_server/management/* 7 | cas_server/tests/* 8 | cas_server/cas.py 9 | 10 | [report] 11 | exclude_lines = 12 | pragma: no cover 13 | def __repr__ 14 | def __unicode__ 15 | def __str__ 16 | raise AssertionError 17 | raise NotImplementedError 18 | -------------------------------------------------------------------------------- /cas_server/templates/cas_server/bs3/warn.html: -------------------------------------------------------------------------------- 1 | {% extends "cas_server/bs3/base.html" %} 2 | {% load static %} 3 | {% load i18n %} 4 | 5 | {% block content %} 6 |
7 | {% csrf_token %} 8 | {% include "cas_server/bs3/form.html" %} 9 | 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /cas_server/templates/cas_server/bs4/warn.html: -------------------------------------------------------------------------------- 1 | {% extends "cas_server/bs4/base.html" %} 2 | {% load static %} 3 | {% load i18n %} 4 | 5 | {% block content %} 6 |
7 | {% csrf_token %} 8 | {% include "cas_server/bs4/form.html" %} 9 | 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /cas_server/static/cas_server/bs4/fa-lock.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /cas_server/static/cas_server/bs4/fa-user.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. django-cas-server documentation master file, created by 2 | sphinx-quickstart on Tue Jul 5 12:11:50 2016. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | django-cas-server documentation 7 | =============================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 3 13 | 14 | README 15 | package/cas_server 16 | 17 | .. toctree:: 18 | :maxdepth: 2 19 | 20 | CHANGELOG 21 | 22 | Indices and tables 23 | ================== 24 | 25 | * :ref:`genindex` 26 | 27 | .. * :ref:`modindex` 28 | .. * :ref:`search` 29 | 30 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include tox.ini 2 | include LICENSE 3 | include README.rst 4 | include CHANGELOG.rst 5 | include .coveragerc 6 | include Makefile 7 | include pytest.ini 8 | include requirements-dev.txt 9 | include requirements.txt 10 | prune .tox 11 | recursive-include cas_server/static * 12 | recursive-include cas_server/templates * 13 | recursive-include cas_server/locale * 14 | 15 | include docs/conf.py 16 | include docs/index.rst 17 | include docs/Makefile 18 | include docs/README.rst 19 | include docs/CHANGELOG.rst 20 | recursive-include docs/_ext * 21 | recursive-include docs/package * 22 | recursive-include docs/_static * 23 | recursive-include docs/_templates * 24 | -------------------------------------------------------------------------------- /docs/package/cas_server.rst: -------------------------------------------------------------------------------- 1 | cas_server package 2 | ================== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | cas_server.templatetags 10 | 11 | Submodules 12 | ---------- 13 | 14 | .. toctree:: 15 | 16 | cas_server.admin 17 | cas_server.apps 18 | cas_server.auth 19 | cas_server.cas 20 | cas_server.default_settings 21 | cas_server.federate 22 | cas_server.forms 23 | cas_server.models 24 | cas_server.urls 25 | cas_server.utils 26 | cas_server.views 27 | 28 | Module contents 29 | --------------- 30 | 31 | .. automodule:: cas_server 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | -------------------------------------------------------------------------------- /cas_server/templates/cas_server/samlValidateError.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | {{msg}} 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /cas_server/static/cas_server/bs4/styles.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | background-color: #eee; 4 | } 5 | .cover-container { 6 | max-width: 50em; 7 | } 8 | 9 | /* Page title */ 10 | #app-name { 11 | color: #000; 12 | text-shadow: 0 .05rem .2rem rgba(255, 255, 255, .5); 13 | } 14 | #app-name img { 15 | width: 110px; 16 | margin-right: 1rem; 17 | } 18 | @media screen and (max-width: 680px) { 19 | #app-name img { 20 | display: block; 21 | margin: auto; 22 | } 23 | } 24 | 25 | /* Add icons to login form */ 26 | /* Font-Awesome attribution is already done inside SVG files */ 27 | .form-signin input[type="text"] { 28 | background: right 1rem top 50% / 5% no-repeat url('fa-user.svg'); 29 | padding-right: 3rem; 30 | } 31 | .form-signin input[type="password"] { 32 | background: right 1rem top 50% / 5% no-repeat url('fa-lock.svg'); 33 | padding-right: 3rem; 34 | } 35 | -------------------------------------------------------------------------------- /cas_server/__init__.py: -------------------------------------------------------------------------------- 1 | # This program is distributed in the hope that it will be useful, but WITHOUT 2 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 3 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License version 3 for 4 | # more details. 5 | # 6 | # You should have received a copy of the GNU General Public License version 3 7 | # along with this program; if not, write to the Free Software Foundation, Inc., 51 8 | # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 9 | # 10 | # (c) 2015-2025 Valentin Samir 11 | """A django CAS server application""" 12 | try: 13 | import django 14 | except ModuleNotFoundError: 15 | django = None 16 | 17 | #: version of the application 18 | VERSION = '3.1.0' 19 | 20 | if django is None or django.VERSION < (3, 2): 21 | #: path the the application configuration class 22 | default_app_config = 'cas_server.apps.CasAppConfig' 23 | -------------------------------------------------------------------------------- /cas_server/templates/cas_server/bs3/form.html: -------------------------------------------------------------------------------- 1 | {% load cas_server %} 2 | {% for error in form.non_field_errors %} 3 |
4 | 5 | {{error}} 6 |
7 | {% endfor %} 8 | {% for field in form %}{% if not field|is_hidden %} 9 |
{% spaceless %} 16 | {% if field|is_checkbox %} 17 |
18 | {% else %} 19 | 20 | {{field}} 21 | {% endif %} 22 | {% for error in field.errors %} 23 | {{error}} 24 | {% endfor %} 25 | {% endspaceless %}
26 | {% else %}{{field}}{% endif %}{% endfor %} 27 | -------------------------------------------------------------------------------- /cas_server/templates/cas_server/bs3/logged.html: -------------------------------------------------------------------------------- 1 | {% extends "cas_server/bs3/base.html" %} 2 | {% load i18n %} 3 | {% block content %} 4 | 5 |
6 |
7 | 10 |
11 | {% if settings.CAS_FEDERATE and request.COOKIES.remember_provider %} 12 |
13 | 16 |
17 | {% endif %} 18 | 19 |
20 | {% endblock %} 21 | 22 | -------------------------------------------------------------------------------- /cas_server/templates/cas_server/bs4/logged.html: -------------------------------------------------------------------------------- 1 | {% extends "cas_server/bs4/base.html" %} 2 | {% load i18n %} 3 | {% block content %} 4 | 5 |
6 |
7 | 10 |
11 | {% if settings.CAS_FEDERATE and request.COOKIES.remember_provider %} 12 |
13 | 16 |
17 | {% endif %} 18 | 19 |
20 | {% endblock %} 21 | 22 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file for Sphinx projects 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | # Required 5 | version: 2 6 | 7 | # Set the OS, Python version and other tools you might need 8 | build: 9 | os: ubuntu-22.04 10 | tools: 11 | python: "3.12" 12 | # You can also specify other tool versions: 13 | # nodejs: "20" 14 | # rust: "1.70" 15 | # golang: "1.20" 16 | 17 | # Build documentation in the "docs/" directory with Sphinx 18 | sphinx: 19 | configuration: docs/conf.py 20 | # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs 21 | # builder: "dirhtml" 22 | # Fail on all warnings to avoid broken references 23 | # fail_on_warning: true 24 | 25 | formats: 26 | - pdf 27 | - epub 28 | 29 | # Optional but recommended, declare the Python requirements required 30 | # to build your documentation 31 | # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 32 | python: 33 | install: 34 | - requirements: docs/requirements.txt 35 | -------------------------------------------------------------------------------- /cas_server/tests/urls.py: -------------------------------------------------------------------------------- 1 | """cas URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.9/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | 17 | try: 18 | from django.urls import re_path 19 | except ImportError: 20 | # re_path is not available in Django 2 21 | from django.conf.urls import url as re_path 22 | 23 | from django.conf.urls import include 24 | from django.contrib import admin 25 | 26 | urlpatterns = [ 27 | re_path(r'^admin/', admin.site.urls), 28 | re_path(r'^', include('cas_server.urls', namespace='cas_server')), 29 | ] 30 | -------------------------------------------------------------------------------- /cas_server/management/commands/cas_clean_federate.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This program is distributed in the hope that it will be useful, but WITHOUT 3 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 4 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License version 3 for 5 | # more details. 6 | # 7 | # You should have received a copy of the GNU General Public License version 3 8 | # along with this program; if not, write to the Free Software Foundation, Inc., 51 9 | # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 10 | # 11 | # (c) 2016-2020 Valentin Samir 12 | from django.core.management.base import BaseCommand 13 | 14 | from ... import models 15 | 16 | import sys 17 | if sys.version_info < (3, ): 18 | from django.utils.translation import ugettext_lazy as _ 19 | else: 20 | from django.utils.translation import gettext_lazy as _ 21 | 22 | 23 | class Command(BaseCommand): 24 | args = '' 25 | help = _(u"Clean old federated users") 26 | 27 | def handle(self, *args, **options): 28 | models.FederatedUser.clean_old_entries() 29 | models.FederateSLO.clean_deleted_sessions() 30 | -------------------------------------------------------------------------------- /cas_server/templates/cas_server/bs3/login.html: -------------------------------------------------------------------------------- 1 | {% extends "cas_server/bs3/base.html" %} 2 | {% load i18n %} 3 | 4 | {% block ante_messages %} 5 | {% if auto_submit %}{% endif %} 8 | {% endblock %} 9 | {% block content %} 10 |
11 | {% csrf_token %} 12 | {% include "cas_server/bs3/form.html" %} 13 | {% if auto_submit %}{% endif %} 16 |
17 | {% endblock %} 18 | {% block javascript_inline %} 19 | jQuery(function( $ ){ 20 | $("#id_warn").click(function(e){ 21 | if($("#id_warn").is(':checked')){ 22 | createCookie("warn", "on", 10 * 365); 23 | } else { 24 | eraseCookie("warn"); 25 | } 26 | }); 27 | });{% if auto_submit %} 28 | document.getElementById('login_form').submit(); // SUBMIT FORM{% endif %} 29 | {% endblock %} 30 | 31 | -------------------------------------------------------------------------------- /cas_server/apps.py: -------------------------------------------------------------------------------- 1 | # This program is distributed in the hope that it will be useful, but WITHOUT 2 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 3 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License version 3 for 4 | # more details. 5 | # 6 | # You should have received a copy of the GNU General Public License version 3 7 | # along with this program; if not, write to the Free Software Foundation, Inc., 51 8 | # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 9 | # 10 | # (c) 2015-2020 Valentin Samir 11 | """django config module""" 12 | from django.apps import AppConfig 13 | 14 | import sys 15 | if sys.version_info < (3, ): 16 | from django.utils.translation import ugettext_lazy as _ 17 | else: 18 | from django.utils.translation import gettext_lazy as _ 19 | 20 | 21 | class CasAppConfig(AppConfig): 22 | """ 23 | Bases: :class:`django.apps.AppConfig` 24 | 25 | django CAS application config class 26 | """ 27 | #: Full Python path to the application. It must be unique across a Django project. 28 | name = 'cas_server' 29 | #: Human-readable name for the application. 30 | verbose_name = _('Central Authentication Service') 31 | -------------------------------------------------------------------------------- /cas_server/templates/cas_server/bs4/form.html: -------------------------------------------------------------------------------- 1 | {% load cas_server %} 2 | 3 | {% for error in form.non_field_errors %} 4 |
5 | 6 | {{error}} 7 |
8 | {% endfor %} 9 | 10 | {% for field in form %}{% if not field|is_hidden %} 11 |
{% spaceless %} 18 | {% if field|is_checkbox %} 19 |
20 | {{field}} 21 | 24 |
25 | {% else %} 26 | 27 | {{field}} 28 | {% endif %} 29 | {% for error in field.errors %} 30 | {{error}} 31 | {% endfor %} 32 | {% endspaceless %}
33 | {% else %}{{field}}{% endif %}{% endfor %} 34 | -------------------------------------------------------------------------------- /cas_server/templates/cas_server/bs4/login.html: -------------------------------------------------------------------------------- 1 | {% extends "cas_server/bs4/base.html" %} 2 | {% load i18n %} 3 | 4 | {% block ante_messages %} 5 | {% if auto_submit %}{% endif %} 10 | {% endblock %} 11 | 12 | {% block content %} 13 |
14 | {% csrf_token %} 15 | {% include "cas_server/bs4/form.html" %} 16 | {% if auto_submit %}{% endif %} 19 |
20 | {% endblock %} 21 | 22 | {% block javascript_inline %} 23 | jQuery(function( $ ){ 24 | $("#id_warn").click(function(e){ 25 | if($("#id_warn").is(':checked')){ 26 | createCookie("warn", "on", 10 * 365); 27 | } else { 28 | eraseCookie("warn"); 29 | } 30 | }); 31 | }); 32 | {% if auto_submit %}document.getElementById('login_form').submit(); // SUBMIT FORM{% endif %} 33 | {% endblock %} 34 | -------------------------------------------------------------------------------- /cas_server/tests/auth.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This program is distributed in the hope that it will be useful, but WITHOUT 3 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 4 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License version 3 for 5 | # more details. 6 | # 7 | # You should have received a copy of the GNU General Public License version 3 8 | # along with this program; if not, write to the Free Software Foundation, Inc., 51 9 | # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 10 | # 11 | # (c) 2016 Valentin Samir 12 | """Some test authentication classes for the CAS""" 13 | from cas_server import auth 14 | 15 | 16 | class TestCachedAttributesAuthUser(auth.TestAuthUser): 17 | """ 18 | A test authentication class only working for one unique user. 19 | 20 | :param unicode username: A username, stored in the :attr:`username` 21 | class attribute. The uniq valid value is ``settings.CAS_TEST_USER``. 22 | """ 23 | def attributs(self): 24 | """ 25 | The user attributes. 26 | 27 | :raises NotImplementedError: as this class do not support fetching user attributes 28 | """ 29 | raise NotImplementedError() 30 | -------------------------------------------------------------------------------- /cas_server/templates/cas_server/serviceValidate.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{username}} 4 | 5 | {{auth_date}} 6 | false{# we do not support long-term (Remember-Me) auth #} 7 | {{is_new_login}} 8 | {% for key, value in attributes %} {{value}} 9 | {% endfor %} 10 | 11 | 12 | 13 | {% for key, value in attributes %} 14 | {% endfor %}{% if proxyGrantingTicket %} {{proxyGrantingTicket}} 15 | {% endif %}{% if proxies %} 16 | {% for proxy in proxies %} {{proxy}} 17 | {% endfor %} 18 | {% endif %} 19 | 20 | -------------------------------------------------------------------------------- /cas_server/management/commands/cas_clean_sessions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This program is distributed in the hope that it will be useful, but WITHOUT 3 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 4 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License version 3 for 5 | # more details. 6 | # 7 | # You should have received a copy of the GNU General Public License version 3 8 | # along with this program; if not, write to the Free Software Foundation, Inc., 51 9 | # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 10 | # 11 | # (c) 2016-2020 Valentin Samir 12 | """Clean deleted sessions management command""" 13 | from django.core.management.base import BaseCommand 14 | 15 | from ... import models 16 | 17 | import sys 18 | if sys.version_info < (3,): 19 | from django.utils.translation import ugettext_lazy as _ 20 | else: 21 | from django.utils.translation import gettext_lazy as _ 22 | 23 | 24 | class Command(BaseCommand): 25 | """Clean deleted sessions""" 26 | args = '' 27 | help = _(u"Clean deleted sessions") 28 | 29 | def handle(self, *args, **options): 30 | models.User.clean_deleted_sessions() 31 | models.UserAttributes.clean_old_entries() 32 | models.NewVersionWarning.send_mails() 33 | -------------------------------------------------------------------------------- /cas_server/management/commands/cas_clean_tickets.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This program is distributed in the hope that it will be useful, but WITHOUT 3 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 4 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License version 3 for 5 | # more details. 6 | # 7 | # You should have received a copy of the GNU General Public License version 3 8 | # along with this program; if not, write to the Free Software Foundation, Inc., 51 9 | # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 10 | # 11 | # (c) 2016-2020 Valentin Samir 12 | """Clean old trickets management command""" 13 | from django.core.management.base import BaseCommand 14 | 15 | from ... import models 16 | 17 | import sys 18 | if sys.version_info < (3, ): 19 | from django.utils.translation import ugettext_lazy as _ 20 | else: 21 | from django.utils.translation import gettext_lazy as _ 22 | 23 | 24 | class Command(BaseCommand): 25 | """Clean old trickets""" 26 | args = '' 27 | help = _(u"Clean old tickets") 28 | 29 | def handle(self, *args, **options): 30 | models.User.clean_old_entries() 31 | for ticket_class in [models.ServiceTicket, models.ProxyTicket, models.ProxyGrantingTicket]: 32 | ticket_class.clean_old_entries() 33 | -------------------------------------------------------------------------------- /cas_server/static/cas_server/functions.js: -------------------------------------------------------------------------------- 1 | function createCookie(name, value, days){ 2 | var expires; 3 | var date; 4 | if(days){ 5 | date = new Date(); 6 | date.setTime(date.getTime()+(days*24*60*60*1000)); 7 | expires = "; expires="+date.toGMTString(); 8 | } 9 | else{ 10 | expires = ""; 11 | } 12 | document.cookie = name + "=" + value + expires + "; path=/"; 13 | } 14 | 15 | function readCookie(name){ 16 | var nameEQ = name + "="; 17 | var ca = document.cookie.split(";"); 18 | for(var i=0;i < ca.length;i++) { 19 | var c = ca[i]; 20 | while (c.charAt(0) === " "){ 21 | c = c.substring(1,c.length); 22 | } 23 | if (c.indexOf(nameEQ) === 0){ 24 | return c.substring(nameEQ.length,c.length); 25 | } 26 | } 27 | return null; 28 | } 29 | 30 | function eraseCookie(name) { 31 | createCookie(name,"",-1); 32 | } 33 | 34 | function discard_and_remember(id, cookie_name, token, days=10*365){ 35 | jQuery(function( $ ){ 36 | $(id).click(function( e ){ 37 | e.preventDefault(); 38 | createCookie(cookie_name, token, days); 39 | }); 40 | if(readCookie(cookie_name) === token){ 41 | $(id).parent().hide(); 42 | } 43 | }); 44 | } 45 | -------------------------------------------------------------------------------- /cas_server/tests/test_templatetags.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This program is distributed in the hope that it will be useful, but WITHOUT 3 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 4 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License version 3 for 5 | # more details. 6 | # 7 | # You should have received a copy of the GNU General Public License version 3 8 | # along with this program; if not, write to the Free Software Foundation, Inc., 51 9 | # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 10 | # 11 | # (c) 2016 Valentin Samir 12 | """tests for the customs template tags""" 13 | from django.test import TestCase 14 | 15 | from cas_server import forms 16 | from cas_server.templatetags import cas_server 17 | 18 | 19 | class TemplateTagsTestCase(TestCase): 20 | """tests for the customs template tags""" 21 | 22 | def test_is_checkbox(self): 23 | """test for the template filter is_checkbox""" 24 | form = forms.UserCredential() 25 | self.assertFalse(cas_server.is_checkbox(form["username"])) 26 | self.assertTrue(cas_server.is_checkbox(form["warn"])) 27 | 28 | def test_is_hidden(self): 29 | """test for the template filter is_hidden""" 30 | form = forms.UserCredential() 31 | self.assertFalse(cas_server.is_hidden(form["username"])) 32 | self.assertTrue(cas_server.is_hidden(form["lt"])) 33 | -------------------------------------------------------------------------------- /cas_server/templatetags/cas_server.py: -------------------------------------------------------------------------------- 1 | # This program is distributed in the hope that it will be useful, but WITHOUT 2 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 3 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License version 3 for 4 | # more details. 5 | # 6 | # You should have received a copy of the GNU General Public License version 3 7 | # along with this program; if not, write to the Free Software Foundation, Inc., 51 8 | # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 9 | # 10 | # (c) 2015-2016 Valentin Samir 11 | """template tags for the app""" 12 | from django import template 13 | from django import forms 14 | 15 | register = template.Library() 16 | 17 | 18 | @register.filter(name='is_checkbox') 19 | def is_checkbox(field): 20 | """ 21 | check if a form bound field is a checkbox 22 | 23 | :param django.forms.BoundField field: A bound field 24 | :return: ``True`` if the field is a checkbox, ``False`` otherwise. 25 | :rtype: bool 26 | """ 27 | return isinstance(field.field.widget, forms.CheckboxInput) 28 | 29 | 30 | @register.filter(name='is_hidden') 31 | def is_hidden(field): 32 | """ 33 | check if a form bound field is hidden 34 | 35 | :param django.forms.BoundField field: A bound field 36 | :return: ``True`` if the field is hidden, ``False`` otherwise. 37 | :rtype: bool 38 | """ 39 | return isinstance(field.field.widget, forms.HiddenInput) 40 | -------------------------------------------------------------------------------- /.update_coverage: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | set -e 4 | BASEDIR="$(realpath "$1")" 5 | PROJECT_NAME="$2" 6 | 7 | TITLE="Coverage report of $PROJECT_NAME" 8 | 9 | # build by gitlab CI 10 | if [ -n "$CI_BUILD_REF_NAME" ]; then 11 | BRANCH="$CI_BUILD_REF_NAME" 12 | TITLE="$TITLE, $BRANCH branch" 13 | # build by travis 14 | elif [ -n "$TRAVIS_BRANCH" ]; then 15 | # if this a pull request ? 16 | if [[ "$TRAVIS_PULL_REQUEST" != "false" ]]; then 17 | BRANCH="pull-request-$TRAVIS_PULL_REQUEST" 18 | TITLE="$TITLE, pull request n°$BRANCH" 19 | else 20 | BRANCH="$TRAVIS_BRANCH" 21 | TITLE="$TITLE, $BRANCH branch" 22 | fi 23 | else 24 | BRANCH="$(git rev-parse --abbrev-ref HEAD)" 25 | TITLE="$TITLE, $BRANCH branch" 26 | fi 27 | 28 | if [[ "$BRANCH" = "HEAD" ]] || [ -z "$BRANCH" ]; then 29 | echo "bad branch name '$BRANCH', ignoring it" 30 | exit 0 31 | fi 32 | 33 | 34 | VENV="$(mktemp -d)" 35 | HTMLREPORT="$(mktemp -d)" 36 | virtualenv -p python3 "$VENV" 37 | "$VENV/bin/pip" install coverage 38 | "$VENV/bin/coverage" html --title "$TITLE" --directory "$HTMLREPORT" 39 | rm -rf "$VENV" 40 | 41 | 42 | cd "$HTMLREPORT"; tar czf "$BASEDIR/coverage.tar.gz" ./ 43 | 44 | cd "$BASEDIR" 45 | 46 | rm -rf "$HTMLREPORT" 47 | 48 | set +x 49 | echo "curl https://badges.genua.fr/coverage/ ..." 50 | curl https://badges.genua.fr/coverage/ \ 51 | -F "secret=$COVERAGE_TOKEN" \ 52 | -F "tar=@$BASEDIR/coverage.tar.gz" \ 53 | -F "project=$PROJECT_NAME" \ 54 | -F "branch=$BRANCH" 55 | 56 | set -x 57 | rm "$BASEDIR/coverage.tar.gz" 58 | 59 | -------------------------------------------------------------------------------- /cas_server/static/cas_server/cas.js: -------------------------------------------------------------------------------- 1 | function cas_login(cas_server_login, service, login_service, callback){ 2 | var url = cas_server_login + "?service=" + encodeURIComponent(service); 3 | $.ajax({ 4 | type: "GET", 5 | url, 6 | beforeSend(request) { 7 | request.setRequestHeader("X-AJAX", "1"); 8 | }, 9 | xhrFields: { 10 | withCredentials: true 11 | }, 12 | success(data, textStatus, request){ 13 | if(data.status === "success"){ 14 | $.ajax({ 15 | type: "GET", 16 | url: data.url, 17 | xhrFields: { 18 | withCredentials: true 19 | }, 20 | success: callback, 21 | error(request, textStatus, errorThrown) {}, 22 | }); 23 | } else { 24 | if(data.detail === "login required"){ 25 | window.location.href = cas_server_login + "?service=" + encodeURIComponent(login_service); 26 | } else { 27 | alert("error: " + data.messages[1].message); 28 | } 29 | } 30 | }, 31 | error(request, textStatus, errorThrown) {}, 32 | }); 33 | } 34 | 35 | function cas_logout(cas_server_logout){ 36 | $.ajax({ 37 | type: "GET", 38 | url: cas_server_logout, 39 | beforeSend(request) { 40 | request.setRequestHeader("X-AJAX", "1"); 41 | }, 42 | xhrFields: { 43 | withCredentials: true 44 | }, 45 | error(request, textStatus, errorThrown) {}, 46 | success(data, textStatus, request){ 47 | if(data.status === "error"){ 48 | alert("error: " + data.messages[1].message); 49 | } 50 | }, 51 | }); 52 | } 53 | -------------------------------------------------------------------------------- /cas_server/static/cas_server/bs3/styles.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | } 4 | body { 5 | padding-top: 40px; 6 | padding-bottom: 0; 7 | background-color: #eee; 8 | } 9 | 10 | .form-signin .form-signin-heading, 11 | .form-signin .checkbox { 12 | margin-bottom: 10px; 13 | } 14 | .form-signin .checkbox { 15 | font-weight: normal; 16 | } 17 | .form-signin .form-control { 18 | position: relative; 19 | height: auto; 20 | -webkit-box-sizing: border-box; 21 | -moz-box-sizing: border-box; 22 | box-sizing: border-box; 23 | padding: 10px; 24 | font-size: 16px; 25 | } 26 | .form-signin .form-control:focus { 27 | z-index: 2; 28 | } 29 | .form-signin input[type="text"] { 30 | margin-bottom: -1px; 31 | border-bottom-right-radius: 0; 32 | border-bottom-left-radius: 0; 33 | } 34 | .form-signin input[type="password"] { 35 | margin-bottom: 10px; 36 | border-top-left-radius: 0; 37 | border-top-right-radius: 0; 38 | } 39 | 40 | #app-name { 41 | text-align: center; 42 | } 43 | #app-name img { 44 | width:110px; 45 | } 46 | 47 | /* Wrapper for page content to push down footer */ 48 | #wrap { 49 | min-height: 100%; 50 | height: auto; 51 | height: 100%; 52 | /* Negative indent footer by it's height */ 53 | margin: 0 auto -40px; 54 | } 55 | #footer { 56 | height: 40px; 57 | text-align: center; 58 | } 59 | #footer p { 60 | padding-top: 10px; 61 | } 62 | 63 | @media screen and (max-width: 680px) { 64 | #app-name { 65 | margin: 0; 66 | } 67 | #app-name img { 68 | display: block; 69 | margin: auto; 70 | } 71 | body { 72 | padding-top: 0; 73 | padding-bottom: 0; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | before_script: 2 | - pip install tox setuptools 3 | 4 | flake8: 5 | image: "python:3.9" 6 | cache: 7 | key: flake8 8 | paths: 9 | - .tox/flake8 10 | script: 11 | - tox -e flake8 12 | 13 | check_rst: 14 | image: "python:3.9" 15 | cache: 16 | key: check_rst 17 | paths: 18 | - .tox/check_rst 19 | script: 20 | - tox -e check_rst 21 | 22 | py27-django111: 23 | image: "python:2.7" 24 | cache: 25 | key: py27-django111 26 | paths: 27 | - .tox/py27-django111 28 | script: 29 | - tox -e py27-django111 30 | 31 | py35-django111: 32 | image: "python:3.5" 33 | cache: 34 | key: py35-django111 35 | paths: 36 | - .tox/py35-django111 37 | script: 38 | - tox -e py35-django111 39 | 40 | py36-django111: 41 | image: "python:3.6" 42 | cache: 43 | key: py36-django111 44 | paths: 45 | - .tox/py36-django111 46 | script: 47 | - tox -e py36-django111 48 | 49 | py37-django22: 50 | image: "python:3.7" 51 | cache: 52 | key: py37-django22 53 | paths: 54 | - .tox/py37-django22 55 | script: 56 | - tox -e py37-django22 57 | 58 | py38-django22: 59 | image: "python:3.8" 60 | cache: 61 | key: py38-django22 62 | paths: 63 | - .tox/py38-django22 64 | script: 65 | - tox -e py38-django22 66 | 67 | py38-django30: 68 | image: "python:3.8" 69 | cache: 70 | key: py38-django30 71 | paths: 72 | - .tox/py38-django30 73 | script: 74 | - tox -e py38-django30 75 | 76 | py38-django31: 77 | image: "python:3.8" 78 | cache: 79 | key: py38-django31 80 | paths: 81 | - .tox/py38-django31 82 | script: 83 | - tox -e py38-django31 84 | 85 | py39-django22: 86 | image: "python:3.9" 87 | cache: 88 | key: py39-django22 89 | paths: 90 | - .tox/py39-django22 91 | script: 92 | - tox -e py39-django22 93 | 94 | py39-django30: 95 | image: "python:3.9" 96 | cache: 97 | key: py39-django30 98 | paths: 99 | - .tox/py39-django30 100 | script: 101 | - tox -e py39-django30 102 | 103 | py39-django31: 104 | image: "python:3.9" 105 | cache: 106 | key: py39-django31 107 | paths: 108 | - .tox/py39-django31 109 | script: 110 | - tox -e py39-django31 111 | 112 | coverage: 113 | image: "python:3.9" 114 | cache: 115 | key: coverage 116 | paths: 117 | - .tox/coverage 118 | script: 119 | - tox -e coverage 120 | -------------------------------------------------------------------------------- /cas_server/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This program is distributed in the hope that it will be useful, but WITHOUT 3 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 4 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License version 3 for 5 | # more details. 6 | # 7 | # You should have received a copy of the GNU General Public License version 3 8 | # along with this program; if not, write to the Free Software Foundation, Inc., 51 9 | # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 10 | # 11 | # (c) 2015-2021 Valentin Samir 12 | """urls for the app""" 13 | 14 | try: 15 | from django.urls import re_path 16 | except ImportError: 17 | # re_path is not available in Django 2 18 | from django.conf.urls import url as re_path 19 | 20 | from django.views.generic import RedirectView 21 | from django.views.decorators.debug import sensitive_post_parameters, sensitive_variables 22 | 23 | from cas_server import views 24 | 25 | app_name = "cas_server" 26 | 27 | urlpatterns = [ 28 | re_path( 29 | r'^$', 30 | RedirectView.as_view(pattern_name="cas_server:login", permanent=False, query_string=True) 31 | ), 32 | re_path( 33 | '^login$', 34 | sensitive_post_parameters('password')( 35 | views.LoginView.as_view() 36 | ), 37 | name='login' 38 | ), 39 | re_path('^logout$', views.LogoutView.as_view(), name='logout'), 40 | re_path('^validate$', views.Validate.as_view(), name='validate'), 41 | re_path( 42 | '^serviceValidate$', 43 | views.ValidateService.as_view(allow_proxy_ticket=False), 44 | name='serviceValidate' 45 | ), 46 | re_path( 47 | '^proxyValidate$', 48 | views.ValidateService.as_view(allow_proxy_ticket=True), 49 | name='proxyValidate' 50 | ), 51 | re_path('^proxy$', views.Proxy.as_view(), name='proxy'), 52 | re_path( 53 | '^p3/serviceValidate$', 54 | views.ValidateService.as_view(allow_proxy_ticket=False), 55 | name='p3_serviceValidate' 56 | ), 57 | re_path( 58 | '^p3/proxyValidate$', 59 | views.ValidateService.as_view(allow_proxy_ticket=True), 60 | name='p3_proxyValidate' 61 | ), 62 | re_path('^samlValidate$', views.SamlValidate.as_view(), name='samlValidate'), 63 | re_path( 64 | '^auth$', 65 | sensitive_variables('password', 'secret')( 66 | sensitive_post_parameters('password', 'secret')( 67 | views.Auth.as_view() 68 | ) 69 | ), 70 | name='auth' 71 | ), 72 | re_path("^federate(?:/(?P([^/]+)))?$", 73 | views.FederateAuth.as_view(), name='federateAuth'), 74 | ] 75 | -------------------------------------------------------------------------------- /cas_server/templates/cas_server/samlValidate.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | {{Recipient}} 20 | 21 | 22 | 23 | 24 | 25 | {{username}} 26 | 27 | 28 | urn:oasis:names:tc:SAML:1.0:cm:artifact 29 | 30 | 31 | 32 | 33 | {{auth_date}} 34 | 35 | 36 | false{# we do not support long-term (Remember-Me) auth #} 37 | 38 | 39 | {{is_new_login}} 40 | 41 | {% for name, value in attributes %} 42 | {{value}} 43 | 44 | {% endfor %} 45 | 47 | 48 | {{username}} 49 | 50 | 51 | urn:oasis:names:tc:SAML:1.0:cm:artifact 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build dist docs 2 | VERSION=`python3 setup.py -V` 3 | 4 | WHL_FILES := $(wildcard dist/*.whl) 5 | WHL_ASC := $(WHL_FILES:=.asc) 6 | DIST_FILE := $(wildcard dist/*.tar.gz) 7 | DIST_ASC := $(DIST_FILE:=.asc) 8 | 9 | build: 10 | python3 setup.py build 11 | 12 | install: dist 13 | pip3 -V 14 | pip3 install --no-cache-dir --no-deps --upgrade --force-reinstall --find-links ./dist/django-cas-server-${VERSION}.tar.gz django-cas-server 15 | 16 | uninstall: 17 | pip3 uninstall django-cas-server || true 18 | 19 | clean_pyc: 20 | find ./ -name '*.pyc' -delete 21 | find ./ -name __pycache__ -delete 22 | clean_build: 23 | rm -rf build django_cas_server.egg-info dist 24 | clean_tox: 25 | rm -rf .tox tox_logs 26 | clean_test_venv: 27 | rm -rf test_venv 28 | clean_coverage: 29 | rm -rf coverage.xml .coverage htmlcov 30 | clean_tild_backup: 31 | find ./ -name '*~' -delete 32 | clean_docs: 33 | rm -rf docs/_build/ docs/django.inv docs/python.inv 34 | clean_eggs: 35 | rm -rf .eggs/ 36 | 37 | clean: clean_pyc clean_build clean_coverage clean_tild_backup 38 | 39 | clean_all: clean clean_tox clean_test_venv clean_docs clean_eggs 40 | 41 | dist: 42 | python3 setup.py sdist 43 | python3 setup.py bdist_wheel 44 | 45 | test_venv/bin/python: 46 | python3 -m venv test_venv 47 | test_venv/bin/pip install -U --requirement requirements-dev.txt 'Django>=5.2,<5.3' 48 | 49 | test_venv/cas/manage.py: test_venv 50 | mkdir -p test_venv/cas 51 | test_venv/bin/django-admin startproject cas test_venv/cas 52 | ln -s ../../cas_server test_venv/cas/cas_server 53 | sed -i "s/'django.contrib.staticfiles',/'django.contrib.staticfiles',\n 'cas_server',/" test_venv/cas/cas/settings.py 54 | sed -i "s/'django.middleware.clickjacking.XFrameOptionsMiddleware',/'django.middleware.clickjacking.XFrameOptionsMiddleware',\n 'django.middleware.locale.LocaleMiddleware',/" test_venv/cas/cas/settings.py 55 | sed -i 's/from django.urls import path/from django.urls import path, include/' test_venv/cas/cas/urls.py 56 | 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 57 | test_venv/bin/python test_venv/cas/manage.py migrate 58 | test_venv/bin/python test_venv/cas/manage.py createsuperuser 59 | 60 | test_venv: test_venv/bin/python 61 | 62 | test_project: test_venv/cas/manage.py 63 | @echo "##############################################################" 64 | @echo "A test django project was created in $(realpath test_venv/cas)" 65 | 66 | run_server: test_project 67 | test_venv/bin/python test_venv/cas/manage.py runserver 68 | 69 | run_tests: test_venv 70 | python3 setup.py check --restructuredtext --stric 71 | test_venv/bin/py.test -rw -x --cov=cas_server --cov-report html --cov-report term 72 | rm htmlcov/coverage_html.js # I am really pissed off by those keybord shortcuts 73 | 74 | test_venv/bin/sphinx-build: test_venv 75 | test_venv/bin/pip install -r docs/requirements.txt 76 | 77 | docs: test_venv/bin/sphinx-build 78 | bash -c "source test_venv/bin/activate; cd docs; make html" 79 | 80 | sign_release: $(WHL_ASC) $(DIST_ASC) 81 | 82 | dist/%.asc: 83 | gpg --detach-sign -a $(@:.asc=) 84 | 85 | test_venv/bin/twine: test_venv 86 | test_venv/bin/pip install twine 87 | 88 | publish_pypi_release: test_venv test_venv/bin/twine dist sign_release 89 | test_venv/bin/twine upload --sign dist/* 90 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pkg_resources 3 | from setuptools import setup 4 | from cas_server import VERSION 5 | 6 | with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as readme: 7 | README = readme.read() 8 | 9 | if __name__ == '__main__': 10 | # allow setup.py to be run from any path 11 | os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) 12 | 13 | setup( 14 | name='django-cas-server', 15 | version=VERSION, 16 | packages=[ 17 | 'cas_server', 'cas_server.migrations', 18 | 'cas_server.management', 'cas_server.management.commands', 19 | 'cas_server.tests', 'cas_server.templatetags' 20 | ], 21 | include_package_data=True, 22 | license='GPLv3', 23 | description=( 24 | 'A Django Central Authentication Service server ' 25 | 'implementing the CAS Protocol 3.0 Specification' 26 | ), 27 | long_description=README, 28 | author='Valentin Samir', 29 | author_email='valentin.samir@crans.org', 30 | classifiers=[ 31 | 'Environment :: Web Environment', 32 | 'Development Status :: 5 - Production/Stable', 33 | 'Framework :: Django', 34 | 'Framework :: Django :: 1.11', 35 | 'Framework :: Django :: 2.2', 36 | 'Framework :: Django :: 3.2', 37 | 'Framework :: Django :: 4.2', 38 | 'Framework :: Django :: 5.2', 39 | 'Intended Audience :: Developers', 40 | 'Intended Audience :: System Administrators', 41 | 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', 42 | 'Operating System :: OS Independent', 43 | 'Programming Language :: Python', 44 | 'Programming Language :: Python :: 3', 45 | 'Programming Language :: Python :: 3.6', 46 | 'Programming Language :: Python :: 3.7', 47 | 'Programming Language :: Python :: 3.8', 48 | 'Programming Language :: Python :: 3.9', 49 | 'Programming Language :: Python :: 3.10', 50 | 'Programming Language :: Python :: 3.11', 51 | 'Programming Language :: Python :: 3.12', 52 | 'Programming Language :: Python :: 3.13', 53 | 'Topic :: Software Development :: Libraries :: Python Modules', 54 | 'Topic :: Internet :: WWW/HTTP', 55 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 56 | 'Topic :: System :: Systems Administration :: Authentication/Directory' 57 | ], 58 | package_data={ 59 | 'cas_server': [ 60 | 'templates/cas_server/*', 61 | 'static/cas_server/*', 62 | 'locale/*/LC_MESSAGES/*', 63 | ] 64 | }, 65 | keywords=['django', 'cas', 'cas3', 'server', 'sso', 'single sign-on', 'authentication', 'auth'], 66 | install_requires=[ 67 | 'Django >= 1.11,<5.3', 'requests >= 2.4', 'requests_futures >= 0.9.5', 68 | 'lxml >= 3.4' 69 | ], 70 | url="https://github.com/nitmir/django-cas-server", 71 | download_url="https://github.com/nitmir/django-cas-server/releases/latest", 72 | zip_safe=False, 73 | setup_requires=[], 74 | tests_require=['pytest', 'pytest-django', 'pytest-pythonpath', 'pytest-warnings', 'mock>=1'], 75 | ) 76 | -------------------------------------------------------------------------------- /cas_server/tests/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django test settings for cas_server application. 3 | 4 | Generated by 'django-admin startproject' using Django 1.9.7. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.9/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.9/ref/settings/ 11 | """ 12 | 13 | import os 14 | import django 15 | 16 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 17 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 18 | 19 | 20 | # Quick-start development settings - unsuitable for production 21 | # See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/ 22 | 23 | # SECURITY WARNING: keep the secret key used in production secret! 24 | SECRET_KEY = 'changeme' 25 | 26 | # SECURITY WARNING: don't run with debug turned on in production! 27 | DEBUG = True 28 | 29 | ALLOWED_HOSTS = [] 30 | 31 | 32 | # Application definition 33 | 34 | INSTALLED_APPS = [ 35 | 'django.contrib.admin', 36 | 'django.contrib.auth', 37 | 'django.contrib.contenttypes', 38 | 'django.contrib.sessions', 39 | 'django.contrib.messages', 40 | 'django.contrib.staticfiles', 41 | 'cas_server', 42 | ] 43 | 44 | MIDDLEWARE = [ 45 | 'django.contrib.sessions.middleware.SessionMiddleware', 46 | 'django.middleware.common.CommonMiddleware', 47 | 'django.middleware.csrf.CsrfViewMiddleware', 48 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 | 'django.contrib.messages.middleware.MessageMiddleware', 50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 51 | 'django.middleware.locale.LocaleMiddleware', 52 | ] 53 | if django.VERSION < (1, 10): 54 | MIDDLEWARE_CLASSES = MIDDLEWARE 55 | 56 | TEMPLATES = [ 57 | { 58 | 'APP_DIRS': True, 59 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 60 | 'DIRS': [], 61 | 'OPTIONS': { 62 | 'context_processors': [ 63 | 'django.template.context_processors.debug', 64 | 'django.template.context_processors.request', 65 | 'django.contrib.auth.context_processors.auth', 66 | 'django.contrib.messages.context_processors.messages' 67 | ] 68 | } 69 | } 70 | ] 71 | 72 | ROOT_URLCONF = 'cas_server.tests.urls' 73 | 74 | # Database 75 | # https://docs.djangoproject.com/en/1.9/ref/settings/#databases 76 | 77 | DATABASES = { 78 | 'default': { 79 | 'ENGINE': 'django.db.backends.sqlite3', 80 | 'NAME': ':memory:', 81 | } 82 | } 83 | 84 | # Internationalization 85 | # https://docs.djangoproject.com/en/1.9/topics/i18n/ 86 | 87 | LANGUAGE_CODE = 'en-us' 88 | 89 | TIME_ZONE = 'UTC' 90 | 91 | USE_I18N = True 92 | 93 | if django.VERSION < (4, 0): 94 | USE_L10N = True 95 | 96 | USE_TZ = True 97 | 98 | 99 | # Static files (CSS, JavaScript, Images) 100 | # https://docs.djangoproject.com/en/1.9/howto/static-files/ 101 | 102 | STATIC_URL = '/static/' 103 | 104 | CAS_NEW_VERSION_HTML_WARNING = False 105 | CAS_NEW_VERSION_EMAIL_WARNING = False 106 | 107 | LOGGING = { 108 | 'version': 1, 109 | 'disable_existing_loggers': False, 110 | 'formatters': { 111 | 'cas_file': { 112 | 'format': '%(asctime)s %(levelname)s %(message)s' 113 | }, 114 | }, 115 | 'handlers': { 116 | 'cas_stream': { 117 | 'level': 'INFO', 118 | 'class': 'logging.StreamHandler', 119 | 'formatter': 'cas_file', 120 | }, 121 | }, 122 | 'loggers': { 123 | 'cas_server': { 124 | 'handlers': ['cas_stream'], 125 | 'level': 'INFO', 126 | 'propagate': True, 127 | }, 128 | }, 129 | } 130 | -------------------------------------------------------------------------------- /.github/workflows/github-actions.yml: -------------------------------------------------------------------------------- 1 | name: django-cas-server 2 | run-name: ${{ github.actor }} is running django-cas-server CI tests 3 | on: [push] 4 | concurrency: 5 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 6 | cancel-in-progress: true 7 | jobs: 8 | flake8: 9 | runs-on: ubuntu-latest 10 | container: 11 | image: python:bookworm 12 | steps: 13 | - uses: actions/checkout@v3 14 | - run: pip install tox 15 | - run: tox -e flake8 16 | check_rst: 17 | runs-on: ubuntu-latest 18 | container: 19 | image: python:bookworm 20 | steps: 21 | - uses: actions/checkout@v3 22 | - run: pip install tox 23 | - run: tox -e check_rst 24 | coverage: 25 | runs-on: ubuntu-latest 26 | container: 27 | image: python:bookworm 28 | steps: 29 | - uses: actions/checkout@v3 30 | - run: pip install tox 31 | - run: tox -e coverage 32 | env: 33 | COVERAGE_TOKEN: ${{ secrets.COVERAGE_TOKEN }} 34 | tests: 35 | runs-on: ubuntu-latest 36 | strategy: 37 | fail-fast: false 38 | matrix: 39 | arch: ["amd64"] 40 | tox: 41 | # REHL 7 support and Ubuntu bionic 42 | - python: "3.6" 43 | env: "py36-django111" 44 | # RHEL 8 support 45 | - python: "3.6" 46 | env: "py36-django22" 47 | # Debian buster support 48 | - python: "3.7" 49 | env: "py37-django111" 50 | # Ubuntu focal support 51 | - python: "3.8" 52 | env: "py38-django22" 53 | # Debian bullseye 54 | - python: "3.9" 55 | env: "py39-django22" 56 | # Debian bookworm 57 | - python: "3.11" 58 | env: "py311-django32" 59 | # Django additional supported version 60 | - python: "3.10" 61 | env: "py310-django52" 62 | - python: "3.11" 63 | env: "py311-django52" 64 | - python: "3.12" 65 | env: "py312-django52" 66 | - python: "3.13" 67 | env: "py313-django52" 68 | steps: 69 | - uses: actions/checkout@v3 70 | - if: matrix.arch != 'amd64' 71 | name: "Install docker multiarch support" 72 | run: | 73 | sudo docker run --rm --privileged tonistiigi/binfmt --install ${{ matrix.arch }} 74 | sudo docker run --rm --privileged multiarch/qemu-user-static --reset -p yes --credential yes 75 | - name: "Check docker arch ${{ matrix.arch }} support" 76 | 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;" 77 | - name: "Run tests on arch ${{ matrix.arch }}" 78 | 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 }}" 79 | tests-ubuntu: 80 | runs-on: ubuntu-latest 81 | strategy: 82 | fail-fast: false 83 | matrix: 84 | arch: ["amd64", "ppc64le"] 85 | tox: 86 | # Ubuntu jammy 87 | - python: "3.10" 88 | env: "py310-django32" 89 | # Ubuntu noble 90 | - python: "3.12" 91 | env: "py312-django42" 92 | steps: 93 | - uses: actions/checkout@v3 94 | - if: matrix.arch != 'amd64' 95 | name: "Install docker multiarch support" 96 | run: | 97 | sudo docker run --rm --privileged tonistiigi/binfmt --install ${{ matrix.arch }} 98 | sudo docker run --rm --privileged multiarch/qemu-user-static --reset -p yes --credential yes 99 | - name: "Check docker arch ${{ matrix.arch }} support" 100 | 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;" 101 | - name: "Run tests on arch ${{ matrix.arch }}" 102 | 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 }}" 103 | -------------------------------------------------------------------------------- /cas_server/templates/cas_server/bs4/base.html: -------------------------------------------------------------------------------- 1 | {% load i18n %}{% load static %}{% get_current_language as LANGUAGE_CODE %} 2 | 3 | 4 | 5 | 6 | {% block title %}{% trans "Central Authentication Service" %}{% endblock %} 7 | 8 | {% if settings.CAS_FAVICON_URL %}{% endif %} 9 | 10 | 11 | 12 |
13 | {% if auto_submit %}{% endif %} 33 | 34 |
35 | {% block ante_messages %}{% endblock %} 36 |
37 | {% for message in messages %} 38 |
51 |

{{message}}

52 |
53 | {% endfor %} 54 | {% block content %}{% endblock %} 55 |
56 |
57 | 58 | {% if settings.CAS_SHOW_POWERED %} 59 | 66 | {% endif %} 67 |
68 | 69 | 70 | 71 | 72 | 83 | {% block javascript %}{% endblock %} 84 | 85 | 86 | 92 | -------------------------------------------------------------------------------- /cas_server/templates/cas_server/bs3/base.html: -------------------------------------------------------------------------------- 1 | {% load i18n %}{% load static %}{% get_current_language as LANGUAGE_CODE %} 2 | 3 | 4 | 5 | 6 | 7 | {% block title %}{% trans "Central Authentication Service" %}{% endblock %} 8 | 9 | 10 | 11 | 15 | {% if settings.CAS_FAVICON_URL %}{% endif %} 16 | 17 | 18 | 19 |
20 |
21 | {% if auto_submit %}{% endif %} 30 |
31 |
32 |
33 | {% if auto_submit %}{% endif %} 65 | {% block content %}{% endblock %} 66 |
67 |
68 |
69 |
70 |
71 |
72 | {% if settings.CAS_SHOW_POWERED %} 73 | 76 | {% endif %} 77 | 78 | 79 | 80 | 91 | {% block javascript %}{% endblock %} 92 | 93 | 94 | 100 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist= 3 | flake8, 4 | check_rst, 5 | py3-django111, 6 | py3-django22, 7 | py3-django32, 8 | py3-django42, 9 | py3-django52, 10 | 11 | ################## 12 | # generic config # 13 | ################## 14 | 15 | [flake8] 16 | max-line-length=100 17 | exclude=migrations 18 | allowlist_externals={[post_cmd]allowlist_external} 19 | 20 | [base] 21 | deps = 22 | -r{toxinidir}/requirements-dev.txt 23 | 24 | [post_cmd] 25 | commands= 26 | find {toxworkdir} -name '*.pyc' -delete 27 | mkdir -p {toxinidir}/tox_logs/ 28 | bash -c "mv {toxworkdir}/{envname}/log/* {toxinidir}/tox_logs/" 29 | allowlist_externals= 30 | find 31 | bash 32 | mkdir 33 | 34 | [testenv] 35 | setenv= 36 | PYTHONWARNINGS=always 37 | commands= 38 | py.test -rw {posargs:cas_server/tests/} 39 | {[post_cmd]commands} 40 | allowlist_externals={[post_cmd]allowlist_externals} 41 | 42 | ################### 43 | # genercic checks # 44 | ################### 45 | 46 | [testenv:flake8] 47 | basepython=python3 48 | deps=flake8 49 | skip_install=True 50 | commands= 51 | flake8 {toxinidir}/cas_server 52 | {[post_cmd]commands} 53 | allowlist_externals={[post_cmd]allowlist_externals} 54 | 55 | [testenv:check_rst] 56 | basepython=python3 57 | deps= 58 | docutils 59 | Pygments 60 | skip_install=True 61 | commands= 62 | rst2html --strict {toxinidir}/README.rst /dev/null 63 | rst2html --halt=warning {toxinidir}/CHANGELOG.rst /dev/null 64 | {[post_cmd]commands} 65 | allowlist_externals={[post_cmd]allowlist_externals} 66 | 67 | [testenv:coverage] 68 | basepython=python3 69 | passenv= 70 | COVERAGE_TOKEN 71 | CI_BUILD_REF_NAME 72 | TRAVIS_BRANCH 73 | TRAVIS_PULL_REQUEST 74 | deps= 75 | -r{toxinidir}/requirements.txt 76 | -r{toxinidir}/requirements-dev.txt 77 | skip_install=True 78 | commands= 79 | py.test --cov=cas_server --cov-report term --cov-report html 80 | git config --global --add safe.directory "{toxinidir}" 81 | {toxinidir}/.update_coverage "{toxinidir}" "django-cas-server" 82 | {[post_cmd]commands} 83 | allowlist_externals= 84 | {toxinidir}/.update_coverage 85 | git 86 | {[post_cmd]allowlist_externals} 87 | 88 | 89 | #################### 90 | # Python 2 support # 91 | #################### 92 | 93 | [testenv:py27-django111] 94 | basepython=python2.7 95 | deps = 96 | Django>=1.11,<1.12 97 | {[base]deps} 98 | 99 | ################################## 100 | # Generic Python 3 for local use # 101 | ################################## 102 | 103 | [testenv:py3-django111] 104 | basepython=python3 105 | deps = 106 | Django>=1.11,<1.12 107 | {[base]deps} 108 | 109 | [testenv:py3-django22] 110 | basepython=python3 111 | deps = 112 | Django>=2.2,<2.3 113 | {[base]deps} 114 | 115 | [testenv:py3-django32] 116 | basepython=python3 117 | deps = 118 | Django>=3.2,<3.3 119 | {[base]deps} 120 | 121 | [testenv:py3-django42] 122 | basepython=python3 123 | deps = 124 | Django>=4.2,<4.3 125 | {[base]deps} 126 | 127 | [testenv:py3-django52] 128 | basepython=python3 129 | deps = 130 | Django>=5.2,<5.3 131 | {[base]deps} 132 | 133 | ######################### 134 | # Debian strech support # 135 | ######################### 136 | 137 | [testenv:py35-django111] 138 | basepython=python3.5 139 | deps = 140 | Django>=1.11,<1.12 141 | {[base]deps} 142 | 143 | #################################### 144 | # Ubuntu bionic and EPEL 7 support # 145 | #################################### 146 | 147 | [testenv:py36-django111] 148 | basepython=python3.6 149 | deps = 150 | Django>=1.11,<1.12 151 | {[base]deps} 152 | 153 | ################## 154 | # RHEL 8 support # 155 | ################## 156 | 157 | [testenv:py36-django22] 158 | basepython=python3.6 159 | deps = 160 | Django>=2.2,<3.0 161 | {[base]deps} 162 | 163 | ######################### 164 | # Debian buster support # 165 | ######################### 166 | 167 | [testenv:py37-django111] 168 | basepython=python3.7 169 | deps = 170 | Django>=1.11,<1.12 171 | {[base]deps} 172 | 173 | ######################## 174 | # Ubuntu focal support # 175 | ######################## 176 | 177 | [testenv:py38-django22] 178 | basepython=python3.8 179 | deps = 180 | Django>=2.2,<3.0 181 | {[base]deps} 182 | 183 | ################### 184 | # Debian bullseye # 185 | ################### 186 | 187 | [testenv:py39-django22] 188 | basepython=python3.9 189 | deps = 190 | Django>=2.2,<3.0 191 | {[base]deps} 192 | 193 | ################ 194 | # Ubuntu jammy # 195 | ################ 196 | 197 | [testenv:py310-django32] 198 | basepython=python3.10 199 | deps = 200 | Django>=3.2,<3.3 201 | {[base]deps} 202 | 203 | 204 | ########################### 205 | # Debian bookworm support # 206 | ########################### 207 | 208 | [testenv:py311-django32] 209 | basepython=python3.11 210 | deps = 211 | Django>=3.2,<3.3 212 | {[base]deps} 213 | 214 | 215 | ################ 216 | # Ubuntu noble # 217 | ################ 218 | 219 | [testenv:py312-django42] 220 | basepython=python3.12 221 | deps = 222 | Django>=4.2,<4.3 223 | {[base]deps} 224 | 225 | 226 | ####################################### 227 | # Django additional supported version # 228 | ####################################### 229 | 230 | [testenv:py310-django52] 231 | basepython=python3.10 232 | deps = 233 | Django>=5.2,<5.3 234 | {[base]deps} 235 | 236 | [testenv:py311-django52] 237 | basepython=python3.11 238 | deps = 239 | Django>=5.2,<5.3 240 | {[base]deps} 241 | 242 | [testenv:py312-django52] 243 | basepython=python3.12 244 | deps = 245 | Django>=5.2,<5.3 246 | {[base]deps} 247 | 248 | [testenv:py313-django52] 249 | basepython=python3.13 250 | deps = 251 | Django>=5.2,<5.3 252 | {[base]deps} 253 | -------------------------------------------------------------------------------- /cas_server/federate.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This program is distributed in the hope that it will be useful, but WITHOUT 3 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 4 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License version 3 for 5 | # more details. 6 | # 7 | # You should have received a copy of the GNU General Public License version 3 8 | # along with this program; if not, write to the Free Software Foundation, Inc., 51 9 | # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 10 | # 11 | # (c) 2016-2025 Valentin Samir 12 | """federated mode helper classes""" 13 | from .default_settings import SessionStore 14 | from django.db import IntegrityError 15 | 16 | from .cas import CASClient 17 | from .models import FederatedUser, FederateSLO, User 18 | 19 | import logging 20 | import urllib 21 | 22 | #: logger facility 23 | logger = logging.getLogger(__name__) 24 | 25 | 26 | class CASFederateValidateUser(object): 27 | """ 28 | Class CAS client used to authenticate the user again a CAS provider 29 | 30 | :param cas_server.models.FederatedIendityProvider provider: The provider to use for 31 | authenticate the user. 32 | :param unicode service_url: The service url to transmit to the ``provider``. 33 | """ 34 | #: the provider returned username 35 | username = None 36 | #: the provider returned attributes 37 | attributs = {} 38 | #: the CAS client instance 39 | client = None 40 | #: the provider returned username this the provider suffix appended 41 | federated_username = None 42 | #: the identity provider 43 | provider = None 44 | 45 | def __init__(self, provider, service_url, renew=False): 46 | self.provider = provider 47 | self.client = CASClient( 48 | service_url=service_url, 49 | version=provider.cas_protocol_version, 50 | server_url=provider.server_url, 51 | renew=renew, 52 | ) 53 | 54 | def get_login_url(self): 55 | """ 56 | :return: the CAS provider login url 57 | :rtype: unicode 58 | """ 59 | return self.client.get_login_url() 60 | 61 | def get_logout_url(self, redirect_url=None): 62 | """ 63 | :param redirect_url: The url to redirect to after logout from the provider, if provided. 64 | :type redirect_url: :obj:`unicode` or :obj:`NoneType` 65 | :return: the CAS provider logout url 66 | :rtype: unicode 67 | """ 68 | return self.client.get_logout_url(redirect_url) 69 | 70 | def verify_ticket(self, ticket): 71 | """ 72 | test ``ticket`` against the CAS provider, if valid, create a 73 | :class:`FederatedUser` matching provider returned 74 | username and attributes. 75 | 76 | :param unicode ticket: The ticket to validate against the provider CAS 77 | :return: ``True`` if the validation succeed, else ``False``. 78 | :rtype: bool 79 | """ 80 | try: 81 | username, attributs = self.client.verify_ticket(ticket)[:2] 82 | except urllib.error.URLError: 83 | return False 84 | if username is not None: 85 | if attributs is None: 86 | attributs = {} 87 | attributs["provider"] = self.provider.suffix 88 | self.username = username 89 | self.attributs = attributs 90 | user = FederatedUser.objects.update_or_create( 91 | username=username, 92 | provider=self.provider, 93 | defaults=dict(attributs=attributs, ticket=ticket) 94 | )[0] 95 | user.save() 96 | self.federated_username = user.federated_username 97 | return True 98 | else: 99 | return False 100 | 101 | @staticmethod 102 | def register_slo(username, session_key, ticket): 103 | """ 104 | association a ``ticket`` with a (``username``, ``session_key``) for processing later SLO 105 | request by creating a :class:`cas_server.models.FederateSLO` object. 106 | 107 | :param unicode username: A logged user username, with the ``@`` component. 108 | :param unicode session_key: A logged user session_key matching ``username``. 109 | :param unicode ticket: A ticket used to authentication ``username`` for the session 110 | ``session_key``. 111 | """ 112 | try: 113 | FederateSLO.objects.create( 114 | username=username, 115 | session_key=session_key, 116 | ticket=ticket 117 | ) 118 | except IntegrityError: # pragma: no cover (ignore if the FederateSLO already exists) 119 | pass 120 | 121 | def clean_sessions(self, logout_request): 122 | """ 123 | process a SLO request: Search for ticket values in ``logout_request``. For each 124 | ticket value matching a :class:`cas_server.models.FederateSLO`, disconnect the 125 | corresponding user. 126 | 127 | :param unicode logout_request: An XML document contening one or more Single Log Out 128 | requests. 129 | """ 130 | try: 131 | slos = self.client.get_saml_slos(logout_request) or [] 132 | except NameError: # pragma: no cover (should not happen) 133 | slos = [] 134 | for slo in slos: 135 | for federate_slo in FederateSLO.objects.filter(ticket=slo.text): 136 | logger.info( 137 | "Got an SLO requests for ticket %s, logging out user %s" % ( 138 | federate_slo.username, 139 | federate_slo.ticket 140 | ) 141 | ) 142 | session = SessionStore(session_key=federate_slo.session_key) 143 | session.flush() 144 | try: 145 | user = User.objects.get( 146 | username=federate_slo.username, 147 | session_key=federate_slo.session_key 148 | ) 149 | user.logout() 150 | user.delete() 151 | except User.DoesNotExist: # pragma: no cover (should not happen) 152 | pass 153 | federate_slo.delete() 154 | -------------------------------------------------------------------------------- /cas_server/admin.py: -------------------------------------------------------------------------------- 1 | # This program is distributed in the hope that it will be useful, but WITHOUT 2 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 3 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License version 3 for 4 | # more details. 5 | # 6 | # You should have received a copy of the GNU General Public License version 3 7 | # along with this program; if not, write to the Free Software Foundation, Inc., 51 8 | # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 9 | # 10 | # (c) 2015-2024 Valentin Samir 11 | """module for the admin interface of the app""" 12 | from .default_settings import settings 13 | 14 | from django.contrib import admin 15 | from .models import ServiceTicket, ProxyTicket, ProxyGrantingTicket, User, ServicePattern 16 | from .models import Username, ReplaceAttributName, ReplaceAttributValue, FilterAttributValue 17 | from .models import FederatedIendityProvider, FederatedUser, UserAttributes 18 | from .utils import import_attr 19 | 20 | 21 | class BaseInlines(admin.TabularInline): 22 | """ 23 | Bases: :class:`django.contrib.admin.TabularInline` 24 | 25 | Base class for inlines in the admin interface. 26 | """ 27 | #: This controls the number of extra forms the formset will display in addition to 28 | #: the initial forms. 29 | extra = 0 30 | 31 | 32 | class UserAdminInlines(BaseInlines): 33 | """ 34 | Bases: :class:`BaseInlines` 35 | 36 | Base class for inlines in :class:`UserAdmin` interface 37 | """ 38 | #: The form :class:`TicketForm` used to display tickets. 39 | form = import_attr(settings.CAS_TICKET_FORM) 40 | #: Fields to display on a object that are read only (not editable). 41 | readonly_fields = ( 42 | 'validate', 'service', 'service_pattern', 43 | 'creation', 'renew', 'single_log_out', 'value' 44 | ) 45 | #: Fields to display on a object. 46 | fields = ( 47 | 'validate', 'service', 'service_pattern', 48 | 'creation', 'renew', 'single_log_out' 49 | ) 50 | 51 | 52 | class ServiceTicketInline(UserAdminInlines): 53 | """ 54 | Bases: :class:`UserAdminInlines` 55 | 56 | :class:`ServiceTicket` in admin interface 57 | """ 58 | #: The model which the inline is using. 59 | model = ServiceTicket 60 | 61 | 62 | class ProxyTicketInline(UserAdminInlines): 63 | """ 64 | Bases: :class:`UserAdminInlines` 65 | 66 | :class:`ProxyTicket` in admin interface 67 | """ 68 | #: The model which the inline is using. 69 | model = ProxyTicket 70 | 71 | 72 | class ProxyGrantingInline(UserAdminInlines): 73 | """ 74 | Bases: :class:`UserAdminInlines` 75 | 76 | :class:`ProxyGrantingTicket` in admin interface 77 | """ 78 | #: The model which the inline is using. 79 | model = ProxyGrantingTicket 80 | 81 | 82 | class UserAdmin(admin.ModelAdmin): 83 | """ 84 | Bases: :class:`django.contrib.admin.ModelAdmin` 85 | 86 | :class:`User` in admin interface 87 | """ 88 | #: See :class:`ServiceTicketInline`, :class:`ProxyTicketInline`, :class:`ProxyGrantingInline` 89 | #: objects below the :class:`UserAdmin` fields. 90 | inlines = (ServiceTicketInline, ProxyTicketInline, ProxyGrantingInline) 91 | #: Fields to display on a object that are read only (not editable). 92 | readonly_fields = ('username', 'date', "session_key") 93 | #: Fields to display on a object. 94 | fields = ('username', 'date', "session_key") 95 | #: Fields to display on the list of class:`UserAdmin` objects. 96 | list_display = ('username', 'date', "session_key") 97 | 98 | 99 | class UsernamesInline(BaseInlines): 100 | """ 101 | Bases: :class:`BaseInlines` 102 | 103 | :class:`Username` in admin interface 104 | """ 105 | #: The model which the inline is using. 106 | model = Username 107 | 108 | 109 | class ReplaceAttributNameInline(BaseInlines): 110 | """ 111 | Bases: :class:`BaseInlines` 112 | 113 | :class:`ReplaceAttributName` in admin interface 114 | """ 115 | #: The model which the inline is using. 116 | model = ReplaceAttributName 117 | 118 | 119 | class ReplaceAttributValueInline(BaseInlines): 120 | """ 121 | Bases: :class:`BaseInlines` 122 | 123 | :class:`ReplaceAttributValue` in admin interface 124 | """ 125 | #: The model which the inline is using. 126 | model = ReplaceAttributValue 127 | 128 | 129 | class FilterAttributValueInline(BaseInlines): 130 | """ 131 | Bases: :class:`BaseInlines` 132 | 133 | :class:`FilterAttributValue` in admin interface 134 | """ 135 | #: The model which the inline is using. 136 | model = FilterAttributValue 137 | 138 | 139 | class ServicePatternAdmin(admin.ModelAdmin): 140 | """ 141 | Bases: :class:`django.contrib.admin.ModelAdmin` 142 | 143 | :class:`ServicePattern` in admin interface 144 | """ 145 | #: See :class:`UsernamesInline`, :class:`ReplaceAttributNameInline`, 146 | #: :class:`ReplaceAttributValueInline`, :class:`FilterAttributValueInline` objects below 147 | #: the :class:`ServicePatternAdmin` fields. 148 | inlines = ( 149 | UsernamesInline, 150 | ReplaceAttributNameInline, 151 | ReplaceAttributValueInline, 152 | FilterAttributValueInline 153 | ) 154 | #: Fields to display on the list of class:`ServicePatternAdmin` objects. 155 | list_display = ('pos', 'name', 'pattern', 'proxy', 156 | 'single_log_out', 'proxy_callback', 'restrict_users') 157 | 158 | 159 | class FederatedIendityProviderAdmin(admin.ModelAdmin): 160 | """ 161 | Bases: :class:`django.contrib.admin.ModelAdmin` 162 | 163 | :class:`FederatedIendityProvider` in admin 164 | interface 165 | """ 166 | #: Fields to display on a object. 167 | fields = ('pos', 'suffix', 'server_url', 'cas_protocol_version', 'verbose_name', 'display') 168 | #: Fields to display on the list of class:`FederatedIendityProviderAdmin` objects. 169 | list_display = ('verbose_name', 'suffix', 'display') 170 | 171 | 172 | class FederatedUserAdmin(admin.ModelAdmin): 173 | """ 174 | Bases: :class:`django.contrib.admin.ModelAdmin` 175 | 176 | :class:`FederatedUser` in admin 177 | interface 178 | """ 179 | #: Fields to display on a object. 180 | fields = ('username', 'provider', 'last_update') 181 | #: Fields to display on the list of class:`FederatedUserAdmin` objects. 182 | list_display = ('username', 'provider', 'last_update') 183 | 184 | 185 | class UserAttributesAdmin(admin.ModelAdmin): 186 | """ 187 | Bases: :class:`django.contrib.admin.ModelAdmin` 188 | 189 | :class:`UserAttributes` in admin 190 | interface 191 | """ 192 | #: Fields to display on a object. 193 | fields = ('username', '_attributs') 194 | 195 | 196 | admin.site.register(ServicePattern, ServicePatternAdmin) 197 | admin.site.register(FederatedIendityProvider, FederatedIendityProviderAdmin) 198 | if settings.DEBUG: # pragma: no branch (we always test with DEBUG True) 199 | admin.site.register(User, UserAdmin) 200 | admin.site.register(FederatedUser, FederatedUserAdmin) 201 | admin.site.register(UserAttributes, UserAttributesAdmin) 202 | -------------------------------------------------------------------------------- /cas_server/static/cas_server/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 46 | 48 | 49 | 51 | image/svg+xml 52 | 54 | 55 | 56 | 57 | 58 | 63 | 68 | 71 | 76 | 81 | 86 | 91 | 96 | 101 | 102 | CAS 113 | 114 | 115 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help 18 | help: 19 | @echo "Please use \`make ' where is one of" 20 | @echo " html to make standalone HTML files" 21 | @echo " dirhtml to make HTML files named index.html in directories" 22 | @echo " singlehtml to make a single large HTML file" 23 | @echo " pickle to make pickle files" 24 | @echo " json to make JSON files" 25 | @echo " htmlhelp to make HTML files and a HTML help project" 26 | @echo " qthelp to make HTML files and a qthelp project" 27 | @echo " applehelp to make an Apple Help Book" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " epub3 to make an epub3" 31 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 32 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 33 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 34 | @echo " text to make text files" 35 | @echo " man to make manual pages" 36 | @echo " texinfo to make Texinfo files" 37 | @echo " info to make Texinfo files and run them through makeinfo" 38 | @echo " gettext to make PO message catalogs" 39 | @echo " changes to make an overview of all changed/added/deprecated items" 40 | @echo " xml to make Docutils-native XML files" 41 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 42 | @echo " linkcheck to check all external links for integrity" 43 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 44 | @echo " coverage to run coverage check of the documentation (if enabled)" 45 | @echo " dummy to check syntax errors of document sources" 46 | 47 | .PHONY: clean 48 | clean: 49 | rm -rf $(BUILDDIR)/* 50 | 51 | .PHONY: html 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | .PHONY: dirhtml 58 | dirhtml: 59 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 60 | @echo 61 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 62 | 63 | .PHONY: singlehtml 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | .PHONY: pickle 70 | pickle: 71 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 72 | @echo 73 | @echo "Build finished; now you can process the pickle files." 74 | 75 | .PHONY: json 76 | json: 77 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 78 | @echo 79 | @echo "Build finished; now you can process the JSON files." 80 | 81 | .PHONY: htmlhelp 82 | htmlhelp: 83 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 84 | @echo 85 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 86 | ".hhp project file in $(BUILDDIR)/htmlhelp." 87 | 88 | .PHONY: qthelp 89 | qthelp: 90 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 91 | @echo 92 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 93 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 94 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-cas-server.qhcp" 95 | @echo "To view the help file:" 96 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-cas-server.qhc" 97 | 98 | .PHONY: applehelp 99 | applehelp: 100 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 101 | @echo 102 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 103 | @echo "N.B. You won't be able to view it unless you put it in" \ 104 | "~/Library/Documentation/Help or install it in your application" \ 105 | "bundle." 106 | 107 | .PHONY: devhelp 108 | devhelp: 109 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 110 | @echo 111 | @echo "Build finished." 112 | @echo "To view the help file:" 113 | @echo "# mkdir -p $$HOME/.local/share/devhelp/django-cas-server" 114 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-cas-server" 115 | @echo "# devhelp" 116 | 117 | .PHONY: epub 118 | epub: 119 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 120 | @echo 121 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 122 | 123 | .PHONY: epub3 124 | epub3: 125 | $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 126 | @echo 127 | @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." 128 | 129 | .PHONY: latex 130 | latex: 131 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 132 | @echo 133 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 134 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 135 | "(use \`make latexpdf' here to do that automatically)." 136 | 137 | .PHONY: latexpdf 138 | latexpdf: 139 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 140 | @echo "Running LaTeX files through pdflatex..." 141 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 142 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 143 | 144 | .PHONY: latexpdfja 145 | latexpdfja: 146 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 147 | @echo "Running LaTeX files through platex and dvipdfmx..." 148 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 149 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 150 | 151 | .PHONY: text 152 | text: 153 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 154 | @echo 155 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 156 | 157 | .PHONY: man 158 | man: 159 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 160 | @echo 161 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 162 | 163 | .PHONY: texinfo 164 | texinfo: 165 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 166 | @echo 167 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 168 | @echo "Run \`make' in that directory to run these through makeinfo" \ 169 | "(use \`make info' here to do that automatically)." 170 | 171 | .PHONY: info 172 | info: 173 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 174 | @echo "Running Texinfo files through makeinfo..." 175 | make -C $(BUILDDIR)/texinfo info 176 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 177 | 178 | .PHONY: gettext 179 | gettext: 180 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 181 | @echo 182 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 183 | 184 | .PHONY: changes 185 | changes: 186 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 187 | @echo 188 | @echo "The overview file is in $(BUILDDIR)/changes." 189 | 190 | .PHONY: linkcheck 191 | linkcheck: 192 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 193 | @echo 194 | @echo "Link check complete; look for any errors in the above output " \ 195 | "or in $(BUILDDIR)/linkcheck/output.txt." 196 | 197 | .PHONY: doctest 198 | doctest: 199 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 200 | @echo "Testing of doctests in the sources finished, look at the " \ 201 | "results in $(BUILDDIR)/doctest/output.txt." 202 | 203 | .PHONY: coverage 204 | coverage: 205 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 206 | @echo "Testing of coverage in the sources finished, look at the " \ 207 | "results in $(BUILDDIR)/coverage/python.txt." 208 | 209 | .PHONY: xml 210 | xml: 211 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 212 | @echo 213 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 214 | 215 | .PHONY: pseudoxml 216 | pseudoxml: 217 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 218 | @echo 219 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 220 | 221 | .PHONY: dummy 222 | dummy: 223 | $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy 224 | @echo 225 | @echo "Build finished. Dummy builder generates no files." 226 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. epub3 to make an epub3 31 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 32 | echo. text to make text files 33 | echo. man to make manual pages 34 | echo. texinfo to make Texinfo files 35 | echo. gettext to make PO message catalogs 36 | echo. changes to make an overview over all changed/added/deprecated items 37 | echo. xml to make Docutils-native XML files 38 | echo. pseudoxml to make pseudoxml-XML files for display purposes 39 | echo. linkcheck to check all external links for integrity 40 | echo. doctest to run all doctests embedded in the documentation if enabled 41 | echo. coverage to run coverage check of the documentation if enabled 42 | echo. dummy to check syntax errors of document sources 43 | goto end 44 | ) 45 | 46 | if "%1" == "clean" ( 47 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 48 | del /q /s %BUILDDIR%\* 49 | goto end 50 | ) 51 | 52 | 53 | REM Check if sphinx-build is available and fallback to Python version if any 54 | %SPHINXBUILD% 1>NUL 2>NUL 55 | if errorlevel 9009 goto sphinx_python 56 | goto sphinx_ok 57 | 58 | :sphinx_python 59 | 60 | set SPHINXBUILD=python -m sphinx.__init__ 61 | %SPHINXBUILD% 2> nul 62 | if errorlevel 9009 ( 63 | echo. 64 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 65 | echo.installed, then set the SPHINXBUILD environment variable to point 66 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 67 | echo.may add the Sphinx directory to PATH. 68 | echo. 69 | echo.If you don't have Sphinx installed, grab it from 70 | echo.http://sphinx-doc.org/ 71 | exit /b 1 72 | ) 73 | 74 | :sphinx_ok 75 | 76 | 77 | if "%1" == "html" ( 78 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 79 | if errorlevel 1 exit /b 1 80 | echo. 81 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 82 | goto end 83 | ) 84 | 85 | if "%1" == "dirhtml" ( 86 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 87 | if errorlevel 1 exit /b 1 88 | echo. 89 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 90 | goto end 91 | ) 92 | 93 | if "%1" == "singlehtml" ( 94 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 95 | if errorlevel 1 exit /b 1 96 | echo. 97 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 98 | goto end 99 | ) 100 | 101 | if "%1" == "pickle" ( 102 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 103 | if errorlevel 1 exit /b 1 104 | echo. 105 | echo.Build finished; now you can process the pickle files. 106 | goto end 107 | ) 108 | 109 | if "%1" == "json" ( 110 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 111 | if errorlevel 1 exit /b 1 112 | echo. 113 | echo.Build finished; now you can process the JSON files. 114 | goto end 115 | ) 116 | 117 | if "%1" == "htmlhelp" ( 118 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 119 | if errorlevel 1 exit /b 1 120 | echo. 121 | echo.Build finished; now you can run HTML Help Workshop with the ^ 122 | .hhp project file in %BUILDDIR%/htmlhelp. 123 | goto end 124 | ) 125 | 126 | if "%1" == "qthelp" ( 127 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 128 | if errorlevel 1 exit /b 1 129 | echo. 130 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 131 | .qhcp project file in %BUILDDIR%/qthelp, like this: 132 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\django-cas-server.qhcp 133 | echo.To view the help file: 134 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-cas-server.ghc 135 | goto end 136 | ) 137 | 138 | if "%1" == "devhelp" ( 139 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 140 | if errorlevel 1 exit /b 1 141 | echo. 142 | echo.Build finished. 143 | goto end 144 | ) 145 | 146 | if "%1" == "epub" ( 147 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 148 | if errorlevel 1 exit /b 1 149 | echo. 150 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 151 | goto end 152 | ) 153 | 154 | if "%1" == "epub3" ( 155 | %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 156 | if errorlevel 1 exit /b 1 157 | echo. 158 | echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. 159 | goto end 160 | ) 161 | 162 | if "%1" == "latex" ( 163 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 164 | if errorlevel 1 exit /b 1 165 | echo. 166 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdf" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "latexpdfja" ( 181 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 182 | cd %BUILDDIR%/latex 183 | make all-pdf-ja 184 | cd %~dp0 185 | echo. 186 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 187 | goto end 188 | ) 189 | 190 | if "%1" == "text" ( 191 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 192 | if errorlevel 1 exit /b 1 193 | echo. 194 | echo.Build finished. The text files are in %BUILDDIR%/text. 195 | goto end 196 | ) 197 | 198 | if "%1" == "man" ( 199 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 200 | if errorlevel 1 exit /b 1 201 | echo. 202 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 203 | goto end 204 | ) 205 | 206 | if "%1" == "texinfo" ( 207 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 208 | if errorlevel 1 exit /b 1 209 | echo. 210 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 211 | goto end 212 | ) 213 | 214 | if "%1" == "gettext" ( 215 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 216 | if errorlevel 1 exit /b 1 217 | echo. 218 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 219 | goto end 220 | ) 221 | 222 | if "%1" == "changes" ( 223 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 224 | if errorlevel 1 exit /b 1 225 | echo. 226 | echo.The overview file is in %BUILDDIR%/changes. 227 | goto end 228 | ) 229 | 230 | if "%1" == "linkcheck" ( 231 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 232 | if errorlevel 1 exit /b 1 233 | echo. 234 | echo.Link check complete; look for any errors in the above output ^ 235 | or in %BUILDDIR%/linkcheck/output.txt. 236 | goto end 237 | ) 238 | 239 | if "%1" == "doctest" ( 240 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 241 | if errorlevel 1 exit /b 1 242 | echo. 243 | echo.Testing of doctests in the sources finished, look at the ^ 244 | results in %BUILDDIR%/doctest/output.txt. 245 | goto end 246 | ) 247 | 248 | if "%1" == "coverage" ( 249 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 250 | if errorlevel 1 exit /b 1 251 | echo. 252 | echo.Testing of coverage in the sources finished, look at the ^ 253 | results in %BUILDDIR%/coverage/python.txt. 254 | goto end 255 | ) 256 | 257 | if "%1" == "xml" ( 258 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 259 | if errorlevel 1 exit /b 1 260 | echo. 261 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 262 | goto end 263 | ) 264 | 265 | if "%1" == "pseudoxml" ( 266 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 267 | if errorlevel 1 exit /b 1 268 | echo. 269 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 270 | goto end 271 | ) 272 | 273 | if "%1" == "dummy" ( 274 | %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy 275 | if errorlevel 1 exit /b 1 276 | echo. 277 | echo.Build finished. Dummy builder generates no files. 278 | goto end 279 | ) 280 | 281 | :end 282 | -------------------------------------------------------------------------------- /cas_server/forms.py: -------------------------------------------------------------------------------- 1 | # This program is distributed in the hope that it will be useful, but WITHOUT 2 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 3 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License version 3 for 4 | # more details. 5 | # 6 | # You should have received a copy of the GNU General Public License version 3 7 | # along with this program; if not, write to the Free Software Foundation, Inc., 51 8 | # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 9 | # 10 | # (c) 2015-2020 Valentin Samir 11 | """forms for the app""" 12 | from .default_settings import settings 13 | 14 | from django import forms 15 | from django.forms import widgets 16 | 17 | import cas_server.utils as utils 18 | import cas_server.models as models 19 | 20 | import sys 21 | if sys.version_info < (3, ): 22 | from django.utils.translation import ugettext_lazy as _ 23 | else: 24 | from django.utils.translation import gettext_lazy as _ 25 | 26 | 27 | class BootsrapForm(forms.Form): 28 | """ 29 | Bases: :class:`django.forms.Form` 30 | 31 | Form base class to use boostrap then rendering the form fields 32 | """ 33 | def __init__(self, *args, **kwargs): 34 | super(BootsrapForm, self).__init__(*args, **kwargs) 35 | for field in self.fields.values(): 36 | # Only tweak the field if it will be displayed 37 | if not isinstance(field.widget, widgets.HiddenInput): 38 | attrs = {} 39 | if ( 40 | isinstance(field.widget, (widgets.Input, widgets.Select, widgets.Textarea)) and 41 | not isinstance(field.widget, (widgets.CheckboxInput,)) 42 | ): 43 | attrs['class'] = "form-control" 44 | if isinstance(field.widget, (widgets.Input, widgets.Textarea)) and field.label: 45 | attrs["placeholder"] = field.label 46 | if field.required: 47 | attrs["required"] = "required" 48 | field.widget.attrs.update(attrs) 49 | 50 | 51 | class BaseLogin(BootsrapForm): 52 | """ 53 | Bases: :class:`BootsrapForm` 54 | 55 | Base form with all field possibly hidden on the login pages 56 | """ 57 | #: The service url for which the user want a ticket 58 | service = forms.CharField(widget=forms.HiddenInput(), required=False) 59 | #: A valid LoginTicket to prevent POST replay 60 | lt = forms.CharField(widget=forms.HiddenInput(), required=False) 61 | #: Is the service asking the authentication renewal ? 62 | renew = forms.BooleanField(widget=forms.HiddenInput(), required=False) 63 | #: Url to redirect to if the authentication fail (user not authenticated or bad service) 64 | gateway = forms.CharField(widget=forms.HiddenInput(), required=False) 65 | method = forms.CharField(widget=forms.HiddenInput(), required=False) 66 | 67 | 68 | class WarnForm(BaseLogin): 69 | """ 70 | Bases: :class:`BaseLogin` 71 | 72 | Form used on warn page before emiting a ticket 73 | """ 74 | #: ``True`` if the user has been warned of the ticket emission 75 | warned = forms.BooleanField(widget=forms.HiddenInput(), required=False) 76 | 77 | 78 | class FederateSelect(BaseLogin): 79 | """ 80 | Bases: :class:`BaseLogin` 81 | 82 | Form used on the login page when ``settings.CAS_FEDERATE`` is ``True`` 83 | allowing the user to choose an identity provider. 84 | """ 85 | #: The providers the user can choose to be used as authentication backend 86 | provider = forms.ModelChoiceField( 87 | queryset=models.FederatedIendityProvider.objects.filter(display=True).order_by( 88 | "pos", 89 | "verbose_name", 90 | "suffix" 91 | ), 92 | to_field_name="suffix", 93 | label=_('Identity provider'), 94 | ) 95 | #: A checkbox to ask to be warn before emiting a ticket for another service 96 | warn = forms.BooleanField( 97 | label=_('Warn me before logging me into other sites.'), 98 | required=False 99 | ) 100 | #: A checkbox to remember the user choices of :attr:`provider` 101 | remember = forms.BooleanField(label=_('Remember the identity provider'), required=False) 102 | 103 | 104 | class UserCredential(BaseLogin): 105 | """ 106 | Bases: :class:`BaseLogin` 107 | 108 | Form used on the login page to retrive user credentials 109 | """ 110 | #: The user username 111 | username = forms.CharField( 112 | label=_('username'), 113 | widget=forms.TextInput(attrs={'autofocus': 'autofocus'}) 114 | ) 115 | #: The user password 116 | password = forms.CharField(label=_('password'), widget=forms.PasswordInput) 117 | #: A checkbox to ask to be warn before emiting a ticket for another service 118 | warn = forms.BooleanField( 119 | label=_('Warn me before logging me into other sites.'), 120 | required=False 121 | ) 122 | 123 | def clean(self): 124 | """ 125 | Validate that the submited :attr:`username` and :attr:`password` are valid 126 | 127 | :raises django.forms.ValidationError: if the :attr:`username` and :attr:`password` 128 | are not valid. 129 | :return: The cleaned POST data 130 | :rtype: dict 131 | """ 132 | cleaned_data = super(UserCredential, self).clean() 133 | if "username" in cleaned_data and "password" in cleaned_data: 134 | auth = utils.import_attr(settings.CAS_AUTH_CLASS)(cleaned_data["username"]) 135 | if auth.test_password(cleaned_data["password"]): 136 | cleaned_data["username"] = auth.username 137 | else: 138 | raise forms.ValidationError( 139 | _(u"The credentials you provided cannot be determined to be authentic.") 140 | ) 141 | return cleaned_data 142 | 143 | 144 | class FederateUserCredential(UserCredential): 145 | """ 146 | Bases: :class:`UserCredential` 147 | 148 | Form used on a auto submited page for linking the views 149 | :class:`FederateAuth` and 150 | :class:`LoginView`. 151 | 152 | On successful authentication on a provider, in the view 153 | :class:`FederateAuth` a 154 | :class:`FederatedUser` is created by 155 | :meth:`cas_server.federate.CASFederateValidateUser.verify_ticket` and the user is redirected 156 | to :class:`LoginView`. This form is then automatically filled 157 | with infos matching the created :class:`FederatedUser` 158 | using the ``ticket`` as one time password and submited using javascript. If javascript is 159 | not enabled, a connect button is displayed. 160 | 161 | This stub authentication form, allow to implement the federated mode with very few 162 | modificatons to the :class:`LoginView` view. 163 | """ 164 | 165 | def __init__(self, *args, **kwargs): 166 | super(FederateUserCredential, self).__init__(*args, **kwargs) 167 | # All fields are hidden and auto filled by the /login view logic 168 | for name, field in self.fields.items(): 169 | field.widget = forms.HiddenInput() 170 | self[name].display = False 171 | 172 | def clean(self): 173 | """ 174 | Validate that the submited :attr:`username` and :attr:`password` are valid using 175 | the :class:`CASFederateAuth` auth class. 176 | 177 | :raises django.forms.ValidationError: if the :attr:`username` and :attr:`password` 178 | do not correspond to a :class:`FederatedUser`. 179 | :return: The cleaned POST data 180 | :rtype: dict 181 | """ 182 | cleaned_data = super(FederateUserCredential, self).clean() 183 | try: 184 | user = models.FederatedUser.get_from_federated_username(cleaned_data["username"]) 185 | user.ticket = "" 186 | user.save() 187 | # should not happed as if the FederatedUser do not exists, super should 188 | # raise before a ValidationError("bad user") 189 | except models.FederatedUser.DoesNotExist: # pragma: no cover (should not happend) 190 | raise forms.ValidationError( 191 | _(u"User not found in the temporary database, please try to reconnect") 192 | ) 193 | return cleaned_data 194 | 195 | 196 | class TicketForm(forms.ModelForm): 197 | """ 198 | Bases: :class:`django.forms.ModelForm` 199 | 200 | Form for Tickets in the admin interface 201 | """ 202 | class Meta: 203 | model = models.Ticket 204 | exclude = [] 205 | service = forms.CharField(label=_('service'), widget=forms.TextInput) 206 | -------------------------------------------------------------------------------- /cas_server/locale/zh_Hans/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2021-08-01 22:18+0800\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=1; plural=0;\n" 20 | 21 | #: apps.py:30 templates/cas_server/bs3/base.html:7 22 | #: templates/cas_server/bs3/base.html:26 templates/cas_server/bs4/base.html:6 23 | #: templates/cas_server/bs4/base.html:17 24 | msgid "Central Authentication Service" 25 | msgstr "认证中心服务" 26 | 27 | #: default_settings.py:230 28 | msgid "" 29 | "The Central Authentication Service grants you access to most of our websites " 30 | "by authenticating only once, so you don't need to type your credentials " 31 | "again unless your session expires or you logout." 32 | msgstr "" 33 | "您仅需在认证中心认证一次,就可以访问您的多数网站, " 34 | "这样您不再需要重复输入认证,除非您的会话过期,或者您登出了." 35 | 36 | #: forms.py:93 37 | msgid "Identity provider" 38 | msgstr "身份提供者" 39 | 40 | #: forms.py:97 forms.py:119 41 | msgid "Warn me before logging me into other sites." 42 | msgstr "登录到其它网站时警告我" 43 | 44 | #: forms.py:101 45 | msgid "Remember the identity provider" 46 | msgstr "记住此身份提供者" 47 | 48 | #: forms.py:112 models.py:646 49 | msgid "username" 50 | msgstr "用户名" 51 | 52 | #: forms.py:116 53 | msgid "password" 54 | msgstr "密码" 55 | 56 | #: forms.py:139 57 | msgid "The credentials you provided cannot be determined to be authentic." 58 | msgstr "您提供的令牌不能通过鉴权" 59 | 60 | #: forms.py:191 61 | msgid "User not found in the temporary database, please try to reconnect" 62 | msgstr "在临时数据库找不到此用户,请尝试重新连接" 63 | 64 | #: forms.py:205 65 | msgid "service" 66 | msgstr "服务" 67 | 68 | #: management/commands/cas_clean_federate.py:25 69 | msgid "Clean old federated users" 70 | msgstr "清除过期联盟用户" 71 | 72 | #: management/commands/cas_clean_sessions.py:27 73 | msgid "Clean deleted sessions" 74 | msgstr "清除被删除的会话" 75 | 76 | #: management/commands/cas_clean_tickets.py:27 77 | msgid "Clean old tickets" 78 | msgstr "清除过期凭证" 79 | 80 | #: models.py:79 81 | msgid "identity provider" 82 | msgstr "身份提供者" 83 | 84 | #: models.py:80 85 | msgid "identity providers" 86 | msgstr "身份证供者" 87 | 88 | #: models.py:86 89 | msgid "suffix" 90 | msgstr "后缀" 91 | 92 | #: models.py:88 93 | msgid "" 94 | "Suffix append to backend CAS returned username: ``returned_username`` @ " 95 | "``suffix``." 96 | msgstr "后端 CAS 附加后缀返回的用户名: ``returned_username`` @ " 97 | "``suffix``." 98 | 99 | #: models.py:95 100 | msgid "server url" 101 | msgstr "服务 url" 102 | 103 | #: models.py:105 104 | msgid "CAS protocol version" 105 | msgstr "CAS 协议版本" 106 | 107 | #: models.py:107 108 | msgid "" 109 | "Version of the CAS protocol to use when sending requests the the backend CAS." 110 | msgstr "" 111 | "后端 CAS 发送请求时使用的 CAS 协议版本" 112 | 113 | #: models.py:114 114 | msgid "verbose name" 115 | msgstr "详细名称" 116 | 117 | #: models.py:115 118 | msgid "Name for this identity provider displayed on the login page." 119 | msgstr "在登录页显示的身份提供者的名字" 120 | 121 | #: models.py:121 models.py:498 122 | msgid "position" 123 | msgstr "位置" 124 | 125 | #: models.py:135 126 | msgid "display" 127 | msgstr "显示" 128 | 129 | #: models.py:136 130 | msgid "Display the provider on the login page." 131 | msgstr "在登录页显示提供者" 132 | 133 | #: models.py:174 134 | msgid "Federated user" 135 | msgstr "联盟用户" 136 | 137 | #: models.py:175 138 | msgid "Federated users" 139 | msgstr "联盟用户" 140 | 141 | #: models.py:254 142 | msgid "User attributes cache" 143 | msgstr "用户属性缓存" 144 | 145 | #: models.py:255 146 | msgid "User attributes caches" 147 | msgstr "用户属性缓存" 148 | 149 | #: models.py:279 150 | msgid "User" 151 | msgstr "用户" 152 | 153 | #: models.py:280 154 | msgid "Users" 155 | msgstr "用户" 156 | 157 | #: models.py:372 158 | #, python-format 159 | msgid "Error during service logout %s" 160 | msgstr "服务登出中的异常 %s" 161 | 162 | #: models.py:492 163 | msgid "Service pattern" 164 | msgstr "服务范式" 165 | 166 | #: models.py:493 167 | msgid "Services patterns" 168 | msgstr "服务范式" 169 | 170 | #: models.py:499 171 | msgid "service patterns are sorted using the position attribute" 172 | msgstr "服务范式会按照位置属性排序" 173 | 174 | #: models.py:507 models.py:676 175 | msgid "name" 176 | msgstr "名称" 177 | 178 | #: models.py:508 179 | msgid "A name for the service" 180 | msgstr "服务的名称" 181 | 182 | #: models.py:516 models.py:723 models.py:757 183 | msgid "pattern" 184 | msgstr "范式" 185 | 186 | #: models.py:518 187 | msgid "" 188 | "A regular expression matching services. Will usually looks like '^https://" 189 | "some\\.server\\.com/path/.*$'.As it is a regular expression, special " 190 | "character must be escaped with a '\\'." 191 | msgstr "" 192 | "用一个正则表示式来匹配服务。一般如 '^https://" 193 | "some\\.server\\.com/path/.*$'. 在正则表达式中,特殊" 194 | "字符必须用 '\\' 转码." 195 | 196 | #: models.py:529 197 | msgid "user field" 198 | msgstr "用户字段" 199 | 200 | #: models.py:530 201 | msgid "Name of the attribute to transmit as username, empty = login" 202 | msgstr "被转译作为用户名的属性字段,空 = login" 203 | 204 | #: models.py:535 205 | msgid "restrict username" 206 | msgstr "用户名限制" 207 | 208 | #: models.py:536 209 | msgid "Limit username allowed to connect to the list provided bellow" 210 | msgstr "只允许下面列表提供的用户名连接" 211 | 212 | #: models.py:541 213 | msgid "proxy" 214 | msgstr "代理" 215 | 216 | #: models.py:542 217 | msgid "Proxy tickets can be delivered to the service" 218 | msgstr "可以对服务分发的代理凭证" 219 | 220 | #: models.py:548 221 | msgid "proxy callback" 222 | msgstr "代理回调" 223 | 224 | #: models.py:549 225 | msgid "can be used as a proxy callback to deliver PGT" 226 | msgstr "可以作为代理回调来分发PGT" 227 | 228 | #: models.py:556 229 | msgid "single log out" 230 | msgstr "单点登出" 231 | 232 | #: models.py:557 233 | msgid "Enable SLO for the service" 234 | msgstr "为服务启用 SLO" 235 | 236 | #: models.py:565 237 | msgid "single log out callback" 238 | msgstr "单点登出回调" 239 | 240 | #: models.py:566 241 | msgid "" 242 | "URL where the SLO request will be POST. empty = service url\n" 243 | "This is usefull for non HTTP proxied services." 244 | msgstr "" 245 | "SLO 的 POST 请求使用的 URL. 空 = 服务地址\n" 246 | "在为非 HTTP 代理服务时有用" 247 | 248 | #: models.py:647 249 | msgid "username allowed to connect to the service" 250 | msgstr "允许连接到服务的用户名" 251 | 252 | #: models.py:677 253 | msgid "name of an attribute to send to the service, use * for all attributes" 254 | msgstr "发给服务的属性名, 使用 * 表示所有属性" 255 | 256 | #: models.py:684 models.py:765 257 | msgid "replace" 258 | msgstr "替换" 259 | 260 | #: models.py:685 261 | msgid "" 262 | "name under which the attribute will be show to the service. empty = default " 263 | "name of the attribut" 264 | msgstr "" 265 | "展示给服务的属性的名字. 空 = default" 266 | "属性的名字" 267 | 268 | #: models.py:716 models.py:751 269 | msgid "attribute" 270 | msgstr "属性" 271 | 272 | #: models.py:717 273 | msgid "Name of the attribute which must verify pattern" 274 | msgstr "必须校验范式的属性的名字" 275 | 276 | #: models.py:724 277 | msgid "a regular expression" 278 | msgstr "一个正则表达式" 279 | 280 | #: models.py:752 281 | msgid "Name of the attribute for which the value must be replace" 282 | msgstr "必须被替换的值的属性的名字" 283 | 284 | #: models.py:758 285 | msgid "An regular expression maching whats need to be replaced" 286 | msgstr "一个正则表达式,符合的将要被替换" 287 | 288 | #: models.py:766 289 | msgid "replace expression, groups are capture by \\1, \\2 …" 290 | msgstr "替换表达式, 用 \\`, \\2 等等来替换组" 291 | 292 | #: templates/cas_server/bs3/base.html:43 templates/cas_server/bs4/base.html:28 293 | #, python-format 294 | msgid "" 295 | "A new version of the application is available. This instance runs " 296 | "%(VERSION)s and the last version is %(LAST_VERSION)s. Please consider " 297 | "upgrading." 298 | msgstr "" 299 | "此应用有一个新版本可用. 此实例运行于 %(VERSION)s, 最新的版本是 %(LAST_VERSION)s. 请考虑升级" 300 | 301 | #: templates/cas_server/bs3/logged.html:4 302 | #: templates/cas_server/bs4/logged.html:4 303 | msgid "" 304 | "

Log In Successful

You have successfully logged into the Central " 305 | "Authentication Service.
For security reasons, please Log Out and Exit " 306 | "your web browser when you are done accessing services that require " 307 | "authentication!" 308 | msgstr "" 309 | "

登入成功

您已经成功登入认证中心." 310 | "
出于安全考虑, 当您用完需要认证的服务时,请您登出并退出您的浏览器!" 311 | 312 | #: templates/cas_server/bs3/logged.html:8 313 | #: templates/cas_server/bs4/logged.html:8 314 | msgid "Log me out from all my sessions" 315 | msgstr "从我的所有会话中登出" 316 | 317 | #: templates/cas_server/bs3/logged.html:14 318 | #: templates/cas_server/bs4/logged.html:14 319 | msgid "Forget the identity provider" 320 | msgstr "忘掉身份提供者" 321 | 322 | #: templates/cas_server/bs3/logged.html:18 323 | #: templates/cas_server/bs4/logged.html:18 324 | msgid "Logout" 325 | msgstr "登出" 326 | 327 | #: templates/cas_server/bs3/login.html:6 templates/cas_server/bs4/login.html:7 328 | msgid "Please log in" 329 | msgstr "请登录" 330 | 331 | #: templates/cas_server/bs3/login.html:14 332 | #: templates/cas_server/bs4/login.html:17 333 | msgid "Login" 334 | msgstr "登录" 335 | 336 | #: templates/cas_server/bs3/warn.html:9 templates/cas_server/bs4/warn.html:9 337 | msgid "Connect to the service" 338 | msgstr "连接到服务" 339 | 340 | #: utils.py:753 341 | #, python-format 342 | msgid "\"%(value)s\" is not a valid regular expression" 343 | msgstr "\"%(value)s\" 不是一个有效的正则表达式" 344 | 345 | #: views.py:197 346 | msgid "" 347 | "

Logout successful

You have successfully logged out from the Central " 348 | "Authentication Service. For security reasons, close your web browser." 349 | msgstr "" 350 | "

登出成功

您成功从认证中心登出." 351 | "安全起见,请关闭您的浏览器" 352 | 353 | 354 | #: views.py:203 355 | #, python-format 356 | msgid "" 357 | "

Logout successful

You have successfully logged out from %d sessions " 358 | "of the Central Authentication Service. For security reasons, close your web " 359 | "browser." 360 | msgstr "" 361 | "

登出成功

您已经从认证中心服务的会话 %d 中成功登出" 362 | "为安全起见,请关闭您的浏览器" 363 | 364 | #: views.py:210 365 | msgid "" 366 | "

Logout successful

You were already logged out from the Central " 367 | "Authentication Service. For security reasons, close your web browser." 368 | msgstr "" 369 | "

登出成功

您已经从认证中心服务登出. " 370 | "为安全起见,请关闭您的浏览器" 371 | 372 | #: views.py:391 373 | #, python-format 374 | msgid "" 375 | "Invalid response from your identity provider CAS upon ticket %(ticket)s " 376 | "validation: %(error)r" 377 | msgstr "" 378 | "您的身份提供者 CAS 对凭证 %(ticket)s 返回了无效响应" 379 | "校验: %(error)r" 380 | 381 | #: views.py:513 382 | msgid "Invalid login ticket, please try to log in again" 383 | msgstr "无效登录凭证, 请尝试重新登录" 384 | 385 | #: views.py:706 386 | #, python-format 387 | msgid "Authentication has been required by service %(name)s (%(url)s)" 388 | msgstr "服务 %(name)s (%(url)s) 需要认证" 389 | 390 | #: views.py:744 391 | #, python-format 392 | msgid "Service %(url)s not allowed." 393 | msgstr "不允许的服务 %(url)s" 394 | 395 | #: views.py:751 396 | msgid "Username not allowed" 397 | msgstr "不允许的用户名" 398 | 399 | #: views.py:758 400 | msgid "User characteristics not allowed" 401 | msgstr "不允许的用户特征" 402 | 403 | #: views.py:765 404 | #, python-format 405 | msgid "The attribute %(field)s is needed to use that service" 406 | msgstr "使用那个服务需要属性 %(field)s" 407 | 408 | #: views.py:857 409 | #, python-format 410 | msgid "Authentication renewal required by service %(name)s (%(url)s)." 411 | msgstr "服务 %(name)s (%(url)s) 需要更新认证" 412 | 413 | #: views.py:864 414 | #, python-format 415 | msgid "Authentication required by service %(name)s (%(url)s)." 416 | msgstr "服务 %(name)s (%(url)s) 需要认证." 417 | 418 | #: views.py:872 419 | #, python-format 420 | msgid "Service %s not allowed" 421 | msgstr "不允许的服务 %s" 422 | -------------------------------------------------------------------------------- /cas_server/tests/mixin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This program is distributed in the hope that it will be useful, but WITHOUT 3 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 4 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License version 3 for 5 | # more details. 6 | # 7 | # You should have received a copy of the GNU General Public License version 3 8 | # along with this program; if not, write to the Free Software Foundation, Inc., 51 9 | # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 10 | # 11 | # (c) 2016-2019 Valentin Samir 12 | """Some mixin classes for tests""" 13 | from cas_server.default_settings import settings 14 | from django.utils import timezone 15 | 16 | import re 17 | from lxml import etree 18 | from datetime import timedelta 19 | 20 | from cas_server import models 21 | from cas_server.tests.utils import get_auth_client 22 | 23 | 24 | class BaseServicePattern(object): 25 | """Mixing for setting up service pattern for testing""" 26 | @classmethod 27 | def setup_service_patterns(cls, proxy=False): 28 | """setting up service pattern""" 29 | # For general purpose testing 30 | cls.service = "https://www.example.com" 31 | cls.service_pattern = models.ServicePattern.objects.create( 32 | name="example", 33 | pattern=r"^https://www\.example\.com(/.*)?$", 34 | proxy=proxy, 35 | ) 36 | models.ReplaceAttributName.objects.create(name="*", service_pattern=cls.service_pattern) 37 | 38 | # For testing the restrict_users attributes 39 | cls.service_restrict_user_fail = "https://restrict_user_fail.example.com" 40 | cls.service_pattern_restrict_user_fail = models.ServicePattern.objects.create( 41 | name="restrict_user_fail", 42 | pattern=r"^https://restrict_user_fail\.example\.com(/.*)?$", 43 | restrict_users=True, 44 | proxy=proxy, 45 | ) 46 | cls.service_restrict_user_success = "https://restrict_user_success.example.com" 47 | cls.service_pattern_restrict_user_success = models.ServicePattern.objects.create( 48 | name="restrict_user_success", 49 | pattern=r"^https://restrict_user_success\.example\.com(/.*)?$", 50 | restrict_users=True, 51 | proxy=proxy, 52 | ) 53 | models.Username.objects.create( 54 | value=settings.CAS_TEST_USER, 55 | service_pattern=cls.service_pattern_restrict_user_success 56 | ) 57 | 58 | # For testing the user attributes filtering conditions 59 | cls.service_filter_fail = "https://filter_fail.example.com" 60 | cls.service_pattern_filter_fail = models.ServicePattern.objects.create( 61 | name="filter_fail", 62 | pattern=r"^https://filter_fail\.example\.com(/.*)?$", 63 | proxy=proxy, 64 | ) 65 | models.FilterAttributValue.objects.create( 66 | attribut="right", 67 | pattern="^admin$", 68 | service_pattern=cls.service_pattern_filter_fail 69 | ) 70 | cls.service_filter_fail_alt = "https://filter_fail_alt.example.com" 71 | cls.service_pattern_filter_fail_alt = models.ServicePattern.objects.create( 72 | name="filter_fail_alt", 73 | pattern=r"^https://filter_fail_alt\.example\.com(/.*)?$", 74 | proxy=proxy, 75 | ) 76 | models.FilterAttributValue.objects.create( 77 | attribut="nom", 78 | pattern="^toto$", 79 | service_pattern=cls.service_pattern_filter_fail_alt 80 | ) 81 | cls.service_filter_success = "https://filter_success.example.com" 82 | cls.service_pattern_filter_success = models.ServicePattern.objects.create( 83 | name="filter_success", 84 | pattern=r"^https://filter_success\.example\.com(/.*)?$", 85 | proxy=proxy, 86 | ) 87 | models.FilterAttributValue.objects.create( 88 | attribut="email", 89 | pattern="^%s$" % re.escape(settings.CAS_TEST_ATTRIBUTES['email']), 90 | service_pattern=cls.service_pattern_filter_success 91 | ) 92 | 93 | # For testing the user_field attributes 94 | cls.service_field_needed_fail = "https://field_needed_fail.example.com" 95 | cls.service_pattern_field_needed_fail = models.ServicePattern.objects.create( 96 | name="field_needed_fail", 97 | pattern=r"^https://field_needed_fail\.example\.com(/.*)?$", 98 | user_field="uid", 99 | proxy=proxy, 100 | ) 101 | cls.service_field_needed_success = "https://field_needed_success.example.com" 102 | cls.service_pattern_field_needed_success = models.ServicePattern.objects.create( 103 | name="field_needed_success", 104 | pattern=r"^https://field_needed_success\.example\.com(/.*)?$", 105 | user_field="alias", 106 | proxy=proxy, 107 | ) 108 | cls.service_field_needed_success_alt = "https://field_needed_success_alt.example.com" 109 | cls.service_pattern_field_needed_success = models.ServicePattern.objects.create( 110 | name="field_needed_success_alt", 111 | pattern=r"^https://field_needed_success_alt\.example\.com(/.*)?$", 112 | user_field="nom", 113 | proxy=proxy, 114 | ) 115 | 116 | 117 | class XmlContent(object): 118 | """Mixin for test on CAS XML responses""" 119 | def assert_error(self, response, code, text=None): 120 | """Assert a validation error""" 121 | self.assertEqual(response.status_code, 200) 122 | root = etree.fromstring(response.content) 123 | error = root.xpath( 124 | "//cas:authenticationFailure", 125 | namespaces={'cas': "http://www.yale.edu/tp/cas"} 126 | ) 127 | self.assertEqual(len(error), 1) 128 | self.assertEqual(error[0].attrib['code'], code) 129 | if text is not None: 130 | self.assertEqual(error[0].text, text) 131 | 132 | def assert_success(self, response, username, original_attributes): 133 | """assert a ticket validation success""" 134 | self.assertEqual(response.status_code, 200) 135 | 136 | root = etree.fromstring(response.content) 137 | sucess = root.xpath( 138 | "//cas:authenticationSuccess", 139 | namespaces={'cas': "http://www.yale.edu/tp/cas"} 140 | ) 141 | self.assertTrue(sucess) 142 | 143 | users = root.xpath("//cas:user", namespaces={'cas': "http://www.yale.edu/tp/cas"}) 144 | self.assertEqual(len(users), 1) 145 | self.assertEqual(users[0].text, username) 146 | 147 | attributes = root.xpath( 148 | "//cas:attributes", 149 | namespaces={'cas': "http://www.yale.edu/tp/cas"} 150 | ) 151 | self.assertEqual(len(attributes), 1) 152 | ignore_attrs = { 153 | "authenticationDate", "longTermAuthenticationRequestTokenUsed", "isFromNewLogin" 154 | } 155 | ignored_attrs = 0 156 | attrs1 = set() 157 | for attr in attributes[0]: 158 | name = attr.tag[len("http://www.yale.edu/tp/cas")+2:] 159 | if name not in ignore_attrs: 160 | attrs1.add((name, attr.text)) 161 | else: 162 | ignored_attrs += 1 163 | 164 | attributes = root.xpath("//cas:attribute", namespaces={'cas': "http://www.yale.edu/tp/cas"}) 165 | self.assertEqual(len(attributes), len(attrs1) + ignored_attrs) 166 | attrs2 = set() 167 | for attr in attributes: 168 | name = attr.attrib['name'] 169 | if name not in ignore_attrs: 170 | attrs2.add((name, attr.attrib['value'])) 171 | original = set() 172 | for key, value in original_attributes.items(): 173 | if isinstance(value, list): 174 | for sub_value in value: 175 | original.add((key, sub_value)) 176 | else: 177 | original.add((key, value)) 178 | self.assertEqual(attrs1, attrs2) 179 | self.assertEqual(attrs1, original) 180 | 181 | return root 182 | 183 | 184 | class UserModels(object): 185 | """Mixin for test on CAS user models""" 186 | @staticmethod 187 | def expire_user(): 188 | """return an expired user""" 189 | client = get_auth_client() 190 | 191 | new_date = timezone.now() - timedelta(seconds=(settings.SESSION_COOKIE_AGE + 600)) 192 | models.User.objects.filter( 193 | username=settings.CAS_TEST_USER, 194 | session_key=client.session.session_key 195 | ).update(date=new_date) 196 | return client 197 | 198 | @staticmethod 199 | def tgt_expired_user(sec): 200 | """return a user logged since sec seconds""" 201 | client = get_auth_client() 202 | new_date = timezone.now() - timedelta(seconds=(sec)) 203 | models.User.objects.filter( 204 | username=settings.CAS_TEST_USER, 205 | session_key=client.session.session_key 206 | ).update(last_login=new_date) 207 | return client 208 | 209 | @staticmethod 210 | def get_user(client): 211 | """return the user associated with an authenticated client""" 212 | return models.User.objects.get( 213 | username=settings.CAS_TEST_USER, 214 | session_key=client.session.session_key 215 | ) 216 | 217 | 218 | class CanLogin(object): 219 | """Assertion about login""" 220 | def assert_logged( 221 | self, client, response, warn=False, 222 | code=200, username=settings.CAS_TEST_USER 223 | ): 224 | """Assertions testing that client is well authenticated""" 225 | self.assertEqual(response.status_code, code) 226 | # this message is displayed to the user upon successful authentication 227 | self.assertIn( 228 | ( 229 | b"You have successfully logged into " 230 | b"the Central Authentication Service" 231 | ), 232 | response.content 233 | ) 234 | # these session variables a set if usccessfully authenticated 235 | self.assertEqual(client.session["username"], username) 236 | self.assertIs(client.session["warn"], warn) 237 | self.assertIs(client.session["authenticated"], True) 238 | 239 | # on successfull authentication, a corresponding user object is created 240 | self.assertTrue( 241 | models.User.objects.get( 242 | username=username, 243 | session_key=client.session.session_key 244 | ) 245 | ) 246 | 247 | def assert_login_failed(self, client, response, code=200): 248 | """Assertions testing a failed login attempt""" 249 | self.assertEqual(response.status_code, code) 250 | # this message is displayed to the user upon successful authentication, so it should not 251 | # appear 252 | self.assertNotIn( 253 | ( 254 | b"You have successfully logged into " 255 | b"the Central Authentication Service" 256 | ), 257 | response.content 258 | ) 259 | 260 | # if authentication has failed, these session variables should not be set 261 | self.assertTrue(client.session.get("username") is None) 262 | self.assertTrue(client.session.get("warn") is None) 263 | self.assertTrue(client.session.get("authenticated") is None) 264 | 265 | 266 | class FederatedIendityProviderModel(object): 267 | """Mixin for test classes using the FederatedIendityProvider model""" 268 | @staticmethod 269 | def setup_federated_identity_provider(providers): 270 | """setting up federated identity providers""" 271 | for suffix, (server_url, cas_protocol_version, verbose_name) in providers.items(): 272 | models.FederatedIendityProvider.objects.create( 273 | suffix=suffix, 274 | server_url=server_url, 275 | cas_protocol_version=cas_protocol_version, 276 | verbose_name=verbose_name 277 | ) 278 | -------------------------------------------------------------------------------- /cas_server/tests/test_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This program is distributed in the hope that it will be useful, but WITHOUT 3 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 4 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License version 3 for 5 | # more details. 6 | # 7 | # You should have received a copy of the GNU General Public License version 3 8 | # along with this program; if not, write to the Free Software Foundation, Inc., 51 9 | # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 10 | # 11 | # (c) 2016-2025 Valentin Samir 12 | """Tests module for utils""" 13 | import django 14 | from django.test import TestCase, RequestFactory 15 | from django.db import connection 16 | 17 | import warnings 18 | import datetime 19 | 20 | from cas_server import utils 21 | 22 | 23 | class CheckPasswordCase(TestCase): 24 | """Tests for the utils function `utils.check_password`""" 25 | 26 | def setUp(self): 27 | """Generate random bytes string that will be used ass passwords""" 28 | self.password1 = utils.gen_saml_id() 29 | self.password2 = utils.gen_saml_id() 30 | if not isinstance(self.password1, bytes): # pragma: no cover executed only in python3 31 | self.password1 = self.password1.encode("utf8") 32 | self.password2 = self.password2.encode("utf8") 33 | 34 | def test_setup(self): 35 | """check that generated password are bytes""" 36 | self.assertIsInstance(self.password1, bytes) 37 | self.assertIsInstance(self.password2, bytes) 38 | 39 | def test_plain(self): 40 | """test the plain auth method""" 41 | self.assertTrue(utils.check_password("plain", self.password1, self.password1, "utf8")) 42 | self.assertFalse(utils.check_password("plain", self.password1, self.password2, "utf8")) 43 | 44 | def test_plain_unicode(self): 45 | """test the plain auth method with unicode input""" 46 | self.assertTrue( 47 | utils.check_password( 48 | "plain", 49 | self.password1.decode("utf8"), 50 | self.password1.decode("utf8"), 51 | "utf8" 52 | ) 53 | ) 54 | self.assertFalse( 55 | utils.check_password( 56 | "plain", 57 | self.password1.decode("utf8"), 58 | self.password2.decode("utf8"), 59 | "utf8" 60 | ) 61 | ) 62 | 63 | def test_crypt(self): 64 | """test the crypt auth method""" 65 | # Only run test if crypt is available 66 | if utils.crypt is None: 67 | return 68 | salts = ["$6$UVVAQvrMyXMF3FF3", "aa"] 69 | hashed_password1 = [] 70 | for salt in salts: 71 | hashed_password1.append( 72 | utils.crypt.crypt( 73 | self.password1.decode("utf8"), 74 | salt 75 | ).encode("utf8") 76 | ) 77 | 78 | for hp1 in hashed_password1: 79 | self.assertTrue(utils.check_password("crypt", self.password1, hp1, "utf8")) 80 | self.assertFalse(utils.check_password("crypt", self.password2, hp1, "utf8")) 81 | 82 | with self.assertRaises(ValueError): 83 | utils.check_password("crypt", self.password1, b"$truc$s$dsdsd", "utf8") 84 | 85 | def test_ldap_password_valid(self): 86 | """test the ldap auth method with all the schemes""" 87 | salt = b"UVVAQvrMyXMF3FF3" 88 | schemes_salt = [b"{SMD5}", b"{SSHA}", b"{SSHA256}", b"{SSHA384}", b"{SSHA512}"] 89 | schemes_nosalt = [b"{MD5}", b"{SHA}", b"{SHA256}", b"{SHA384}", b"{SHA512}"] 90 | hashed_password1 = [] 91 | for scheme in schemes_salt: 92 | hashed_password1.append( 93 | utils.LdapHashUserPassword.hash(scheme, self.password1, salt, charset="utf8") 94 | ) 95 | for scheme in schemes_nosalt: 96 | hashed_password1.append( 97 | utils.LdapHashUserPassword.hash(scheme, self.password1, charset="utf8") 98 | ) 99 | if utils.crypt is not None: 100 | hashed_password1.append( 101 | utils.LdapHashUserPassword.hash( 102 | b"{CRYPT}", 103 | self.password1, 104 | b"$6$UVVAQvrMyXMF3FF3", 105 | charset="utf8" 106 | ) 107 | ) 108 | for hp1 in hashed_password1: 109 | self.assertIsInstance(hp1, bytes) 110 | self.assertTrue(utils.check_password("ldap", self.password1, hp1, "utf8")) 111 | self.assertFalse(utils.check_password("ldap", self.password2, hp1, "utf8")) 112 | 113 | def test_ldap_password_fail(self): 114 | """test the ldap auth method with malformed hash or bad schemes""" 115 | salt = b"UVVAQvrMyXMF3FF3" 116 | schemes_salt = [b"{SMD5}", b"{SSHA}", b"{SSHA256}", b"{SSHA384}", b"{SSHA512}"] 117 | schemes_nosalt = [b"{MD5}", b"{SHA}", b"{SHA256}", b"{SHA384}", b"{SHA512}"] 118 | 119 | # first try to hash with bad parameters 120 | with self.assertRaises(utils.LdapHashUserPassword.BadScheme): 121 | utils.LdapHashUserPassword.hash(b"TOTO", self.password1) 122 | for scheme in schemes_nosalt: 123 | with self.assertRaises(utils.LdapHashUserPassword.BadScheme): 124 | utils.LdapHashUserPassword.hash(scheme, self.password1, salt) 125 | for scheme in schemes_salt: 126 | with self.assertRaises(utils.LdapHashUserPassword.BadScheme): 127 | utils.LdapHashUserPassword.hash(scheme, self.password1) 128 | if utils.crypt is not None: 129 | with self.assertRaises(utils.LdapHashUserPassword.BadSalt): 130 | utils.LdapHashUserPassword.hash(b'{CRYPT}', self.password1, b"$truc$toto") 131 | 132 | # then try to check hash with bad hashes 133 | with self.assertRaises(utils.LdapHashUserPassword.BadHash): 134 | utils.check_password("ldap", self.password1, b"TOTOssdsdsd", "utf8") 135 | for scheme in schemes_salt: 136 | # bad length 137 | with self.assertRaises(utils.LdapHashUserPassword.BadHash): 138 | utils.check_password("ldap", self.password1, scheme + b"dG90b3E8ZHNkcw==", "utf8") 139 | # bad base64 140 | with self.assertRaises(utils.LdapHashUserPassword.BadHash): 141 | utils.check_password("ldap", self.password1, scheme + b"dG90b3E8ZHNkcw", "utf8") 142 | 143 | def test_hex(self): 144 | """test all the hex_HASH method: the hashed password is a simple hash of the password""" 145 | hashes = ["md5", "sha1", "sha224", "sha256", "sha384", "sha512"] 146 | hashed_password1 = [] 147 | for hash_scheme in hashes: 148 | hashed_password1.append( 149 | ( 150 | "hex_%s" % hash_scheme, 151 | getattr(utils.hashlib, hash_scheme)(self.password1).hexdigest() 152 | ) 153 | ) 154 | for (method, hp1) in hashed_password1: 155 | self.assertTrue(utils.check_password(method, self.password1, hp1, "utf8")) 156 | self.assertFalse(utils.check_password(method, self.password2, hp1, "utf8")) 157 | 158 | def test_bad_method(self): 159 | """try to check password with a bad method, should raise a ValueError""" 160 | with self.assertRaises(ValueError): 161 | utils.check_password("test", self.password1, b"$truc$s$dsdsd", "utf8") 162 | 163 | 164 | class UtilsTestCase(TestCase): 165 | """tests for some little utils functions""" 166 | def test_import_attr(self): 167 | """ 168 | test the import_attr function. Feeded with a dotted path string, it should 169 | import the dotted module and return that last componend of the dotted path 170 | (function, class or variable) 171 | """ 172 | with self.assertRaises(ImportError): 173 | utils.import_attr('toto.titi.tutu') 174 | with self.assertRaises(AttributeError): 175 | utils.import_attr('cas_server.utils.toto') 176 | with self.assertRaises(ValueError): 177 | utils.import_attr('toto') 178 | if django.VERSION < (3, 2): 179 | self.assertEqual( 180 | utils.import_attr('cas_server.default_app_config'), 181 | 'cas_server.apps.CasAppConfig' 182 | ) 183 | self.assertEqual(utils.import_attr(utils), utils) 184 | 185 | def test_update_url(self): 186 | """ 187 | test the update_url function. Given an url with possible GET parameter and a dict 188 | the function build a url with GET parameters updated by the dictionnary 189 | """ 190 | url1 = utils.update_url(u"https://www.example.com?toto=1", {u"tata": u"2"}) 191 | url2 = utils.update_url(b"https://www.example.com?toto=1", {b"tata": b"2"}) 192 | self.assertEqual(url1, u"https://www.example.com?tata=2&toto=1") 193 | self.assertEqual(url2, u"https://www.example.com?tata=2&toto=1") 194 | 195 | url3 = utils.update_url(u"https://www.example.com?toto=1", {u"toto": u"2"}) 196 | self.assertEqual(url3, u"https://www.example.com?toto=2") 197 | 198 | def test_crypt_salt_is_valid(self): 199 | """test the function crypt_salt_is_valid who test if a crypt salt is valid""" 200 | self.assertFalse(utils.crypt_salt_is_valid("")) # len 0 201 | self.assertFalse(utils.crypt_salt_is_valid("a")) # len 1 202 | self.assertFalse(utils.crypt_salt_is_valid("$$")) # start with $ followed by $ 203 | self.assertFalse(utils.crypt_salt_is_valid("$toto")) # start with $ but no secondary $ 204 | self.assertFalse(utils.crypt_salt_is_valid("$toto$toto")) # algorithm toto not known 205 | 206 | def test_get_current_url(self): 207 | """test the function get_current_url""" 208 | factory = RequestFactory() 209 | request = factory.get('/truc/muche?test=1') 210 | self.assertEqual(utils.get_current_url(request), 'http://testserver/truc/muche?test=1') 211 | self.assertEqual( 212 | utils.get_current_url(request, ignore_params={'test'}), 213 | 'http://testserver/truc/muche' 214 | ) 215 | 216 | def test_get_tuple(self): 217 | """test the function get_tuple""" 218 | test_tuple = (1, 2, 3) 219 | for index, value in enumerate(test_tuple): 220 | self.assertEqual(utils.get_tuple(test_tuple, index), value) 221 | self.assertEqual(utils.get_tuple(test_tuple, 3), None) 222 | self.assertEqual(utils.get_tuple(test_tuple, 3, 'toto'), 'toto') 223 | self.assertEqual(utils.get_tuple(None, 3), None) 224 | 225 | def test_last_version(self): 226 | """ 227 | test the function last_version. An internet connection is needed, if you do not have 228 | one, this test will fail and you should ignore it. 229 | """ 230 | try: 231 | # first check if pypi is available 232 | utils.requests.get("https://pypi.org/simple/django-cas-server/") 233 | except utils.requests.exceptions.RequestException: 234 | warnings.warn( 235 | ( 236 | "Pypi seems not available, perhaps you do not have internet access. " 237 | "Consequently, the test cas_server.tests.test_utils.UtilsTestCase.test_last_" 238 | "version is ignored" 239 | ), 240 | RuntimeWarning 241 | ) 242 | else: 243 | version = utils.last_version() 244 | self.assertIsInstance(version, str) 245 | self.assertEqual(len(version.split('.')), 3) 246 | 247 | # version is cached 24h so calling it a second time should return the save value 248 | self.assertEqual(version, utils.last_version()) 249 | 250 | def test_dictfetchall(self): 251 | """test the function dictfetchall""" 252 | with connection.cursor() as curs: 253 | curs.execute("SELECT * FROM django_migrations") 254 | results = utils.dictfetchall(curs) 255 | self.assertIsInstance(results, list) 256 | self.assertTrue(len(results) > 0) 257 | for result in results: 258 | self.assertIsInstance(result, dict) 259 | self.assertIn('applied', result) 260 | self.assertIsInstance(result['applied'], datetime.datetime) 261 | 262 | def test_regexpr_validator(self): 263 | """test the function regexpr_validator""" 264 | utils.regexpr_validator("^a$") 265 | with self.assertRaises(utils.ValidationError): 266 | utils.regexpr_validator("[") 267 | -------------------------------------------------------------------------------- /cas_server/locale/pt_BR/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: \n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2017-08-22 08:18-0300\n" 11 | "PO-Revision-Date: 2017-08-29 18:09+0200\n" 12 | "Language-Team: Roberto Morati \n" 13 | "Language: pt_BR\n" 14 | "MIME-Version: 1.0\n" 15 | "Content-Type: text/plain; charset=UTF-8\n" 16 | "Content-Transfer-Encoding: 8bit\n" 17 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 18 | "Last-Translator: Valentin Samir \n" 19 | "X-Generator: Poedit 1.8.11\n" 20 | 21 | #: cas_server/apps.py:25 cas_server/templates/cas_server/base.html:7 22 | #: cas_server/templates/cas_server/base.html:26 23 | msgid "Central Authentication Service" 24 | msgstr "Central de Autenticação de Serviços" 25 | 26 | #: cas_server/default_settings.py:201 27 | msgid "" 28 | "The Central Authentication Service grants you access to most of our websites by " 29 | "authenticating only once, so you don't need to type your credentials again unless your " 30 | "session expires or you logout." 31 | msgstr "" 32 | "A Central de Autenticação de Serviços garante seu acesso à maioria dos nossos sitespor " 33 | "meio de uma única autenticação, então você não precisa digitar suas " 34 | "credenciaisnovamente, ao menos que sua sessão expire ou seu logout." 35 | 36 | #: cas_server/forms.py:85 37 | msgid "Identity provider" 38 | msgstr "Provedor de identidade" 39 | 40 | #: cas_server/forms.py:89 cas_server/forms.py:111 41 | msgid "Warn me before logging me into other sites." 42 | msgstr "Avise-me antes de me registrar em outros sites" 43 | 44 | #: cas_server/forms.py:93 45 | msgid "Remember the identity provider" 46 | msgstr "Relembrar o provedor de identidade" 47 | 48 | #: cas_server/forms.py:104 cas_server/models.py:638 49 | msgid "username" 50 | msgstr "usuário" 51 | 52 | #: cas_server/forms.py:108 53 | msgid "password" 54 | msgstr "senha" 55 | 56 | #: cas_server/forms.py:131 57 | msgid "The credentials you provided cannot be determined to be authentic." 58 | msgstr "As credenciais que você forneceu não podem ser determinadas como autênticas." 59 | 60 | #: cas_server/forms.py:183 61 | msgid "User not found in the temporary database, please try to reconnect" 62 | msgstr "Usuário não encontrado na base de dados temporária, por favor, tente se reconectar" 63 | 64 | #: cas_server/forms.py:197 65 | msgid "service" 66 | msgstr "" 67 | 68 | #: cas_server/management/commands/cas_clean_federate.py:20 69 | msgid "Clean old federated users" 70 | msgstr "" 71 | 72 | #: cas_server/management/commands/cas_clean_sessions.py:22 73 | msgid "Clean deleted sessions" 74 | msgstr "" 75 | 76 | #: cas_server/management/commands/cas_clean_tickets.py:22 77 | msgid "Clean old tickets" 78 | msgstr "" 79 | 80 | #: cas_server/models.py:71 81 | msgid "identity provider" 82 | msgstr "provedor de identidade" 83 | 84 | #: cas_server/models.py:72 85 | msgid "identity providers" 86 | msgstr "provedores de identidade" 87 | 88 | #: cas_server/models.py:78 89 | msgid "suffix" 90 | msgstr "" 91 | 92 | #: cas_server/models.py:80 93 | msgid "Suffix append to backend CAS returned username: ``returned_username`` @ ``suffix``." 94 | msgstr "" 95 | 96 | #: cas_server/models.py:87 97 | msgid "server url" 98 | msgstr "" 99 | 100 | #: cas_server/models.py:97 101 | msgid "CAS protocol version" 102 | msgstr "" 103 | 104 | #: cas_server/models.py:99 105 | msgid "Version of the CAS protocol to use when sending requests the the backend CAS." 106 | msgstr "" 107 | 108 | #: cas_server/models.py:106 109 | msgid "verbose name" 110 | msgstr "" 111 | 112 | #: cas_server/models.py:107 113 | msgid "Name for this identity provider displayed on the login page." 114 | msgstr "Nome para exibir o provedor de identidade na página de login." 115 | 116 | #: cas_server/models.py:113 cas_server/models.py:490 117 | msgid "position" 118 | msgstr "" 119 | 120 | #: cas_server/models.py:127 121 | msgid "display" 122 | msgstr "" 123 | 124 | #: cas_server/models.py:128 125 | msgid "Display the provider on the login page." 126 | msgstr "" 127 | 128 | #: cas_server/models.py:166 129 | msgid "Federated user" 130 | msgstr "" 131 | 132 | #: cas_server/models.py:167 133 | msgid "Federated users" 134 | msgstr "" 135 | 136 | #: cas_server/models.py:246 137 | msgid "User attributes cache" 138 | msgstr "" 139 | 140 | #: cas_server/models.py:247 141 | msgid "User attributes caches" 142 | msgstr "" 143 | 144 | #: cas_server/models.py:271 145 | msgid "User" 146 | msgstr "" 147 | 148 | #: cas_server/models.py:272 149 | msgid "Users" 150 | msgstr "" 151 | 152 | #: cas_server/models.py:364 153 | #, python-format 154 | msgid "Error during service logout %s" 155 | msgstr "" 156 | 157 | #: cas_server/models.py:484 158 | msgid "Service pattern" 159 | msgstr "" 160 | 161 | #: cas_server/models.py:485 162 | msgid "Services patterns" 163 | msgstr "" 164 | 165 | #: cas_server/models.py:491 166 | msgid "service patterns are sorted using the position attribute" 167 | msgstr "" 168 | 169 | #: cas_server/models.py:499 cas_server/models.py:664 170 | msgid "name" 171 | msgstr "" 172 | 173 | #: cas_server/models.py:500 174 | msgid "A name for the service" 175 | msgstr "" 176 | 177 | #: cas_server/models.py:508 cas_server/models.py:707 cas_server/models.py:737 178 | msgid "pattern" 179 | msgstr "" 180 | 181 | #: cas_server/models.py:510 182 | msgid "" 183 | "A regular expression matching services. Will usually looks like '^https://some\\.server" 184 | "\\.com/path/.*$'.As it is a regular expression, special character must be escaped with a " 185 | "'\\'." 186 | msgstr "" 187 | 188 | #: cas_server/models.py:521 189 | msgid "user field" 190 | msgstr "" 191 | 192 | #: cas_server/models.py:522 193 | msgid "Name of the attribute to transmit as username, empty = login" 194 | msgstr "" 195 | 196 | #: cas_server/models.py:527 197 | msgid "restrict username" 198 | msgstr "" 199 | 200 | #: cas_server/models.py:528 201 | msgid "Limit username allowed to connect to the list provided bellow" 202 | msgstr "" 203 | 204 | #: cas_server/models.py:533 205 | msgid "proxy" 206 | msgstr "" 207 | 208 | #: cas_server/models.py:534 209 | msgid "Proxy tickets can be delivered to the service" 210 | msgstr "" 211 | 212 | #: cas_server/models.py:540 213 | msgid "proxy callback" 214 | msgstr "" 215 | 216 | #: cas_server/models.py:541 217 | msgid "can be used as a proxy callback to deliver PGT" 218 | msgstr "" 219 | 220 | #: cas_server/models.py:548 221 | msgid "single log out" 222 | msgstr "" 223 | 224 | #: cas_server/models.py:549 225 | msgid "Enable SLO for the service" 226 | msgstr "" 227 | 228 | #: cas_server/models.py:558 229 | msgid "" 230 | "URL where the SLO request will be POST. empty = service url\n" 231 | "This is usefull for non HTTP proxied services." 232 | msgstr "" 233 | 234 | #: cas_server/models.py:639 235 | msgid "username allowed to connect to the service" 236 | msgstr "" 237 | 238 | #: cas_server/models.py:665 239 | msgid "name of an attribute to send to the service, use * for all attributes" 240 | msgstr "" 241 | 242 | #: cas_server/models.py:672 cas_server/models.py:745 243 | msgid "replace" 244 | msgstr "" 245 | 246 | #: cas_server/models.py:673 247 | msgid "" 248 | "name under which the attribute will be show to the service. empty = default name of the " 249 | "attribut" 250 | msgstr "" 251 | 252 | #: cas_server/models.py:700 cas_server/models.py:731 253 | msgid "attribute" 254 | msgstr "" 255 | 256 | #: cas_server/models.py:701 257 | msgid "Name of the attribute which must verify pattern" 258 | msgstr "" 259 | 260 | #: cas_server/models.py:708 261 | msgid "a regular expression" 262 | msgstr "" 263 | 264 | #: cas_server/models.py:732 265 | msgid "Name of the attribute for which the value must be replace" 266 | msgstr "" 267 | 268 | #: cas_server/models.py:738 269 | msgid "An regular expression maching whats need to be replaced" 270 | msgstr "" 271 | 272 | #: cas_server/models.py:746 273 | msgid "replace expression, groups are capture by \\1, \\2 …" 274 | msgstr "" 275 | 276 | #: cas_server/templates/cas_server/base.html:43 277 | #, python-format 278 | msgid "" 279 | "A new version of the application is available. This instance runs %(VERSION)s and the " 280 | "last version is %(LAST_VERSION)s. Please consider upgrading." 281 | msgstr "" 282 | "Uma nova versão da aplicação está disponível. Está instância usa a versão %(VERSION)s e " 283 | "a última versão é %(LAST_VERSION)s. Por favor, considere a atualização." 284 | 285 | #: cas_server/templates/cas_server/logged.html:4 286 | msgid "" 287 | "

Log In Successful

You have successfully logged into the Central Authentication " 288 | "Service.
For security reasons, please Log Out and Exit your web browser when you are " 289 | "done accessing services that require authentication!" 290 | msgstr "" 291 | "

Log In realizado com sucesso

Você foi conectado com sucesso a Central de " 292 | "Autenticação de Serviços.
Por razões de segurança, faça o Log Out e saia do seu " 293 | "navegador quando você terminar de acessar os serviços que exigem auntenticação!" 294 | 295 | #: cas_server/templates/cas_server/logged.html:8 296 | msgid "Log me out from all my sessions" 297 | msgstr "Desconecte-me de todas as sessões" 298 | 299 | #: cas_server/templates/cas_server/logged.html:14 300 | msgid "Forget the identity provider" 301 | msgstr "Esquecer o provedor de identidade" 302 | 303 | #: cas_server/templates/cas_server/logged.html:18 304 | msgid "Logout" 305 | msgstr "" 306 | 307 | #: cas_server/templates/cas_server/login.html:6 308 | msgid "Please log in" 309 | msgstr "Por favor, faça log in" 310 | 311 | #: cas_server/templates/cas_server/login.html:14 312 | msgid "Login" 313 | msgstr "" 314 | 315 | #: cas_server/templates/cas_server/warn.html:9 316 | msgid "Connect to the service" 317 | msgstr "" 318 | 319 | #: cas_server/utils.py:744 320 | #, python-format 321 | msgid "\"%(value)s\" is not a valid regular expression" 322 | msgstr "" 323 | 324 | #: cas_server/views.py:185 325 | msgid "" 326 | "

Logout successful

You have successfully logged out from the Central " 327 | "Authentication Service. For security reasons, close your web browser." 328 | msgstr "" 329 | "

Logout realizado com sucesso

Você foi desconectado com sucesso da Central de " 330 | "Autenticação de Serviços. Por razões de segurança, feche seu navegador." 331 | 332 | #: cas_server/views.py:191 333 | #, python-format 334 | msgid "" 335 | "

Logout successful

You have successfully logged out from %s sessions of the " 336 | "Central Authentication Service. For security reasons, close your web browser." 337 | msgstr "" 338 | "

Logout realizado com sucesso

Você foi desconectado com sucesso da %s sessão da " 339 | "Centralde Autenticação de Serviços. Por razões de segurança, feche seu navegador." 340 | 341 | #: cas_server/views.py:198 342 | msgid "" 343 | "

Logout successful

You were already logged out from the Central Authentication " 344 | "Service. For security reasons, close your web browser." 345 | msgstr "" 346 | "

Logout realizado com sucesso

Você já está desconectado da Central de " 347 | "Autenticação de Serviços. Por razões de segurança, feche seu navegador." 348 | 349 | #: cas_server/views.py:378 350 | #, python-format 351 | msgid "" 352 | "Invalid response from your identity provider CAS upon ticket %(ticket)s validation: " 353 | "%(error)r" 354 | msgstr "" 355 | "Resposta inválida do provedor de identidade CAS sobre o ticket %(ticket)svalidação: " 356 | "%(error)r" 357 | 358 | #: cas_server/views.py:500 359 | msgid "Invalid login ticket, please try to log in again" 360 | msgstr "Ticket de login inválido, por favor tente novamente" 361 | 362 | #: cas_server/views.py:693 363 | #, python-format 364 | msgid "Authentication has been required by service %(name)s (%(url)s)" 365 | msgstr "Autenticação requerida pelo serviço %(name)s (%(url)s)" 366 | 367 | #: cas_server/views.py:731 368 | #, python-format 369 | msgid "Service %(url)s not allowed." 370 | msgstr "Serviço %(url)s não permitido" 371 | 372 | #: cas_server/views.py:738 373 | msgid "Username not allowed" 374 | msgstr "Usuário não permitido" 375 | 376 | #: cas_server/views.py:745 377 | msgid "User characteristics not allowed" 378 | msgstr "Características de usuário não permitida" 379 | 380 | #: cas_server/views.py:752 381 | #, python-format 382 | msgid "The attribute %(field)s is needed to use that service" 383 | msgstr "O atributo %(field)s é necessário para usar o serviço" 384 | 385 | #: cas_server/views.py:842 386 | #, python-format 387 | msgid "Authentication renewal required by service %(name)s (%(url)s)." 388 | msgstr "Renovação da autenticação requerida pelo serviço %(name)s (%(url)s)." 389 | 390 | #: cas_server/views.py:849 391 | #, python-format 392 | msgid "Authentication required by service %(name)s (%(url)s)." 393 | msgstr "Autenticação requerida pelo serviço %(name)s (%(url)s)." 394 | 395 | #: cas_server/views.py:856 396 | #, python-format 397 | msgid "Service %s not allowed" 398 | msgstr "Serviço %s não permitido" 399 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # django-cas-server documentation build configuration file, created by 4 | # sphinx-quickstart on Tue Jul 5 12:11:50 2016. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | # If extensions (or modules to document with autodoc) are in another directory, 16 | # add these directories to sys.path here. If the directory is relative to the 17 | # documentation root, use os.path.abspath to make it absolute, like shown here. 18 | # 19 | import os 20 | import sys 21 | sys.path.insert(0, os.path.abspath('.')) 22 | sys.path.append(os.path.abspath('..')) 23 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "_ext"))) 24 | 25 | import setup as mysetup 26 | 27 | os.environ['DJANGO_SETTINGS_MODULE'] = 'cas_server.tests.settings' 28 | 29 | import django 30 | django.setup() 31 | # -- General configuration ------------------------------------------------ 32 | 33 | # If your documentation needs a minimal Sphinx version, state it here. 34 | # 35 | # needs_sphinx = '1.0' 36 | 37 | # Add any Sphinx extension module names here, as strings. They can be 38 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 39 | # ones. 40 | extensions = [ 41 | 'djangodocs', 42 | 'sphinx.ext.autodoc', 43 | 'sphinx.ext.doctest', 44 | 'sphinx.ext.intersphinx', 45 | 'sphinx.ext.coverage', 46 | 'sphinx.ext.viewcode', 47 | ] 48 | 49 | # Add any paths that contain templates here, relative to this directory. 50 | templates_path = ['_templates'] 51 | 52 | # The suffix(es) of source filenames. 53 | # You can specify multiple suffix as a list of string: 54 | # 55 | # source_suffix = ['.rst', '.md'] 56 | source_suffix = '.rst' 57 | 58 | # The encoding of source files. 59 | # 60 | # source_encoding = 'utf-8-sig' 61 | 62 | # The master toctree document. 63 | master_doc = 'index' 64 | 65 | # General information about the project. 66 | project = u'django-cas-server' 67 | copyright = u'2016-2025, Valentin Samir' 68 | author = u'Valentin Samir' 69 | 70 | # The version info for the project you're documenting, acts as replacement for 71 | # |version| and |release|, also used in various other places throughout the 72 | # built documents. 73 | # 74 | # The short X.Y version. 75 | version = mysetup.VERSION 76 | # The full version, including alpha/beta/rc tags. 77 | release = version 78 | 79 | # The language for content autogenerated by Sphinx. Refer to documentation 80 | # for a list of supported languages. 81 | # 82 | # This is also used if you do content translation via gettext catalogs. 83 | # Usually you set "language" from the command line for these cases. 84 | language = 'en' 85 | 86 | # There are two options for replacing |today|: either, you set today to some 87 | # non-false value, then it is used: 88 | # 89 | # today = '' 90 | # 91 | # Else, today_fmt is used as the format for a strftime call. 92 | # 93 | # today_fmt = '%B %d, %Y' 94 | 95 | # List of patterns, relative to source directory, that match files and 96 | # directories to ignore when looking for source files. 97 | # This patterns also effect to html_static_path and html_extra_path 98 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 99 | 100 | # The reST default role (used for this markup: `text`) to use for all 101 | # documents. 102 | # 103 | # default_role = None 104 | 105 | # If true, '()' will be appended to :func: etc. cross-reference text. 106 | # 107 | # add_function_parentheses = True 108 | 109 | # If true, the current module name will be prepended to all description 110 | # unit titles (such as .. function::). 111 | # 112 | # add_module_names = True 113 | 114 | # If true, sectionauthor and moduleauthor directives will be shown in the 115 | # output. They are ignored by default. 116 | # 117 | # show_authors = False 118 | 119 | # The name of the Pygments (syntax highlighting) style to use. 120 | pygments_style = 'sphinx' 121 | 122 | # A list of ignored prefixes for module index sorting. 123 | # modindex_common_prefix = [] 124 | 125 | # If true, keep warnings as "system message" paragraphs in the built documents. 126 | # keep_warnings = False 127 | 128 | # If true, `todo` and `todoList` produce output, else they produce nothing. 129 | todo_include_todos = False 130 | 131 | 132 | # -- Options for HTML output ---------------------------------------------- 133 | 134 | # The theme to use for HTML and HTML Help pages. See the documentation for 135 | # a list of builtin themes. 136 | # 137 | #html_theme = 'alabaster' 138 | html_theme = 'sphinx_rtd_theme' 139 | 140 | # Theme options are theme-specific and customize the look and feel of a theme 141 | # further. For a list of options available for each theme, see the 142 | # documentation. 143 | # 144 | # html_theme_options = {} 145 | 146 | # Add any paths that contain custom themes here, relative to this directory. 147 | # html_theme_path = [] 148 | 149 | # The name for this set of Sphinx documents. 150 | # " v documentation" by default. 151 | # 152 | # html_title = u'django-cas-server v5.0' 153 | 154 | # A shorter title for the navigation bar. Default is the same as html_title. 155 | # 156 | # html_short_title = None 157 | 158 | # The name of an image file (relative to this directory) to place at the top 159 | # of the sidebar. 160 | # 161 | # html_logo = None 162 | 163 | # The name of an image file (relative to this directory) to use as a favicon of 164 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 165 | # pixels large. 166 | # 167 | # html_favicon = None 168 | 169 | # Add any paths that contain custom static files (such as style sheets) here, 170 | # relative to this directory. They are copied after the builtin static files, 171 | # so a file named "default.css" will overwrite the builtin "default.css". 172 | html_static_path = ['_static'] 173 | 174 | # Add any extra paths that contain custom files (such as robots.txt or 175 | # .htaccess) here, relative to this directory. These files are copied 176 | # directly to the root of the documentation. 177 | # 178 | # html_extra_path = [] 179 | 180 | # If not None, a 'Last updated on:' timestamp is inserted at every page 181 | # bottom, using the given strftime format. 182 | # The empty string is equivalent to '%b %d, %Y'. 183 | # 184 | # html_last_updated_fmt = None 185 | 186 | # If true, SmartyPants will be used to convert quotes and dashes to 187 | # typographically correct entities. 188 | # 189 | # html_use_smartypants = True 190 | 191 | # Custom sidebar templates, maps document names to template names. 192 | # 193 | # html_sidebars = {} 194 | 195 | # Additional templates that should be rendered to pages, maps page names to 196 | # template names. 197 | # 198 | # html_additional_pages = {} 199 | 200 | # If false, no module index is generated. 201 | # 202 | # html_domain_indices = True 203 | 204 | # If false, no index is generated. 205 | # 206 | # html_use_index = True 207 | 208 | # If true, the index is split into individual pages for each letter. 209 | # 210 | # html_split_index = False 211 | 212 | # If true, links to the reST sources are added to the pages. 213 | # 214 | # html_show_sourcelink = True 215 | 216 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 217 | # 218 | # html_show_sphinx = True 219 | 220 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 221 | # 222 | # html_show_copyright = True 223 | 224 | # If true, an OpenSearch description file will be output, and all pages will 225 | # contain a tag referring to it. The value of this option must be the 226 | # base URL from which the finished HTML is served. 227 | # 228 | # html_use_opensearch = '' 229 | 230 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 231 | # html_file_suffix = None 232 | 233 | # Language to be used for generating the HTML full-text search index. 234 | # Sphinx supports the following languages: 235 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 236 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' 237 | # 238 | # html_search_language = 'en' 239 | 240 | # A dictionary with options for the search language support, empty by default. 241 | # 'ja' uses this config value. 242 | # 'zh' user can custom change `jieba` dictionary path. 243 | # 244 | # html_search_options = {'type': 'default'} 245 | 246 | # The name of a javascript file (relative to the configuration directory) that 247 | # implements a search results scorer. If empty, the default will be used. 248 | # 249 | # html_search_scorer = 'scorer.js' 250 | 251 | # Output file base name for HTML help builder. 252 | htmlhelp_basename = 'django-cas-serverdoc' 253 | 254 | # -- Options for LaTeX output --------------------------------------------- 255 | 256 | latex_elements = { 257 | # The paper size ('letterpaper' or 'a4paper'). 258 | # 259 | # 'papersize': 'letterpaper', 260 | 261 | # The font size ('10pt', '11pt' or '12pt'). 262 | # 263 | # 'pointsize': '10pt', 264 | 265 | # Additional stuff for the LaTeX preamble. 266 | # 267 | # 'preamble': '', 268 | 269 | # Latex figure (float) alignment 270 | # 271 | # 'figure_align': 'htbp', 272 | } 273 | 274 | # Grouping the document tree into LaTeX files. List of tuples 275 | # (source start file, target name, title, 276 | # author, documentclass [howto, manual, or own class]). 277 | latex_documents = [ 278 | (master_doc, 'django-cas-server.tex', u'django-cas-server Documentation', 279 | u'Valentin Samir', 'manual'), 280 | ] 281 | 282 | # The name of an image file (relative to this directory) to place at the top of 283 | # the title page. 284 | # 285 | # latex_logo = None 286 | 287 | # For "manual" documents, if this is true, then toplevel headings are parts, 288 | # not chapters. 289 | # 290 | # latex_use_parts = False 291 | 292 | # If true, show page references after internal links. 293 | # 294 | # latex_show_pagerefs = False 295 | 296 | # If true, show URL addresses after external links. 297 | # 298 | # latex_show_urls = False 299 | 300 | # Documents to append as an appendix to all manuals. 301 | # 302 | # latex_appendices = [] 303 | 304 | # If false, no module index is generated. 305 | # 306 | # latex_domain_indices = True 307 | 308 | 309 | # -- Options for manual page output --------------------------------------- 310 | 311 | # One entry per manual page. List of tuples 312 | # (source start file, name, description, authors, manual section). 313 | man_pages = [ 314 | (master_doc, 'django-cas-server', u'django-cas-server Documentation', 315 | [author], 1) 316 | ] 317 | 318 | # If true, show URL addresses after external links. 319 | # 320 | # man_show_urls = False 321 | 322 | 323 | # -- Options for Texinfo output ------------------------------------------- 324 | 325 | # Grouping the document tree into Texinfo files. List of tuples 326 | # (source start file, target name, title, author, 327 | # dir menu entry, description, category) 328 | texinfo_documents = [ 329 | (master_doc, 'django-cas-server', u'django-cas-server Documentation', 330 | author, 'django-cas-server', 'One line description of project.', 331 | 'Miscellaneous'), 332 | ] 333 | 334 | # Documents to append as an appendix to all manuals. 335 | # 336 | # texinfo_appendices = [] 337 | 338 | # If false, no module index is generated. 339 | # 340 | # texinfo_domain_indices = True 341 | 342 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 343 | # 344 | # texinfo_show_urls = 'footnote' 345 | 346 | # If true, do not generate a @detailmenu in the "Top" node's menu. 347 | # 348 | # texinfo_no_detailmenu = False 349 | 350 | # Example configuration for intersphinx: refer to the Python standard library. 351 | intersphinx_mapping_download_missing = True 352 | 353 | def check_object_path(key, url, path, fallback=None): 354 | if os.path.isfile(path): 355 | return {key: (url, path)} 356 | if fallback is not None: 357 | path = os.path.abspath(os.path.join(os.path.dirname(__file__), "{}.inv".format(key))) 358 | if not os.path.isfile(path) and intersphinx_mapping_download_missing: 359 | import requests 360 | r = requests.get(fallback) 361 | with open(path + '.new', 'wb') as f: 362 | f.write(r.content) 363 | os.rename(path + '.new', path) 364 | if os.path.isfile(path): 365 | return {key: (url, path)} 366 | return {} 367 | 368 | intersphinx_mapping = {} 369 | intersphinx_mapping.update(check_object_path( 370 | 'python', 371 | 'https://docs.python.org/{v}/'.format( 372 | v='.'.join(map(str, sys.version_info[0:2])) 373 | ), 374 | '/usr/share/doc/python{v}/html/objects.inv'.format( 375 | v='.'.join(map(str, sys.version_info[0:2])) 376 | ), 377 | 'https://docs.python.org/{v}/objects.inv'.format( 378 | v='.'.join(map(str, sys.version_info[0:2])) 379 | ) 380 | )) 381 | 382 | intersphinx_mapping.update(check_object_path( 383 | 'django', 384 | 'https://docs.djangoproject.com/en/stable/', 385 | '/usr/share/doc/python-django-doc/html/objects.inv', 386 | "https://docs.djangoproject.com/en/stable/_objects" 387 | )) 388 | 389 | autodoc_member_order = 'bysource' 390 | 391 | locale_dirs = ['../test_venv/lib/python2.7/site-packages/django/conf/locale/'] 392 | 393 | -------------------------------------------------------------------------------- /cas_server/locale/nl/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2017-03-29 12:24+0200\n" 12 | "PO-Revision-Date: 2017-03-29 13:06+0020\n" 13 | "Last-Translator: Joriks Kraaikamp \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 20 | "X-Translated-Using: Mobetta 0.0.2\n" 21 | 22 | #: apps.py:25 templates/cas_server/base.html:7 23 | #: templates/cas_server/base.html:26 24 | msgid "Central Authentication Service" 25 | msgstr "Centrale Authenticatie Service" 26 | 27 | #: default_settings.py:201 28 | msgid "" 29 | "The Central Authentication Service grants you access to most of our websites" 30 | " by authenticating only once, so you don't need to type your credentials " 31 | "again unless your session expires or you logout." 32 | msgstr "" 33 | "De Centrale Authenticatie Service geeft je toegang tot onze sites door 1 " 34 | "keer in te loggen, behalve als je sessie is verlopen of als je hebt " 35 | "uitgelogd." 36 | 37 | #: forms.py:85 38 | msgid "Identity provider" 39 | msgstr "Identiteit provider" 40 | 41 | #: forms.py:89 forms.py:111 42 | msgid "Warn me before logging me into other sites." 43 | msgstr "Waarschuw me voor het inloggen op andere websites." 44 | 45 | #: forms.py:93 46 | msgid "Remember the identity provider" 47 | msgstr "Onthoudt deze identiteit provider" 48 | 49 | #: forms.py:104 models.py:638 50 | msgid "username" 51 | msgstr "gebruikersnaam" 52 | 53 | #: forms.py:108 54 | msgid "password" 55 | msgstr "wachtwoord" 56 | 57 | #: forms.py:131 58 | msgid "The credentials you provided cannot be determined to be authentic." 59 | msgstr "De inloggegevens waren niet correct." 60 | 61 | #: forms.py:183 62 | msgid "User not found in the temporary database, please try to reconnect" 63 | msgstr "" 64 | "De gebruiker is niet gevonden in de tijdelijke database, probeer het opnieuw" 65 | 66 | #: forms.py:197 67 | msgid "service" 68 | msgstr "dienst" 69 | 70 | #: management/commands/cas_clean_federate.py:20 71 | msgid "Clean old federated users" 72 | msgstr "Schoon oude gefedereerde gebruikers op" 73 | 74 | #: management/commands/cas_clean_sessions.py:22 75 | msgid "Clean deleted sessions" 76 | msgstr "Schoon verwijderde sessies op" 77 | 78 | #: management/commands/cas_clean_tickets.py:22 79 | msgid "Clean old tickets" 80 | msgstr "Schoon oude tickets op" 81 | 82 | #: models.py:71 83 | msgid "identity provider" 84 | msgstr "identiteit provider" 85 | 86 | #: models.py:72 87 | msgid "identity providers" 88 | msgstr "Identiteit providers" 89 | 90 | #: models.py:78 91 | msgid "suffix" 92 | msgstr "achtervoegsel" 93 | 94 | #: models.py:80 95 | msgid "" 96 | "Suffix append to backend CAS returned username: ``returned_username`` @ " 97 | "``suffix``." 98 | msgstr "" 99 | "Achtervoegsel toevoegen aan de gebruikersnaam door CAS backend: " 100 | "``teruggegeven_gebruikersnaam`` @ ``achtervoegsel``." 101 | 102 | #: models.py:87 103 | msgid "server url" 104 | msgstr "server url" 105 | 106 | #: models.py:97 107 | msgid "CAS protocol version" 108 | msgstr "CAS protocol versie" 109 | 110 | #: models.py:99 111 | msgid "" 112 | "Version of the CAS protocol to use when sending requests the the backend " 113 | "CAS." 114 | msgstr "" 115 | "Versie van het CAS protocol wanneer we een request sturen van de CAS " 116 | "backend." 117 | 118 | #: models.py:106 119 | msgid "verbose name" 120 | msgstr "uitgebreide naam" 121 | 122 | #: models.py:107 123 | msgid "Name for this identity provider displayed on the login page." 124 | msgstr "Naam van de identiteit provider weergegeven op de login pagina." 125 | 126 | #: models.py:113 models.py:490 127 | msgid "position" 128 | msgstr "positie" 129 | 130 | #: models.py:127 131 | msgid "display" 132 | msgstr "weergave" 133 | 134 | #: models.py:128 135 | msgid "Display the provider on the login page." 136 | msgstr "geef de provider weer op de login pagina." 137 | 138 | #: models.py:166 139 | msgid "Federated user" 140 | msgstr "Gefedereerde gebruiker" 141 | 142 | #: models.py:167 143 | msgid "Federated users" 144 | msgstr "Gefedereerde gebruikers" 145 | 146 | #: models.py:246 147 | msgid "User attributes cache" 148 | msgstr "Gebruiker attributen cache" 149 | 150 | #: models.py:247 151 | msgid "User attributes caches" 152 | msgstr "Gebruiker attributen caches" 153 | 154 | #: models.py:271 155 | msgid "User" 156 | msgstr "Gebruiker" 157 | 158 | #: models.py:272 159 | msgid "Users" 160 | msgstr "Gebruikers" 161 | 162 | #: models.py:364 163 | #, python-format 164 | msgid "Error during service logout %s" 165 | msgstr "Een error is opgetreden tijdens het uitloggen %s" 166 | 167 | #: models.py:484 168 | msgid "Service pattern" 169 | msgstr "Service patroon" 170 | 171 | #: models.py:485 172 | msgid "Services patterns" 173 | msgstr "Service patronen" 174 | 175 | #: models.py:491 176 | msgid "service patterns are sorted using the position attribute" 177 | msgstr "service patronen worden gesorteerd aan de hand van de positie." 178 | 179 | #: models.py:499 models.py:664 180 | msgid "name" 181 | msgstr "naam" 182 | 183 | #: models.py:500 184 | msgid "A name for the service" 185 | msgstr "Een naam voor de service" 186 | 187 | #: models.py:508 models.py:707 models.py:737 188 | msgid "pattern" 189 | msgstr "patroon" 190 | 191 | #: models.py:510 192 | msgid "" 193 | "A regular expression matching services. Will usually looks like " 194 | "'^https://some\\.server\\.com/path/.*$'.As it is a regular expression, " 195 | "special character must be escaped with a '\\'." 196 | msgstr "" 197 | "A reguliere expressie die de matched met de services. Ziet er vaak zou uit: " 198 | "'^https://some\\.server\\.com/path/.*$'. Omdat het een reguliere expressie " 199 | "is, moeten speciale characters worden geescaped met een '\\'." 200 | 201 | #: models.py:521 202 | msgid "user field" 203 | msgstr "gebruiker veld" 204 | 205 | #: models.py:522 206 | msgid "Name of the attribute to transmit as username, empty = login" 207 | msgstr "" 208 | "Naam van het attribuut om verstuurd te worden al gebruikersnaam, leeg = " 209 | "login" 210 | 211 | #: models.py:527 212 | msgid "restrict username" 213 | msgstr "beperk gebruikersnamen" 214 | 215 | #: models.py:528 216 | msgid "Limit username allowed to connect to the list provided bellow" 217 | msgstr "" 218 | "Beperk de gebruikersnamen die deze provider mogen gebruiken met de list " 219 | "hieronder." 220 | 221 | #: models.py:533 222 | msgid "proxy" 223 | msgstr "proxy" 224 | 225 | #: models.py:534 226 | msgid "Proxy tickets can be delivered to the service" 227 | msgstr "Proxy tickets kunnen worden aangeleverd aan de service" 228 | 229 | #: models.py:540 230 | msgid "proxy callback" 231 | msgstr "proxy callback" 232 | 233 | #: models.py:541 234 | msgid "can be used as a proxy callback to deliver PGT" 235 | msgstr "kan gebruikt worden als proxy callback voor geleverde PGT" 236 | 237 | #: models.py:548 238 | msgid "single log out" 239 | msgstr "eenmalig afmelden" 240 | 241 | #: models.py:549 242 | msgid "Enable SLO for the service" 243 | msgstr "Activeer eenmalig afmelden voor de service" 244 | 245 | #: models.py:557 246 | msgid "single log out callback" 247 | msgstr "eenmalig afmelden callback" 248 | 249 | #: models.py:558 250 | msgid "" 251 | "URL where the SLO request will be POST. empty = service url\n" 252 | "This is usefull for non HTTP proxied services." 253 | msgstr "" 254 | "De URL waar de 'eenmalige afmelding' request naar wordt gepost. empty = service url\n" 255 | "Dit is handig voor niet HTTP geproxide services." 256 | 257 | #: models.py:639 258 | msgid "username allowed to connect to the service" 259 | msgstr "" 260 | "gebruikersnamen die zijn toegestaan om een connectie te maken via deze " 261 | "service." 262 | 263 | #: models.py:665 264 | msgid "name of an attribute to send to the service, use * for all attributes" 265 | msgstr "" 266 | "naam van een attribuut dat naar de service verstuurd wordt. Gebruik * voor " 267 | "alle attributen." 268 | 269 | #: models.py:672 models.py:745 270 | msgid "replace" 271 | msgstr "vervangen" 272 | 273 | #: models.py:673 274 | msgid "" 275 | "name under which the attribute will be show to the service. empty = default " 276 | "name of the attribut" 277 | msgstr "" 278 | "onder deze naam wordt het attribuut naar de service verstuurd. leeg = " 279 | "attribuut naam" 280 | 281 | #: models.py:700 models.py:731 282 | msgid "attribute" 283 | msgstr "attribuut" 284 | 285 | #: models.py:701 286 | msgid "Name of the attribute which must verify pattern" 287 | msgstr "Naam van het attribuut dat het patroon moet valideren." 288 | 289 | #: models.py:708 290 | msgid "a regular expression" 291 | msgstr "een reguliere expressie" 292 | 293 | #: models.py:732 294 | msgid "Name of the attribute for which the value must be replace" 295 | msgstr "Naam van het attribuut waarvoor de waarde moet worden vervangen" 296 | 297 | #: models.py:738 298 | msgid "An regular expression maching whats need to be replaced" 299 | msgstr "Een reguliere expressie dat matched wat vervangen moet worden" 300 | 301 | #: models.py:746 302 | msgid "replace expression, groups are capture by \\1, \\2 …" 303 | msgstr "vervang expressie, groepen zijn vast te leggen door \\1. \\2 ..." 304 | 305 | #: templates/cas_server/base.html:43 306 | #, python-format 307 | msgid "" 308 | "A new version of the application is available. This instance runs " 309 | "%(VERSION)s and the last version is %(LAST_VERSION)s. Please consider " 310 | "upgrading." 311 | msgstr "" 312 | "Een nieuwe versie van de applicatie is beschikbaar. Deze applicatie draait " 313 | "op versie %(VERSION)s en de nieuwste versie is %(LAST_VERSION)s. Denk na " 314 | "over het uitvoegen van een upgrade." 315 | 316 | #: templates/cas_server/logged.html:4 317 | msgid "" 318 | "

Log In Successful

You have successfully logged into the Central " 319 | "Authentication Service.
For security reasons, please Log Out and Exit " 320 | "your web browser when you are done accessing services that require " 321 | "authentication!" 322 | msgstr "" 323 | "

Succesvol ingelogd.

Je bent succesvol ingelogd op het CAS.
Voor " 324 | "veiligheids-redenen, log uit en sluit de webbrowsers wanneer je klaar bent " 325 | "met de service!" 326 | 327 | #: templates/cas_server/logged.html:8 328 | msgid "Log me out from all my sessions" 329 | msgstr "Meld me af bij alle sessies" 330 | 331 | #: templates/cas_server/logged.html:14 332 | msgid "Forget the identity provider" 333 | msgstr "Vergeet de identiteits-provider" 334 | 335 | #: templates/cas_server/logged.html:18 336 | msgid "Logout" 337 | msgstr "Afmelden" 338 | 339 | #: templates/cas_server/login.html:6 340 | msgid "Please log in" 341 | msgstr "Log alstublieft in" 342 | 343 | #: templates/cas_server/login.html:14 344 | msgid "Login" 345 | msgstr "Log in" 346 | 347 | #: templates/cas_server/warn.html:9 348 | msgid "Connect to the service" 349 | msgstr "Verbind met de service" 350 | 351 | #: utils.py:744 352 | #, python-format 353 | msgid "\"%(value)s\" is not a valid regular expression" 354 | msgstr "\"%(value)s\" is geen geldige reguliere expressie" 355 | 356 | #: views.py:185 357 | msgid "" 358 | "

Logout successful

You have successfully logged out from the Central " 359 | "Authentication Service. For security reasons, close your web browser." 360 | msgstr "" 361 | "

Succesvol afgemeld.

Je bent succesvol afgemeld van de CAS.
Voor " 362 | "veiligheids-redenen, sluit de webbrowser!" 363 | 364 | #: views.py:191 365 | #, python-format 366 | msgid "" 367 | "

Logout successful

You have successfully logged out from %s sessions " 368 | "of the Central Authentication Service. For security reasons, close your web " 369 | "browser." 370 | msgstr "" 371 | "

Succesvol afgemeld.

Je bent succesvol afgemeld van de %s " 372 | "sessie.
Voor veiligheids-redenen, sluit de webbrowser!" 373 | 374 | #: views.py:198 375 | msgid "" 376 | "

Logout successful

You were already logged out from the Central " 377 | "Authentication Service. For security reasons, close your web browser." 378 | msgstr "" 379 | "

Succesvol afgemeld.

Je was al afgemeld van de CAS.
Voor " 380 | "veiligheids-redenen, sluit de webbrowser!" 381 | 382 | #: views.py:378 383 | #, python-format 384 | msgid "" 385 | "Invalid response from your identity provider CAS upon ticket %(ticket)s " 386 | "validation: %(error)r" 387 | msgstr "" 388 | "Ongeldig response van de identiteit provider bij ticket %(ticket)s " 389 | "validatie: %(error)s" 390 | 391 | #: views.py:500 392 | msgid "Invalid login ticket, please try to log in again" 393 | msgstr "Ongeldig login ticket. probeer het alstublieft opnieuw" 394 | 395 | #: views.py:693 396 | #, python-format 397 | msgid "Authentication has been required by service %(name)s (%(url)s)" 398 | msgstr "service %(name)s (%(url)s) vraagt om authenticatie" 399 | 400 | #: views.py:731 401 | #, python-format 402 | msgid "Service %(url)s not allowed." 403 | msgstr "Service %(url)s is niet toegestaan." 404 | 405 | #: views.py:738 406 | msgid "Username not allowed" 407 | msgstr "Gebruikersnaam niet teogestaan" 408 | 409 | #: views.py:745 410 | msgid "User characteristics not allowed" 411 | msgstr "Gebruikers karakteristieken niet toegestaan." 412 | 413 | #: views.py:752 414 | #, python-format 415 | msgid "The attribute %(field)s is needed to use that service" 416 | msgstr "Het attribuut %(field)s is nodig voor die service" 417 | 418 | #: views.py:842 419 | #, python-format 420 | msgid "Authentication renewal required by service %(name)s (%(url)s)." 421 | msgstr "" 422 | "Service %(name)s (%(url)s) vraagt om vernieuwing van de authenticatie." 423 | 424 | #: views.py:849 425 | #, python-format 426 | msgid "Authentication required by service %(name)s (%(url)s)." 427 | msgstr "Service %(name)s (%(url)s) vraagt om authenticatie" 428 | 429 | #: views.py:856 430 | #, python-format 431 | msgid "Service %s not allowed" 432 | msgstr "Service %s niet toegestaan" 433 | -------------------------------------------------------------------------------- /cas_server/default_settings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This program is distributed in the hope that it will be useful, but WITHOUT 3 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 4 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License version 3 for 5 | # more details. 6 | # 7 | # You should have received a copy of the GNU General Public License version 3 8 | # along with this program; if not, write to the Free Software Foundation, Inc., 51 9 | # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 10 | # 11 | # (c) 2015-2025 Valentin Samir 12 | """Default values for the app's settings""" 13 | from django.conf import settings 14 | from django.templatetags.static import static 15 | 16 | from importlib import import_module 17 | 18 | import sys 19 | if sys.version_info < (3, ): 20 | from django.utils.translation import ugettext_lazy as _ 21 | else: 22 | from django.utils.translation import gettext_lazy as _ 23 | 24 | 25 | try: 26 | #: URL to the logo showed in the up left corner on the default templates. 27 | CAS_LOGO_URL = static("cas_server/logo.png") 28 | #: URL to the favicon (shortcut icon) used by the default templates. Default is a key icon. 29 | CAS_FAVICON_URL = static("cas_server/favicon.ico") 30 | # is settings.DEBUG is False and collectstatics has not been run yet, the static function will 31 | # raise a ValueError because the file is not found. 32 | except ValueError: 33 | #: URL to the logo showed in the up left corner on the default templates. 34 | CAS_LOGO_URL = None 35 | #: URL to the favicon (shortcut icon) used by the default templates. Default is a key icon. 36 | CAS_FAVICON_URL = None 37 | 38 | 39 | #: Show the powered by footer if set to ``True`` 40 | CAS_SHOW_POWERED = True 41 | #: URLs to css and javascript external components. 42 | CAS_COMPONENT_URLS = { 43 | "bootstrap3_css": "https://cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/css/bootstrap.min.css", 44 | "bootstrap3_js": "https://cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/js/bootstrap.min.js", 45 | "html5shiv": "https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js", 46 | "respond": "https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js", 47 | "bootstrap4_css": "https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css", 48 | "bootstrap4_js": "https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js", 49 | "jquery": "https://code.jquery.com/jquery.min.js", 50 | } 51 | #: Path to the template showed on /login then the user is not autenticated. 52 | CAS_LOGIN_TEMPLATE = 'cas_server/bs4/login.html' 53 | #: Path to the template showed on /login?service=... then the user is authenticated and has asked 54 | #: to be warned before being connected to a service. 55 | CAS_WARN_TEMPLATE = 'cas_server/bs4/warn.html' 56 | #: Path to the template showed on /login then to user is authenticated. 57 | CAS_LOGGED_TEMPLATE = 'cas_server/bs4/logged.html' 58 | #: Path to the template showed on /logout then to user is being disconnected. 59 | CAS_LOGOUT_TEMPLATE = 'cas_server/bs4/logout.html' 60 | #: Should we redirect users to /login after they logged out instead of displaying 61 | #: :obj:`CAS_LOGOUT_TEMPLATE`. 62 | CAS_REDIRECT_TO_LOGIN_AFTER_LOGOUT = False 63 | 64 | 65 | #: A dotted path to a class or a class implementing cas_server.auth.AuthUser. 66 | CAS_AUTH_CLASS = 'cas_server.auth.DjangoAuthUser' 67 | #: Activate Kerberos authentication (not compatible with federate mode or auth class 68 | #: requiring access to the user credential to retrieve user attributes). 69 | #: See https://web.mit.edu/kerberos/krb5-1.13/doc/admin/env_variables.html 70 | #: for environment variables allowing to configure the underlying GSSAPI C library 71 | #: Username retrieved form kerberos auth MUST match username used by the ``CAS_AUTH_CLASS`` 72 | CAS_AUTH_GSSAPI_ENABLE = False 73 | #: SPN to use for Kerberos authentication (must be available in the loaded keytab) 74 | CAS_AUTH_GSSAPI_SERVICENAME = "host/myhost.example.com@AD.EXAMPLE.COM" 75 | #: Path to certificate authorities file. Usually on linux the local CAs are in 76 | #: /etc/ssl/certs/ca-certificates.crt. ``True`` tell requests to use its internal certificat 77 | #: authorities. 78 | CAS_PROXY_CA_CERTIFICATE_PATH = True 79 | #: Maximum number of parallel single log out requests send 80 | #: if more requests need to be send, there are queued 81 | CAS_SLO_MAX_PARALLEL_REQUESTS = 10 82 | #: Timeout for a single SLO request in seconds. 83 | CAS_SLO_TIMEOUT = 5 84 | #: Shared to transmit then using the view :class:`cas_server.views.Auth` 85 | CAS_AUTH_SHARED_SECRET = '' 86 | #: Max time after with the user MUST reauthenticate. Let it to `None` for no max time. 87 | #: This can be used to force refreshing cached informations only available upon user authentication 88 | #: like the user attributes in federation mode or with the ldap auth in bind mode. 89 | CAS_TGT_VALIDITY = None 90 | 91 | 92 | #: Number of seconds the service tickets and proxy tickets are valid. This is the maximal time 93 | #: between ticket issuance by the CAS and ticket validation by an application. 94 | CAS_TICKET_VALIDITY = 60 95 | #: Number of seconds the proxy granting tickets are valid. 96 | CAS_PGT_VALIDITY = 3600 97 | #: Number of seconds a ticket is kept in the database before sending Single Log Out request and 98 | #: being cleared. 99 | CAS_TICKET_TIMEOUT = 24*3600 100 | 101 | 102 | #: All CAS implementation MUST support ST and PT up to 32 chars, 103 | #: PGT and PGTIOU up to 64 chars and it is RECOMMENDED that all 104 | #: tickets up to 256 chars are supports so we use 64 for the default 105 | #: len. 106 | CAS_TICKET_LEN = 64 107 | 108 | #: alias of :obj:`settings.CAS_TICKET_LEN` 109 | CAS_LT_LEN = getattr(settings, 'CAS_TICKET_LEN', CAS_TICKET_LEN) 110 | #: alias of :obj:`settings.CAS_TICKET_LEN` 111 | #: Services MUST be able to accept service tickets of up to 32 characters in length. 112 | CAS_ST_LEN = getattr(settings, 'CAS_TICKET_LEN', CAS_TICKET_LEN) 113 | #: alias of :obj:`settings.CAS_TICKET_LEN` 114 | #: Back-end services MUST be able to accept proxy tickets of up to 32 characters. 115 | CAS_PT_LEN = getattr(settings, 'CAS_TICKET_LEN', CAS_TICKET_LEN) 116 | #: alias of :obj:`settings.CAS_TICKET_LEN` 117 | #: Services MUST be able to handle proxy-granting tickets of up to 64 118 | CAS_PGT_LEN = getattr(settings, 'CAS_TICKET_LEN', CAS_TICKET_LEN) 119 | #: alias of :obj:`settings.CAS_TICKET_LEN` 120 | #: Services MUST be able to handle PGTIOUs of up to 64 characters in length. 121 | CAS_PGTIOU_LEN = getattr(settings, 'CAS_TICKET_LEN', CAS_TICKET_LEN) 122 | 123 | #: Prefix of login tickets. 124 | CAS_LOGIN_TICKET_PREFIX = u'LT' 125 | #: Prefix of service tickets. Service tickets MUST begin with the characters ST so you should not 126 | #: change this. 127 | CAS_SERVICE_TICKET_PREFIX = u'ST' 128 | #: Prefix of proxy ticket. Proxy tickets SHOULD begin with the characters, PT. 129 | CAS_PROXY_TICKET_PREFIX = u'PT' 130 | #: Prefix of proxy granting ticket. Proxy-granting tickets SHOULD begin with the characters PGT. 131 | CAS_PROXY_GRANTING_TICKET_PREFIX = u'PGT' 132 | #: Prefix of proxy granting ticket IOU. Proxy-granting ticket IOUs SHOULD begin with the characters 133 | #: PGTIOU. 134 | CAS_PROXY_GRANTING_TICKET_IOU_PREFIX = u'PGTIOU' 135 | 136 | 137 | #: Host for the SQL server. 138 | CAS_SQL_HOST = 'localhost' 139 | #: Username for connecting to the SQL server. 140 | CAS_SQL_USERNAME = '' 141 | #: Password for connecting to the SQL server. 142 | CAS_SQL_PASSWORD = '' 143 | #: Database name. 144 | CAS_SQL_DBNAME = '' 145 | #: Database charset. 146 | CAS_SQL_DBCHARSET = 'utf8' 147 | 148 | #: The query performed upon user authentication. 149 | CAS_SQL_USER_QUERY = 'SELECT user AS username, pass AS password, users.* FROM users WHERE user = %s' 150 | #: The method used to check the user password. Must be one of ``"crypt"``, ``"ldap"``, 151 | #: ``"hex_md5"``, ``"hex_sha1"``, ``"hex_sha224"``, ``"hex_sha256"``, ``"hex_sha384"``, 152 | #: ``"hex_sha512"``, ``"plain"``. 153 | CAS_SQL_PASSWORD_CHECK = 'crypt' 154 | #: charset the SQL users passwords was hash with 155 | CAS_SQL_PASSWORD_CHARSET = "utf-8" 156 | 157 | 158 | #: Address of the LDAP server 159 | CAS_LDAP_SERVER = 'localhost' 160 | #: LDAP user bind address, for example ``"cn=admin,dc=crans,dc=org"`` for connecting to the LDAP 161 | #: server. 162 | CAS_LDAP_USER = None 163 | #: LDAP connection password 164 | CAS_LDAP_PASSWORD = None 165 | #: LDAP seach base DN, for example ``"ou=data,dc=crans,dc=org"``. 166 | CAS_LDAP_BASE_DN = None 167 | #: LDAP search filter for searching user by username. User inputed usernames are escaped using 168 | #: :func:`ldap3.utils.conv.escape_bytes`. 169 | CAS_LDAP_USER_QUERY = "(uid=%(username)s)" 170 | #: LDAP attribute used for users usernames 171 | CAS_LDAP_USERNAME_ATTR = "uid" 172 | #: LDAP attribute used for users passwords 173 | CAS_LDAP_PASSWORD_ATTR = "userPassword" 174 | #: The method used to check the user password. Must be one of ``"crypt"``, ``"ldap"``, 175 | #: ``"hex_md5"``, ``"hex_sha1"``, ``"hex_sha224"``, ``"hex_sha256"``, ``"hex_sha384"``, 176 | #: ``"hex_sha512"``, ``"plain"``, ``"bind"``. 177 | CAS_LDAP_PASSWORD_CHECK = "ldap" 178 | #: charset the LDAP users passwords was hash with 179 | CAS_LDAP_PASSWORD_CHARSET = "utf-8" 180 | #: This parameter is only used then ``CAS_LDAP_PASSWORD_CHECK`` is set to ``"bind"``. 181 | #: 182 | #: * if ``0`` the user attributes are retrieved by connecting to the ldap as ``CAS_LDAP_USER``. 183 | #: * if ``1`` the user attributes are retrieve then the user authenticate using 184 | #: the user credentials. These attributes are then cached for the session. 185 | #: 186 | #: The default is ``0``. 187 | CAS_LDAP_ATTRS_VIEW = 0 188 | 189 | 190 | #: Username of the test user. 191 | CAS_TEST_USER = 'test' 192 | #: Password of the test user. 193 | CAS_TEST_PASSWORD = 'test' 194 | #: Attributes of the test user. 195 | CAS_TEST_ATTRIBUTES = { 196 | 'nom': 'Nymous', 197 | 'prenom': 'Ano', 198 | 'email': 'anonymous@example.net', 199 | 'alias': ['demo1', 'demo2'] 200 | } 201 | 202 | 203 | #: A :class:`bool` for activatinc the hability to fetch tickets using javascript. 204 | CAS_ENABLE_AJAX_AUTH = False 205 | 206 | 207 | #: A :class:`bool` for activating the federated mode 208 | CAS_FEDERATE = False 209 | #: Time after witch the cookie use for “remember my identity provider” expire (one week). 210 | CAS_FEDERATE_REMEMBER_TIMEOUT = 604800 211 | 212 | #: A :class:`bool` for diplaying a warning on html pages then a new version of the application 213 | #: is avaible. Once closed by a user, it is not displayed to this user until the next new version. 214 | CAS_NEW_VERSION_HTML_WARNING = True 215 | #: A :class:`bool` for sending emails to ``settings.ADMINS`` when a new version is available. 216 | CAS_NEW_VERSION_EMAIL_WARNING = True 217 | #: URL to the pypi json of the application. Used to retreive the version number of the last version. 218 | #: You should not change it. 219 | CAS_NEW_VERSION_JSON_URL = "https://pypi.org/pypi/django-cas-server/json" 220 | 221 | #: If the service message should be displayed on the login page 222 | CAS_SHOW_SERVICE_MESSAGES = True 223 | 224 | #: Messages displayed in a info-box on the html pages of the default templates. 225 | #: ``CAS_INFO_MESSAGES`` is a :class:`dict` mapping message name to a message :class:`dict`. 226 | #: A message :class:`dict` has 3 keys: 227 | #: 228 | #: * ``message``: A :class:`unicode`, the message to display, potentially wrapped around 229 | #: ugettex_lazy 230 | #: * ``discardable``: A :class:`bool`, specify if the users can close the message info-box 231 | #: * ``type``: One of info, success, info, warning, danger. The type of the info-box. 232 | #: 233 | #: ``CAS_INFO_MESSAGES`` contains by default one message, ``cas_explained``, which explain 234 | #: roughly the purpose of a CAS. 235 | CAS_INFO_MESSAGES = { 236 | "cas_explained": { 237 | "message": _( 238 | u"The Central Authentication Service grants you access to most of our websites by " 239 | u"authenticating only once, so you don't need to type your credentials again unless " 240 | u"your session expires or you logout." 241 | ), 242 | "discardable": True, 243 | "type": "info", # one of info, success, info, warning, danger 244 | }, 245 | } 246 | #: :class:`list` of message names. Order in which info-box messages are displayed. 247 | #: Let the list empty to disable messages display. 248 | CAS_INFO_MESSAGES_ORDER = [] 249 | 250 | #: :class:`bool` If `True` Django session cookie will be removed on logout from CAS server 251 | CAS_REMOVE_DJANGO_SESSION_COOKIE_ON_LOGOUT = False 252 | #: :class:`bool` If `True` Django csrf cookie will be removed on logout from CAS server 253 | CAS_REMOVE_DJANGO_CSRF_COOKIE_ON_LOGOUT = False 254 | #: :class:`bool` If `True` Django language cookie will be removed on logout from CAS server 255 | CAS_REMOVE_DJANGO_LANGUAGE_COOKIE_ON_LOGOUT = False 256 | 257 | #: A dotted path to a form or a form used on the login page to retrieve user credentials 258 | CAS_USER_CREDENTIAL_FORM = "cas_server.forms.UserCredential" 259 | #: A dotted path to a form or a form used on warn page before emitting a ticket 260 | CAS_WARN_FORM = "cas_server.forms.WarnForm" 261 | #: A dotted path to a form or a form used on the login page to select another CAS in federated mode 262 | CAS_FEDERATE_SELECT_FORM = "cas_server.forms.FederateSelect" 263 | #: A dotted path to a form or a form used on the login page in federated mode 264 | CAS_FEDERATE_USER_CREDENTIAL_FORM = "cas_server.forms.FederateUserCredential" 265 | #: A dotted path to a form or a form for Tickets in the admin interface 266 | CAS_TICKET_FORM = "cas_server.forms.TicketForm" 267 | 268 | GLOBALS = globals().copy() 269 | for name, default_value in GLOBALS.items(): 270 | # only care about parameter begining by CAS_ 271 | if name.startswith("CAS_"): 272 | # get the current setting value, falling back to default_value 273 | value = getattr(settings, name, default_value) 274 | # set the setting value to its value if defined, else to the default_value. 275 | setattr(settings, name, value) 276 | 277 | # Allow the user defined CAS_COMPONENT_URLS to omit not changed values 278 | MERGED_CAS_COMPONENT_URLS = CAS_COMPONENT_URLS.copy() 279 | MERGED_CAS_COMPONENT_URLS.update(settings.CAS_COMPONENT_URLS) 280 | settings.CAS_COMPONENT_URLS = MERGED_CAS_COMPONENT_URLS 281 | 282 | # if the federated mode is enabled, we must use the :class`cas_server.auth.CASFederateAuth` auth 283 | # backend. 284 | if settings.CAS_FEDERATE: 285 | settings.CAS_AUTH_CLASS = "cas_server.auth.CASFederateAuth" 286 | 287 | 288 | #: SessionStore class depending of :django:setting:`SESSION_ENGINE` 289 | SessionStore = import_module(settings.SESSION_ENGINE).SessionStore 290 | --------------------------------------------------------------------------------