@@ -1,8 +1,6 @@
 | 
			
		||||
language: python
 | 
			
		||||
matrix:
 | 
			
		||||
  include:
 | 
			
		||||
  - python: "2.7"
 | 
			
		||||
    env: TOX_ENV=coverage
 | 
			
		||||
  - python: "2.7"
 | 
			
		||||
    env: TOX_ENV=flake8
 | 
			
		||||
  - python: "2.7"
 | 
			
		||||
@@ -23,6 +21,8 @@ matrix:
 | 
			
		||||
    env: TOX_ENV=py35-django18
 | 
			
		||||
  - python: "3.5"
 | 
			
		||||
    env: TOX_ENV=py35-django19
 | 
			
		||||
  - python: "2.7"
 | 
			
		||||
    env: TOX_ENV=coverage
 | 
			
		||||
cache:
 | 
			
		||||
  directories:
 | 
			
		||||
    - $HOME/.cache/pip/http/
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								Makefile
									
									
									
									
									
								
							@@ -38,7 +38,7 @@ dist:
 | 
			
		||||
 | 
			
		||||
test_venv/bin/python:
 | 
			
		||||
	virtualenv test_venv
 | 
			
		||||
	test_venv/bin/pip install -U --requirement requirements-dev.txt Django
 | 
			
		||||
	test_venv/bin/pip install -U --requirement requirements-dev.txt 'Django<1.10'
 | 
			
		||||
 | 
			
		||||
test_venv/cas/manage.py: test_venv
 | 
			
		||||
	mkdir -p test_venv/cas
 | 
			
		||||
@@ -62,7 +62,7 @@ run_server: test_project
 | 
			
		||||
 | 
			
		||||
run_tests: test_venv
 | 
			
		||||
	python setup.py check --restructuredtext --stric
 | 
			
		||||
	test_venv/bin/py.test --cov=cas_server --cov-report html
 | 
			
		||||
	test_venv/bin/py.test -rw -x --cov=cas_server --cov-report html
 | 
			
		||||
	rm htmlcov/coverage_html.js  # I am really pissed off by those keybord shortcuts
 | 
			
		||||
 | 
			
		||||
test_venv/bin/sphinx-build: test_venv
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										152
									
								
								README.rst
									
									
									
									
									
								
							
							
						
						
									
										152
									
								
								README.rst
									
									
									
									
									
								
							@@ -29,11 +29,49 @@ Dependencies
 | 
			
		||||
 | 
			
		||||
``django-cas-server`` depends on the following python packages:
 | 
			
		||||
 | 
			
		||||
* Django >= 1.7 < 1.10
 | 
			
		||||
* Django >= 1.7.1 < 1.10
 | 
			
		||||
* requests >= 2.4
 | 
			
		||||
* requests_futures >= 0.9.5
 | 
			
		||||
* lxml >= 3.4
 | 
			
		||||
* six >= 1
 | 
			
		||||
* six >= 1.8
 | 
			
		||||
 | 
			
		||||
Minimal version of packages dependancy are just indicative and meens that ``django-cas-server`` has
 | 
			
		||||
been tested with it. Previous versions of dependencies may or may not work.
 | 
			
		||||
 | 
			
		||||
Additionally, denpending of the authentication backend you plan to use, you may need the following
 | 
			
		||||
python packages:
 | 
			
		||||
 | 
			
		||||
* ldap3
 | 
			
		||||
* psycopg2
 | 
			
		||||
* mysql-python
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Here there is a table with the name of python packages and the corresponding packages providing
 | 
			
		||||
them on debian like systems and centos like systems.
 | 
			
		||||
You should try as much as possible to use system packages as there are automatically updated then
 | 
			
		||||
you update your system. You can then install Not Available (N/A)
 | 
			
		||||
packages on your system using pip inside a virtualenv as described in the `Installation`_ section.
 | 
			
		||||
For use with python3, just replace python(2) in the table by python3.
 | 
			
		||||
 | 
			
		||||
+------------------+-------------------------+---------------------+
 | 
			
		||||
| python package   | debian like systems     | centos like systems |
 | 
			
		||||
+==================+=========================+=====================+
 | 
			
		||||
| Django           | python-django           | python-django       |
 | 
			
		||||
+------------------+-------------------------+---------------------+
 | 
			
		||||
| requests         | python-requests         | python-requests     |
 | 
			
		||||
+------------------+-------------------------+---------------------+
 | 
			
		||||
| requests_futures | python-requests-futures | N/A                 |
 | 
			
		||||
+------------------+-------------------------+---------------------+
 | 
			
		||||
| lxml             | python-lxml             | python-lxml         |
 | 
			
		||||
+------------------+-------------------------+---------------------+
 | 
			
		||||
| six              | python-six              | python-six          |
 | 
			
		||||
+------------------+-------------------------+---------------------+
 | 
			
		||||
| ldap3            | python-ldap3            | python-ldap3        |
 | 
			
		||||
+------------------+-------------------------+---------------------+
 | 
			
		||||
| psycopg2         | python-psycopg2         | python-psycopg2     |
 | 
			
		||||
+------------------+-------------------------+---------------------+
 | 
			
		||||
| mysql-python     | python-mysqldb          | python2-mysql       |
 | 
			
		||||
+------------------+-------------------------+---------------------+
 | 
			
		||||
 | 
			
		||||
Installation
 | 
			
		||||
============
 | 
			
		||||
@@ -63,14 +101,17 @@ The recommended installation mode is to use a virtualenv with ``--system-site-pa
 | 
			
		||||
    New python executable in cas/bin/python2
 | 
			
		||||
    Also creating executable in cas/bin/python
 | 
			
		||||
    Installing setuptools, pip...done.
 | 
			
		||||
 | 
			
		||||
4. And `activate it <https://virtualenv.pypa.io/en/stable/userguide/#activate-script>`__::
 | 
			
		||||
 | 
			
		||||
    $ cd cas_venv/; . bin/activate
 | 
			
		||||
 | 
			
		||||
4. Create a django project::
 | 
			
		||||
5. Create a django project::
 | 
			
		||||
 | 
			
		||||
   $ django-admin startproject cas_project
 | 
			
		||||
   $ cd cas_project
 | 
			
		||||
 | 
			
		||||
5. Install `django-cas-server`. To use the last published release, run::
 | 
			
		||||
6. Install `django-cas-server`. To use the last published release, run::
 | 
			
		||||
 | 
			
		||||
    $ pip install django-cas-server
 | 
			
		||||
 | 
			
		||||
@@ -81,11 +122,11 @@ The recommended installation mode is to use a virtualenv with ``--system-site-pa
 | 
			
		||||
    $ pip install -r requirements.txt
 | 
			
		||||
 | 
			
		||||
   Then, either run ``make install`` to create a python package using the sources of the repository
 | 
			
		||||
   and install it with pip, or place the `cas_server` directory into your
 | 
			
		||||
   and install it with pip, or place the ``cas_server`` directory into your
 | 
			
		||||
   `PYTHONPATH <https://docs.python.org/2/using/cmdline.html#envvar-PYTHONPATH>`_
 | 
			
		||||
   (for instance by symlinking `cas_server` to the root of your django project).
 | 
			
		||||
   (for instance by symlinking ``cas_server`` to the root of your django project).
 | 
			
		||||
 | 
			
		||||
6. Open ``cas_project/settings.py`` in you favourite editor and follow the quick start section.
 | 
			
		||||
7. Open ``cas_project/settings.py`` in you favourite editor and follow the quick start section.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Quick start
 | 
			
		||||
@@ -145,7 +186,7 @@ Quick start
 | 
			
		||||
 | 
			
		||||
6. Start the development server and visit http://127.0.0.1:8000/admin/
 | 
			
		||||
   to add a first service allowed to authenticate user against the CAS
 | 
			
		||||
   (you'll need the Admin app enabled). See the Service Patterns section bellow.
 | 
			
		||||
   (you'll need the Admin app enabled). See the `Service Patterns`_ section bellow.
 | 
			
		||||
 | 
			
		||||
7. Visit http://127.0.0.1:8000/cas/ to login with your django users.
 | 
			
		||||
 | 
			
		||||
@@ -163,6 +204,8 @@ Template settings
 | 
			
		||||
 | 
			
		||||
* ``CAS_LOGO_URL``: URL to the logo showed in the up left corner on the default
 | 
			
		||||
  templates. Set it to ``False`` to disable it.
 | 
			
		||||
* ``CAS_FAVICON_URL``: URL to the favicon (shortcut icon) used by the default templates.
 | 
			
		||||
  Default is a key icon. Set it to ``False`` to disable it.
 | 
			
		||||
* ``CAS_COMPONENT_URLS``: URLs to css and javascript external components. It is a dictionnary
 | 
			
		||||
  and it must have the five following keys: ``"bootstrap3_css"``, ``"bootstrap3_js"``,
 | 
			
		||||
  ``"html5shiv"``, ``"respond"``, ``"jquery"``. The default is::
 | 
			
		||||
@@ -191,12 +234,14 @@ Template settings
 | 
			
		||||
Authentication settings
 | 
			
		||||
-----------------------
 | 
			
		||||
 | 
			
		||||
*  ``CAS_AUTH_CLASS``: A dotted path to a class or a class implementing
 | 
			
		||||
   ``cas_server.auth.AuthUser``. The default is ``"cas_server.auth.DjangoAuthUser"``
 | 
			
		||||
* ``CAS_AUTH_CLASS``: A dotted path to a class or a class implementing
 | 
			
		||||
  ``cas_server.auth.AuthUser``. The default is ``"cas_server.auth.DjangoAuthUser"``
 | 
			
		||||
  Available classes bundled with ``django-cas-server`` are listed below in the
 | 
			
		||||
  `Authentication backend`_ section.
 | 
			
		||||
 | 
			
		||||
*  ``SESSION_COOKIE_AGE``: This is a django settings. Here, it control the delay in seconds after
 | 
			
		||||
   which inactive users are logged out. The default is ``1209600`` (2 weeks). You probably should
 | 
			
		||||
   reduce it to something like ``86400`` seconds (1 day).
 | 
			
		||||
* ``SESSION_COOKIE_AGE``: This is a django settings. Here, it control the delay in seconds after
 | 
			
		||||
  which inactive users are logged out. The default is ``1209600`` (2 weeks). You probably should
 | 
			
		||||
  reduce it to something like ``86400`` seconds (1 day).
 | 
			
		||||
 | 
			
		||||
* ``CAS_PROXY_CA_CERTIFICATE_PATH``: Path to certificate authorities file. Usually on linux
 | 
			
		||||
  the local CAs are in ``/etc/ssl/certs/ca-certificates.crt``. The default is ``True`` which
 | 
			
		||||
@@ -212,13 +257,23 @@ Authentication settings
 | 
			
		||||
Federation settings
 | 
			
		||||
-------------------
 | 
			
		||||
 | 
			
		||||
* ``CAS_FEDERATE``: A boolean for activating the federated mode (see the federate section below).
 | 
			
		||||
  The default is ``False``.
 | 
			
		||||
* ``CAS_FEDERATE``: A boolean for activating the federated mode (see the `Federation mode`_
 | 
			
		||||
  section below). The default is ``False``.
 | 
			
		||||
* ``CAS_FEDERATE_REMEMBER_TIMEOUT``: Time after witch the cookie use for "remember my identity
 | 
			
		||||
  provider" expire. The default is ``604800``, one week. The cookie is called
 | 
			
		||||
  ``_remember_provider``.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
New version warnings settings
 | 
			
		||||
-----------------------------
 | 
			
		||||
 | 
			
		||||
* ``CAS_NEW_VERSION_HTML_WARNING``: A boolean for diplaying a warning on html pages then a new
 | 
			
		||||
  version of the application is avaible. Once closed by a user, it is not displayed to this user
 | 
			
		||||
  until the next new version. The default is ``True``.
 | 
			
		||||
* ``CAS_NEW_VERSION_EMAIL_WARNING``: A bolean sot sending a email to ``settings.ADMINS`` when a new
 | 
			
		||||
  version is available. The default is ``True``.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Tickets validity settings
 | 
			
		||||
-------------------------
 | 
			
		||||
 | 
			
		||||
@@ -257,6 +312,7 @@ Tickets miscellaneous settings
 | 
			
		||||
 | 
			
		||||
Mysql backend settings
 | 
			
		||||
----------------------
 | 
			
		||||
Deprecated, see the `Sql backend settings`_.
 | 
			
		||||
Only usefull if you are using the mysql authentication backend:
 | 
			
		||||
 | 
			
		||||
* ``CAS_SQL_HOST``: Host for the SQL server. The default is ``"localhost"``.
 | 
			
		||||
@@ -283,6 +339,64 @@ Only usefull if you are using the mysql authentication backend:
 | 
			
		||||
  The default is ``"crypt"``.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Sql backend settings
 | 
			
		||||
--------------------
 | 
			
		||||
Only usefull if you are using the sql authentication backend. You must add a ``"cas_server"``
 | 
			
		||||
database to `settings.DATABASES <https://docs.djangoproject.com/fr/1.9/ref/settings/#std:setting-DATABASES>`__
 | 
			
		||||
as defined in the django documentation. It is then the database
 | 
			
		||||
use by the sql backend.
 | 
			
		||||
 | 
			
		||||
* ``CAS_SQL_USER_QUERY``: The query performed upon user authentication.
 | 
			
		||||
  The username must be in field ``username``, the password in ``password``,
 | 
			
		||||
  additional fields are used as the user attributes.
 | 
			
		||||
  The default is ``"SELECT user AS username, pass AS password, users.* FROM users WHERE user = %s"``
 | 
			
		||||
* ``CAS_SQL_PASSWORD_CHECK``: The method used to check the user password. Must be one of the following:
 | 
			
		||||
 | 
			
		||||
  * ``"crypt"`` (see <https://en.wikipedia.org/wiki/Crypt_(C)>), the password in the database
 | 
			
		||||
    should begin this $
 | 
			
		||||
  * ``"ldap"`` (see https://tools.ietf.org/id/draft-stroeder-hashed-userpassword-values-01.html)
 | 
			
		||||
    the password in the database must begin with one of {MD5}, {SMD5}, {SHA}, {SSHA}, {SHA256},
 | 
			
		||||
    {SSHA256}, {SHA384}, {SSHA384}, {SHA512}, {SSHA512}, {CRYPT}.
 | 
			
		||||
  * ``"hex_HASH_NAME"`` with ``HASH_NAME`` in md5, sha1, sha224, sha256, sha384, sha512.
 | 
			
		||||
    The hashed password in the database is compare to the hexadecimal digest of the clear
 | 
			
		||||
    password hashed with the corresponding algorithm.
 | 
			
		||||
  * ``"plain"``, the password in the database must be in clear.
 | 
			
		||||
 | 
			
		||||
  The default is ``"crypt"``.
 | 
			
		||||
* ``CAS_SQL_PASSWORD_CHARSET``: Charset the SQL users passwords was hash with. This is needed to
 | 
			
		||||
  encode the user sended password before hashing it for comparison. The default is ``"utf-8"``.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Ldap backend settings
 | 
			
		||||
---------------------
 | 
			
		||||
Only usefull if you are using the ldap authentication backend:
 | 
			
		||||
 | 
			
		||||
* ``CAS_LDAP_SERVER``: Address of the LDAP server. The default is ``"localhost"``.
 | 
			
		||||
* ``CAS_LDAP_USER``: User bind address, for example ``"cn=admin,dc=crans,dc=org"`` for
 | 
			
		||||
  connecting to the LDAP server.
 | 
			
		||||
* ``CAS_LDAP_PASSWORD``: Password for connecting to the LDAP server.
 | 
			
		||||
* ``CAS_LDAP_BASE_DN``: LDAP search base DN, for example ``"ou=data,dc=crans,dc=org"``.
 | 
			
		||||
* ``CAS_LDAP_USER_QUERY``: Search filter for searching user by username. User inputed usernames are
 | 
			
		||||
  escaped using ``ldap3.utils.conv.escape_bytes``. The default is ``"(uid=%s)"``
 | 
			
		||||
* ``CAS_LDAP_USERNAME_ATTR``: Attribute used for users usernames. The default is ``"uid"``
 | 
			
		||||
* ``CAS_LDAP_PASSWORD_ATTR``: Attribute used for users passwords. The default is ``"userPassword"``
 | 
			
		||||
* ``CAS_LDAP_PASSWORD_CHECK``: The method used to check the user password. Must be one of the following:
 | 
			
		||||
 | 
			
		||||
  * ``"crypt"`` (see <https://en.wikipedia.org/wiki/Crypt_(C)>), the password in the database
 | 
			
		||||
    should begin this $
 | 
			
		||||
  * ``"ldap"`` (see https://tools.ietf.org/id/draft-stroeder-hashed-userpassword-values-01.html)
 | 
			
		||||
    the password in the database must begin with one of {MD5}, {SMD5}, {SHA}, {SSHA}, {SHA256},
 | 
			
		||||
    {SSHA256}, {SHA384}, {SSHA384}, {SHA512}, {SSHA512}, {CRYPT}.
 | 
			
		||||
  * ``"hex_HASH_NAME"`` with ``HASH_NAME`` in md5, sha1, sha224, sha256, sha384, sha512.
 | 
			
		||||
    The hashed password in the database is compare to the hexadecimal digest of the clear
 | 
			
		||||
    password hashed with the corresponding algorithm.
 | 
			
		||||
  * ``"plain"``, the password in the database must be in clear.
 | 
			
		||||
 | 
			
		||||
  The default is ``"ldap"``.
 | 
			
		||||
* ``CAS_LDAP_PASSWORD_CHARSET``: Charset the LDAP users passwords was hash with. This is needed to
 | 
			
		||||
  encode the user sended password before hashing it for comparison. The default is ``"utf-8"``.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Test backend settings
 | 
			
		||||
---------------------
 | 
			
		||||
Only usefull if you are using the test authentication backend:
 | 
			
		||||
@@ -304,11 +418,17 @@ Authentication backend
 | 
			
		||||
  for the user are defined by the ``CAS_TEST_*`` settings.
 | 
			
		||||
* django backend ``cas_server.auth.DjangoAuthUser``: Users are authenticated against django users system.
 | 
			
		||||
  This is the default backend. The returned attributes are the fields available on the user model.
 | 
			
		||||
* mysql backend ``cas_server.auth.MysqlAuthUser``: see the 'Mysql backend settings' section.
 | 
			
		||||
* mysql backend ``cas_server.auth.MysqlAuthUser``: Deprecated, use the sql backend instead.
 | 
			
		||||
  see the `Mysql backend settings`_ section. The returned attributes are those return by sql query
 | 
			
		||||
  ``CAS_SQL_USER_QUERY``.
 | 
			
		||||
* sql backend ``cas_server.auth.SqlAuthUser``: see the `Sql backend settings`_ section.
 | 
			
		||||
  The returned attributes are those return by sql query ``CAS_SQL_USER_QUERY``.
 | 
			
		||||
* ldap backend ``cas_server.auth.LdapAuthUser``: see the `Ldap backend settings`_ section.
 | 
			
		||||
  The returned attributes are those of the ldap node returned by the query filter ``CAS_LDAP_USER_QUERY``.
 | 
			
		||||
* federated backend ``cas_server.auth.CASFederateAuth``: It is automatically used then ``CAS_FEDERATE`` is ``True``.
 | 
			
		||||
  You should not set it manually without setting ``CAS_FEDERATE`` to ``True``.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Logs
 | 
			
		||||
====
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,5 +9,9 @@
 | 
			
		||||
#
 | 
			
		||||
# (c) 2015-2016 Valentin Samir
 | 
			
		||||
"""A django CAS server application"""
 | 
			
		||||
 | 
			
		||||
#: version of the application
 | 
			
		||||
VERSION = '0.6.2'
 | 
			
		||||
 | 
			
		||||
#: path the the application configuration class
 | 
			
		||||
default_app_config = 'cas_server.apps.CasAppConfig'
 | 
			
		||||
 
 | 
			
		||||
@@ -13,16 +13,25 @@
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.contrib.auth import get_user_model
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from django.db import connections, DatabaseError
 | 
			
		||||
 | 
			
		||||
import warnings
 | 
			
		||||
from datetime import timedelta
 | 
			
		||||
from six.moves import range
 | 
			
		||||
try:  # pragma: no cover
 | 
			
		||||
    import MySQLdb
 | 
			
		||||
    import MySQLdb.cursors
 | 
			
		||||
    from utils import check_password
 | 
			
		||||
except ImportError:
 | 
			
		||||
    MySQLdb = None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
try:  # pragma: no cover
 | 
			
		||||
    import ldap3
 | 
			
		||||
except ImportError:
 | 
			
		||||
    ldap3 = None
 | 
			
		||||
 | 
			
		||||
from .models import FederatedUser
 | 
			
		||||
from .utils import check_password, dictfetchall
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AuthUser(object):
 | 
			
		||||
@@ -116,19 +125,46 @@ class TestAuthUser(AuthUser):
 | 
			
		||||
            return {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MysqlAuthUser(AuthUser):  # pragma: no cover
 | 
			
		||||
class DBAuthUser(AuthUser):  # pragma: no cover
 | 
			
		||||
    """base class for databate based auth classes"""
 | 
			
		||||
    #: DB user attributes as a :class:`dict` if the username is found in the database.
 | 
			
		||||
    user = None
 | 
			
		||||
 | 
			
		||||
    def attributs(self):
 | 
			
		||||
        """
 | 
			
		||||
            The user attributes.
 | 
			
		||||
 | 
			
		||||
            :return: a :class:`dict` with the user attributes. Attributes may be :func:`unicode`
 | 
			
		||||
                or :class:`list` of :func:`unicode`. If the user do not exists, the returned
 | 
			
		||||
                :class:`dict` is empty.
 | 
			
		||||
            :rtype: dict
 | 
			
		||||
        """
 | 
			
		||||
        if self.user:
 | 
			
		||||
            return self.user
 | 
			
		||||
        else:
 | 
			
		||||
            return {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MysqlAuthUser(DBAuthUser):  # pragma: no cover
 | 
			
		||||
    """
 | 
			
		||||
        A mysql authentication class: authentication user agains a mysql database
 | 
			
		||||
        DEPRECATED, use :class:`SqlAuthUser` instead.
 | 
			
		||||
 | 
			
		||||
        A mysql authentication class: authenticate user agains a mysql database
 | 
			
		||||
 | 
			
		||||
        :param unicode username: A username, stored in the :attr:`username<AuthUser.username>`
 | 
			
		||||
            class attribute. Valid value are fetched from the MySQL database set with
 | 
			
		||||
            ``settings.CAS_SQL_*`` settings parameters using the query
 | 
			
		||||
            ``settings.CAS_SQL_USER_QUERY``.
 | 
			
		||||
    """
 | 
			
		||||
    #: Mysql user attributes as a :class:`dict` if the username is found in the database.
 | 
			
		||||
    user = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, username):
 | 
			
		||||
        warnings.warn(
 | 
			
		||||
            (
 | 
			
		||||
                "MysqlAuthUser authentication class is deprecated: "
 | 
			
		||||
                "use cas_server.auth.SqlAuthUser instead"
 | 
			
		||||
            ),
 | 
			
		||||
            UserWarning
 | 
			
		||||
        )
 | 
			
		||||
        # see the connect function at
 | 
			
		||||
        # http://mysql-python.sourceforge.net/MySQLdb.html#functions-and-attributes
 | 
			
		||||
        # for possible mysql config parameters.
 | 
			
		||||
@@ -169,24 +205,130 @@ class MysqlAuthUser(AuthUser):  # pragma: no cover
 | 
			
		||||
        else:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    def attributs(self):
 | 
			
		||||
        """
 | 
			
		||||
            The user attributes.
 | 
			
		||||
 | 
			
		||||
            :return: a :class:`dict` with the user attributes. Attributes may be :func:`unicode`
 | 
			
		||||
                or :class:`list` of :func:`unicode`. If the user do not exists, the returned
 | 
			
		||||
                :class:`dict` is empty.
 | 
			
		||||
            :rtype: dict
 | 
			
		||||
class SqlAuthUser(DBAuthUser):  # pragma: no cover
 | 
			
		||||
    """
 | 
			
		||||
        A SQL authentication class: authenticate user agains a SQL database. The SQL database
 | 
			
		||||
        must be configures in settings.py as ``settings.DATABASES['cas_server']``.
 | 
			
		||||
 | 
			
		||||
        :param unicode username: A username, stored in the :attr:`username<AuthUser.username>`
 | 
			
		||||
            class attribute. Valid value are fetched from the MySQL database set with
 | 
			
		||||
            ``settings.CAS_SQL_*`` settings parameters using the query
 | 
			
		||||
            ``settings.CAS_SQL_USER_QUERY``.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, username):
 | 
			
		||||
        if "cas_server" not in connections:
 | 
			
		||||
            raise RuntimeError("Please configure the 'cas_server' database in settings.DATABASES")
 | 
			
		||||
        for retry_nb in range(3):
 | 
			
		||||
            try:
 | 
			
		||||
                with connections["cas_server"].cursor() as curs:
 | 
			
		||||
                    curs.execute(settings.CAS_SQL_USER_QUERY, (username,))
 | 
			
		||||
                    results = dictfetchall(curs)
 | 
			
		||||
                    if len(results) == 1:
 | 
			
		||||
                        self.user = results[0]
 | 
			
		||||
                        super(SqlAuthUser, self).__init__(self.user['username'])
 | 
			
		||||
                    else:
 | 
			
		||||
                        super(SqlAuthUser, self).__init__(username)
 | 
			
		||||
                break
 | 
			
		||||
            except DatabaseError:
 | 
			
		||||
                connections["cas_server"].close()
 | 
			
		||||
                if retry_nb == 2:
 | 
			
		||||
                    raise
 | 
			
		||||
 | 
			
		||||
    def test_password(self, password):
 | 
			
		||||
        """
 | 
			
		||||
            Tests ``password`` agains the user password.
 | 
			
		||||
 | 
			
		||||
            :param unicode password: a clear text password as submited by the user.
 | 
			
		||||
            :return: ``True`` if :attr:`username<AuthUser.username>` is valid and ``password`` is
 | 
			
		||||
                correct, ``False`` otherwise.
 | 
			
		||||
            :rtype: bool
 | 
			
		||||
        """
 | 
			
		||||
        if self.user:
 | 
			
		||||
            return self.user
 | 
			
		||||
            return check_password(
 | 
			
		||||
                settings.CAS_SQL_PASSWORD_CHECK,
 | 
			
		||||
                password,
 | 
			
		||||
                self.user["password"],
 | 
			
		||||
                settings.CAS_SQL_PASSWORD_CHARSET
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            return {}
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LdapAuthUser(DBAuthUser):  # pragma: no cover
 | 
			
		||||
    """
 | 
			
		||||
        A ldap authentication class: authenticate user against a ldap database
 | 
			
		||||
 | 
			
		||||
        :param unicode username: A username, stored in the :attr:`username<AuthUser.username>`
 | 
			
		||||
            class attribute. Valid value are fetched from the ldap database set with
 | 
			
		||||
            ``settings.CAS_LDAP_*`` settings parameters.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    _conn = None
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_conn(cls):
 | 
			
		||||
        """Return a connection object to the ldap database"""
 | 
			
		||||
        conn = cls._conn
 | 
			
		||||
        if conn is None or conn.closed:
 | 
			
		||||
            conn = ldap3.Connection(
 | 
			
		||||
                settings.CAS_LDAP_SERVER,
 | 
			
		||||
                settings.CAS_LDAP_USER,
 | 
			
		||||
                settings.CAS_LDAP_PASSWORD,
 | 
			
		||||
                auto_bind=True
 | 
			
		||||
            )
 | 
			
		||||
            cls._conn = conn
 | 
			
		||||
        return conn
 | 
			
		||||
 | 
			
		||||
    def __init__(self, username):
 | 
			
		||||
        if not ldap3:
 | 
			
		||||
            raise RuntimeError("Please install ldap3 before using the LdapAuthUser backend")
 | 
			
		||||
        # in case we got deconnected from the database, retry to connect 2 times
 | 
			
		||||
        for retry_nb in range(3):
 | 
			
		||||
            try:
 | 
			
		||||
                conn = self.get_conn()
 | 
			
		||||
                if conn.search(
 | 
			
		||||
                    settings.CAS_LDAP_BASE_DN,
 | 
			
		||||
                    settings.CAS_LDAP_USER_QUERY % ldap3.utils.conv.escape_bytes(username),
 | 
			
		||||
                    attributes=ldap3.ALL_ATTRIBUTES
 | 
			
		||||
                ) and len(conn.entries) == 1:
 | 
			
		||||
                    user = conn.entries[0].entry_get_attributes_dict()
 | 
			
		||||
                    if user.get(settings.CAS_LDAP_USERNAME_ATTR):
 | 
			
		||||
                        self.user = user
 | 
			
		||||
                        super(LdapAuthUser, self).__init__(user[settings.CAS_LDAP_USERNAME_ATTR][0])
 | 
			
		||||
                    else:
 | 
			
		||||
                        super(LdapAuthUser, self).__init__(username)
 | 
			
		||||
                else:
 | 
			
		||||
                    super(LdapAuthUser, self).__init__(username)
 | 
			
		||||
                break
 | 
			
		||||
            except ldap3.LDAPCommunicationError:
 | 
			
		||||
                if retry_nb == 2:
 | 
			
		||||
                    raise
 | 
			
		||||
 | 
			
		||||
    def test_password(self, password):
 | 
			
		||||
        """
 | 
			
		||||
            Tests ``password`` agains the user password.
 | 
			
		||||
 | 
			
		||||
            :param unicode password: a clear text password as submited by the user.
 | 
			
		||||
            :return: ``True`` if :attr:`username<AuthUser.username>` is valid and ``password`` is
 | 
			
		||||
                correct, ``False`` otherwise.
 | 
			
		||||
            :rtype: bool
 | 
			
		||||
        """
 | 
			
		||||
        if self.user and self.user.get(settings.CAS_LDAP_PASSWORD_ATTR):
 | 
			
		||||
            return check_password(
 | 
			
		||||
                settings.CAS_LDAP_PASSWORD_CHECK,
 | 
			
		||||
                password,
 | 
			
		||||
                self.user[settings.CAS_LDAP_PASSWORD_ATTR][0],
 | 
			
		||||
                settings.CAS_LDAP_PASSWORD_CHARSET
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DjangoAuthUser(AuthUser):  # pragma: no cover
 | 
			
		||||
    """
 | 
			
		||||
        A django auth class: authenticate user agains django internal users
 | 
			
		||||
        A django auth class: authenticate user against django internal users
 | 
			
		||||
 | 
			
		||||
        :param unicode username: A username, stored in the :attr:`username<AuthUser.username>`
 | 
			
		||||
            class attribute. Valid value are usernames of django internal users.
 | 
			
		||||
 
 | 
			
		||||
@@ -134,6 +134,14 @@ class CASClientBase(object):
 | 
			
		||||
                raise CASError(errors[0].attrib['code'], errors[0].text)
 | 
			
		||||
        raise CASError("Bad http code %s" % response.code)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_page_charset(page, default="utf-8"):
 | 
			
		||||
        content_type = page.info().get('Content-type')
 | 
			
		||||
        if content_type and "charset=" in content_type:
 | 
			
		||||
            return content_type.split("charset=")[-1]
 | 
			
		||||
        else:
 | 
			
		||||
            return default
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CASClientV1(CASClientBase, ReturnUnicode):
 | 
			
		||||
    """CAS Client Version 1"""
 | 
			
		||||
@@ -146,17 +154,15 @@ class CASClientV1(CASClientBase, ReturnUnicode):
 | 
			
		||||
        Returns username on success and None on failure.
 | 
			
		||||
        """
 | 
			
		||||
        params = [('ticket', ticket), ('service', self.service_url)]
 | 
			
		||||
        if self.renew:
 | 
			
		||||
            params.append(('renew', 'true'))
 | 
			
		||||
        url = (urllib_parse.urljoin(self.server_url, 'validate') + '?' +
 | 
			
		||||
               urllib_parse.urlencode(params))
 | 
			
		||||
        page = urllib_request.urlopen(url)
 | 
			
		||||
        try:
 | 
			
		||||
            verified = page.readline().strip()
 | 
			
		||||
            if verified == b'yes':
 | 
			
		||||
                content_type = page.info().get('Content-type')
 | 
			
		||||
                if "charset=" in content_type:
 | 
			
		||||
                    charset = content_type.split("charset=")[-1]
 | 
			
		||||
                else:
 | 
			
		||||
                    charset = "ascii"
 | 
			
		||||
                charset = self.get_page_charset(page, default="ascii")
 | 
			
		||||
                user = self.u(page.readline().strip(), charset)
 | 
			
		||||
                return user, None, None
 | 
			
		||||
            else:
 | 
			
		||||
@@ -183,17 +189,15 @@ class CASClientV2(CASClientBase, ReturnUnicode):
 | 
			
		||||
 | 
			
		||||
    def get_verification_response(self, ticket):
 | 
			
		||||
        params = [('ticket', ticket), ('service', self.service_url)]
 | 
			
		||||
        if self.renew:
 | 
			
		||||
            params.append(('renew', 'true'))
 | 
			
		||||
        if self.proxy_callback:
 | 
			
		||||
            params.append(('pgtUrl', self.proxy_callback))
 | 
			
		||||
        base_url = urllib_parse.urljoin(self.server_url, self.url_suffix)
 | 
			
		||||
        url = base_url + '?' + urllib_parse.urlencode(params)
 | 
			
		||||
        page = urllib_request.urlopen(url)
 | 
			
		||||
        try:
 | 
			
		||||
            content_type = page.info().get('Content-type')
 | 
			
		||||
            if "charset=" in content_type:
 | 
			
		||||
                charset = content_type.split("charset=")[-1]
 | 
			
		||||
            else:
 | 
			
		||||
                charset = "ascii"
 | 
			
		||||
            charset = self.get_page_charset(page)
 | 
			
		||||
            return (page.read(), charset)
 | 
			
		||||
        finally:
 | 
			
		||||
            page.close()
 | 
			
		||||
@@ -306,11 +310,7 @@ class CASClientWithSAMLV1(CASClientV2, SingleLogoutMixin):
 | 
			
		||||
            from elementtree import ElementTree
 | 
			
		||||
 | 
			
		||||
        page = self.fetch_saml_validation(ticket)
 | 
			
		||||
        content_type = page.info().get('Content-type')
 | 
			
		||||
        if "charset=" in content_type:
 | 
			
		||||
            charset = content_type.split("charset=")[-1]
 | 
			
		||||
        else:
 | 
			
		||||
            charset = "ascii"
 | 
			
		||||
        charset = self.get_page_charset(page)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            user = None
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,8 @@ from importlib import import_module
 | 
			
		||||
 | 
			
		||||
#: URL to the logo showed in the up left corner on the default templates.
 | 
			
		||||
CAS_LOGO_URL = static("cas_server/logo.png")
 | 
			
		||||
#: URL to the favicon (shortcut icon) used by the default templates. Default is a key icon.
 | 
			
		||||
CAS_FAVICON_URL = static("cas_server/favicon.ico")
 | 
			
		||||
#: URLs to css and javascript external components.
 | 
			
		||||
CAS_COMPONENT_URLS = {
 | 
			
		||||
    "bootstrap3_css": "//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css",
 | 
			
		||||
@@ -110,12 +112,39 @@ CAS_SQL_PASSWORD = ''
 | 
			
		||||
CAS_SQL_DBNAME = ''
 | 
			
		||||
#: Database charset.
 | 
			
		||||
CAS_SQL_DBCHARSET = 'utf8'
 | 
			
		||||
 | 
			
		||||
#: The query performed upon user authentication.
 | 
			
		||||
CAS_SQL_USER_QUERY = 'SELECT user AS usersame, pass AS password, users.* FROM users WHERE user = %s'
 | 
			
		||||
CAS_SQL_USER_QUERY = 'SELECT user AS username, pass AS password, users.* FROM users WHERE user = %s'
 | 
			
		||||
#: The method used to check the user password. Must be one of ``"crypt"``, ``"ldap"``,
 | 
			
		||||
#: ``"hex_md5"``, ``"hex_sha1"``, ``"hex_sha224"``, ``"hex_sha256"``, ``"hex_sha384"``,
 | 
			
		||||
#: ``"hex_sha512"``, ``"plain"``.
 | 
			
		||||
CAS_SQL_PASSWORD_CHECK = 'crypt'  # crypt or plain
 | 
			
		||||
CAS_SQL_PASSWORD_CHECK = 'crypt'
 | 
			
		||||
#: charset the SQL users passwords was hash with
 | 
			
		||||
CAS_SQL_PASSWORD_CHARSET = "utf-8"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#: Address of the LDAP server
 | 
			
		||||
CAS_LDAP_SERVER = 'localhost'
 | 
			
		||||
#: LDAP user bind address, for example ``"cn=admin,dc=crans,dc=org"`` for connecting to the LDAP
 | 
			
		||||
#: server.
 | 
			
		||||
CAS_LDAP_USER = None
 | 
			
		||||
#: LDAP connection password
 | 
			
		||||
CAS_LDAP_PASSWORD = None
 | 
			
		||||
#: LDAP seach base DN, for example ``"ou=data,dc=crans,dc=org"``.
 | 
			
		||||
CAS_LDAP_BASE_DN = None
 | 
			
		||||
#: LDAP search filter for searching user by username. User inputed usernames are escaped using
 | 
			
		||||
#: :func:`ldap3.utils.conv.escape_bytes`.
 | 
			
		||||
CAS_LDAP_USER_QUERY = "(uid=%s)"
 | 
			
		||||
#: LDAP attribute used for users usernames
 | 
			
		||||
CAS_LDAP_USERNAME_ATTR = "uid"
 | 
			
		||||
#: LDAP attribute used for users passwords
 | 
			
		||||
CAS_LDAP_PASSWORD_ATTR = "userPassword"
 | 
			
		||||
#: The method used to check the user password. Must be one of ``"crypt"``, ``"ldap"``,
 | 
			
		||||
#: ``"hex_md5"``, ``"hex_sha1"``, ``"hex_sha224"``, ``"hex_sha256"``, ``"hex_sha384"``,
 | 
			
		||||
#: ``"hex_sha512"``, ``"plain"``.
 | 
			
		||||
CAS_LDAP_PASSWORD_CHECK = "ldap"
 | 
			
		||||
#: charset the LDAP users passwords was hash with
 | 
			
		||||
CAS_LDAP_PASSWORD_CHARSET = "utf-8"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#: Username of the test user.
 | 
			
		||||
@@ -140,6 +169,15 @@ CAS_FEDERATE = False
 | 
			
		||||
#: Time after witch the cookie use for “remember my identity provider” expire (one week).
 | 
			
		||||
CAS_FEDERATE_REMEMBER_TIMEOUT = 604800
 | 
			
		||||
 | 
			
		||||
#: A :class:`bool` for diplaying a warning on html pages then a new version of the application
 | 
			
		||||
#: is avaible. Once closed by a user, it is not displayed to this user until the next new version.
 | 
			
		||||
CAS_NEW_VERSION_HTML_WARNING = True
 | 
			
		||||
#: A :class:`bool` for sending emails to ``settings.ADMINS`` when a new version is available.
 | 
			
		||||
CAS_NEW_VERSION_EMAIL_WARNING = True
 | 
			
		||||
#: URL to the pypi json of the application. Used to retreive the version number of the last version.
 | 
			
		||||
#: You should not change it.
 | 
			
		||||
CAS_NEW_VERSION_JSON_URL = "https://pypi.python.org/pypi/django-cas-server/json"
 | 
			
		||||
 | 
			
		||||
GLOBALS = globals().copy()
 | 
			
		||||
for name, default_value in GLOBALS.items():
 | 
			
		||||
    # get the current setting value, falling back to default_value
 | 
			
		||||
 
 | 
			
		||||
@@ -42,13 +42,13 @@ class CASFederateValidateUser(object):
 | 
			
		||||
    #: the identity provider
 | 
			
		||||
    provider = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, provider, service_url):
 | 
			
		||||
    def __init__(self, provider, service_url, renew=False):
 | 
			
		||||
        self.provider = provider
 | 
			
		||||
        self.client = CASClient(
 | 
			
		||||
            service_url=service_url,
 | 
			
		||||
            version=provider.cas_protocol_version,
 | 
			
		||||
            server_url=provider.server_url,
 | 
			
		||||
            renew=False,
 | 
			
		||||
            renew=renew,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def get_login_url(self):
 | 
			
		||||
 
 | 
			
		||||
@@ -19,49 +19,56 @@ import cas_server.models as models
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BootsrapForm(forms.Form):
 | 
			
		||||
    """Form base class to use boostrap then rendering the form fields"""
 | 
			
		||||
    """
 | 
			
		||||
        Bases: :class:`django.forms.Form`
 | 
			
		||||
 | 
			
		||||
        Form base class to use boostrap then rendering the form fields
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super(BootsrapForm, self).__init__(*args, **kwargs)
 | 
			
		||||
        for (name, field) in self.fields.items():
 | 
			
		||||
        for field in self.fields.values():
 | 
			
		||||
            # Only tweak the fiel if it will be displayed
 | 
			
		||||
            if not isinstance(field.widget, forms.HiddenInput):
 | 
			
		||||
                # tell to display the field (used in form.html)
 | 
			
		||||
                self[name].display = True
 | 
			
		||||
                attrs = {}
 | 
			
		||||
                if isinstance(field.widget, forms.CheckboxInput):
 | 
			
		||||
                    self[name].checkbox = True
 | 
			
		||||
                else:
 | 
			
		||||
                if not isinstance(field.widget, forms.CheckboxInput):
 | 
			
		||||
                    attrs['class'] = "form-control"
 | 
			
		||||
                    if field.label:
 | 
			
		||||
                    if field.label:  # pragma: no branch (currently all field are hidden or labeled)
 | 
			
		||||
                        attrs["placeholder"] = field.label
 | 
			
		||||
                if field.required:
 | 
			
		||||
                    attrs["required"] = "required"
 | 
			
		||||
                field.widget.attrs.update(attrs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class WarnForm(BootsrapForm):
 | 
			
		||||
class BaseLogin(BootsrapForm):
 | 
			
		||||
    """
 | 
			
		||||
        Bases: :class:`django.forms.Form`
 | 
			
		||||
        Bases: :class:`BootsrapForm`
 | 
			
		||||
 | 
			
		||||
        Form used on warn page before emiting a ticket
 | 
			
		||||
        Base form with all field possibly hidden on the login pages
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    #: The service url for which the user want a ticket
 | 
			
		||||
    service = forms.CharField(widget=forms.HiddenInput(), required=False)
 | 
			
		||||
    #: A valid LoginTicket to prevent POST replay
 | 
			
		||||
    lt = forms.CharField(widget=forms.HiddenInput(), required=False)
 | 
			
		||||
    #: Is the service asking the authentication renewal ?
 | 
			
		||||
    renew = forms.BooleanField(widget=forms.HiddenInput(), required=False)
 | 
			
		||||
    #: Url to redirect to if the authentication fail (user not authenticated or bad service)
 | 
			
		||||
    gateway = forms.CharField(widget=forms.HiddenInput(), required=False)
 | 
			
		||||
    method = forms.CharField(widget=forms.HiddenInput(), required=False)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class WarnForm(BaseLogin):
 | 
			
		||||
    """
 | 
			
		||||
        Bases: :class:`BaseLogin`
 | 
			
		||||
 | 
			
		||||
        Form used on warn page before emiting a ticket
 | 
			
		||||
    """
 | 
			
		||||
    #: ``True`` if the user has been warned of the ticket emission
 | 
			
		||||
    warned = forms.BooleanField(widget=forms.HiddenInput(), required=False)
 | 
			
		||||
    #: A valid LoginTicket to prevent POST replay
 | 
			
		||||
    lt = forms.CharField(widget=forms.HiddenInput(), required=False)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FederateSelect(BootsrapForm):
 | 
			
		||||
class FederateSelect(BaseLogin):
 | 
			
		||||
    """
 | 
			
		||||
        Bases: :class:`django.forms.Form`
 | 
			
		||||
        Bases: :class:`BaseLogin`
 | 
			
		||||
 | 
			
		||||
        Form used on the login page when ``settings.CAS_FEDERATE`` is ``True``
 | 
			
		||||
        allowing the user to choose an identity provider.
 | 
			
		||||
@@ -76,39 +83,30 @@ class FederateSelect(BootsrapForm):
 | 
			
		||||
        to_field_name="suffix",
 | 
			
		||||
        label=_('Identity provider'),
 | 
			
		||||
    )
 | 
			
		||||
    #: The service url for which the user want a ticket
 | 
			
		||||
    service = forms.CharField(label=_('service'), widget=forms.HiddenInput(), required=False)
 | 
			
		||||
    method = forms.CharField(widget=forms.HiddenInput(), required=False)
 | 
			
		||||
    #: A checkbox to ask to be warn before emiting a ticket for another service
 | 
			
		||||
    warn = forms.BooleanField(
 | 
			
		||||
        label=_('Warn me before logging me into other sites.'),
 | 
			
		||||
        required=False
 | 
			
		||||
    )
 | 
			
		||||
    #: A checkbox to remember the user choices of :attr:`provider<FederateSelect.provider>`
 | 
			
		||||
    remember = forms.BooleanField(label=_('Remember the identity provider'), required=False)
 | 
			
		||||
    #: A checkbox to ask to be warn before emiting a ticket for another service
 | 
			
		||||
    warn = forms.BooleanField(label=_('warn'), required=False)
 | 
			
		||||
    #: Is the service asking the authentication renewal ?
 | 
			
		||||
    renew = forms.BooleanField(widget=forms.HiddenInput(), required=False)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserCredential(BootsrapForm):
 | 
			
		||||
class UserCredential(BaseLogin):
 | 
			
		||||
    """
 | 
			
		||||
         Bases: :class:`django.forms.Form`
 | 
			
		||||
         Bases: :class:`BaseLogin`
 | 
			
		||||
 | 
			
		||||
         Form used on the login page to retrive user credentials
 | 
			
		||||
     """
 | 
			
		||||
    #: The user username
 | 
			
		||||
    username = forms.CharField(label=_('login'))
 | 
			
		||||
    #: The service url for which the user want a ticket
 | 
			
		||||
    service = forms.CharField(label=_('service'), widget=forms.HiddenInput(), required=False)
 | 
			
		||||
    username = forms.CharField(label=_('username'))
 | 
			
		||||
    #: The user password
 | 
			
		||||
    password = forms.CharField(label=_('password'), widget=forms.PasswordInput)
 | 
			
		||||
    #: A valid LoginTicket to prevent POST replay
 | 
			
		||||
    lt = forms.CharField(widget=forms.HiddenInput(), required=False)
 | 
			
		||||
    method = forms.CharField(widget=forms.HiddenInput(), required=False)
 | 
			
		||||
    #: A checkbox to ask to be warn before emiting a ticket for another service
 | 
			
		||||
    warn = forms.BooleanField(label=_('warn'), required=False)
 | 
			
		||||
    #: Is the service asking the authentication renewal ?
 | 
			
		||||
    renew = forms.BooleanField(widget=forms.HiddenInput(), required=False)
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super(UserCredential, self).__init__(*args, **kwargs)
 | 
			
		||||
    warn = forms.BooleanField(
 | 
			
		||||
        label=_('Warn me before logging me into other sites.'),
 | 
			
		||||
        required=False
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def clean(self):
 | 
			
		||||
        """
 | 
			
		||||
@@ -124,7 +122,9 @@ class UserCredential(BootsrapForm):
 | 
			
		||||
        if auth.test_password(cleaned_data.get("password")):
 | 
			
		||||
            cleaned_data["username"] = auth.username
 | 
			
		||||
        else:
 | 
			
		||||
            raise forms.ValidationError(_(u"Bad user"))
 | 
			
		||||
            raise forms.ValidationError(
 | 
			
		||||
                _(u"The credentials you provided cannot be determined to be authentic.")
 | 
			
		||||
            )
 | 
			
		||||
        return cleaned_data
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -148,21 +148,13 @@ class FederateUserCredential(UserCredential):
 | 
			
		||||
        This stub authentication form, allow to implement the federated mode with very few
 | 
			
		||||
        modificatons to the :class:`LoginView<cas_server.views.LoginView>` view.
 | 
			
		||||
    """
 | 
			
		||||
    #: the user username with the ``@`` component
 | 
			
		||||
    username = forms.CharField(widget=forms.HiddenInput())
 | 
			
		||||
    #: The service url for which the user want a ticket
 | 
			
		||||
    service = forms.CharField(widget=forms.HiddenInput(), required=False)
 | 
			
		||||
    #: The ``ticket`` used to authenticate the user against a provider
 | 
			
		||||
    password = forms.CharField(widget=forms.HiddenInput())
 | 
			
		||||
    #: alias of :attr:`password`
 | 
			
		||||
    ticket = forms.CharField(widget=forms.HiddenInput())
 | 
			
		||||
    #: A valid LoginTicket to prevent POST replay
 | 
			
		||||
    lt = forms.CharField(widget=forms.HiddenInput(), required=False)
 | 
			
		||||
    method = forms.CharField(widget=forms.HiddenInput(), required=False)
 | 
			
		||||
    #: Has the user asked to be warn before emiting a ticket for another service
 | 
			
		||||
    warn = forms.BooleanField(widget=forms.HiddenInput(), required=False)
 | 
			
		||||
    #: Is the service asking the authentication renewal ?
 | 
			
		||||
    renew = forms.BooleanField(widget=forms.HiddenInput(), required=False)
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super(FederateUserCredential, self).__init__(*args, **kwargs)
 | 
			
		||||
        # All fields are hidden and auto filled by the /login view logic
 | 
			
		||||
        for name, field in self.fields.items():
 | 
			
		||||
            field.widget = forms.HiddenInput()
 | 
			
		||||
            self[name].display = False
 | 
			
		||||
 | 
			
		||||
    def clean(self):
 | 
			
		||||
        """
 | 
			
		||||
 
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							@@ -1,371 +0,0 @@
 | 
			
		||||
# SOME DESCRIPTIVE TITLE.
 | 
			
		||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
 | 
			
		||||
# This file is distributed under the same license as the PACKAGE package.
 | 
			
		||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
 | 
			
		||||
#
 | 
			
		||||
msgid ""
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Project-Id-Version: cas_server\n"
 | 
			
		||||
"Report-Msgid-Bugs-To: \n"
 | 
			
		||||
"POT-Creation-Date: 2016-07-04 17:36+0200\n"
 | 
			
		||||
"PO-Revision-Date: 2016-07-04 17:39+0200\n"
 | 
			
		||||
"Last-Translator: Valentin Samir <valentin.samir@crans.org>\n"
 | 
			
		||||
"Language-Team: django <LL@li.org>\n"
 | 
			
		||||
"Language: en\n"
 | 
			
		||||
"MIME-Version: 1.0\n"
 | 
			
		||||
"Content-Type: text/plain; charset=UTF-8\n"
 | 
			
		||||
"Content-Transfer-Encoding: 8bit\n"
 | 
			
		||||
"X-Generator: Poedit 1.8.8\n"
 | 
			
		||||
 | 
			
		||||
#: apps.py:19 templates/cas_server/base.html:3
 | 
			
		||||
#: templates/cas_server/base.html:20
 | 
			
		||||
msgid "Central Authentication Service"
 | 
			
		||||
msgstr "Central Authentication Service"
 | 
			
		||||
 | 
			
		||||
#: forms.py:43
 | 
			
		||||
msgid "Identity provider"
 | 
			
		||||
msgstr "Identity provider"
 | 
			
		||||
 | 
			
		||||
#: forms.py:45 forms.py:55 forms.py:106
 | 
			
		||||
msgid "service"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: forms.py:47
 | 
			
		||||
msgid "Remember the identity provider"
 | 
			
		||||
msgstr "Remember the identity provider"
 | 
			
		||||
 | 
			
		||||
#: forms.py:48 forms.py:59
 | 
			
		||||
msgid "warn"
 | 
			
		||||
msgstr " Warn me before logging me into other sites."
 | 
			
		||||
 | 
			
		||||
#: forms.py:54
 | 
			
		||||
msgid "login"
 | 
			
		||||
msgstr "username"
 | 
			
		||||
 | 
			
		||||
#: forms.py:56
 | 
			
		||||
msgid "password"
 | 
			
		||||
msgstr "password"
 | 
			
		||||
 | 
			
		||||
#: forms.py:71
 | 
			
		||||
msgid "Bad user"
 | 
			
		||||
msgstr "The credentials you provided cannot be determined to be authentic."
 | 
			
		||||
 | 
			
		||||
#: forms.py:96
 | 
			
		||||
msgid "User not found in the temporary database, please try to reconnect"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: management/commands/cas_clean_federate.py:20
 | 
			
		||||
msgid "Clean old federated users"
 | 
			
		||||
msgstr "Clean old federated users"
 | 
			
		||||
 | 
			
		||||
#: management/commands/cas_clean_sessions.py:22
 | 
			
		||||
msgid "Clean deleted sessions"
 | 
			
		||||
msgstr "Clean deleted sessions"
 | 
			
		||||
 | 
			
		||||
#: management/commands/cas_clean_tickets.py:22
 | 
			
		||||
msgid "Clean old trickets"
 | 
			
		||||
msgstr "Clean old trickets"
 | 
			
		||||
 | 
			
		||||
#: models.py:42
 | 
			
		||||
msgid "identity provider"
 | 
			
		||||
msgstr "identity provider"
 | 
			
		||||
 | 
			
		||||
#: models.py:43
 | 
			
		||||
msgid "identity providers"
 | 
			
		||||
msgstr "identity providers"
 | 
			
		||||
 | 
			
		||||
#: models.py:47
 | 
			
		||||
msgid "suffix"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: models.py:48
 | 
			
		||||
msgid ""
 | 
			
		||||
"Suffix append to backend CAS returner username: `returned_username`@`suffix`"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: models.py:50
 | 
			
		||||
msgid "server url"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: models.py:59
 | 
			
		||||
msgid "CAS protocol version"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: models.py:60
 | 
			
		||||
msgid ""
 | 
			
		||||
"Version of the CAS protocol to use when sending requests the the backend CAS"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: models.py:65
 | 
			
		||||
msgid "verbose name"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: models.py:66
 | 
			
		||||
msgid "Name for this identity provider displayed on the login page"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: models.py:70 models.py:317
 | 
			
		||||
msgid "position"
 | 
			
		||||
msgstr "position"
 | 
			
		||||
 | 
			
		||||
#: models.py:80
 | 
			
		||||
msgid "display"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: models.py:81
 | 
			
		||||
msgid "Display the provider on the login page"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: models.py:164
 | 
			
		||||
msgid "User"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: models.py:165
 | 
			
		||||
msgid "Users"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: models.py:234
 | 
			
		||||
#, python-format
 | 
			
		||||
msgid "Error during service logout %s"
 | 
			
		||||
msgstr "Error during service logout %s"
 | 
			
		||||
 | 
			
		||||
#: models.py:312
 | 
			
		||||
msgid "Service pattern"
 | 
			
		||||
msgstr "Service pattern"
 | 
			
		||||
 | 
			
		||||
#: models.py:313
 | 
			
		||||
msgid "Services patterns"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: models.py:318
 | 
			
		||||
msgid "service patterns are sorted using the position attribute"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: models.py:325 models.py:449
 | 
			
		||||
msgid "name"
 | 
			
		||||
msgstr "name"
 | 
			
		||||
 | 
			
		||||
#: models.py:326
 | 
			
		||||
msgid "A name for the service"
 | 
			
		||||
msgstr "A name for the service"
 | 
			
		||||
 | 
			
		||||
#: models.py:331 models.py:478 models.py:497
 | 
			
		||||
msgid "pattern"
 | 
			
		||||
msgstr "pattern"
 | 
			
		||||
 | 
			
		||||
#: models.py:333
 | 
			
		||||
msgid ""
 | 
			
		||||
"A regular expression matching services. Will usually looks like '^https://"
 | 
			
		||||
"some\\.server\\.com/path/.*$'.As it is a regular expression, special "
 | 
			
		||||
"character must be escaped with a '\\'."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"A regular expression matching services. Will usually looks like '^https://"
 | 
			
		||||
"some\\.server\\.com/path/.*$'.As it is a regular expression, special "
 | 
			
		||||
"character must be escaped with a '\\'."
 | 
			
		||||
 | 
			
		||||
#: models.py:342
 | 
			
		||||
msgid "user field"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: models.py:343
 | 
			
		||||
msgid "Name of the attribut to transmit as username, empty = login"
 | 
			
		||||
msgstr "Name of the attribut to transmit as username, empty = login"
 | 
			
		||||
 | 
			
		||||
#: models.py:347
 | 
			
		||||
msgid "restrict username"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: models.py:348
 | 
			
		||||
msgid "Limit username allowed to connect to the list provided bellow"
 | 
			
		||||
msgstr "Limit username allowed to connect to the list provided bellow"
 | 
			
		||||
 | 
			
		||||
#: models.py:352
 | 
			
		||||
msgid "proxy"
 | 
			
		||||
msgstr "proxy"
 | 
			
		||||
 | 
			
		||||
#: models.py:353
 | 
			
		||||
msgid "Proxy tickets can be delivered to the service"
 | 
			
		||||
msgstr "Proxy tickets can be delivered to the service"
 | 
			
		||||
 | 
			
		||||
#: models.py:357
 | 
			
		||||
msgid "proxy callback"
 | 
			
		||||
msgstr "proxy callback"
 | 
			
		||||
 | 
			
		||||
#: models.py:358
 | 
			
		||||
msgid "can be used as a proxy callback to deliver PGT"
 | 
			
		||||
msgstr "can be used as a proxy callback to deliver PGT"
 | 
			
		||||
 | 
			
		||||
#: models.py:362
 | 
			
		||||
msgid "single log out"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: models.py:363
 | 
			
		||||
msgid "Enable SLO for the service"
 | 
			
		||||
msgstr "Enable SLO for the service"
 | 
			
		||||
 | 
			
		||||
#: models.py:370
 | 
			
		||||
msgid "single log out callback"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: models.py:371
 | 
			
		||||
msgid ""
 | 
			
		||||
"URL where the SLO request will be POST. empty = service url\n"
 | 
			
		||||
"This is usefull for non HTTP proxied services."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: models.py:433
 | 
			
		||||
msgid "username"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: models.py:434
 | 
			
		||||
msgid "username allowed to connect to the service"
 | 
			
		||||
msgstr "username allowed to connect to the service"
 | 
			
		||||
 | 
			
		||||
#: models.py:450
 | 
			
		||||
msgid "name of an attribut to send to the service, use * for all attributes"
 | 
			
		||||
msgstr "name of an attribut to send to the service, use * for all attributes"
 | 
			
		||||
 | 
			
		||||
#: models.py:455 models.py:503
 | 
			
		||||
msgid "replace"
 | 
			
		||||
msgstr "replace"
 | 
			
		||||
 | 
			
		||||
#: models.py:456
 | 
			
		||||
msgid ""
 | 
			
		||||
"name under which the attribut will be showto the service. empty = default "
 | 
			
		||||
"name of the attribut"
 | 
			
		||||
msgstr ""
 | 
			
		||||
"name under which the attribut will be showto the service. empty = default "
 | 
			
		||||
"name of the attribut"
 | 
			
		||||
 | 
			
		||||
#: models.py:473 models.py:492
 | 
			
		||||
msgid "attribut"
 | 
			
		||||
msgstr "attribut"
 | 
			
		||||
 | 
			
		||||
#: models.py:474
 | 
			
		||||
msgid "Name of the attribut which must verify pattern"
 | 
			
		||||
msgstr "Name of the attribut which must verify pattern"
 | 
			
		||||
 | 
			
		||||
#: models.py:479
 | 
			
		||||
msgid "a regular expression"
 | 
			
		||||
msgstr "a regular expression"
 | 
			
		||||
 | 
			
		||||
#: models.py:493
 | 
			
		||||
msgid "Name of the attribut for which the value must be replace"
 | 
			
		||||
msgstr "Name of the attribut for which the value must be replace"
 | 
			
		||||
 | 
			
		||||
#: models.py:498
 | 
			
		||||
msgid "An regular expression maching whats need to be replaced"
 | 
			
		||||
msgstr "An regular expression maching whats need to be replaced"
 | 
			
		||||
 | 
			
		||||
#: models.py:504
 | 
			
		||||
msgid "replace expression, groups are capture by \\1, \\2 …"
 | 
			
		||||
msgstr "replace expression, groups are capture by \\1, \\2 …"
 | 
			
		||||
 | 
			
		||||
#: templates/cas_server/logged.html:6
 | 
			
		||||
msgid "Logged"
 | 
			
		||||
msgstr ""
 | 
			
		||||
"<h3>Log In Successful</h3>You have successfully logged into the Central "
 | 
			
		||||
"Authentication Service.<br/>For security reasons, please Log Out and Exit "
 | 
			
		||||
"your web browser when you are done accessing services that require "
 | 
			
		||||
"authentication!"
 | 
			
		||||
 | 
			
		||||
#: templates/cas_server/logged.html:10
 | 
			
		||||
msgid "Log me out from all my sessions"
 | 
			
		||||
msgstr "Log me out from all my sessions"
 | 
			
		||||
 | 
			
		||||
#: templates/cas_server/logged.html:13
 | 
			
		||||
msgid "Logout"
 | 
			
		||||
msgstr "Logout"
 | 
			
		||||
 | 
			
		||||
#: templates/cas_server/login.html:8
 | 
			
		||||
msgid "Please log in"
 | 
			
		||||
msgstr "Please log in"
 | 
			
		||||
 | 
			
		||||
#: templates/cas_server/login.html:13
 | 
			
		||||
msgid "Login"
 | 
			
		||||
msgstr "Login"
 | 
			
		||||
 | 
			
		||||
#: templates/cas_server/warn.html:10
 | 
			
		||||
msgid "Connect to the service"
 | 
			
		||||
msgstr "Connect to the service"
 | 
			
		||||
 | 
			
		||||
#: views.py:152
 | 
			
		||||
msgid ""
 | 
			
		||||
"<h3>Logout successful</h3>You have successfully logged out from the Central "
 | 
			
		||||
"Authentication Service. For security reasons, exit your web browser."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"<h3>Logout successful</h3>You have successfully logged out from the Central "
 | 
			
		||||
"Authentication Service. For security reasons, exit your web browser."
 | 
			
		||||
 | 
			
		||||
#: views.py:158
 | 
			
		||||
#, python-format
 | 
			
		||||
msgid ""
 | 
			
		||||
"<h3>Logout successful</h3>You have successfully logged out from %s sessions "
 | 
			
		||||
"of the Central Authentication Service. For security reasons, exit your web "
 | 
			
		||||
"browser."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"<h3>Logout successful</h3>You have successfully logged out from %s sessions "
 | 
			
		||||
"of the Central Authentication Service. For security reasons, exit your web "
 | 
			
		||||
"browser."
 | 
			
		||||
 | 
			
		||||
#: views.py:165
 | 
			
		||||
msgid ""
 | 
			
		||||
"<h3>Logout successful</h3>You were already logged out from the Central "
 | 
			
		||||
"Authentication Service. For security reasons, exit your web browser."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"<h3>Logout successful</h3>You were already logged out from the Central "
 | 
			
		||||
"Authentication Service. For security reasons, exit your web browser."
 | 
			
		||||
 | 
			
		||||
#: views.py:349
 | 
			
		||||
msgid "Invalid login ticket"
 | 
			
		||||
msgstr "Invalid login ticket, please retry to login"
 | 
			
		||||
 | 
			
		||||
#: views.py:470
 | 
			
		||||
#, python-format
 | 
			
		||||
msgid "Authentication has been required by service %(name)s (%(url)s)"
 | 
			
		||||
msgstr "Authentication has been required by service %(name)s (%(url)s)"
 | 
			
		||||
 | 
			
		||||
#: views.py:508
 | 
			
		||||
#, python-format
 | 
			
		||||
msgid "Service %(url)s non allowed."
 | 
			
		||||
msgstr "Service %(url)s non allowed."
 | 
			
		||||
 | 
			
		||||
#: views.py:515
 | 
			
		||||
msgid "Username non allowed"
 | 
			
		||||
msgstr "Username non allowed"
 | 
			
		||||
 | 
			
		||||
#: views.py:522
 | 
			
		||||
msgid "User charateristics non allowed"
 | 
			
		||||
msgstr "User charateristics non allowed"
 | 
			
		||||
 | 
			
		||||
#: views.py:529
 | 
			
		||||
#, python-format
 | 
			
		||||
msgid "The attribut %(field)s is needed to use that service"
 | 
			
		||||
msgstr "The attribut %(field)s is needed to use that service"
 | 
			
		||||
 | 
			
		||||
#: views.py:599
 | 
			
		||||
#, python-format
 | 
			
		||||
msgid "Authentication renewal required by service %(name)s (%(url)s)."
 | 
			
		||||
msgstr "Authentication renewal required by service %(name)s (%(url)s)."
 | 
			
		||||
 | 
			
		||||
#: views.py:606
 | 
			
		||||
#, python-format
 | 
			
		||||
msgid "Authentication required by service %(name)s (%(url)s)."
 | 
			
		||||
msgstr "Authentication required by service %(name)s (%(url)s)."
 | 
			
		||||
 | 
			
		||||
#: views.py:613
 | 
			
		||||
#, python-format
 | 
			
		||||
msgid "Service %s non allowed"
 | 
			
		||||
msgstr "Service %s non allowed"
 | 
			
		||||
 | 
			
		||||
#~ msgid ""
 | 
			
		||||
#~ "Error during service logout %(service)s:\n"
 | 
			
		||||
#~ "%(error)s"
 | 
			
		||||
#~ msgstr ""
 | 
			
		||||
#~ "Error during service logout %(service)s:\n"
 | 
			
		||||
#~ "%(error)s"
 | 
			
		||||
 | 
			
		||||
#~ msgid "Successfully logout"
 | 
			
		||||
#~ msgstr ""
 | 
			
		||||
#~ "<h3>Logout successful</h3>You have successfully logged out of the Central "
 | 
			
		||||
#~ "Authentication Service.</br>For security reasons, exit your web browser."
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							@@ -7,8 +7,8 @@ msgid ""
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Project-Id-Version: cas_server\n"
 | 
			
		||||
"Report-Msgid-Bugs-To: \n"
 | 
			
		||||
"POT-Creation-Date: 2016-07-04 17:36+0200\n"
 | 
			
		||||
"PO-Revision-Date: 2016-07-04 17:37+0200\n"
 | 
			
		||||
"POT-Creation-Date: 2016-08-01 12:01+0200\n"
 | 
			
		||||
"PO-Revision-Date: 2016-08-01 12:01+0200\n"
 | 
			
		||||
"Last-Translator: Valentin Samir <valentin.samir@crans.org>\n"
 | 
			
		||||
"Language-Team: django <LL@li.org>\n"
 | 
			
		||||
"Language: fr\n"
 | 
			
		||||
@@ -18,45 +18,45 @@ msgstr ""
 | 
			
		||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
 | 
			
		||||
"X-Generator: Poedit 1.8.8\n"
 | 
			
		||||
 | 
			
		||||
#: apps.py:19 templates/cas_server/base.html:3
 | 
			
		||||
#: templates/cas_server/base.html:20
 | 
			
		||||
#: apps.py:25 templates/cas_server/base.html:9
 | 
			
		||||
#: templates/cas_server/base.html:27
 | 
			
		||||
msgid "Central Authentication Service"
 | 
			
		||||
msgstr "Service Central d'Authentification"
 | 
			
		||||
 | 
			
		||||
#: forms.py:43
 | 
			
		||||
#: forms.py:88
 | 
			
		||||
msgid "Identity provider"
 | 
			
		||||
msgstr "fournisseur d'identité"
 | 
			
		||||
 | 
			
		||||
#: forms.py:45 forms.py:55 forms.py:106
 | 
			
		||||
msgid "service"
 | 
			
		||||
msgstr "service"
 | 
			
		||||
#: forms.py:92 forms.py:111
 | 
			
		||||
msgid "Warn me before logging me into other sites."
 | 
			
		||||
msgstr "Prévenez-moi avant d'accéder à d'autres services."
 | 
			
		||||
 | 
			
		||||
#: forms.py:47
 | 
			
		||||
#: forms.py:96
 | 
			
		||||
msgid "Remember the identity provider"
 | 
			
		||||
msgstr "Se souvenir du fournisseur d'identité"
 | 
			
		||||
 | 
			
		||||
#: forms.py:48 forms.py:59
 | 
			
		||||
msgid "warn"
 | 
			
		||||
msgstr "Prévenez-moi avant d'accéder à d'autres services."
 | 
			
		||||
#: forms.py:106 models.py:600
 | 
			
		||||
msgid "username"
 | 
			
		||||
msgstr "nom d'utilisateur"
 | 
			
		||||
 | 
			
		||||
#: forms.py:54
 | 
			
		||||
msgid "login"
 | 
			
		||||
msgstr "Identifiant"
 | 
			
		||||
 | 
			
		||||
#: forms.py:56
 | 
			
		||||
#: forms.py:108
 | 
			
		||||
msgid "password"
 | 
			
		||||
msgstr "mot de passe"
 | 
			
		||||
 | 
			
		||||
#: forms.py:71
 | 
			
		||||
msgid "Bad user"
 | 
			
		||||
#: forms.py:130
 | 
			
		||||
msgid "The credentials you provided cannot be determined to be authentic."
 | 
			
		||||
msgstr "Les informations transmises n'ont pas permis de vous authentifier."
 | 
			
		||||
 | 
			
		||||
#: forms.py:96
 | 
			
		||||
#: forms.py:182
 | 
			
		||||
msgid "User not found in the temporary database, please try to reconnect"
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Utilisateur non trouvé dans la base de donnée temporaire, essayez de vous "
 | 
			
		||||
"reconnecter"
 | 
			
		||||
 | 
			
		||||
#: forms.py:196
 | 
			
		||||
msgid "service"
 | 
			
		||||
msgstr "service"
 | 
			
		||||
 | 
			
		||||
#: management/commands/cas_clean_federate.py:20
 | 
			
		||||
msgid "Clean old federated users"
 | 
			
		||||
msgstr "Nettoyer les anciens utilisateurs fédéré"
 | 
			
		||||
@@ -69,98 +69,99 @@ msgstr "Nettoyer les sessions supprimées"
 | 
			
		||||
msgid "Clean old trickets"
 | 
			
		||||
msgstr "Nettoyer les vieux tickets"
 | 
			
		||||
 | 
			
		||||
#: models.py:42
 | 
			
		||||
#: models.py:46
 | 
			
		||||
msgid "identity provider"
 | 
			
		||||
msgstr "fournisseur d'identité"
 | 
			
		||||
 | 
			
		||||
#: models.py:43
 | 
			
		||||
#: models.py:47
 | 
			
		||||
msgid "identity providers"
 | 
			
		||||
msgstr "fournisseurs d'identités"
 | 
			
		||||
 | 
			
		||||
#: models.py:47
 | 
			
		||||
#: models.py:53
 | 
			
		||||
msgid "suffix"
 | 
			
		||||
msgstr "suffixe"
 | 
			
		||||
 | 
			
		||||
#: models.py:48
 | 
			
		||||
#: models.py:55
 | 
			
		||||
msgid ""
 | 
			
		||||
"Suffix append to backend CAS returner username: `returned_username`@`suffix`"
 | 
			
		||||
"Suffix append to backend CAS returned username: ``returned_username`` @ "
 | 
			
		||||
"``suffix``."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Suffixe ajouté au nom d'utilisateur retourné par le CAS du fournisseur "
 | 
			
		||||
"d'identité : `nom retourné`@`suffixe`"
 | 
			
		||||
"d'identité : `nom retourné`@`suffixe`."
 | 
			
		||||
 | 
			
		||||
#: models.py:50
 | 
			
		||||
#: models.py:62
 | 
			
		||||
msgid "server url"
 | 
			
		||||
msgstr "url du serveur"
 | 
			
		||||
 | 
			
		||||
#: models.py:59
 | 
			
		||||
#: models.py:72
 | 
			
		||||
msgid "CAS protocol version"
 | 
			
		||||
msgstr "Version du protocole CAS"
 | 
			
		||||
 | 
			
		||||
#: models.py:60
 | 
			
		||||
#: models.py:74
 | 
			
		||||
msgid ""
 | 
			
		||||
"Version of the CAS protocol to use when sending requests the the backend CAS"
 | 
			
		||||
"Version of the CAS protocol to use when sending requests the the backend CAS."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Version du protocole CAS à utiliser lorsque l'on envoie des requête au CAS "
 | 
			
		||||
"du fournisseur d'identité"
 | 
			
		||||
"du fournisseur d'identité."
 | 
			
		||||
 | 
			
		||||
#: models.py:65
 | 
			
		||||
#: models.py:81
 | 
			
		||||
msgid "verbose name"
 | 
			
		||||
msgstr "Nom du fournisseur"
 | 
			
		||||
 | 
			
		||||
#: models.py:66
 | 
			
		||||
msgid "Name for this identity provider displayed on the login page"
 | 
			
		||||
msgstr "Nom affiché pour ce fournisseur d'identité sur la page de connexion"
 | 
			
		||||
#: models.py:82
 | 
			
		||||
msgid "Name for this identity provider displayed on the login page."
 | 
			
		||||
msgstr "Nom affiché pour ce fournisseur d'identité sur la page de connexion."
 | 
			
		||||
 | 
			
		||||
#: models.py:70 models.py:317
 | 
			
		||||
#: models.py:88 models.py:446
 | 
			
		||||
msgid "position"
 | 
			
		||||
msgstr "position"
 | 
			
		||||
 | 
			
		||||
#: models.py:80
 | 
			
		||||
#: models.py:102
 | 
			
		||||
msgid "display"
 | 
			
		||||
msgstr "afficher"
 | 
			
		||||
 | 
			
		||||
#: models.py:81
 | 
			
		||||
msgid "Display the provider on the login page"
 | 
			
		||||
msgstr "Afficher le fournisseur d'identité sur la page de connexion"
 | 
			
		||||
#: models.py:103
 | 
			
		||||
msgid "Display the provider on the login page."
 | 
			
		||||
msgstr "Afficher le fournisseur d'identité sur la page de connexion."
 | 
			
		||||
 | 
			
		||||
#: models.py:164
 | 
			
		||||
#: models.py:233
 | 
			
		||||
msgid "User"
 | 
			
		||||
msgstr "Utilisateur"
 | 
			
		||||
 | 
			
		||||
#: models.py:165
 | 
			
		||||
#: models.py:234
 | 
			
		||||
msgid "Users"
 | 
			
		||||
msgstr "Utilisateurs"
 | 
			
		||||
 | 
			
		||||
#: models.py:234
 | 
			
		||||
#: models.py:320
 | 
			
		||||
#, python-format
 | 
			
		||||
msgid "Error during service logout %s"
 | 
			
		||||
msgstr "Une erreur est survenue durant la déconnexion du service %s"
 | 
			
		||||
 | 
			
		||||
#: models.py:312
 | 
			
		||||
#: models.py:440
 | 
			
		||||
msgid "Service pattern"
 | 
			
		||||
msgstr "Motif de service"
 | 
			
		||||
 | 
			
		||||
#: models.py:313
 | 
			
		||||
#: models.py:441
 | 
			
		||||
msgid "Services patterns"
 | 
			
		||||
msgstr "Motifs de services"
 | 
			
		||||
 | 
			
		||||
#: models.py:318
 | 
			
		||||
#: models.py:447
 | 
			
		||||
msgid "service patterns are sorted using the position attribute"
 | 
			
		||||
msgstr "Les motifs de service sont trié selon l'attribut position"
 | 
			
		||||
 | 
			
		||||
#: models.py:325 models.py:449
 | 
			
		||||
#: models.py:455 models.py:626
 | 
			
		||||
msgid "name"
 | 
			
		||||
msgstr "nom"
 | 
			
		||||
 | 
			
		||||
#: models.py:326
 | 
			
		||||
#: models.py:456
 | 
			
		||||
msgid "A name for the service"
 | 
			
		||||
msgstr "Un nom pour le service"
 | 
			
		||||
 | 
			
		||||
#: models.py:331 models.py:478 models.py:497
 | 
			
		||||
#: models.py:464 models.py:669 models.py:698
 | 
			
		||||
msgid "pattern"
 | 
			
		||||
msgstr "motif"
 | 
			
		||||
 | 
			
		||||
#: models.py:333
 | 
			
		||||
#: models.py:466
 | 
			
		||||
msgid ""
 | 
			
		||||
"A regular expression matching services. Will usually looks like '^https://"
 | 
			
		||||
"some\\.server\\.com/path/.*$'.As it is a regular expression, special "
 | 
			
		||||
@@ -171,55 +172,55 @@ msgstr ""
 | 
			
		||||
"expression rationnelle, les caractères spéciaux doivent être échappés avec "
 | 
			
		||||
"un '\\'."
 | 
			
		||||
 | 
			
		||||
#: models.py:342
 | 
			
		||||
#: models.py:476
 | 
			
		||||
msgid "user field"
 | 
			
		||||
msgstr "champ utilisateur"
 | 
			
		||||
 | 
			
		||||
#: models.py:343
 | 
			
		||||
msgid "Name of the attribut to transmit as username, empty = login"
 | 
			
		||||
#: models.py:477
 | 
			
		||||
msgid "Name of the attribute to transmit as username, empty = login"
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Nom de l'attribut devant être transmis comme nom d'utilisateur au service. "
 | 
			
		||||
"vide = nom de connection"
 | 
			
		||||
"vide = nom de connexion"
 | 
			
		||||
 | 
			
		||||
#: models.py:347
 | 
			
		||||
#: models.py:482
 | 
			
		||||
msgid "restrict username"
 | 
			
		||||
msgstr "limiter les noms d'utilisateurs"
 | 
			
		||||
 | 
			
		||||
#: models.py:348
 | 
			
		||||
#: models.py:483
 | 
			
		||||
msgid "Limit username allowed to connect to the list provided bellow"
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Limiter les noms d'utilisateurs autorisé à se connecter à la liste fournie "
 | 
			
		||||
"ci-dessous"
 | 
			
		||||
 | 
			
		||||
#: models.py:352
 | 
			
		||||
#: models.py:488
 | 
			
		||||
msgid "proxy"
 | 
			
		||||
msgstr "proxy"
 | 
			
		||||
 | 
			
		||||
#: models.py:353
 | 
			
		||||
#: models.py:489
 | 
			
		||||
msgid "Proxy tickets can be delivered to the service"
 | 
			
		||||
msgstr "des proxy tickets peuvent être délivrés au service"
 | 
			
		||||
 | 
			
		||||
#: models.py:357
 | 
			
		||||
#: models.py:495
 | 
			
		||||
msgid "proxy callback"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: models.py:358
 | 
			
		||||
#: models.py:496
 | 
			
		||||
msgid "can be used as a proxy callback to deliver PGT"
 | 
			
		||||
msgstr "peut être utilisé comme un callback pour recevoir un PGT"
 | 
			
		||||
 | 
			
		||||
#: models.py:362
 | 
			
		||||
#: models.py:503
 | 
			
		||||
msgid "single log out"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: models.py:363
 | 
			
		||||
#: models.py:504
 | 
			
		||||
msgid "Enable SLO for the service"
 | 
			
		||||
msgstr "Active le SLO pour le service"
 | 
			
		||||
 | 
			
		||||
#: models.py:370
 | 
			
		||||
#: models.py:512
 | 
			
		||||
msgid "single log out callback"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: models.py:371
 | 
			
		||||
#: models.py:513
 | 
			
		||||
msgid ""
 | 
			
		||||
"URL where the SLO request will be POST. empty = service url\n"
 | 
			
		||||
"This is usefull for non HTTP proxied services."
 | 
			
		||||
@@ -228,83 +229,98 @@ msgstr ""
 | 
			
		||||
"service\n"
 | 
			
		||||
"Ceci n'est utilise que pour des services non HTTP proxifiés"
 | 
			
		||||
 | 
			
		||||
#: models.py:433
 | 
			
		||||
msgid "username"
 | 
			
		||||
msgstr "nom d'utilisateur"
 | 
			
		||||
 | 
			
		||||
#: models.py:434
 | 
			
		||||
#: models.py:601
 | 
			
		||||
msgid "username allowed to connect to the service"
 | 
			
		||||
msgstr "noms d'utilisateurs autorisé à se connecter au service"
 | 
			
		||||
 | 
			
		||||
#: models.py:450
 | 
			
		||||
msgid "name of an attribut to send to the service, use * for all attributes"
 | 
			
		||||
#: models.py:627
 | 
			
		||||
msgid "name of an attribute to send to the service, use * for all attributes"
 | 
			
		||||
msgstr ""
 | 
			
		||||
"nom d'un attribut a envoyer au service, utiliser * pour tous les attributs"
 | 
			
		||||
 | 
			
		||||
#: models.py:455 models.py:503
 | 
			
		||||
#: models.py:634 models.py:705
 | 
			
		||||
msgid "replace"
 | 
			
		||||
msgstr "remplacement"
 | 
			
		||||
 | 
			
		||||
#: models.py:456
 | 
			
		||||
#: models.py:635
 | 
			
		||||
msgid ""
 | 
			
		||||
"name under which the attribut will be showto the service. empty = default "
 | 
			
		||||
"name under which the attribute will be showto the service. empty = default "
 | 
			
		||||
"name of the attribut"
 | 
			
		||||
msgstr ""
 | 
			
		||||
"nom sous lequel l'attribut sera rendu visible au service. vide = inchangé"
 | 
			
		||||
 | 
			
		||||
#: models.py:473 models.py:492
 | 
			
		||||
msgid "attribut"
 | 
			
		||||
#: models.py:662 models.py:692
 | 
			
		||||
msgid "attribute"
 | 
			
		||||
msgstr "attribut"
 | 
			
		||||
 | 
			
		||||
#: models.py:474
 | 
			
		||||
msgid "Name of the attribut which must verify pattern"
 | 
			
		||||
#: models.py:663
 | 
			
		||||
msgid "Name of the attribute which must verify pattern"
 | 
			
		||||
msgstr "Nom de l'attribut devant vérifier un motif"
 | 
			
		||||
 | 
			
		||||
#: models.py:479
 | 
			
		||||
#: models.py:670
 | 
			
		||||
msgid "a regular expression"
 | 
			
		||||
msgstr "une expression régulière"
 | 
			
		||||
 | 
			
		||||
#: models.py:493
 | 
			
		||||
msgid "Name of the attribut for which the value must be replace"
 | 
			
		||||
msgstr "nom de l'attribue pour lequel la valeur doit être remplacé"
 | 
			
		||||
#: models.py:693
 | 
			
		||||
msgid "Name of the attribute for which the value must be replace"
 | 
			
		||||
msgstr "nom de l'attribut pour lequel la valeur doit être remplacé"
 | 
			
		||||
 | 
			
		||||
#: models.py:498
 | 
			
		||||
#: models.py:699
 | 
			
		||||
msgid "An regular expression maching whats need to be replaced"
 | 
			
		||||
msgstr "une expression régulière reconnaissant ce qui doit être remplacé"
 | 
			
		||||
 | 
			
		||||
#: models.py:504
 | 
			
		||||
#: models.py:706
 | 
			
		||||
msgid "replace expression, groups are capture by \\1, \\2 …"
 | 
			
		||||
msgstr "expression de remplacement, les groupe sont capturé par \\1, \\2"
 | 
			
		||||
 | 
			
		||||
#: templates/cas_server/logged.html:6
 | 
			
		||||
msgid "Logged"
 | 
			
		||||
#: templates/cas_server/base.html:38
 | 
			
		||||
#, python-format
 | 
			
		||||
msgid ""
 | 
			
		||||
"A new version of the application is available. This instance runs "
 | 
			
		||||
"%(VERSION)s and the last version is %(LAST_VERSION)s. Please consider "
 | 
			
		||||
"upgrading."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"<h3>Connexion réussie</h3>Vous vous êtes authentifié(e) auprès du Service "
 | 
			
		||||
"Central d'Authentification.<br/>Pour des raisons de sécurité, veuillez vous "
 | 
			
		||||
"déconnecter et fermer votre navigateur lorsque vous avez fini d'accéder aux "
 | 
			
		||||
"services authentifiés."
 | 
			
		||||
"Une nouvelle version de l'application est disponible. Cette instance utilise "
 | 
			
		||||
"la version %(VERSION)s et la dernière version est %(LAST_VERSION)s. Merci de "
 | 
			
		||||
"vous mettre a jour."
 | 
			
		||||
 | 
			
		||||
#: templates/cas_server/logged.html:10
 | 
			
		||||
#: templates/cas_server/logged.html:4
 | 
			
		||||
msgid ""
 | 
			
		||||
"<h3>Log In Successful</h3>You have successfully logged into the Central "
 | 
			
		||||
"Authentication Service.<br/>For security reasons, please Log Out and Exit "
 | 
			
		||||
"your web browser when you are done accessing services that require "
 | 
			
		||||
"authentication!"
 | 
			
		||||
msgstr ""
 | 
			
		||||
"<h3>Déconnexion réussie</h3>Vous vous êtes déconnecté(e) du Service Central "
 | 
			
		||||
"d'Authentification. Pour des raisons de sécurité, veuillez fermer votre "
 | 
			
		||||
"navigateur après avoir fini d'accéder a des services demandant une "
 | 
			
		||||
"authentification !"
 | 
			
		||||
 | 
			
		||||
#: templates/cas_server/logged.html:8
 | 
			
		||||
msgid "Log me out from all my sessions"
 | 
			
		||||
msgstr "Me déconnecter de toutes mes sessions"
 | 
			
		||||
 | 
			
		||||
#: templates/cas_server/logged.html:13
 | 
			
		||||
#: templates/cas_server/logged.html:14
 | 
			
		||||
msgid "Forget the identity provider"
 | 
			
		||||
msgstr "Oublier le fournisseur d'identité"
 | 
			
		||||
 | 
			
		||||
#: templates/cas_server/logged.html:18
 | 
			
		||||
msgid "Logout"
 | 
			
		||||
msgstr "Se déconnecter"
 | 
			
		||||
 | 
			
		||||
#: templates/cas_server/login.html:8
 | 
			
		||||
#: templates/cas_server/login.html:6
 | 
			
		||||
msgid "Please log in"
 | 
			
		||||
msgstr "Merci de se connecter"
 | 
			
		||||
 | 
			
		||||
#: templates/cas_server/login.html:13
 | 
			
		||||
#: templates/cas_server/login.html:14
 | 
			
		||||
msgid "Login"
 | 
			
		||||
msgstr "Connexion"
 | 
			
		||||
 | 
			
		||||
#: templates/cas_server/warn.html:10
 | 
			
		||||
#: templates/cas_server/warn.html:9
 | 
			
		||||
msgid "Connect to the service"
 | 
			
		||||
msgstr "Se connecter au service"
 | 
			
		||||
 | 
			
		||||
#: views.py:152
 | 
			
		||||
#: views.py:168
 | 
			
		||||
msgid ""
 | 
			
		||||
"<h3>Logout successful</h3>You have successfully logged out from the Central "
 | 
			
		||||
"Authentication Service. For security reasons, exit your web browser."
 | 
			
		||||
@@ -313,7 +329,7 @@ msgstr ""
 | 
			
		||||
"d'Authentification. Pour des raisons de sécurité, veuillez fermer votre "
 | 
			
		||||
"navigateur."
 | 
			
		||||
 | 
			
		||||
#: views.py:158
 | 
			
		||||
#: views.py:174
 | 
			
		||||
#, python-format
 | 
			
		||||
msgid ""
 | 
			
		||||
"<h3>Logout successful</h3>You have successfully logged out from %s sessions "
 | 
			
		||||
@@ -324,7 +340,7 @@ msgstr ""
 | 
			
		||||
"Service Central d'Authentification. Pour des raisons de sécurité, veuillez "
 | 
			
		||||
"fermer votre navigateur."
 | 
			
		||||
 | 
			
		||||
#: views.py:165
 | 
			
		||||
#: views.py:181
 | 
			
		||||
msgid ""
 | 
			
		||||
"<h3>Logout successful</h3>You were already logged out from the Central "
 | 
			
		||||
"Authentication Service. For security reasons, exit your web browser."
 | 
			
		||||
@@ -333,50 +349,75 @@ msgstr ""
 | 
			
		||||
"d'Authentification. Pour des raisons de sécurité, veuillez fermer votre "
 | 
			
		||||
"navigateur."
 | 
			
		||||
 | 
			
		||||
#: views.py:349
 | 
			
		||||
msgid "Invalid login ticket"
 | 
			
		||||
#: views.py:361
 | 
			
		||||
#, python-format
 | 
			
		||||
msgid ""
 | 
			
		||||
"Invalid response from your identity provider CAS upon ticket %(ticket)s "
 | 
			
		||||
"validation: %(error)r"
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Réponse invalide du CAS du fournisseur d'identité lors de la validation du "
 | 
			
		||||
"ticket %(ticket)s: %(error)r"
 | 
			
		||||
 | 
			
		||||
#: views.py:483
 | 
			
		||||
msgid "Invalid login ticket, please retry to login"
 | 
			
		||||
msgstr "Ticket de connexion invalide, merci de réessayé de vous connecter"
 | 
			
		||||
 | 
			
		||||
#: views.py:470
 | 
			
		||||
#: views.py:675
 | 
			
		||||
#, python-format
 | 
			
		||||
msgid "Authentication has been required by service %(name)s (%(url)s)"
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Une demande d'authentification a été émise pour le service %(name)s "
 | 
			
		||||
"(%(url)s)."
 | 
			
		||||
 | 
			
		||||
#: views.py:508
 | 
			
		||||
#: views.py:713
 | 
			
		||||
#, python-format
 | 
			
		||||
msgid "Service %(url)s non allowed."
 | 
			
		||||
msgstr "le service %(url)s n'est pas autorisé."
 | 
			
		||||
 | 
			
		||||
#: views.py:515
 | 
			
		||||
#: views.py:720
 | 
			
		||||
msgid "Username non allowed"
 | 
			
		||||
msgstr "Nom d'utilisateur non authorisé"
 | 
			
		||||
 | 
			
		||||
#: views.py:522
 | 
			
		||||
msgid "User charateristics non allowed"
 | 
			
		||||
#: views.py:727
 | 
			
		||||
msgid "User characteristics non allowed"
 | 
			
		||||
msgstr "Caractéristique utilisateur non autorisée"
 | 
			
		||||
 | 
			
		||||
#: views.py:529
 | 
			
		||||
#: views.py:734
 | 
			
		||||
#, python-format
 | 
			
		||||
msgid "The attribut %(field)s is needed to use that service"
 | 
			
		||||
msgid "The attribute %(field)s is needed to use that service"
 | 
			
		||||
msgstr "L'attribut %(field)s est nécessaire pour se connecter à ce service"
 | 
			
		||||
 | 
			
		||||
#: views.py:599
 | 
			
		||||
#: views.py:824
 | 
			
		||||
#, python-format
 | 
			
		||||
msgid "Authentication renewal required by service %(name)s (%(url)s)."
 | 
			
		||||
msgstr "Demande de réauthentification pour le service %(name)s (%(url)s)."
 | 
			
		||||
 | 
			
		||||
#: views.py:606
 | 
			
		||||
#: views.py:831
 | 
			
		||||
#, python-format
 | 
			
		||||
msgid "Authentication required by service %(name)s (%(url)s)."
 | 
			
		||||
msgstr "Authentification requise par le service %(name)s (%(url)s)."
 | 
			
		||||
 | 
			
		||||
#: views.py:613
 | 
			
		||||
#: views.py:838
 | 
			
		||||
#, python-format
 | 
			
		||||
msgid "Service %s non allowed"
 | 
			
		||||
msgstr "Le service %s n'est pas autorisé"
 | 
			
		||||
 | 
			
		||||
#~ msgid "Logged"
 | 
			
		||||
#~ msgstr ""
 | 
			
		||||
#~ "<h3>Connexion réussie</h3>Vous vous êtes authentifié(e) auprès du Service "
 | 
			
		||||
#~ "Central d'Authentification.<br/>Pour des raisons de sécurité, veuillez "
 | 
			
		||||
#~ "vous déconnecter et fermer votre navigateur lorsque vous avez fini "
 | 
			
		||||
#~ "d'accéder aux services authentifiés."
 | 
			
		||||
 | 
			
		||||
#~ msgid "warn"
 | 
			
		||||
#~ msgstr "Prévenez-moi avant d'accéder à d'autres services."
 | 
			
		||||
 | 
			
		||||
#~ msgid "login"
 | 
			
		||||
#~ msgstr "Identifiant"
 | 
			
		||||
 | 
			
		||||
#~ msgid "Bad user"
 | 
			
		||||
#~ msgstr "Les informations transmises n'ont pas permis de vous authentifier."
 | 
			
		||||
 | 
			
		||||
#~ msgid ""
 | 
			
		||||
#~ "Error during service logout %(service)s:\n"
 | 
			
		||||
#~ "%(error)s"
 | 
			
		||||
 
 | 
			
		||||
@@ -23,3 +23,4 @@ class Command(BaseCommand):
 | 
			
		||||
 | 
			
		||||
    def handle(self, *args, **options):
 | 
			
		||||
        models.User.clean_deleted_sessions()
 | 
			
		||||
        models.NewVersionWarning.send_mails()
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										22
									
								
								cas_server/migrations/0008_newversionwarning.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								cas_server/migrations/0008_newversionwarning.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
# Generated by Django 1.9.7 on 2016-07-27 21:59
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('cas_server', '0007_auto_20160723_2252'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='NewVersionWarning',
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 | 
			
		||||
                ('version', models.CharField(max_length=255)),
 | 
			
		||||
            ],
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@@ -18,15 +18,18 @@ from django.contrib import messages
 | 
			
		||||
from django.utils.translation import ugettext_lazy as _
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from django.utils.encoding import python_2_unicode_compatible
 | 
			
		||||
from django.core.mail import send_mail
 | 
			
		||||
 | 
			
		||||
import re
 | 
			
		||||
import sys
 | 
			
		||||
import smtplib
 | 
			
		||||
import logging
 | 
			
		||||
from datetime import timedelta
 | 
			
		||||
from concurrent.futures import ThreadPoolExecutor
 | 
			
		||||
from requests_futures.sessions import FuturesSession
 | 
			
		||||
 | 
			
		||||
import cas_server.utils as utils
 | 
			
		||||
from . import VERSION
 | 
			
		||||
 | 
			
		||||
#: logger facility
 | 
			
		||||
logger = logging.getLogger(__name__)
 | 
			
		||||
@@ -465,13 +468,13 @@ class ServicePattern(models.Model):
 | 
			
		||||
            "As it is a regular expression, special character must be escaped with a '\\'."
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
    #: Name of the attribut to transmit as username, if empty the user login is used
 | 
			
		||||
    #: Name of the attribute to transmit as username, if empty the user login is used
 | 
			
		||||
    user_field = models.CharField(
 | 
			
		||||
        max_length=255,
 | 
			
		||||
        default="",
 | 
			
		||||
        blank=True,
 | 
			
		||||
        verbose_name=_(u"user field"),
 | 
			
		||||
        help_text=_("Name of the attribut to transmit as username, empty = login")
 | 
			
		||||
        help_text=_("Name of the attribute to transmit as username, empty = login")
 | 
			
		||||
    )
 | 
			
		||||
    #: A boolean allowing to limit username allowed to connect to :attr:`usernames`.
 | 
			
		||||
    restrict_users = models.BooleanField(
 | 
			
		||||
@@ -621,7 +624,7 @@ class ReplaceAttributName(models.Model):
 | 
			
		||||
    name = models.CharField(
 | 
			
		||||
        max_length=255,
 | 
			
		||||
        verbose_name=_(u"name"),
 | 
			
		||||
        help_text=_(u"name of an attribut to send to the service, use * for all attributes")
 | 
			
		||||
        help_text=_(u"name of an attribute to send to the service, use * for all attributes")
 | 
			
		||||
    )
 | 
			
		||||
    #: The name of the attribute to transmit to the service. If empty, the value of :attr:`name`
 | 
			
		||||
    #: is used.
 | 
			
		||||
@@ -629,7 +632,7 @@ class ReplaceAttributName(models.Model):
 | 
			
		||||
        max_length=255,
 | 
			
		||||
        blank=True,
 | 
			
		||||
        verbose_name=_(u"replace"),
 | 
			
		||||
        help_text=_(u"name under which the attribut will be show"
 | 
			
		||||
        help_text=_(u"name under which the attribute will be show"
 | 
			
		||||
                    u"to the service. empty = default name of the attribut")
 | 
			
		||||
    )
 | 
			
		||||
    #: ForeignKey to a :class:`ServicePattern`. :class:`ReplaceAttributName` instances for a
 | 
			
		||||
@@ -656,8 +659,8 @@ class FilterAttributValue(models.Model):
 | 
			
		||||
    #: The name of a user attribute
 | 
			
		||||
    attribut = models.CharField(
 | 
			
		||||
        max_length=255,
 | 
			
		||||
        verbose_name=_(u"attribut"),
 | 
			
		||||
        help_text=_(u"Name of the attribut which must verify pattern")
 | 
			
		||||
        verbose_name=_(u"attribute"),
 | 
			
		||||
        help_text=_(u"Name of the attribute which must verify pattern")
 | 
			
		||||
    )
 | 
			
		||||
    #: A regular expression the attribute :attr:`attribut` value must verify. If :attr:`attribut`
 | 
			
		||||
    #: if a list, only one of the list values needs to match.
 | 
			
		||||
@@ -686,8 +689,8 @@ class ReplaceAttributValue(models.Model):
 | 
			
		||||
    #: Name the attribute: a key of :attr:`User.attributs`
 | 
			
		||||
    attribut = models.CharField(
 | 
			
		||||
        max_length=255,
 | 
			
		||||
        verbose_name=_(u"attribut"),
 | 
			
		||||
        help_text=_(u"Name of the attribut for which the value must be replace")
 | 
			
		||||
        verbose_name=_(u"attribute"),
 | 
			
		||||
        help_text=_(u"Name of the attribute for which the value must be replace")
 | 
			
		||||
    )
 | 
			
		||||
    #: A regular expression matching the part of the attribute value that need to be changed
 | 
			
		||||
    pattern = models.CharField(
 | 
			
		||||
@@ -1003,3 +1006,60 @@ class Proxy(models.Model):
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return self.url
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NewVersionWarning(models.Model):
 | 
			
		||||
    """
 | 
			
		||||
        Bases: :class:`django.db.models.Model`
 | 
			
		||||
 | 
			
		||||
        The last new version available version sent
 | 
			
		||||
    """
 | 
			
		||||
    version = models.CharField(max_length=255)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def send_mails(cls):
 | 
			
		||||
        """
 | 
			
		||||
            For each new django-cas-server version, if the current instance is not up to date
 | 
			
		||||
            send one mail to ``settings.ADMINS``.
 | 
			
		||||
        """
 | 
			
		||||
        if settings.CAS_NEW_VERSION_EMAIL_WARNING and settings.ADMINS:
 | 
			
		||||
            try:
 | 
			
		||||
                obj = cls.objects.get()
 | 
			
		||||
            except cls.DoesNotExist:
 | 
			
		||||
                obj = NewVersionWarning.objects.create(version=VERSION)
 | 
			
		||||
            LAST_VERSION = utils.last_version()
 | 
			
		||||
            if LAST_VERSION is not None and LAST_VERSION != obj.version:
 | 
			
		||||
                if utils.decode_version(VERSION) < utils.decode_version(LAST_VERSION):
 | 
			
		||||
                    try:
 | 
			
		||||
                        send_mail(
 | 
			
		||||
                            (
 | 
			
		||||
                                '%sA new version of django-cas-server is available'
 | 
			
		||||
                            ) % settings.EMAIL_SUBJECT_PREFIX,
 | 
			
		||||
                            u'''
 | 
			
		||||
A new version of the django-cas-server is available.
 | 
			
		||||
 | 
			
		||||
Your version: %s
 | 
			
		||||
New version: %s
 | 
			
		||||
 | 
			
		||||
Upgrade using:
 | 
			
		||||
    * pip install -U django-cas-server
 | 
			
		||||
    * fetching the last release on
 | 
			
		||||
      https://github.com/nitmir/django-cas-server/ or on
 | 
			
		||||
      https://pypi.python.org/pypi/django-cas-server
 | 
			
		||||
 | 
			
		||||
After upgrade, do not forget to run:
 | 
			
		||||
    * ./manage.py migrate
 | 
			
		||||
    * ./manage.py collectstatic
 | 
			
		||||
and to reload your wsgi server (apache2, uwsgi, gunicord, etc…)
 | 
			
		||||
 | 
			
		||||
--\u0020
 | 
			
		||||
django-cas-server
 | 
			
		||||
'''.strip() % (VERSION, LAST_VERSION),
 | 
			
		||||
                            settings.SERVER_EMAIL,
 | 
			
		||||
                            ["%s <%s>" % admin for admin in settings.ADMINS],
 | 
			
		||||
                            fail_silently=False,
 | 
			
		||||
                        )
 | 
			
		||||
                        obj.version = LAST_VERSION
 | 
			
		||||
                        obj.save()
 | 
			
		||||
                    except smtplib.SMTPException as error:  # pragma: no cover (should not happen)
 | 
			
		||||
                        logger.error("Unable to send new version mail: %s" % error)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								cas_server/static/cas_server/alert-version.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								cas_server/static/cas_server/alert-version.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
function alert_version(last_version){
 | 
			
		||||
    jQuery(function( $ ){
 | 
			
		||||
        $("#alert-version").click(function( e ){
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
            var date = new Date();
 | 
			
		||||
            date.setTime(date.getTime()+(10*365*24*60*60*1000));
 | 
			
		||||
            var expires = "; expires="+date.toGMTString();
 | 
			
		||||
            document.cookie = "cas-alert-version=" + last_version + expires + "; path=/";
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        var nameEQ="cas-alert-version=";
 | 
			
		||||
        var ca = document.cookie.split(";");
 | 
			
		||||
        var value;
 | 
			
		||||
        for(var i=0;i < ca.length;i++) {
 | 
			
		||||
            var c = ca[i];
 | 
			
		||||
            while(c.charAt(0) === " "){
 | 
			
		||||
                c = c.substring(1,c.length);
 | 
			
		||||
            }
 | 
			
		||||
            if(c.indexOf(nameEQ) === 0){
 | 
			
		||||
                value = c.substring(nameEQ.length,c.length);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if(value === last_version){
 | 
			
		||||
            $("#alert-version").parent().hide();
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
@@ -14,8 +14,8 @@
 | 
			
		||||
        <script src="{{settings.CAS_COMPONENT_URLS.html5shiv}}"></script>
 | 
			
		||||
        <script src="{{settings.CAS_COMPONENT_URLS.respond}}"></script>
 | 
			
		||||
        <![endif]-->
 | 
			
		||||
        <link rel="shortcut icon" href="{% static "cas_server/favicon.ico?v=1" %}" />
 | 
			
		||||
        <link href="{% static "cas_server/login.css" %}" rel="stylesheet">
 | 
			
		||||
        {% if settings.CAS_FAVICON_URL %}<link rel="shortcut icon" href="{{settings.CAS_FAVICON_URL}}" />{% endif %}
 | 
			
		||||
        <link href="{% static "cas_server/styles.css" %}" rel="stylesheet">
 | 
			
		||||
    </head>
 | 
			
		||||
    <body>
 | 
			
		||||
        <div class="container">
 | 
			
		||||
@@ -31,23 +31,28 @@
 | 
			
		||||
            <div class="row">
 | 
			
		||||
            <div class="col-lg-3 col-md-3 col-sm-2 col-xs-12"></div>
 | 
			
		||||
            <div class="col-lg-6 col-md-6 col-sm-8 col-xs-12">
 | 
			
		||||
            {% block ante_messages %}{% endblock %}
 | 
			
		||||
            {% if auto_submit %}<noscript>{% endif %}
 | 
			
		||||
            {% if settings.CAS_NEW_VERSION_HTML_WARNING and upgrade_available %}
 | 
			
		||||
              <div class="alert alert-info alert-dismissable">
 | 
			
		||||
                <button type="button" class="close" data-dismiss="alert" aria-hidden="true" id="alert-version">×</button>
 | 
			
		||||
                {% blocktrans %}A new version of the application is available. This instance runs {{VERSION}} and the last version is {{LAST_VERSION}}. Please consider upgrading.{% endblocktrans %}
 | 
			
		||||
              </div>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
            {% block ante_messages %}{% endblock %}
 | 
			
		||||
            {% for message in messages %}
 | 
			
		||||
                <div {% spaceless %}
 | 
			
		||||
                    {% if message.level == message_levels.DEBUG %}
 | 
			
		||||
                        class="alert alert-warning alert-dismissable"
 | 
			
		||||
                        class="alert alert-warning"
 | 
			
		||||
                    {% elif message.level == message_levels.INFO %}
 | 
			
		||||
                        class="alert alert-info alert-dismissable"
 | 
			
		||||
                        class="alert alert-info"
 | 
			
		||||
                    {% elif message.level == message_levels.SUCCESS %}
 | 
			
		||||
                        class="alert alert-success alert-dismissable"
 | 
			
		||||
                        class="alert alert-success"
 | 
			
		||||
                    {% elif message.level == message_levels.WARNING %}
 | 
			
		||||
                        class="alert alert-warning alert-dismissable"
 | 
			
		||||
                        class="alert alert-warning"
 | 
			
		||||
                    {% else %}
 | 
			
		||||
                        class="alert alert-danger alert-dismissable"
 | 
			
		||||
                        class="alert alert-danger"
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                {% endspaceless %}>
 | 
			
		||||
                    <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
 | 
			
		||||
                    {{ message }}
 | 
			
		||||
                </div>
 | 
			
		||||
            {% endfor %}
 | 
			
		||||
@@ -59,5 +64,9 @@
 | 
			
		||||
        </div> <!-- /container -->
 | 
			
		||||
        <script src="{{settings.CAS_COMPONENT_URLS.jquery}}"></script>
 | 
			
		||||
        <script src="{{settings.CAS_COMPONENT_URLS.bootstrap3_js}}"></script>
 | 
			
		||||
        {% if settings.CAS_NEW_VERSION_HTML_WARNING and upgrade_available %}
 | 
			
		||||
        <script src="{% static "cas_server/alert-version.js" %}"></script>
 | 
			
		||||
        <script>alert_version("{{LAST_VERSION}}")</script>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    </body>
 | 
			
		||||
</html>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,11 @@
 | 
			
		||||
{% load cas_server %}
 | 
			
		||||
{% for error in form.non_field_errors %}
 | 
			
		||||
<div class="alert alert-danger alert-dismissable">
 | 
			
		||||
  <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
 | 
			
		||||
  {{error}}
 | 
			
		||||
</div>
 | 
			
		||||
{% endfor %}
 | 
			
		||||
{% for field in form %}{% if field.display %}
 | 
			
		||||
{% for field in form %}{% if not field|is_hidden %}
 | 
			
		||||
<div class="form-group{% spaceless %}
 | 
			
		||||
  {% if not form.non_field_errors %}
 | 
			
		||||
    {% if field.errors %} has-error
 | 
			
		||||
@@ -12,7 +13,7 @@
 | 
			
		||||
    {% endif %}
 | 
			
		||||
  {% endif %}"
 | 
			
		||||
{% endspaceless %}>{% spaceless %}
 | 
			
		||||
  {% if field.checkbox %}
 | 
			
		||||
  {% if field|is_checkbox %}
 | 
			
		||||
    <div class="checkbox"><label for="{{field.auto_id}}">{{field}}{{field.label}}</label>
 | 
			
		||||
  {% else %}
 | 
			
		||||
    <label class="control-label" for="{{field.auto_id}}">{{field.label}}</label>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,20 @@
 | 
			
		||||
{% extends "cas_server/base.html" %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="alert alert-success" role="alert">{% trans "Logged" %}</div>
 | 
			
		||||
<div class="alert alert-success" role="alert">{% blocktrans %}<h3>Log In Successful</h3>You have successfully logged into the Central Authentication Service.<br/>For security reasons, please Log Out and Exit your web browser when you are done accessing services that require authentication!{% endblocktrans %}</div>
 | 
			
		||||
<form class="form-signin" method="get" action="logout">
 | 
			
		||||
  <div class="checkbox">
 | 
			
		||||
    <label>
 | 
			
		||||
      <input type="checkbox" name="all" value="1"> {% trans "Log me out from all my sessions" %}
 | 
			
		||||
      <input type="checkbox" name="all" value="1">{% trans "Log me out from all my sessions" %}
 | 
			
		||||
    </label>
 | 
			
		||||
  </div>
 | 
			
		||||
  {% if settings.CAS_FEDERATE and request.COOKIES.remember_provider %}
 | 
			
		||||
  <div class="checkbox">
 | 
			
		||||
    <label>
 | 
			
		||||
      <input type="checkbox" name="forget_provider" value="1">{% trans "Forget the identity provider" %}
 | 
			
		||||
    </label>
 | 
			
		||||
  </div>
 | 
			
		||||
  {% endif %}
 | 
			
		||||
  <button class="btn btn-danger btn-block btn-lg" type="submit">{% trans "Logout" %}</button>
 | 
			
		||||
</form>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										0
									
								
								cas_server/templatetags/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								cas_server/templatetags/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										14
									
								
								cas_server/templatetags/cas_server.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								cas_server/templatetags/cas_server.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
from django import template
 | 
			
		||||
from django import forms
 | 
			
		||||
 | 
			
		||||
register = template.Library()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.filter(name='is_checkbox')
 | 
			
		||||
def is_checkbox(field):
 | 
			
		||||
    return isinstance(field.field.widget, forms.CheckboxInput)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.filter(name='is_hidden')
 | 
			
		||||
def is_hidden(field):
 | 
			
		||||
    return isinstance(field.field.widget, forms.HiddenInput)
 | 
			
		||||
@@ -51,6 +51,22 @@ MIDDLEWARE_CLASSES = [
 | 
			
		||||
    'django.middleware.locale.LocaleMiddleware',
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
TEMPLATES = [
 | 
			
		||||
    {
 | 
			
		||||
        'APP_DIRS': True,
 | 
			
		||||
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
 | 
			
		||||
        'DIRS': [],
 | 
			
		||||
        'OPTIONS': {
 | 
			
		||||
            'context_processors': [
 | 
			
		||||
                'django.template.context_processors.debug',
 | 
			
		||||
                'django.template.context_processors.request',
 | 
			
		||||
                'django.contrib.auth.context_processors.auth',
 | 
			
		||||
                'django.contrib.messages.context_processors.messages'
 | 
			
		||||
            ]
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
ROOT_URLCONF = 'cas_server.tests.urls'
 | 
			
		||||
 | 
			
		||||
# Database
 | 
			
		||||
@@ -81,3 +97,30 @@ USE_TZ = True
 | 
			
		||||
# https://docs.djangoproject.com/en/1.9/howto/static-files/
 | 
			
		||||
 | 
			
		||||
STATIC_URL = '/static/'
 | 
			
		||||
 | 
			
		||||
CAS_NEW_VERSION_HTML_WARNING = False
 | 
			
		||||
CAS_NEW_VERSION_EMAIL_WARNING = False
 | 
			
		||||
 | 
			
		||||
LOGGING = {
 | 
			
		||||
    'version': 1,
 | 
			
		||||
    'disable_existing_loggers': False,
 | 
			
		||||
    'formatters': {
 | 
			
		||||
        'cas_file': {
 | 
			
		||||
            'format': '%(asctime)s %(levelname)s %(message)s'
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    'handlers': {
 | 
			
		||||
        'cas_stream': {
 | 
			
		||||
            'level': 'INFO',
 | 
			
		||||
            'class': 'logging.StreamHandler',
 | 
			
		||||
            'formatter': 'cas_file',
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    'loggers': {
 | 
			
		||||
        'cas_server': {
 | 
			
		||||
            'handlers': ['cas_stream'],
 | 
			
		||||
            'level': 'INFO',
 | 
			
		||||
            'propagate': True,
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -84,29 +84,31 @@ class FederateAuthLoginLogoutTestCase(
 | 
			
		||||
            params['provider'] = provider.suffix
 | 
			
		||||
            if remember:
 | 
			
		||||
                params['remember'] = 'on'
 | 
			
		||||
            # just try for one suffix
 | 
			
		||||
            if suffix == "example.com":
 | 
			
		||||
                # if renew=False is posted it should be ignored
 | 
			
		||||
                params["renew"] = False
 | 
			
		||||
            # post the choosed provider
 | 
			
		||||
            response = client.post('/federate', params)
 | 
			
		||||
            # we are redirected to the provider CAS client url
 | 
			
		||||
            self.assertEqual(response.status_code, 302)
 | 
			
		||||
            if remember:
 | 
			
		||||
                self.assertEqual(response["Location"], '%s/federate/%s?remember=on' % (
 | 
			
		||||
                    'http://testserver' if django.VERSION < (1, 9) else "",
 | 
			
		||||
                    provider.suffix
 | 
			
		||||
                ))
 | 
			
		||||
            else:
 | 
			
		||||
                self.assertEqual(response["Location"], '%s/federate/%s' % (
 | 
			
		||||
                    'http://testserver' if django.VERSION < (1, 9) else "",
 | 
			
		||||
                    provider.suffix
 | 
			
		||||
                ))
 | 
			
		||||
            self.assertEqual(response["Location"], '%s/federate/%s%s' % (
 | 
			
		||||
                'http://testserver' if django.VERSION < (1, 9) else "",
 | 
			
		||||
                provider.suffix,
 | 
			
		||||
                "?remember=on" if remember else ""
 | 
			
		||||
            ))
 | 
			
		||||
            # let's follow the redirect
 | 
			
		||||
            response = client.get('/federate/%s' % provider.suffix)
 | 
			
		||||
            response = client.get(
 | 
			
		||||
                '/federate/%s%s' % (provider.suffix, "?remember=on" if remember else "")
 | 
			
		||||
            )
 | 
			
		||||
            # we are redirected to the provider CAS for authentication
 | 
			
		||||
            self.assertEqual(response.status_code, 302)
 | 
			
		||||
            self.assertEqual(
 | 
			
		||||
                response["Location"],
 | 
			
		||||
                "%s/login?service=http%%3A%%2F%%2Ftestserver%%2Ffederate%%2F%s" % (
 | 
			
		||||
                "%s/login?service=http%%3A%%2F%%2Ftestserver%%2Ffederate%%2F%s%s" % (
 | 
			
		||||
                    provider.server_url,
 | 
			
		||||
                    provider.suffix
 | 
			
		||||
                    provider.suffix,
 | 
			
		||||
                    "%3Fremember%3Don" if remember else ""
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
            # let's generate a ticket
 | 
			
		||||
@@ -114,7 +116,10 @@ class FederateAuthLoginLogoutTestCase(
 | 
			
		||||
            # we lauch a dummy CAS server that only validate once for the service
 | 
			
		||||
            # http://testserver/federate/example.com with `ticket`
 | 
			
		||||
            tests_utils.DummyCAS.run(
 | 
			
		||||
                ("http://testserver/federate/%s" % provider.suffix).encode("ascii"),
 | 
			
		||||
                ("http://testserver/federate/%s%s" % (
 | 
			
		||||
                    provider.suffix,
 | 
			
		||||
                    "?remember=on" if remember else ""
 | 
			
		||||
                )).encode("ascii"),
 | 
			
		||||
                ticket.encode("ascii"),
 | 
			
		||||
                settings.CAS_TEST_USER.encode("utf8"),
 | 
			
		||||
                [],
 | 
			
		||||
@@ -122,7 +127,13 @@ class FederateAuthLoginLogoutTestCase(
 | 
			
		||||
            )
 | 
			
		||||
            # we normally provide a good ticket and should be redirected to /login as the ticket
 | 
			
		||||
            # get successfully validated again the dummy CAS
 | 
			
		||||
            response = client.get('/federate/%s' % provider.suffix, {'ticket': ticket})
 | 
			
		||||
            response = client.get(
 | 
			
		||||
                '/federate/%s' % provider.suffix,
 | 
			
		||||
                {'ticket': ticket, 'remember': 'on' if remember else ''}
 | 
			
		||||
            )
 | 
			
		||||
            if remember:
 | 
			
		||||
                self.assertIn("remember_provider", client.cookies)
 | 
			
		||||
                self.assertEqual(client.cookies["remember_provider"].value, provider.suffix)
 | 
			
		||||
            self.assertEqual(response.status_code, 302)
 | 
			
		||||
            self.assertEqual(response["Location"], "%s/login" % (
 | 
			
		||||
                'http://testserver' if django.VERSION < (1, 9) else ""
 | 
			
		||||
@@ -183,7 +194,8 @@ class FederateAuthLoginLogoutTestCase(
 | 
			
		||||
        """
 | 
			
		||||
            The federated view should redirect to /login if the provider is unknown or not provided,
 | 
			
		||||
            try to fetch a new ticket if the provided ticket validation fail
 | 
			
		||||
            (network error or bad ticket)
 | 
			
		||||
            (network error or bad ticket), redirect to /login with a error message if identity
 | 
			
		||||
            provider CAS return a bad response (invalid XML document)
 | 
			
		||||
        """
 | 
			
		||||
        good_provider = "example.com"
 | 
			
		||||
        bad_provider = "exemple.fr"
 | 
			
		||||
@@ -229,6 +241,18 @@ class FederateAuthLoginLogoutTestCase(
 | 
			
		||||
            'http://testserver' if django.VERSION < (1, 9) else ""
 | 
			
		||||
        ))
 | 
			
		||||
 | 
			
		||||
        # test CAS avaible but return a bad XML doc, should redirect to /login with a error message
 | 
			
		||||
        # use "example.net" as it is CASv3
 | 
			
		||||
        tests_utils.HttpParamsHandler.run(8082)
 | 
			
		||||
        response = client.get("/federate/%s" % "example.net", {'ticket': utils.gen_st()})
 | 
			
		||||
        self.assertEqual(response.status_code, 302)
 | 
			
		||||
        self.assertEqual(response["Location"], "%s/login" % (
 | 
			
		||||
            'http://testserver' if django.VERSION < (1, 9) else ""
 | 
			
		||||
        ))
 | 
			
		||||
        response = client.get("/login")
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
        self.assertIn(b"Invalid response from your identity provider CAS", response.content)
 | 
			
		||||
 | 
			
		||||
    def test_auth_federate_slo(self):
 | 
			
		||||
        """test that SLO receive from backend CAS log out the users"""
 | 
			
		||||
        # get tickets and connected clients
 | 
			
		||||
@@ -331,6 +355,76 @@ class FederateAuthLoginLogoutTestCase(
 | 
			
		||||
                provider.suffix
 | 
			
		||||
            ))
 | 
			
		||||
 | 
			
		||||
    def test_forget_provider(self):
 | 
			
		||||
        """Test the logout option to forget remembered provider"""
 | 
			
		||||
        tickets = self.test_login_post_provider(remember=True)
 | 
			
		||||
        for (provider, _, client) in tickets:
 | 
			
		||||
            self.assertIn("remember_provider", client.cookies)
 | 
			
		||||
            self.assertEqual(client.cookies["remember_provider"].value, provider.suffix)
 | 
			
		||||
            self.assertNotEqual(client.cookies["remember_provider"]["max-age"], 0)
 | 
			
		||||
            client.get("/logout?forget_provider=1")
 | 
			
		||||
            self.assertEqual(client.cookies["remember_provider"]["max-age"], 0)
 | 
			
		||||
 | 
			
		||||
    def test_renew(self):
 | 
			
		||||
        """
 | 
			
		||||
            Test authentication renewal with federation mode
 | 
			
		||||
        """
 | 
			
		||||
        tickets = self.test_login_post_provider()
 | 
			
		||||
        for (provider, _, client) in tickets:
 | 
			
		||||
            # Try to renew authentication(client already authenticated in test_login_post_provider
 | 
			
		||||
            response = client.get("/login?renew=true")
 | 
			
		||||
            # we should be redirected to the user CAS
 | 
			
		||||
            self.assertEqual(response.status_code, 302)
 | 
			
		||||
            self.assertEqual(response["Location"], "%s/federate/%s?renew=true" % (
 | 
			
		||||
                'http://testserver' if django.VERSION < (1, 9) else "",
 | 
			
		||||
                provider.suffix
 | 
			
		||||
            ))
 | 
			
		||||
 | 
			
		||||
            response = client.get("/federate/%s?renew=true" % provider.suffix)
 | 
			
		||||
            self.assertEqual(response.status_code, 302)
 | 
			
		||||
            service_url = (
 | 
			
		||||
                "service=http%%3A%%2F%%2Ftestserver%%2Ffederate%%2F%s%%3Frenew%%3Dtrue"
 | 
			
		||||
            ) % provider.suffix
 | 
			
		||||
            self.assertIn(service_url, response["Location"])
 | 
			
		||||
            self.assertIn("renew=true", response["Location"])
 | 
			
		||||
 | 
			
		||||
            cas_port = int(provider.server_url.split(':')[-1])
 | 
			
		||||
            # let's generate a ticket
 | 
			
		||||
            ticket = utils.gen_st()
 | 
			
		||||
            # we lauch a dummy CAS server that only validate once for the service
 | 
			
		||||
            # http://testserver/federate/example.com?renew=true with `ticket`
 | 
			
		||||
            tests_utils.DummyCAS.run(
 | 
			
		||||
                ("http://testserver/federate/%s?renew=true" % provider.suffix).encode("ascii"),
 | 
			
		||||
                ticket.encode("ascii"),
 | 
			
		||||
                settings.CAS_TEST_USER.encode("utf8"),
 | 
			
		||||
                [],
 | 
			
		||||
                cas_port
 | 
			
		||||
            )
 | 
			
		||||
            # we normally provide a good ticket and should be redirected to /login as the ticket
 | 
			
		||||
            # get successfully validated again the dummy CAS
 | 
			
		||||
            response = client.get(
 | 
			
		||||
                '/federate/%s' % provider.suffix,
 | 
			
		||||
                {'ticket': ticket, 'renew': 'true'}
 | 
			
		||||
            )
 | 
			
		||||
            self.assertEqual(response.status_code, 302)
 | 
			
		||||
            self.assertEqual(response["Location"], "%s/login?renew=true" % (
 | 
			
		||||
                'http://testserver' if django.VERSION < (1, 9) else ""
 | 
			
		||||
            ))
 | 
			
		||||
            # follow the redirect and try to get a ticket to see is it has renew set to True
 | 
			
		||||
            response = client.get("/login?renew=true&service=%s" % self.service)
 | 
			
		||||
            # we should get a page with a from with all widget hidden that auto POST to /login using
 | 
			
		||||
            # javascript. If javascript is disabled, a "connect" button is showed
 | 
			
		||||
            self.assertTrue(response.context['auto_submit'])
 | 
			
		||||
            self.assertEqual(response.context['post_url'], '/login')
 | 
			
		||||
            params = tests_utils.copy_form(response.context["form"])
 | 
			
		||||
            # POST get prefiled from parameters
 | 
			
		||||
            response = client.post("/login", params)
 | 
			
		||||
            self.assertEqual(response.status_code, 302)
 | 
			
		||||
            self.assertTrue(response["Location"].startswith("%s?ticket=" % self.service))
 | 
			
		||||
            ticket_value = response["Location"].split('ticket=')[-1]
 | 
			
		||||
            ticket = models.ServiceTicket.objects.get(value=ticket_value)
 | 
			
		||||
            self.assertTrue(ticket.renew)
 | 
			
		||||
 | 
			
		||||
    def test_login_bad_ticket(self):
 | 
			
		||||
        """
 | 
			
		||||
            Try login with a bad ticket:
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,9 @@ import django
 | 
			
		||||
from django.test import TestCase, Client
 | 
			
		||||
from django.test.utils import override_settings
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from django.core import mail
 | 
			
		||||
 | 
			
		||||
import mock
 | 
			
		||||
from datetime import timedelta
 | 
			
		||||
from importlib import import_module
 | 
			
		||||
 | 
			
		||||
@@ -60,6 +62,25 @@ class FederatedUserTestCase(TestCase, UserModels, FederatedIendityProviderModel)
 | 
			
		||||
        with self.assertRaises(models.FederatedUser.DoesNotExist):
 | 
			
		||||
            models.FederatedUser.objects.get(username="test2")
 | 
			
		||||
 | 
			
		||||
    def test_json_attributes(self):
 | 
			
		||||
        """test the json storage of ``atrributs`` in ``_attributs``"""
 | 
			
		||||
        provider = models.FederatedIendityProvider.objects.get(suffix="example.com")
 | 
			
		||||
        user = models.FederatedUser.objects.create(
 | 
			
		||||
            username=settings.CAS_TEST_USER,
 | 
			
		||||
            provider=provider,
 | 
			
		||||
            attributs=settings.CAS_TEST_ATTRIBUTES,
 | 
			
		||||
            ticket=""
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(utils.json_encode(settings.CAS_TEST_ATTRIBUTES), user._attributs)
 | 
			
		||||
        user.delete()
 | 
			
		||||
        user = models.FederatedUser.objects.create(
 | 
			
		||||
            username=settings.CAS_TEST_USER,
 | 
			
		||||
            provider=provider,
 | 
			
		||||
            ticket=""
 | 
			
		||||
        )
 | 
			
		||||
        self.assertIsNone(user._attributs)
 | 
			
		||||
        self.assertIsNone(user.attributs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FederateSLOTestCase(TestCase, UserModels):
 | 
			
		||||
    """test for the federated SLO model"""
 | 
			
		||||
@@ -231,3 +252,65 @@ class TicketTestCase(TestCase, UserModels, BaseServicePattern):
 | 
			
		||||
        self.assertTrue(b'logoutRequest' in params and params[b'logoutRequest'])
 | 
			
		||||
        # only 1 ticket remain in the db
 | 
			
		||||
        self.assertEqual(len(models.ServiceTicket.objects.all()), 1)
 | 
			
		||||
 | 
			
		||||
    def test_json_attributes(self):
 | 
			
		||||
        """test the json storage of ``atrributs`` in ``_attributs``"""
 | 
			
		||||
        # ge an authenticated client
 | 
			
		||||
        client = get_auth_client()
 | 
			
		||||
        # get the user associated to the client
 | 
			
		||||
        user = self.get_user(client)
 | 
			
		||||
        ticket = models.ServiceTicket.objects.create(
 | 
			
		||||
            user=user,
 | 
			
		||||
            service=self.service,
 | 
			
		||||
            attributs=settings.CAS_TEST_ATTRIBUTES,
 | 
			
		||||
            service_pattern=self.service_pattern
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(utils.json_encode(settings.CAS_TEST_ATTRIBUTES), ticket._attributs)
 | 
			
		||||
        ticket.delete()
 | 
			
		||||
        ticket = models.ServiceTicket.objects.create(
 | 
			
		||||
            user=user,
 | 
			
		||||
            service=self.service,
 | 
			
		||||
            service_pattern=self.service_pattern
 | 
			
		||||
        )
 | 
			
		||||
        self.assertIsNone(ticket._attributs)
 | 
			
		||||
        self.assertIsNone(ticket.attributs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@mock.patch("cas_server.utils.last_version", lambda: "1.2.3")
 | 
			
		||||
@override_settings(ADMINS=[("Ano Nymous", "ano.nymous@example.net")])
 | 
			
		||||
@override_settings(CAS_NEW_VERSION_EMAIL_WARNING=True)
 | 
			
		||||
class NewVersionWarningTestCase(TestCase):
 | 
			
		||||
    """tests for the new version warning model"""
 | 
			
		||||
 | 
			
		||||
    @mock.patch("cas_server.models.VERSION", "0.1.2")
 | 
			
		||||
    def test_send_mails(self):
 | 
			
		||||
        """test the send_mails method with ADMINS and a new version available"""
 | 
			
		||||
        models.NewVersionWarning.send_mails()
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(len(mail.outbox), 1)
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            mail.outbox[0].subject,
 | 
			
		||||
            '%sA new version of django-cas-server is available' % settings.EMAIL_SUBJECT_PREFIX
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        models.NewVersionWarning.send_mails()
 | 
			
		||||
        self.assertEqual(len(mail.outbox), 1)
 | 
			
		||||
 | 
			
		||||
    @mock.patch("cas_server.models.VERSION", "1.2.3")
 | 
			
		||||
    def test_send_mails_same_version(self):
 | 
			
		||||
        """test the send_mails method with with current version being the last"""
 | 
			
		||||
        models.NewVersionWarning.objects.create(version="0.1.2")
 | 
			
		||||
        models.NewVersionWarning.send_mails()
 | 
			
		||||
        self.assertEqual(len(mail.outbox), 0)
 | 
			
		||||
 | 
			
		||||
    @override_settings(ADMINS=[])
 | 
			
		||||
    def test_send_mails_no_admins(self):
 | 
			
		||||
        """test the send_mails method without ADMINS"""
 | 
			
		||||
        models.NewVersionWarning.send_mails()
 | 
			
		||||
        self.assertEqual(len(mail.outbox), 0)
 | 
			
		||||
 | 
			
		||||
    @override_settings(CAS_NEW_VERSION_EMAIL_WARNING=False)
 | 
			
		||||
    def test_send_mails_disabled(self):
 | 
			
		||||
        """test the send_mails method if disabled"""
 | 
			
		||||
        models.NewVersionWarning.send_mails()
 | 
			
		||||
        self.assertEqual(len(mail.outbox), 0)
 | 
			
		||||
 
 | 
			
		||||
@@ -11,8 +11,11 @@
 | 
			
		||||
# (c) 2016 Valentin Samir
 | 
			
		||||
"""Tests module for utils"""
 | 
			
		||||
from django.test import TestCase, RequestFactory
 | 
			
		||||
from django.db import connection
 | 
			
		||||
 | 
			
		||||
import six
 | 
			
		||||
import warnings
 | 
			
		||||
import datetime
 | 
			
		||||
 | 
			
		||||
from cas_server import utils
 | 
			
		||||
 | 
			
		||||
@@ -128,16 +131,23 @@ class CheckPasswordCase(TestCase):
 | 
			
		||||
        with self.assertRaises(utils.LdapHashUserPassword.BadHash):
 | 
			
		||||
            utils.check_password("ldap", self.password1, b"TOTOssdsdsd", "utf8")
 | 
			
		||||
        for scheme in schemes_salt:
 | 
			
		||||
            # bad length
 | 
			
		||||
            with self.assertRaises(utils.LdapHashUserPassword.BadHash):
 | 
			
		||||
                utils.check_password("ldap", self.password1, scheme + b"dG90b3E8ZHNkcw==", "utf8")
 | 
			
		||||
            # bad base64
 | 
			
		||||
            with self.assertRaises(utils.LdapHashUserPassword.BadHash):
 | 
			
		||||
                utils.check_password("ldap", self.password1, scheme + b"dG90b3E8ZHNkcw", "utf8")
 | 
			
		||||
 | 
			
		||||
    def test_hex(self):
 | 
			
		||||
        """test all the hex_HASH method: the hashed password is a simple hash of the password"""
 | 
			
		||||
        hashes = ["md5", "sha1", "sha224", "sha256", "sha384", "sha512"]
 | 
			
		||||
        hashed_password1 = []
 | 
			
		||||
        for hash in hashes:
 | 
			
		||||
        for hash_scheme in hashes:
 | 
			
		||||
            hashed_password1.append(
 | 
			
		||||
                ("hex_%s" % hash, getattr(utils.hashlib, hash)(self.password1).hexdigest())
 | 
			
		||||
                (
 | 
			
		||||
                    "hex_%s" % hash_scheme,
 | 
			
		||||
                    getattr(utils.hashlib, hash_scheme)(self.password1).hexdigest()
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
        for (method, hp1) in hashed_password1:
 | 
			
		||||
            self.assertTrue(utils.check_password(method, self.password1, hp1, "utf8"))
 | 
			
		||||
@@ -208,3 +218,40 @@ class UtilsTestCase(TestCase):
 | 
			
		||||
        self.assertEqual(utils.get_tuple(test_tuple, 3), None)
 | 
			
		||||
        self.assertEqual(utils.get_tuple(test_tuple, 3, 'toto'), 'toto')
 | 
			
		||||
        self.assertEqual(utils.get_tuple(None, 3), None)
 | 
			
		||||
 | 
			
		||||
    def test_last_version(self):
 | 
			
		||||
        """
 | 
			
		||||
            test the function last_version. An internet connection is needed, if you do not have
 | 
			
		||||
            one, this test will fail and you should ignore it.
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            # first check if pypi is available
 | 
			
		||||
            utils.requests.get("https://pypi.python.org/simple/django-cas-server/")
 | 
			
		||||
        except utils.requests.exceptions.RequestException:
 | 
			
		||||
            warnings.warn(
 | 
			
		||||
                (
 | 
			
		||||
                    "Pypi seems not available, perhaps you do not have internet access. "
 | 
			
		||||
                    "Consequently, the test cas_server.tests.test_utils.UtilsTestCase.test_last_"
 | 
			
		||||
                    "version is ignored"
 | 
			
		||||
                ),
 | 
			
		||||
                RuntimeWarning
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            version = utils.last_version()
 | 
			
		||||
            self.assertIsInstance(version, six.text_type)
 | 
			
		||||
            self.assertEqual(len(version.split('.')), 3)
 | 
			
		||||
 | 
			
		||||
            # version is cached 24h so calling it a second time should return the save value
 | 
			
		||||
            self.assertEqual(version, utils.last_version())
 | 
			
		||||
 | 
			
		||||
    def test_dictfetchall(self):
 | 
			
		||||
        """test the function dictfetchall"""
 | 
			
		||||
        with connection.cursor() as curs:
 | 
			
		||||
            curs.execute("SELECT * FROM django_migrations")
 | 
			
		||||
            results = utils.dictfetchall(curs)
 | 
			
		||||
            self.assertIsInstance(results, list)
 | 
			
		||||
            self.assertTrue(len(results) > 0)
 | 
			
		||||
            for result in results:
 | 
			
		||||
                self.assertIsInstance(result, dict)
 | 
			
		||||
                self.assertIn('applied', result)
 | 
			
		||||
                self.assertIsInstance(result['applied'], datetime.datetime)
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@ from django.utils import timezone
 | 
			
		||||
 | 
			
		||||
import random
 | 
			
		||||
import json
 | 
			
		||||
import mock
 | 
			
		||||
from lxml import etree
 | 
			
		||||
from six.moves import range
 | 
			
		||||
 | 
			
		||||
@@ -47,6 +48,33 @@ class LoginTestCase(TestCase, BaseServicePattern, CanLogin):
 | 
			
		||||
        # we prepare a bunch a service url and service patterns for tests
 | 
			
		||||
        self.setup_service_patterns()
 | 
			
		||||
 | 
			
		||||
    @override_settings(CAS_NEW_VERSION_HTML_WARNING=True)
 | 
			
		||||
    @mock.patch("cas_server.utils.last_version", lambda: "1.2.3")
 | 
			
		||||
    @mock.patch("cas_server.utils.VERSION", "0.1.2")
 | 
			
		||||
    def test_new_version_available_ok(self):
 | 
			
		||||
        """test the new version info box"""
 | 
			
		||||
        client = Client()
 | 
			
		||||
        response = client.get("/login")
 | 
			
		||||
        self.assertIn(b"A new version of the application is available", response.content)
 | 
			
		||||
 | 
			
		||||
    @override_settings(CAS_NEW_VERSION_HTML_WARNING=True)
 | 
			
		||||
    @mock.patch("cas_server.utils.last_version", lambda: None)
 | 
			
		||||
    @mock.patch("cas_server.utils.VERSION", "0.1.2")
 | 
			
		||||
    def test_new_version_available_badpypi(self):
 | 
			
		||||
        """
 | 
			
		||||
            test the new version info box if pypi is not available (unable to retreive last version)
 | 
			
		||||
        """
 | 
			
		||||
        client = Client()
 | 
			
		||||
        response = client.get("/login")
 | 
			
		||||
        self.assertNotIn(b"A new version of the application is available", response.content)
 | 
			
		||||
 | 
			
		||||
    @override_settings(CAS_NEW_VERSION_HTML_WARNING=False)
 | 
			
		||||
    def test_new_version_available_disabled(self):
 | 
			
		||||
        """test the new version info box is disabled"""
 | 
			
		||||
        client = Client()
 | 
			
		||||
        response = client.get("/login")
 | 
			
		||||
        self.assertNotIn(b"A new version of the application is available", response.content)
 | 
			
		||||
 | 
			
		||||
    def test_login_view_post_goodpass_goodlt(self):
 | 
			
		||||
        """Test a successul login"""
 | 
			
		||||
        # we get a client who fetch a frist time the login page and the login form default
 | 
			
		||||
@@ -309,7 +337,7 @@ class LoginTestCase(TestCase, BaseServicePattern, CanLogin):
 | 
			
		||||
            response = client.get("/login", {'service': service})
 | 
			
		||||
            # the ticket is not created and a warning is displayed to the user
 | 
			
		||||
            self.assertEqual(response.status_code, 200)
 | 
			
		||||
            self.assertTrue(b"User charateristics non allowed" in response.content)
 | 
			
		||||
            self.assertTrue(b"User characteristics non allowed" in response.content)
 | 
			
		||||
 | 
			
		||||
        # same but with rectriction that a valid upon the test user attributes
 | 
			
		||||
        response = client.get("/login", {'service': self.service_filter_success})
 | 
			
		||||
@@ -327,7 +355,7 @@ class LoginTestCase(TestCase, BaseServicePattern, CanLogin):
 | 
			
		||||
        response = client.get("/login", {'service': self.service_field_needed_fail})
 | 
			
		||||
        # the ticket is not created and a warning is displayed to the user
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
        self.assertTrue(b"The attribut uid is needed to use that service" in response.content)
 | 
			
		||||
        self.assertTrue(b"The attribute uid is needed to use that service" in response.content)
 | 
			
		||||
 | 
			
		||||
        # same but with a attribute that the test user has
 | 
			
		||||
        response = client.get("/login", {'service': self.service_field_needed_success})
 | 
			
		||||
@@ -351,7 +379,7 @@ class LoginTestCase(TestCase, BaseServicePattern, CanLogin):
 | 
			
		||||
        response = client.get("/login", {"service": self.service_field_needed_success})
 | 
			
		||||
        # the ticket is not created and a warning is displayed to the user
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
        self.assertTrue(b"The attribut alias is needed to use that service" in response.content)
 | 
			
		||||
        self.assertTrue(b"The attribute alias is needed to use that service" in response.content)
 | 
			
		||||
 | 
			
		||||
    def test_gateway(self):
 | 
			
		||||
        """test gateway parameter"""
 | 
			
		||||
 
 | 
			
		||||
@@ -12,8 +12,9 @@
 | 
			
		||||
"""Some utils functions for tests"""
 | 
			
		||||
from cas_server.default_settings import settings
 | 
			
		||||
 | 
			
		||||
import django
 | 
			
		||||
from django.test import Client
 | 
			
		||||
from django.template import loader, Context
 | 
			
		||||
from django.template import loader
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
 | 
			
		||||
import cgi
 | 
			
		||||
@@ -21,13 +22,25 @@ import six
 | 
			
		||||
from threading import Thread
 | 
			
		||||
from lxml import etree
 | 
			
		||||
from six.moves import BaseHTTPServer
 | 
			
		||||
from six.moves.urllib.parse import urlparse, parse_qsl
 | 
			
		||||
from six.moves.urllib.parse import urlparse, parse_qsl, parse_qs
 | 
			
		||||
from datetime import timedelta
 | 
			
		||||
 | 
			
		||||
from cas_server import models
 | 
			
		||||
from cas_server import utils
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if django.VERSION < (1, 8):
 | 
			
		||||
    from django.template import Context
 | 
			
		||||
else:
 | 
			
		||||
    def Context(arg):
 | 
			
		||||
        """
 | 
			
		||||
            Starting from django 1.8 render take a dict and deprecated the use of a Context.
 | 
			
		||||
            So this is the identity function, only use for compatibility with django 1.7 where
 | 
			
		||||
            render MUST take a Context as argument.
 | 
			
		||||
        """
 | 
			
		||||
        return arg
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def return_unicode(string, charset):
 | 
			
		||||
    """make `string` a unicode if `string` is a unicode or bytes encoded with `charset`"""
 | 
			
		||||
    if not isinstance(string, six.text_type):
 | 
			
		||||
@@ -166,7 +179,7 @@ class HttpParamsHandler(BaseHTTPServer.BaseHTTPRequestHandler):
 | 
			
		||||
            postvars = cgi.parse_multipart(self.rfile, pdict)
 | 
			
		||||
        elif ctype == 'application/x-www-form-urlencoded':
 | 
			
		||||
            length = int(self.headers.get('content-length'))
 | 
			
		||||
            postvars = cgi.parse_qs(self.rfile.read(length), keep_blank_values=1)
 | 
			
		||||
            postvars = parse_qs(self.rfile.read(length), keep_blank_values=1)
 | 
			
		||||
        else:
 | 
			
		||||
            postvars = {}
 | 
			
		||||
        self.server.PARAMS = postvars
 | 
			
		||||
 
 | 
			
		||||
@@ -16,8 +16,10 @@ from django.views.decorators.debug import sensitive_post_parameters, sensitive_v
 | 
			
		||||
 | 
			
		||||
from cas_server import views
 | 
			
		||||
 | 
			
		||||
app_name = "cas_server"
 | 
			
		||||
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    url(r'^$', RedirectView.as_view(pattern_name="cas_server:login")),
 | 
			
		||||
    url(r'^$', RedirectView.as_view(pattern_name="cas_server:login", permanent=False)),
 | 
			
		||||
    url(
 | 
			
		||||
        '^login$',
 | 
			
		||||
        sensitive_post_parameters('password')(
 | 
			
		||||
@@ -51,8 +53,8 @@ urlpatterns = [
 | 
			
		||||
    url('^samlValidate$', views.SamlValidate.as_view(), name='samlValidate'),
 | 
			
		||||
    url(
 | 
			
		||||
        '^auth$',
 | 
			
		||||
        sensitive_variables('password')(
 | 
			
		||||
            sensitive_post_parameters('password')(
 | 
			
		||||
        sensitive_variables('password', 'secret')(
 | 
			
		||||
            sensitive_post_parameters('password', 'secret')(
 | 
			
		||||
                views.Auth.as_view()
 | 
			
		||||
            )
 | 
			
		||||
        ),
 | 
			
		||||
 
 | 
			
		||||
@@ -25,11 +25,20 @@ import hashlib
 | 
			
		||||
import crypt
 | 
			
		||||
import base64
 | 
			
		||||
import six
 | 
			
		||||
import requests
 | 
			
		||||
import time
 | 
			
		||||
import logging
 | 
			
		||||
import binascii
 | 
			
		||||
 | 
			
		||||
from importlib import import_module
 | 
			
		||||
from datetime import datetime, timedelta
 | 
			
		||||
from six.moves.urllib.parse import urlparse, urlunparse, parse_qsl, urlencode
 | 
			
		||||
 | 
			
		||||
from . import VERSION
 | 
			
		||||
 | 
			
		||||
#: logger facility
 | 
			
		||||
logger = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def json_encode(obj):
 | 
			
		||||
    """Encode a python object to json"""
 | 
			
		||||
@@ -51,6 +60,14 @@ def context(params):
 | 
			
		||||
    """
 | 
			
		||||
    params["settings"] = settings
 | 
			
		||||
    params["message_levels"] = DEFAULT_MESSAGE_LEVELS
 | 
			
		||||
    if settings.CAS_NEW_VERSION_HTML_WARNING:
 | 
			
		||||
        LAST_VERSION = last_version()
 | 
			
		||||
        params["VERSION"] = VERSION
 | 
			
		||||
        params["LAST_VERSION"] = LAST_VERSION
 | 
			
		||||
        if LAST_VERSION is not None:
 | 
			
		||||
            params["upgrade_available"] = decode_version(VERSION) < decode_version(LAST_VERSION)
 | 
			
		||||
        else:
 | 
			
		||||
            params["upgrade_available"] = False
 | 
			
		||||
    return params
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -545,7 +562,10 @@ class LdapHashUserPassword(object):
 | 
			
		||||
        elif scheme == b'{CRYPT}':
 | 
			
		||||
            return b'$'.join(hashed_passord.split(b'$', 3)[:-1])[len(scheme):]
 | 
			
		||||
        else:
 | 
			
		||||
            hashed_passord = base64.b64decode(hashed_passord[len(scheme):])
 | 
			
		||||
            try:
 | 
			
		||||
                hashed_passord = base64.b64decode(hashed_passord[len(scheme):])
 | 
			
		||||
            except (TypeError, binascii.Error) as error:
 | 
			
		||||
                raise cls.BadHash("Bad base64: %s" % error)
 | 
			
		||||
            if len(hashed_passord) < cls._schemes_to_len[scheme]:
 | 
			
		||||
                raise cls.BadHash("Hash too short for the scheme %s" % scheme)
 | 
			
		||||
            return hashed_passord[cls._schemes_to_len[scheme]:]
 | 
			
		||||
@@ -563,7 +583,7 @@ def check_password(method, password, hashed_password, charset):
 | 
			
		||||
        :param hashed_password: The hashed password as stored in the database
 | 
			
		||||
        :type hashed_password: :obj:`str` or :obj:`unicode`
 | 
			
		||||
        :param str charset: The used char encoding (also used internally, so it must be valid for
 | 
			
		||||
            the charset used by ``password`` even if it is inputed as an :obj:`unicode`)
 | 
			
		||||
            the charset used by ``password`` when it was initially )
 | 
			
		||||
        :return: True if ``password`` match ``hashed_password`` using ``method``,
 | 
			
		||||
            ``False`` otherwise
 | 
			
		||||
        :rtype: bool
 | 
			
		||||
@@ -603,3 +623,60 @@ def check_password(method, password, hashed_password, charset):
 | 
			
		||||
        )(password).hexdigest().encode("ascii") == hashed_password.lower()
 | 
			
		||||
    else:
 | 
			
		||||
        raise ValueError("Unknown password method check %r" % method)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def decode_version(version):
 | 
			
		||||
    """
 | 
			
		||||
        decode a version string following version semantic http://semver.org/ input a tuple of int
 | 
			
		||||
 | 
			
		||||
        :param unicode version: A dotted version
 | 
			
		||||
        :return: A tuple a int
 | 
			
		||||
        :rtype: tuple
 | 
			
		||||
    """
 | 
			
		||||
    return tuple(int(sub_version) for sub_version in version.split('.'))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def last_version():
 | 
			
		||||
    """
 | 
			
		||||
        Fetch the last version from pypi and return it. On successful fetch from pypi, the response
 | 
			
		||||
        is cached 24h, on error, it is cached 10 min.
 | 
			
		||||
 | 
			
		||||
        :return: the last django-cas-server version
 | 
			
		||||
        :rtype: unicode
 | 
			
		||||
    """
 | 
			
		||||
    try:
 | 
			
		||||
        last_update, version, success = last_version._cache
 | 
			
		||||
    except AttributeError:
 | 
			
		||||
        last_update = 0
 | 
			
		||||
        version = None
 | 
			
		||||
        success = False
 | 
			
		||||
    cache_delta = 24 * 3600 if success else 600
 | 
			
		||||
    if (time.time() - last_update) < cache_delta:
 | 
			
		||||
        return version
 | 
			
		||||
    else:
 | 
			
		||||
        try:
 | 
			
		||||
            req = requests.get(settings.CAS_NEW_VERSION_JSON_URL)
 | 
			
		||||
            data = json.loads(req.text)
 | 
			
		||||
            versions = list(data["releases"].keys())
 | 
			
		||||
            versions.sort()
 | 
			
		||||
            version = versions[-1]
 | 
			
		||||
            last_version._cache = (time.time(), version, True)
 | 
			
		||||
            return version
 | 
			
		||||
        except (
 | 
			
		||||
            KeyError,
 | 
			
		||||
            ValueError,
 | 
			
		||||
            requests.exceptions.RequestException
 | 
			
		||||
        ) as error:  # pragma: no cover (should not happen unless pypi is not available)
 | 
			
		||||
            logger.error(
 | 
			
		||||
                "Unable to fetch %s: %s" % (settings.CAS_NEW_VERSION_JSON_URL, error)
 | 
			
		||||
            )
 | 
			
		||||
            last_version._cache = (time.time(), version, False)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def dictfetchall(cursor):
 | 
			
		||||
    "Return all rows from a django cursor as a dict"
 | 
			
		||||
    columns = [col[0] for col in cursor.description]
 | 
			
		||||
    return [
 | 
			
		||||
        dict(zip(columns, row))
 | 
			
		||||
        for row in cursor.fetchall()
 | 
			
		||||
    ]
 | 
			
		||||
 
 | 
			
		||||
@@ -147,9 +147,12 @@ class LogoutView(View, LogoutMixin):
 | 
			
		||||
        # current querystring
 | 
			
		||||
        if settings.CAS_FEDERATE:
 | 
			
		||||
            if auth is not None:
 | 
			
		||||
                params = utils.copy_params(request.GET)
 | 
			
		||||
                params = utils.copy_params(request.GET, ignore={"forget_provider"})
 | 
			
		||||
                url = auth.get_logout_url()
 | 
			
		||||
                return HttpResponseRedirect(utils.update_url(url, params))
 | 
			
		||||
                response = HttpResponseRedirect(utils.update_url(url, params))
 | 
			
		||||
                if request.GET.get("forget_provider"):
 | 
			
		||||
                    response.delete_cookie("remember_provider")
 | 
			
		||||
                return response
 | 
			
		||||
        # if service is set, redirect to service after logout
 | 
			
		||||
        if self.service:
 | 
			
		||||
            list(messages.get_messages(request))  # clean messages before leaving the django app
 | 
			
		||||
@@ -209,6 +212,7 @@ class LogoutView(View, LogoutMixin):
 | 
			
		||||
 | 
			
		||||
class FederateAuth(View):
 | 
			
		||||
    """view to authenticated user agains a backend CAS then CAS_FEDERATE is True"""
 | 
			
		||||
 | 
			
		||||
    @method_decorator(csrf_exempt)  # csrf is disabled for allowing SLO requests reception
 | 
			
		||||
    def dispatch(self, request, *args, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
@@ -218,8 +222,7 @@ class FederateAuth(View):
 | 
			
		||||
        """
 | 
			
		||||
        return super(FederateAuth, self).dispatch(request, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_cas_client(request, provider):
 | 
			
		||||
    def get_cas_client(self, request, provider, renew=False):
 | 
			
		||||
        """
 | 
			
		||||
            return a CAS client object matching provider
 | 
			
		||||
 | 
			
		||||
@@ -231,7 +234,8 @@ class FederateAuth(View):
 | 
			
		||||
        """
 | 
			
		||||
        # compute the current url, ignoring ticket dans provider GET parameters
 | 
			
		||||
        service_url = utils.get_current_url(request, {"ticket", "provider"})
 | 
			
		||||
        return CASFederateValidateUser(provider, service_url)
 | 
			
		||||
        self.service_url = service_url
 | 
			
		||||
        return CASFederateValidateUser(provider, service_url, renew=renew)
 | 
			
		||||
 | 
			
		||||
    def post(self, request, provider=None):
 | 
			
		||||
        """
 | 
			
		||||
@@ -264,24 +268,16 @@ class FederateAuth(View):
 | 
			
		||||
            if form.is_valid():
 | 
			
		||||
                params = utils.copy_params(
 | 
			
		||||
                    request.POST,
 | 
			
		||||
                    ignore={"provider", "csrfmiddlewaretoken", "ticket"}
 | 
			
		||||
                    ignore={"provider", "csrfmiddlewaretoken", "ticket", "lt"}
 | 
			
		||||
                )
 | 
			
		||||
                if params.get("renew") == "False":
 | 
			
		||||
                    del params["renew"]
 | 
			
		||||
                url = utils.reverse_params(
 | 
			
		||||
                    "cas_server:federateAuth",
 | 
			
		||||
                    kwargs=dict(provider=form.cleaned_data["provider"].suffix),
 | 
			
		||||
                    params=params
 | 
			
		||||
                )
 | 
			
		||||
                response = HttpResponseRedirect(url)
 | 
			
		||||
                # If the user has checked "remember my identity provider" store it in a cookie
 | 
			
		||||
                if form.cleaned_data["remember"]:
 | 
			
		||||
                    max_age = settings.CAS_FEDERATE_REMEMBER_TIMEOUT
 | 
			
		||||
                    utils.set_cookie(
 | 
			
		||||
                        response,
 | 
			
		||||
                        "_remember_provider",
 | 
			
		||||
                        form.cleaned_data["provider"].suffix,
 | 
			
		||||
                        max_age
 | 
			
		||||
                    )
 | 
			
		||||
                return response
 | 
			
		||||
                return HttpResponseRedirect(url)
 | 
			
		||||
            else:
 | 
			
		||||
                return redirect("cas_server:login")
 | 
			
		||||
 | 
			
		||||
@@ -296,47 +292,81 @@ class FederateAuth(View):
 | 
			
		||||
        if not settings.CAS_FEDERATE:
 | 
			
		||||
            logger.warning("CAS_FEDERATE is False, set it to True to use the federated mode")
 | 
			
		||||
            return redirect("cas_server:login")
 | 
			
		||||
        renew = bool(request.GET.get('renew') and request.GET['renew'] != "False")
 | 
			
		||||
        # Is the user is already authenticated, no need to request authentication to the user
 | 
			
		||||
        # identity provider.
 | 
			
		||||
        if self.request.session.get("authenticated"):
 | 
			
		||||
        if self.request.session.get("authenticated") and not renew:
 | 
			
		||||
            logger.warning("User already authenticated, dropping federate authentication request")
 | 
			
		||||
            return redirect("cas_server:login")
 | 
			
		||||
        try:
 | 
			
		||||
            # get the identity provider from its suffix
 | 
			
		||||
            provider = FederatedIendityProvider.objects.get(suffix=provider)
 | 
			
		||||
            # get a CAS client for the user identity provider
 | 
			
		||||
            auth = self.get_cas_client(request, provider)
 | 
			
		||||
            auth = self.get_cas_client(request, provider, renew)
 | 
			
		||||
            # if no ticket submited, redirect to the identity provider CAS login page
 | 
			
		||||
            if 'ticket' not in request.GET:
 | 
			
		||||
                logger.info("Trying to authenticate again %s" % auth.provider.server_url)
 | 
			
		||||
                return HttpResponseRedirect(auth.get_login_url())
 | 
			
		||||
            else:
 | 
			
		||||
                ticket = request.GET['ticket']
 | 
			
		||||
                # if the ticket validation succeed
 | 
			
		||||
                if auth.verify_ticket(ticket):
 | 
			
		||||
                    logger.info(
 | 
			
		||||
                        "Got a valid ticket for %s from %s" % (
 | 
			
		||||
                            auth.username,
 | 
			
		||||
                            auth.provider.server_url
 | 
			
		||||
                try:
 | 
			
		||||
                    # if the ticket validation succeed
 | 
			
		||||
                    if auth.verify_ticket(ticket):
 | 
			
		||||
                        logger.info(
 | 
			
		||||
                            "Got a valid ticket for %s from %s" % (
 | 
			
		||||
                                auth.username,
 | 
			
		||||
                                auth.provider.server_url
 | 
			
		||||
                            )
 | 
			
		||||
                        )
 | 
			
		||||
                    )
 | 
			
		||||
                    params = utils.copy_params(request.GET, ignore={"ticket"})
 | 
			
		||||
                    request.session["federate_username"] = auth.federated_username
 | 
			
		||||
                    request.session["federate_ticket"] = ticket
 | 
			
		||||
                    auth.register_slo(auth.federated_username, request.session.session_key, ticket)
 | 
			
		||||
                    # redirect to the the login page for the user to become authenticated
 | 
			
		||||
                    # thanks to the `federate_username` and `federate_ticket` session parameters
 | 
			
		||||
                    url = utils.reverse_params("cas_server:login", params)
 | 
			
		||||
                    return HttpResponseRedirect(url)
 | 
			
		||||
                # else redirect to the identity provider CAS login page
 | 
			
		||||
                else:
 | 
			
		||||
                    logger.info(
 | 
			
		||||
                        "Got a invalid ticket for %s from %s. Retrying to authenticate" % (
 | 
			
		||||
                            auth.username,
 | 
			
		||||
                            auth.provider.server_url
 | 
			
		||||
                        params = utils.copy_params(request.GET, ignore={"ticket", "remember"})
 | 
			
		||||
                        request.session["federate_username"] = auth.federated_username
 | 
			
		||||
                        request.session["federate_ticket"] = ticket
 | 
			
		||||
                        auth.register_slo(
 | 
			
		||||
                            auth.federated_username,
 | 
			
		||||
                            request.session.session_key,
 | 
			
		||||
                            ticket
 | 
			
		||||
                        )
 | 
			
		||||
                        # redirect to the the login page for the user to become authenticated
 | 
			
		||||
                        # thanks to the `federate_username` and `federate_ticket` session parameters
 | 
			
		||||
                        url = utils.reverse_params("cas_server:login", params)
 | 
			
		||||
                        response = HttpResponseRedirect(url)
 | 
			
		||||
                        # If the user has checked "remember my identity provider" store it in a
 | 
			
		||||
                        # cookie
 | 
			
		||||
                        if request.GET.get("remember"):
 | 
			
		||||
                            max_age = settings.CAS_FEDERATE_REMEMBER_TIMEOUT
 | 
			
		||||
                            utils.set_cookie(
 | 
			
		||||
                                response,
 | 
			
		||||
                                "remember_provider",
 | 
			
		||||
                                provider.suffix,
 | 
			
		||||
                                max_age
 | 
			
		||||
                            )
 | 
			
		||||
                        return response
 | 
			
		||||
                    # else redirect to the identity provider CAS login page
 | 
			
		||||
                    else:
 | 
			
		||||
                        logger.info(
 | 
			
		||||
                            (
 | 
			
		||||
                                "Got a invalid ticket %s from %s for service %s. "
 | 
			
		||||
                                "Retrying to authenticate"
 | 
			
		||||
                            ) % (
 | 
			
		||||
                                ticket,
 | 
			
		||||
                                auth.provider.server_url,
 | 
			
		||||
                                self.service_url
 | 
			
		||||
                            )
 | 
			
		||||
                        )
 | 
			
		||||
                        return HttpResponseRedirect(auth.get_login_url())
 | 
			
		||||
                # both xml.etree.ElementTree and lxml.etree exceptions inherit from SyntaxError
 | 
			
		||||
                except SyntaxError as error:
 | 
			
		||||
                    messages.add_message(
 | 
			
		||||
                        request,
 | 
			
		||||
                        messages.ERROR,
 | 
			
		||||
                        _(
 | 
			
		||||
                            u"Invalid response from your identity provider CAS upon "
 | 
			
		||||
                            u"ticket %(ticket)s validation: %(error)r"
 | 
			
		||||
                        ) % {'ticket': ticket, 'error': error}
 | 
			
		||||
                    )
 | 
			
		||||
                    return HttpResponseRedirect(auth.get_login_url())
 | 
			
		||||
                    response = redirect("cas_server:login")
 | 
			
		||||
                    response.delete_cookie("remember_provider")
 | 
			
		||||
                    return response
 | 
			
		||||
        except FederatedIendityProvider.DoesNotExist:
 | 
			
		||||
            logger.warning("Identity provider suffix %s not found" % provider)
 | 
			
		||||
            # if the identity provider is not found, redirect to the login page
 | 
			
		||||
@@ -407,7 +437,8 @@ class LoginView(View, LogoutMixin):
 | 
			
		||||
        self.warn = request.POST.get('warn')
 | 
			
		||||
        if settings.CAS_FEDERATE:
 | 
			
		||||
            self.username = request.POST.get('username')
 | 
			
		||||
            self.ticket = request.POST.get('ticket')
 | 
			
		||||
            # in federated mode, the valdated indentity provider CAS ticket is used as password
 | 
			
		||||
            self.ticket = request.POST.get('password')
 | 
			
		||||
 | 
			
		||||
    def gen_lt(self):
 | 
			
		||||
        """Generate a new LoginTicket and add it to the list of valid LT for the user"""
 | 
			
		||||
@@ -451,7 +482,7 @@ class LoginView(View, LogoutMixin):
 | 
			
		||||
            messages.add_message(
 | 
			
		||||
                self.request,
 | 
			
		||||
                messages.ERROR,
 | 
			
		||||
                _(u"Invalid login ticket")
 | 
			
		||||
                _(u"Invalid login ticket, please retry to login")
 | 
			
		||||
            )
 | 
			
		||||
        elif ret == self.USER_LOGIN_OK:
 | 
			
		||||
            # On successful login, update the :class:`models.User<cas_server.models.User>` ``date``
 | 
			
		||||
@@ -477,7 +508,17 @@ class LoginView(View, LogoutMixin):
 | 
			
		||||
        else:  # pragma: no cover (should no happen)
 | 
			
		||||
            raise EnvironmentError("invalid output for LoginView.process_post")
 | 
			
		||||
        # call the GET/POST common part
 | 
			
		||||
        return self.common()
 | 
			
		||||
        response = self.common()
 | 
			
		||||
        if self.warn:
 | 
			
		||||
            utils.set_cookie(
 | 
			
		||||
                response,
 | 
			
		||||
                "warn",
 | 
			
		||||
                "on",
 | 
			
		||||
                10 * 365 * 24 * 3600
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            response.delete_cookie("warn")
 | 
			
		||||
        return response
 | 
			
		||||
 | 
			
		||||
    def process_post(self):
 | 
			
		||||
        """
 | 
			
		||||
@@ -586,7 +627,9 @@ class LoginView(View, LogoutMixin):
 | 
			
		||||
        form_initial = {
 | 
			
		||||
            'service': self.service,
 | 
			
		||||
            'method': self.method,
 | 
			
		||||
            'warn': self.warn or self.request.session.get("warn"),
 | 
			
		||||
            'warn': (
 | 
			
		||||
                self.warn or self.request.session.get("warn") or self.request.COOKIES.get('warn')
 | 
			
		||||
            ),
 | 
			
		||||
            'lt': self.request.session['lt'][-1],
 | 
			
		||||
            'renew': self.renew
 | 
			
		||||
        }
 | 
			
		||||
@@ -683,14 +726,14 @@ class LoginView(View, LogoutMixin):
 | 
			
		||||
            messages.add_message(
 | 
			
		||||
                self.request,
 | 
			
		||||
                messages.ERROR,
 | 
			
		||||
                _(u"User charateristics non allowed")
 | 
			
		||||
                _(u"User characteristics non allowed")
 | 
			
		||||
            )
 | 
			
		||||
        except models.UserFieldNotDefined:
 | 
			
		||||
            error = 4
 | 
			
		||||
            messages.add_message(
 | 
			
		||||
                self.request,
 | 
			
		||||
                messages.ERROR,
 | 
			
		||||
                _(u"The attribut %(field)s is needed to use"
 | 
			
		||||
                _(u"The attribute %(field)s is needed to use"
 | 
			
		||||
                  u" that service") % {'field': service_pattern.user_field}
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
@@ -817,19 +860,37 @@ class LoginView(View, LogoutMixin):
 | 
			
		||||
                    )
 | 
			
		||||
                else:
 | 
			
		||||
                    if (
 | 
			
		||||
                        self.request.COOKIES.get('_remember_provider') and
 | 
			
		||||
                        self.request.COOKIES.get('remember_provider') and
 | 
			
		||||
                        FederatedIendityProvider.objects.filter(
 | 
			
		||||
                            suffix=self.request.COOKIES['_remember_provider']
 | 
			
		||||
                            suffix=self.request.COOKIES['remember_provider']
 | 
			
		||||
                        )
 | 
			
		||||
                    ):
 | 
			
		||||
                        params = utils.copy_params(self.request.GET)
 | 
			
		||||
                        url = utils.reverse_params(
 | 
			
		||||
                            "cas_server:federateAuth",
 | 
			
		||||
                            params=params,
 | 
			
		||||
                            kwargs=dict(provider=self.request.COOKIES['_remember_provider'])
 | 
			
		||||
                            kwargs=dict(provider=self.request.COOKIES['remember_provider'])
 | 
			
		||||
                        )
 | 
			
		||||
                        return HttpResponseRedirect(url)
 | 
			
		||||
                    else:
 | 
			
		||||
                        # if user is authenticated and auth renewal is requested, redirect directly
 | 
			
		||||
                        # to the user identity provider
 | 
			
		||||
                        if self.renew and self.request.session.get("authenticated"):
 | 
			
		||||
                            try:
 | 
			
		||||
                                user = FederatedUser.get_from_federated_username(
 | 
			
		||||
                                    self.request.session.get("username")
 | 
			
		||||
                                )
 | 
			
		||||
                                params = utils.copy_params(self.request.GET)
 | 
			
		||||
                                url = utils.reverse_params(
 | 
			
		||||
                                    "cas_server:federateAuth",
 | 
			
		||||
                                    params=params,
 | 
			
		||||
                                    kwargs=dict(provider=user.provider.suffix)
 | 
			
		||||
                                )
 | 
			
		||||
                                return HttpResponseRedirect(url)
 | 
			
		||||
                            # Should normally not happen: if the user is logged, it exists in the
 | 
			
		||||
                            # database.
 | 
			
		||||
                            except FederatedUser.DoesNotExist:  # pragma: no cover
 | 
			
		||||
                                pass
 | 
			
		||||
                        return render(
 | 
			
		||||
                            self.request,
 | 
			
		||||
                            settings.CAS_LOGIN_TEMPLATE,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,12 @@
 | 
			
		||||
setuptools>=5.5
 | 
			
		||||
requests>=2.4
 | 
			
		||||
requests_futures>=0.9.5
 | 
			
		||||
lxml>=3.4
 | 
			
		||||
six>=1.8
 | 
			
		||||
tox>=1.8.1
 | 
			
		||||
pytest>=2.6.4
 | 
			
		||||
pytest-django>=2.8.0
 | 
			
		||||
pytest-pythonpath>=0.3
 | 
			
		||||
pytest-warnings
 | 
			
		||||
pytest-cov>=2.2.1
 | 
			
		||||
requests>=2.4
 | 
			
		||||
requests_futures>=0.9.5
 | 
			
		||||
lxml>=3.4
 | 
			
		||||
six>=1
 | 
			
		||||
mock>=1
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
Django >= 1.8,<1.10
 | 
			
		||||
Django >= 1.7.1,<1.10
 | 
			
		||||
setuptools>=5.5
 | 
			
		||||
requests>=2.4
 | 
			
		||||
requests_futures>=0.9.5
 | 
			
		||||
lxml>=3.4
 | 
			
		||||
six>=1
 | 
			
		||||
six>=1.8
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										7
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								setup.py
									
									
									
									
									
								
							@@ -1,8 +1,7 @@
 | 
			
		||||
import os
 | 
			
		||||
import pkg_resources
 | 
			
		||||
from setuptools import setup
 | 
			
		||||
 | 
			
		||||
VERSION = '0.6.1'
 | 
			
		||||
from cas_server import VERSION
 | 
			
		||||
 | 
			
		||||
with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as readme:
 | 
			
		||||
    README = readme.read()
 | 
			
		||||
@@ -30,7 +29,7 @@ if __name__ == '__main__':
 | 
			
		||||
        author_email='valentin.samir@crans.org',
 | 
			
		||||
        classifiers=[
 | 
			
		||||
            'Environment :: Web Environment',
 | 
			
		||||
            'evelopment Status :: 5 - Production/Stable',
 | 
			
		||||
            'Development Status :: 5 - Production/Stable',
 | 
			
		||||
            'Framework :: Django',
 | 
			
		||||
            'Framework :: Django :: 1.7',
 | 
			
		||||
            'Framework :: Django :: 1.8',
 | 
			
		||||
@@ -66,5 +65,5 @@ if __name__ == '__main__':
 | 
			
		||||
        download_url="https://github.com/nitmir/django-cas-server/releases",
 | 
			
		||||
        zip_safe=False,
 | 
			
		||||
        setup_requires=['pytest-runner'],
 | 
			
		||||
        tests_require=['pytest', 'pytest-django', 'pytest-pythonpath'],
 | 
			
		||||
        tests_require=['pytest', 'pytest-django', 'pytest-pythonpath', 'pytest-warnings', 'mock>=1'],
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user