diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 0000000..0bd4d8b --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,420 @@ +Change Log +########## + +All notable changes to this project will be documented in this file. + +.. contents:: Table of Contents + :depth: 2 + +v0.7.0 - 2016-08-24 +=================== + +Added +----- +* Add a CHANGELOG.rst file. +* Add a validator to models CharField that should be regular expressions checking that user input + are valids regular expressions. +* Add a CAS_INFO_MESSAGES and CAS_INFO_MESSAGES_ORDER settings allowing to display messages in + info-boxes on the html pages of the default templates. + +Changed +------- +* Allow the user defined CAS_COMPONENT_URLS to omit not changed values. +* replace code-block without language indication by literal blocks. +* Update french translation + +Fixed +----- +* Some README.rst typos. +* some english typos + + +v0.6.4 - 2016-08-14 +=================== + +commit: 282e3a831b3c0b0818881c2f16d056850d572b89 + +Added +----- +* Add a forgotten migration (only change help_text) + + +v0.6.3 - 2016-08-14 +=================== + +commit: 07a537b403c5c5e39a4ddd084f90e3a4de88a54e + +Added +----- +* Add powered by footer +* Add a github version badge +* documents templatetags + +Changed +------- +* Usage of the documented API for models _meta in auth.DjangoAuthUser +* set warn cookie using javascript if possible +* Unfold many to many attributes in auth.DjangoAuthUser attributes + +Fixed +----- +* typos in README.rst +* w3c validation + +Cleaned +------- +* Code factorisation (models.py, views.py) + + +v0.6.2 - 2016-08-02 +=================== + +commit: 773707e6c3c3fa20f697c946e31cafc591e8fee8 + +Added +----- +* Support authentication renewal in federate mode +* Add new version email and info box then new version is available +* Add SqlAuthUser and LdapAuthUser auth classes. + Deprecate the usage of MysqlAuthUser in favor of SqlAuthUser. +* Add pytest-warning to tests +* Add a checkbox to forget the identity provider if we checked "remember the identity provider" +* Add dependancies correspondance between python pypi, debian and centos packages in README + +Changed +------- +* Move coverage computation last in travis +* Enable logging to stderr then running tests +* Remember "warn me before…" using a cookie +* Put favicon (shortcut icon) URL in settings + +Deprecated +---------- +* The auth class MysqlAuthUser is deprecated in favor of the SqlAuthUser class. + +Fixed +----- +* Use custom templatetags instead settings custom attributes to Boundfields + (As it do not work with django 1.7) +* Display an error message on bad response from identity provider in federate mode + instead of crashing. (e.g. Bad XML document) +* Catch base64 decode error on b64decode to raise our custom exception BadHash +* Add secret as sensitive variables/post parameter for /auth +* Only set "remember my provider" in federated mode upon successful authentication +* Since we drop django-boostrap3 dependancies, Django default minimal version is 1.7.1 +* [cas.py] Append renew=true when validating tickets + +Cleaned +------- +* code factorization (cas.py, forms.py) + + +v0.6.1 - 2016-07-27 +=================== + +commit: b168e0a6423c53de31aae6c444fa1d1c5083afa6 + +Added +----- +* Add sphinx docs + autodoc +* Add the possibility to run tests with "setup.py test" +* Include docs, Makefile, coverage config and tests config to source package +* Add serviceValidate ProxyTicket tests +* Add python 3.5 tox/travis tests + +Changed +------- +* Use https://badges.genua.fr for badges + +Fixed +----- +* Keep LoginTicket list upon fail authentication + (It prevent the next login attemps to fail because of bad LT) + +Cleaned +------- +* Compact federated mode migration +* Reformat default_settings.py for documentation using sphinx autodoc +* Factorize some code (from views.py to Ticket models class methods) +* Update urlpattern for django 1.10 +* Drop dependancies django-picklefield and django-bootstrap3 + + +v0.6.0 - 2016-07-06 +=================== + +commit: 4ad4d13baa4236c5cd72cc5216d7ff08dd361476 + +Added +----- +* Add a section describing service patterns options to README.rst +* Add a federation mode: + When the settings CAS_FEDERATE is True, django-cas-server will offer to the user to choose its + CAS backend to authenticate. Hence the login page do not display anymore a username/password form + but a select form with configured CASs backend. + This allow to give access to CAS supported applications to users from multiple organization + seamlessly. + + It was originally developped to mach the need of https://ares.fr (Federated CAS at + https://cas.ares.fr, example of an application using it as https://chat.myares.fr) + +Fixed +----- +* Then a ticket was marked as obtained with the user entering its credentials (aka not by SSO), and + the service did not require it, ticket validation was failing. Now, if the service do not require + authentication to be renewed, both ticket with renewed authentication and non renewed + authentication validate successfully. + + + +v0.5.0 - 2016-07-01 +=================== + +commit: e3ab64271b718a17e4cbbbabda0a2453107a83df + +Added +----- +* Add more password scheme support to the mysql authentication backend: ldap user + attribute scheme encoding and simple password hash in hexa for md5, sha1, sha224, + sha256, sha384, sha512. +* Add a main heading to template "Central Authentication Service" with a logo controled + by CAS_LOGO_URL +* Add logos to the project (svg, png) +* Add coverage computation +* link project to codacy +* Update doc: add debian requirement, correct typos, correct links + +Changed +------- +* Use settings to set tests username password and attributes +* Tweak the css and html for small screens +* Update travis cache for faster build +* clean Makefile, use pip to install, add target for tests + +Fixed +----- +* Fix "warn me": we generate the ticket after the user agree to be connected to the service. + we were generating first and the connect button was a link to the service url with the ?ticket= + this could lead to situation where the ticket validity expire if the user is slow to click the + connect button. +* Fix authentication renewal: the renew parameter were not transmited when POST the login request + and self.renew (aks for auth renewal) was use instead of self.renewed (auth was renewd) + when generating a ticket. +* Fix attribute value replacement when generating a ticket: we were using the 'name' attribute + instead of the 'attribut' attribut on ReplaceAttributValue +* Fix attribute value replacement when generating a ticket then the value is a list: iterate over + each element of the list. +* Fix a NameError in utils.import_attr +* Fix serviceValidate and samlValidate when user_field is an attribute that is a list: we use + the first element of the list as username. we were serializing the list before that. +* Correct typos + + +Cleaned +------- +* Clean some useless conditional branches found with coverage +* Clean cas.js: use compact object declararion +* Use six for python{2|3} compatibility +* Move all unit tests to cas_server.tests and use django primitive. We also have a 100% tests + coverage now. Using the django classes for tests, we do not need to use our own dirty mock. +* Move mysql backend password check to a function in utils + + +v0.4.4 - 2016-04-30 +=================== + +commit: 77d1607b0beefe8b171adcd8e2dcd974e3cdc72a + +Added +----- +* Add sensitive_post_parameters and sensitive_variables for passwords, so passwords are anonymised + before django send an error report. + +Fixed +----- +* Before commit 77fc5b5 the User model had a foreign key to the Session model. After the commit, + Only the session_key is store, allowing to use different backend than the Session SQL backend. + So the first migration (which is 21 migrations combined) was creating the User model with the + foreign key, then delete it and add the field session_key. Somehow, MySQL did not like it. + Now the first migration directly create the User model with the session_key and without the + foreign key to the Session SQL backend. +* Evaluate attributes variables in the template samlValidate.xml. the {{ }} was missing causing + the variable name to be displyed instead of the variable content. +* Return username in CAS 1.0 on the second ligne of the CAS response as specified. + + +Changed +------- +* Update tests + + +v0.4.3 - 2016-03-18 +=================== + +commit: f6d436acb49f8d32b5457c316c18c4892accfd3b + +Fixed +----- +* Currently, one of our dependancy, django-boostrap3, do not support django 1.7 in its last version. + So there is some detection of the current django installed version in setup.py to pin + django-boostrap3 to a version supported by django 1.7 if django 1.7 is installed, or to require + at least django 1.8. + The detection did not handle the case where django was not installed. +* [PEP8] Put line breaks after binary operator and not before. + + +v0.4.2 - 2016-03-18 +=================== + +commit: d1cd17d6103281b03a8c57013671057eab80d21c + +Added +----- +* On logout, display the number of sessions we are logged out from. + +Fixed +----- +* One of our dependancy, django-boostrap3, do not support django 1.7 in its last version. + Some django version detection is added to setup.py to handle that. +* Some typos +* Make errors returned by utils.import_attr clearer (as they are likely to be displayed to the + django admin) + + +v0.4.1 - 2015-12-23 +=================== + +commit: 5e63f39f9b7c678a300ad2f8132166be34d1d35b + +Added +----- +* Add a run_test_server target to make file. Running make run_test_server will build a virtualenv, + create a django projet with django-cas-server and lauch ./management.py runserver. It is quite + handy to test developement version. +* Add verbose name for cas_server app and models +* Add Makefile clean targets for tox tests and test virtualenv. +* Add link on license badge to the GPLv3 + +Changed +------- +* Make Makefile clean targets modular +* Use img.shields.io for PyPi badges +* Get django-cas-server version in Makefile directly from setup.py (so now, the version is only + written in one place) + +Fixed +----- +* Fix MysqlAuthUser when number of results != 1: In that case, call super anyway this the provided + username. + + +v0.4.0 - 2015-12-15 +=================== + +commit: 7b4fac575449e50c2caff07f5798dba7f4e4857c + +Added +----- +* Add a help_text to pattern of ServicePattern +* Add a timeout to SLO requests +* Add logging capabilities (see README.rst for instruction) +* Add management commands that should be called on a regular basis to README.rst + + +v0.3.5 - 2015-12-12 +=================== + +commit: 51fa0861f550723171e52d58025fa789dccb8cde + +Added +----- +* Add badges to README.rst +* Document settings parameter in README.rst +* Add a "Features" section in README.rst + +Changed +------- +* Add a AuthUser auth class and use it as auth classes base class instead of DummyAuthUser + +Fixed +----- +* Fix minor errors and typos in README.rst + + + +v0.3.4 - 2015-12-12 +=================== + +commit: 9fbfe19c550b147e8d0377108cdac8231cf0fb27 + +Added +----- +* Add static files, templates and locales to the PyPi release by adding them to MANIFEST.in +* Add a Makefile with the build/install/clean/dist targets + + +v0.3.3 - 2015-12-12 +=================== + +commit: 16b700d0127abe33a1eabf5d5fe890aeb5167e5a + +Added +----- +* Add management commands and migrations to the package by adding there packages to setup.py + packages list. + + +v0.3.2 - 2015-12-12 [YANKED] +============================ + +commit: eef9490885bf665a53349573ddb9cbe844319b3e + +Added +----- +* Add migrations to setup.py package_data + + +v0.3.1 - 2015-12-12 +=================== + +commit: d0f6ed9ea3a4b3e2bf715fd218c460892c32e39f + +Added +----- +* Add a forgotten migration (remove auto_now_add=True from the User model) + + +v0.3.0 - 2015-12-12 +=================== + +commit: b69769d71a99806a69e300eca0d7c6744a2b327e + +Added +----- +* Django 1.9 compatibility (add tox and travis tests and fix some decrecated) + + +v0.2.1 - 2015-12-12 +=================== + +commit: 90e077dedb991d651822e9bb283470de8bddd7dd + +First github and PyPi release + +Fixed +----- +* Prune .tox in MANIFEST.in +* add dist/ to .gitignore +* typo in setup.cfg + + +v0.2.0 - 2015-12-12 [YANKED] +============================ + +commit: a071ad46d7cd76fc97eb86f2f538d330457c6767 + + +v0.1.0 - 2015-05-22 [YANKED] +============================ + +commit: 6981433bdf8a406992ba0c5e844a47d06ccc08fb diff --git a/MANIFEST.in b/MANIFEST.in index 3f968f7..47669dd 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,7 @@ include tox.ini include LICENSE include README.rst +include CHANGELOG.rst include .coveragerc include Makefile include pytest.ini @@ -15,6 +16,7 @@ include docs/conf.py include docs/index.rst include docs/Makefile include docs/README.rst +include docs/CHANGELOG.rst recursive-include docs/_ext * recursive-include docs/package * recursive-include docs/_static * diff --git a/README.rst b/README.rst index 6c57a7f..d67a3e0 100644 --- a/README.rst +++ b/README.rst @@ -7,7 +7,7 @@ CAS Server is a Django application implementing the `CAS Protocol 3.0 Specificat `_. By default, the authentication process use django internal users but you can easily -use any sources (see auth classes in the auth.py file) +use any sources (see the `Authentication backend`_ section and auth classes in the auth.py file) .. contents:: Table of Contents @@ -38,7 +38,7 @@ Dependencies 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 +Additionally, denpending of the `Authentication backend`_ you plan to use, you may need the following python packages: * ldap3 @@ -174,13 +174,11 @@ Quick start inactive since more than ``SESSION_COOKIE_AGE``. The default value for is ``1209600`` seconds (2 weeks). You probably should reduce it to something like ``86400`` seconds (1 day). - You could for example do as bellow : + You could for example do as bellow:: - .. code-block:: - - 0 0 * * * cas-user /path/to/project/manage.py clearsessions - */5 * * * * cas-user /path/to/project/manage.py cas_clean_tickets - 5 0 * * * cas-user /path/to/project/manage.py cas_clean_sessions + 0 0 * * * cas-user /path/to/project/manage.py clearsessions + */5 * * * * cas-user /path/to/project/manage.py cas_clean_tickets + 5 0 * * * cas-user /path/to/project/manage.py cas_clean_sessions 5. Run ``python manage.py createsuperuser`` to create an administrator user. @@ -208,7 +206,7 @@ Template settings Default is a key icon. Set it to ``False`` to disable it. * ``CAS_SHOW_POWERED``: Set it to ``False`` to hide the powered by footer. The default is ``True``. * ``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"``, + having the five following keys: ``"bootstrap3_css"``, ``"bootstrap3_js"``, ``"html5shiv"``, ``"respond"``, ``"jquery"``. The default is:: { @@ -219,6 +217,32 @@ Template settings "jquery": "//code.jquery.com/jquery.min.js", } + if you omit some keys of the dictionnary, the default value for these keys is used. + +* ``CAS_INFO_MESSAGES``: Messages displayed in info-boxes on the html pages of the default templates. + It is a dictionnary mapping message name to a message dict. A message dict has 3 keys: + + * ``message``: A unicode message to display, potentially wrapped around ugettex_lazy + * ``discardable``: A boolean, specify if the users can close the message info-box + * ``type``: One of info, success, info, warning, danger. The type of the info-box. + + ``CAS_INFO_MESSAGES`` contains by default one message, ``cas_explained``, which explain + roughly the purpose of a CAS. The default is:: + + { + "cas_explained": { + "message":_( + u"The Central Authentication Service grants you access to most of our websites by " + u"authenticating only once, so you don't need to type your credentials again unless " + u"your session expires or you logout." + ), + "discardable": True, + "type": "info", # one of info, success, info, warning, danger + }, + } + +* ``CAS_INFO_MESSAGES_ORDER``: A list of message names. Order in which info-box messages are + displayed. Use an empty list to disable messages display. The default is ``[]``. * ``CAS_LOGIN_TEMPLATE``: Path to the template showed on ``/login`` then the user is not autenticated. The default is ``"cas_server/login.html"``. * ``CAS_WARN_TEMPLATE``: Path to the template showed on ``/login?service=...`` then @@ -228,7 +252,7 @@ Template settings authenticated. The default is ``"cas_server/logged.html"``. * ``CAS_LOGOUT_TEMPLATE``: Path to the template showed on ``/logout`` then to user is being disconnected. The default is ``"cas_server/logout.html"`` -* ``CAS_REDIRECT_TO_LOGIN_AFTER_LOGOUT``: Should we redirect users to `/login` after they +* ``CAS_REDIRECT_TO_LOGIN_AFTER_LOGOUT``: Should we redirect users to ``/login`` after they logged out instead of displaying ``CAS_LOGOUT_TEMPLATE``. The default is ``False``. @@ -271,7 +295,7 @@ 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 +* ``CAS_NEW_VERSION_EMAIL_WARNING``: A boolean for sending a email to ``settings.ADMINS`` when a new version is available. The default is ``True``. @@ -545,10 +569,10 @@ A service pattern has 4 associated models: an email address to connect to it. To do so, put ``email`` in ``Attribute`` and ``.*`` in ``pattern``. Then a user ask a ticket for a service, the service URL is compare against each service patterns -sorted by `position`. The first service pattern that matches the service URL is chosen. -Hence, you should give low `position` to very specific patterns like -``^https://www\.example\.com(/.*)?$`` and higher `position` to generic patterns like ``^https://.*``. -So the service URL `https://www.examle.com` will use the service pattern for +sorted by ``position``. The first service pattern that matches the service URL is chosen. +Hence, you should give low ``position`` to very specific patterns like +``^https://www\.example\.com(/.*)?$`` and higher ``position`` to generic patterns like ``^https://.*``. +So the service URL ``https://www.examle.com`` will use the service pattern for ``^https://www\.example\.com(/.*)?$`` and not the one for ``^https://.*``. @@ -572,7 +596,7 @@ An identity provider comes with 5 fields: * ``Suffix``: the suffix that will be append to the username returned by the identity provider. It must be unique. * ``Server url``: the URL to the identity provider CAS. For instance, if you are using - ``https://cas.example.org/login`` to authenticate on the CAS, the `server url` is + ``https://cas.example.org/login`` to authenticate on the CAS, the ``server url`` is ``https://cas.example.org`` * ``CAS protocol version``: the version of the CAS protocol to use to contact the identity provider. The default is version 3. @@ -593,11 +617,9 @@ Then using federate mode, you should add one command to a daily crontab: ``cas_c This command clean the local cache of federated user from old unused users. -You could for example do as bellow : +You could for example do as bellow:: -.. code-block:: - - 10 0 * * * cas-user /path/to/project/manage.py cas_clean_federate + 10 0 * * * cas-user /path/to/project/manage.py cas_clean_federate diff --git a/cas_server/__init__.py b/cas_server/__init__.py index 5fcbed0..3936504 100644 --- a/cas_server/__init__.py +++ b/cas_server/__init__.py @@ -11,7 +11,7 @@ """A django CAS server application""" #: version of the application -VERSION = '0.6.4' +VERSION = '0.7.0' #: path the the application configuration class default_app_config = 'cas_server.apps.CasAppConfig' diff --git a/cas_server/default_settings.py b/cas_server/default_settings.py index 8474d0b..238fc0a 100644 --- a/cas_server/default_settings.py +++ b/cas_server/default_settings.py @@ -12,6 +12,7 @@ """Default values for the app's settings""" from django.conf import settings from django.contrib.staticfiles.templatetags.staticfiles import static +from django.utils.translation import ugettext_lazy as _ from importlib import import_module @@ -180,13 +181,45 @@ CAS_NEW_VERSION_EMAIL_WARNING = True #: You should not change it. CAS_NEW_VERSION_JSON_URL = "https://pypi.python.org/pypi/django-cas-server/json" + +#: Messages displayed in a info-box on the html pages of the default templates. +#: ``CAS_INFO_MESSAGES`` is a :class:`dict` mapping message name to a message :class:`dict`. +#: A message :class:`dict` has 3 keys: +#: * ``message``: A :class:`unicode`, the message to display, potentially wrapped around +#: ugettex_lazy +#: * ``discardable``: A :class:`bool`, specify if the users can close the message info-box +#: * ``type``: One of info, success, info, warning, danger. The type of the info-box. +#: ``CAS_INFO_MESSAGES`` contains by default one message, ``cas_explained``, which explain +#: roughly the purpose of a CAS. +CAS_INFO_MESSAGES = { + "cas_explained": { + "message": _( + u"The Central Authentication Service grants you access to most of our websites by " + u"authenticating only once, so you don't need to type your credentials again unless " + u"your session expires or you logout." + ), + "discardable": True, + "type": "info", # one of info, success, info, warning, danger + }, +} +#: :class:`list` of message names. Order in which info-box messages are displayed. +#: Let the list empty to disable messages display. +CAS_INFO_MESSAGES_ORDER = [] + + GLOBALS = globals().copy() for name, default_value in GLOBALS.items(): - # get the current setting value, falling back to default_value - value = getattr(settings, name, default_value) - # set the setting value to its value if defined, ellse to the default_value. - setattr(settings, name, value) + # only care about parameter begining by CAS_ + if name.startswith("CAS_"): + # get the current setting value, falling back to default_value + value = getattr(settings, name, default_value) + # set the setting value to its value if defined, ellse to the default_value. + setattr(settings, name, value) +# Allow the user defined CAS_COMPONENT_URLS to omit not changed values +MERGED_CAS_COMPONENT_URLS = CAS_COMPONENT_URLS.copy() +MERGED_CAS_COMPONENT_URLS.update(settings.CAS_COMPONENT_URLS) +settings.CAS_COMPONENT_URLS = MERGED_CAS_COMPONENT_URLS # if the federated mode is enabled, we must use the :class`cas_server.auth.CASFederateAuth` auth # backend. diff --git a/cas_server/locale/fr/LC_MESSAGES/django.mo b/cas_server/locale/fr/LC_MESSAGES/django.mo index d0f80ed..603a6db 100644 Binary files a/cas_server/locale/fr/LC_MESSAGES/django.mo and b/cas_server/locale/fr/LC_MESSAGES/django.mo differ diff --git a/cas_server/locale/fr/LC_MESSAGES/django.po b/cas_server/locale/fr/LC_MESSAGES/django.po index bdcc3a7..049921a 100644 --- a/cas_server/locale/fr/LC_MESSAGES/django.po +++ b/cas_server/locale/fr/LC_MESSAGES/django.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: cas_server\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-08-01 12:01+0200\n" -"PO-Revision-Date: 2016-08-01 12:01+0200\n" +"POT-Creation-Date: 2016-08-24 17:18+0200\n" +"PO-Revision-Date: 2016-08-24 17:18+0200\n" "Last-Translator: Valentin Samir \n" "Language-Team: django \n" "Language: fr\n" @@ -18,70 +18,81 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "X-Generator: Poedit 1.8.8\n" -#: apps.py:25 templates/cas_server/base.html:9 -#: templates/cas_server/base.html:27 +#: apps.py:25 templates/cas_server/base.html:7 +#: templates/cas_server/base.html:26 msgid "Central Authentication Service" msgstr "Service Central d'Authentification" -#: forms.py:88 +#: default_settings.py:197 +msgid "" +"The Central Authentication Service grants you access to most of our websites " +"by authenticating only once, so you don't need to type your credentials " +"again unless your session expires or you logout." +msgstr "" +"Le Service Central d'Authentification permet, en vous authentifiant une " +"seule fois, d'accéder à la plupart de nos sites sans avoir à retaper votre " +"identifiant et votre mot de passe chaque fois que vous changez de site, " +"jusqu'à ce que votre session expire ou que vous vous déconnectiez." + +#: forms.py:84 msgid "Identity provider" msgstr "fournisseur d'identité" -#: forms.py:92 forms.py:111 +#: forms.py:88 forms.py:107 msgid "Warn me before logging me into other sites." msgstr "Prévenez-moi avant d'accéder à d'autres services." -#: forms.py:96 +#: forms.py:92 msgid "Remember the identity provider" msgstr "Se souvenir du fournisseur d'identité" -#: forms.py:106 models.py:600 +#: forms.py:102 models.py:594 msgid "username" msgstr "nom d'utilisateur" -#: forms.py:108 +#: forms.py:104 msgid "password" msgstr "mot de passe" -#: forms.py:130 +#: forms.py:126 msgid "The credentials you provided cannot be determined to be authentic." msgstr "Les informations transmises n'ont pas permis de vous authentifier." -#: forms.py:182 +#: forms.py:178 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 +#: forms.py:192 msgid "service" msgstr "service" #: management/commands/cas_clean_federate.py:20 msgid "Clean old federated users" -msgstr "Nettoyer les anciens utilisateurs fédéré" +msgstr "Nettoyer les anciens utilisateurs fédérés" #: management/commands/cas_clean_sessions.py:22 msgid "Clean deleted sessions" msgstr "Nettoyer les sessions supprimées" #: management/commands/cas_clean_tickets.py:22 -msgid "Clean old trickets" +msgid "Clean old tickets" msgstr "Nettoyer les vieux tickets" -#: models.py:46 +#: models.py:71 msgid "identity provider" msgstr "fournisseur d'identité" -#: models.py:47 +#: models.py:72 msgid "identity providers" msgstr "fournisseurs d'identités" -#: models.py:53 +#: models.py:78 msgid "suffix" msgstr "suffixe" -#: models.py:55 +#: models.py:80 msgid "" "Suffix append to backend CAS returned username: ``returned_username`` @ " "``suffix``." @@ -89,46 +100,46 @@ msgstr "" "Suffixe ajouté au nom d'utilisateur retourné par le CAS du fournisseur " "d'identité : `nom retourné`@`suffixe`." -#: models.py:62 +#: models.py:87 msgid "server url" msgstr "url du serveur" -#: models.py:72 +#: models.py:97 msgid "CAS protocol version" msgstr "Version du protocole CAS" -#: models.py:74 +#: models.py:99 msgid "" "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é." -#: models.py:81 +#: models.py:106 msgid "verbose name" msgstr "Nom du fournisseur" -#: models.py:82 +#: models.py:107 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:88 models.py:446 +#: models.py:113 models.py:446 msgid "position" msgstr "position" -#: models.py:102 +#: models.py:127 msgid "display" msgstr "afficher" -#: models.py:103 +#: models.py:128 msgid "Display the provider on the login page." msgstr "Afficher le fournisseur d'identité sur la page de connexion." -#: models.py:233 +#: models.py:245 msgid "User" msgstr "Utilisateur" -#: models.py:234 +#: models.py:246 msgid "Users" msgstr "Utilisateurs" @@ -149,7 +160,7 @@ msgstr "Motifs de services" msgid "service patterns are sorted using the position attribute" msgstr "Les motifs de service sont trié selon l'attribut position" -#: models.py:455 models.py:626 +#: models.py:455 models.py:620 msgid "name" msgstr "nom" @@ -157,7 +168,7 @@ msgstr "nom" msgid "A name for the service" msgstr "Un nom pour le service" -#: models.py:464 models.py:669 models.py:698 +#: models.py:464 models.py:663 models.py:693 msgid "pattern" msgstr "motif" @@ -172,108 +183,108 @@ msgstr "" "expression rationnelle, les caractères spéciaux doivent être échappés avec " "un '\\'." -#: models.py:476 +#: models.py:477 msgid "user field" msgstr "champ utilisateur" -#: models.py:477 +#: models.py:478 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 connexion" -#: models.py:482 +#: models.py:483 msgid "restrict username" msgstr "limiter les noms d'utilisateurs" -#: models.py:483 +#: models.py:484 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:488 +#: models.py:489 msgid "proxy" msgstr "proxy" -#: models.py:489 +#: models.py:490 msgid "Proxy tickets can be delivered to the service" msgstr "des proxy tickets peuvent être délivrés au service" -#: models.py:495 +#: models.py:496 msgid "proxy callback" msgstr "" -#: models.py:496 +#: models.py:497 msgid "can be used as a proxy callback to deliver PGT" msgstr "peut être utilisé comme un callback pour recevoir un PGT" -#: models.py:503 +#: models.py:504 msgid "single log out" msgstr "" -#: models.py:504 +#: models.py:505 msgid "Enable SLO for the service" msgstr "Active le SLO pour le service" -#: models.py:512 +#: models.py:513 msgid "single log out callback" msgstr "" -#: models.py:513 +#: models.py:514 msgid "" "URL where the SLO request will be POST. empty = service url\n" "This is usefull for non HTTP proxied services." msgstr "" -"URL a laquelle la requête de déconnexion sera postée. vide = l'url du " +"URL à laquelle la requête de déconnexion sera postée. vide = l'url du " "service\n" -"Ceci n'est utilise que pour des services non HTTP proxifiés" +"Ceci n'est en général utilisé que pour des services non HTTP proxifiés" -#: models.py:601 +#: models.py:595 msgid "username allowed to connect to the service" -msgstr "noms d'utilisateurs autorisé à se connecter au service" +msgstr "noms d'utilisateurs autorisés à se connecter au service" -#: models.py:627 +#: models.py:621 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" +"nom d'un attribut à envoyer au service, utiliser * pour tous les attributs" -#: models.py:634 models.py:705 +#: models.py:628 models.py:701 msgid "replace" msgstr "remplacement" -#: models.py:635 +#: models.py:629 msgid "" -"name under which the attribute will be showto the service. empty = default " +"name under which the attribute will be show to the service. empty = default " "name of the attribut" msgstr "" "nom sous lequel l'attribut sera rendu visible au service. vide = inchangé" -#: models.py:662 models.py:692 +#: models.py:656 models.py:687 msgid "attribute" msgstr "attribut" -#: models.py:663 +#: models.py:657 msgid "Name of the attribute which must verify pattern" msgstr "Nom de l'attribut devant vérifier un motif" -#: models.py:670 +#: models.py:664 msgid "a regular expression" msgstr "une expression régulière" -#: models.py:693 +#: models.py:688 msgid "Name of the attribute for which the value must be replace" -msgstr "nom de l'attribut pour lequel la valeur doit être remplacé" +msgstr "Nom de l'attribut pour lequel la valeur doit être remplacé" -#: models.py:699 +#: models.py:694 msgid "An regular expression maching whats need to be replaced" -msgstr "une expression régulière reconnaissant ce qui doit être remplacé" +msgstr "Une expression régulière reconnaissant ce qui doit être remplacé" -#: models.py:706 +#: models.py:702 msgid "replace expression, groups are capture by \\1, \\2 …" msgstr "expression de remplacement, les groupe sont capturé par \\1, \\2" -#: templates/cas_server/base.html:38 +#: templates/cas_server/base.html:43 #, python-format msgid "" "A new version of the application is available. This instance runs " @@ -282,7 +293,7 @@ msgid "" msgstr "" "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." +"vous mettre à jour." #: templates/cas_server/logged.html:4 msgid "" @@ -291,10 +302,10 @@ msgid "" "your web browser when you are done accessing services that require " "authentication!" msgstr "" -"

Déconnexion réussie

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 !" +"

Connexion réussie

Vous vous êtes connecté(e) auprès du Service " +"Central d'Authentification.
Pour des raisons de sécurité, veuillez vous " +"déconnecter et fermer votre navigateur après avoir fini d'accéder à des " +"services demandant une authentification !" #: templates/cas_server/logged.html:8 msgid "Log me out from all my sessions" @@ -310,7 +321,7 @@ msgstr "Se déconnecter" #: templates/cas_server/login.html:6 msgid "Please log in" -msgstr "Merci de se connecter" +msgstr "Veuillez vous authentifier" #: templates/cas_server/login.html:14 msgid "Login" @@ -320,7 +331,12 @@ msgstr "Connexion" msgid "Connect to the service" msgstr "Se connecter au service" -#: views.py:168 +#: utils.py:736 +#, python-format +msgid "\"%(value)s\" is not a valid regular expression" +msgstr "\"%(value)s\" n'est pas une expression rationnelle valide" + +#: views.py:185 msgid "" "

Logout successful

You have successfully logged out from the Central " "Authentication Service. For security reasons, exit your web browser." @@ -329,7 +345,7 @@ msgstr "" "d'Authentification. Pour des raisons de sécurité, veuillez fermer votre " "navigateur." -#: views.py:174 +#: views.py:191 #, python-format msgid "" "

Logout successful

You have successfully logged out from %s sessions " @@ -340,7 +356,7 @@ msgstr "" "Service Central d'Authentification. Pour des raisons de sécurité, veuillez " "fermer votre navigateur." -#: views.py:181 +#: views.py:198 msgid "" "

Logout successful

You were already logged out from the Central " "Authentication Service. For security reasons, exit your web browser." @@ -349,7 +365,7 @@ msgstr "" "d'Authentification. Pour des raisons de sécurité, veuillez fermer votre " "navigateur." -#: views.py:361 +#: views.py:378 #, python-format msgid "" "Invalid response from your identity provider CAS upon ticket %(ticket)s " @@ -358,48 +374,48 @@ msgstr "" "Réponse invalide du CAS du fournisseur d'identité lors de la validation du " "ticket %(ticket)s: %(error)r" -#: views.py:483 +#: views.py:500 msgid "Invalid login ticket, please retry to login" msgstr "Ticket de connexion invalide, merci de réessayé de vous connecter" -#: views.py:675 +#: views.py:692 #, 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:713 +#: views.py:730 #, python-format -msgid "Service %(url)s non allowed." +msgid "Service %(url)s not allowed." msgstr "le service %(url)s n'est pas autorisé." -#: views.py:720 -msgid "Username non allowed" +#: views.py:737 +msgid "Username not allowed" msgstr "Nom d'utilisateur non authorisé" -#: views.py:727 -msgid "User characteristics non allowed" +#: views.py:744 +msgid "User characteristics not allowed" msgstr "Caractéristique utilisateur non autorisée" -#: views.py:734 +#: views.py:751 #, python-format 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:824 +#: views.py:841 #, 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:831 +#: views.py:848 #, python-format msgid "Authentication required by service %(name)s (%(url)s)." msgstr "Authentification requise par le service %(name)s (%(url)s)." -#: views.py:838 +#: views.py:855 #, python-format -msgid "Service %s non allowed" +msgid "Service %s not allowed" msgstr "Le service %s n'est pas autorisé" #~ msgid "Logged" diff --git a/cas_server/management/commands/cas_clean_tickets.py b/cas_server/management/commands/cas_clean_tickets.py index 87d802e..3111016 100644 --- a/cas_server/management/commands/cas_clean_tickets.py +++ b/cas_server/management/commands/cas_clean_tickets.py @@ -19,7 +19,7 @@ from ... import models class Command(BaseCommand): """Clean old trickets""" args = '' - help = _(u"Clean old trickets") + help = _(u"Clean old tickets") def handle(self, *args, **options): models.User.clean_old_entries() diff --git a/cas_server/models.py b/cas_server/models.py index 22aae24..d13f553 100644 --- a/cas_server/models.py +++ b/cas_server/models.py @@ -466,7 +466,8 @@ class ServicePattern(models.Model): "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 '\\'." - ) + ), + validators=[utils.regexpr_validator] ) #: Name of the attribute to transmit as username, if empty the user login is used user_field = models.CharField( @@ -625,7 +626,7 @@ class ReplaceAttributName(models.Model): max_length=255, blank=True, verbose_name=_(u"replace"), - help_text=_(u"name under which the attribute 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 @@ -660,7 +661,8 @@ class FilterAttributValue(models.Model): pattern = models.CharField( max_length=255, verbose_name=_(u"pattern"), - help_text=_(u"a regular expression") + help_text=_(u"a regular expression"), + validators=[utils.regexpr_validator] ) #: ForeignKey to a :class:`ServicePattern`. :class:`FilterAttributValue` instances for a #: :class:`ServicePattern` are accessible thought its :attr:`ServicePattern.filters` @@ -689,7 +691,8 @@ class ReplaceAttributValue(models.Model): pattern = models.CharField( max_length=255, verbose_name=_(u"pattern"), - help_text=_(u"An regular expression maching whats need to be replaced") + help_text=_(u"An regular expression maching whats need to be replaced"), + validators=[utils.regexpr_validator] ) #: The replacement to what is mached by :attr:`pattern`. groups are capture by \\1, \\2 … replace = models.CharField( diff --git a/cas_server/static/cas_server/functions.js b/cas_server/static/cas_server/functions.js index 81a4add..8bbbeaa 100644 --- a/cas_server/static/cas_server/functions.js +++ b/cas_server/static/cas_server/functions.js @@ -31,14 +31,14 @@ function eraseCookie(name) { createCookie(name,"",-1); } -function alert_version(last_version){ +function discard_and_remember(id, cookie_name, token, days=10*365){ jQuery(function( $ ){ - $("#alert-version").click(function( e ){ + $(id).click(function( e ){ e.preventDefault(); - createCookie("cas-alert-version", last_version, 10*365); + createCookie(cookie_name, token, days); }); - if(readCookie("cas-alert-version") === last_version){ - $("#alert-version").parent().hide(); + if(readCookie(cookie_name) === token){ + $(id).parent().hide(); } }); } diff --git a/cas_server/templates/cas_server/base.html b/cas_server/templates/cas_server/base.html index 8a491ca..2a2d8e1 100644 --- a/cas_server/templates/cas_server/base.html +++ b/cas_server/templates/cas_server/base.html @@ -31,10 +31,16 @@
{% if auto_submit %}
{% endfor %} {% if auto_submit %}{% endif %} @@ -71,9 +77,17 @@ - {% if settings.CAS_NEW_VERSION_HTML_WARNING and upgrade_available %} - - {% endif %} + {% block javascript %}{% endblock %} diff --git a/cas_server/templates/cas_server/login.html b/cas_server/templates/cas_server/login.html index ff98c03..51aa19c 100644 --- a/cas_server/templates/cas_server/login.html +++ b/cas_server/templates/cas_server/login.html @@ -15,7 +15,7 @@ {% if auto_submit %}{% endif %} {% endblock %} -{% block javascript %}{% endblock %} +{% endblock %} diff --git a/cas_server/tests/test_utils.py b/cas_server/tests/test_utils.py index 79c3cb2..add692d 100644 --- a/cas_server/tests/test_utils.py +++ b/cas_server/tests/test_utils.py @@ -255,3 +255,9 @@ class UtilsTestCase(TestCase): self.assertIsInstance(result, dict) self.assertIn('applied', result) self.assertIsInstance(result['applied'], datetime.datetime) + + def test_regexpr_validator(self): + """test the function regexpr_validator""" + utils.regexpr_validator("^a$") + with self.assertRaises(utils.ValidationError): + utils.regexpr_validator("[") diff --git a/cas_server/tests/test_view.py b/cas_server/tests/test_view.py index 1297e4a..c623a27 100644 --- a/cas_server/tests/test_view.py +++ b/cas_server/tests/test_view.py @@ -75,6 +75,45 @@ class LoginTestCase(TestCase, BaseServicePattern, CanLogin): response = client.get("/login") self.assertNotIn(b"A new version of the application is available", response.content) + @override_settings(CAS_INFO_MESSAGES_ORDER=["cas_explained"]) + def test_messages_info_box_enabled(self): + """test that the message info-box is displayed then enabled""" + client = Client() + response = client.get("/login") + self.assertIn( + b"The Central Authentication Service grants you access to most of our websites by ", + response.content + ) + + @override_settings(CAS_INFO_MESSAGES_ORDER=[]) + def test_messages_info_box_disabled(self): + """test that the message info-box is not displayed then disabled""" + client = Client() + response = client.get("/login") + self.assertNotIn( + b"The Central Authentication Service grants you access to most of our websites by ", + response.content + ) + + # test1 and test2 are malformed and should be ignored, test3 is ok, test5 do not + # exists and should be ignored + @override_settings(CAS_INFO_MESSAGES_ORDER=["test1", "test2", "test3", "test5"]) + @override_settings(CAS_INFO_MESSAGES={ + "test1": "test", # not a dict, should be ignored + "test2": {"type": "success"}, # not "message" key, should be ignored + "test3": {"message": "test3"}, + "test4": {"message": "test4"}, + }) + def test_messages_info_box_bad_messages(self): + """test that mal formated messages dict are ignored""" + client = Client() + # not errors should be raises + response = client.get("/login") + # test3 is ok est should be there + self.assertIn(b"test3", response.content) + # test4 is not in CAS_INFO_MESSAGES_ORDER and should not be there + self.assertNotIn(b"test4", 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 @@ -234,7 +273,7 @@ class LoginTestCase(TestCase, BaseServicePattern, CanLogin): response = client.get("/login?service=https://www.example.net") self.assertEqual(response.status_code, 200) # we warn the user that https://www.example.net is not an allowed service url - self.assertTrue(b"Service https://www.example.net non allowed" in response.content) + self.assertTrue(b"Service https://www.example.net not allowed" in response.content) def test_view_login_get_auth_allowed_service(self): """Request a ticket for an allowed service by an authenticated client""" @@ -280,7 +319,7 @@ class LoginTestCase(TestCase, BaseServicePattern, CanLogin): self.assertEqual(response.status_code, 200) # we warn the user that https://www.example.net is not an allowed service url # NO ticket are created - self.assertTrue(b"Service https://www.example.org non allowed" in response.content) + self.assertTrue(b"Service https://www.example.org not allowed" in response.content) def test_user_logged_not_in_db(self): """If the user is logged but has been delete from the database, it should be logged out""" @@ -314,7 +353,7 @@ class LoginTestCase(TestCase, BaseServicePattern, CanLogin): response = client.get("/login", {'service': self.service_restrict_user_fail}) self.assertEqual(response.status_code, 200) # the ticket is not created and a warning is displayed to the user - self.assertTrue(b"Username non allowed" in response.content) + self.assertTrue(b"Username not allowed" in response.content) # same but with the tes user username being one of the allowed usernames response = client.get("/login", {'service': self.service_restrict_user_success}) @@ -337,7 +376,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 characteristics non allowed" in response.content) + self.assertTrue(b"User characteristics not 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}) @@ -546,7 +585,7 @@ class LoginTestCase(TestCase, BaseServicePattern, CanLogin): self.assertEqual(data["messages"][0]["level"], "error") self.assertEqual( data["messages"][0]["message"], - "Service https://www.example.org non allowed." + "Service https://www.example.org not allowed." ) @override_settings(CAS_ENABLE_AJAX_AUTH=True) diff --git a/cas_server/utils.py b/cas_server/utils.py index eb04a31..8817b22 100644 --- a/cas_server/utils.py +++ b/cas_server/utils.py @@ -18,7 +18,10 @@ from django.contrib import messages from django.contrib.messages import constants as DEFAULT_MESSAGE_LEVELS from django.core.serializers.json import DjangoJSONEncoder from django.utils import timezone +from django.core.exceptions import ValidationError +from django.utils.translation import ugettext_lazy as _ +import re import random import string import json @@ -61,6 +64,7 @@ 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 @@ -69,6 +73,27 @@ def context(params): params["upgrade_available"] = decode_version(VERSION) < decode_version(LAST_VERSION) else: params["upgrade_available"] = False + + if settings.CAS_INFO_MESSAGES_ORDER: + params["CAS_INFO_RENDER"] = [] + for msg_name in settings.CAS_INFO_MESSAGES_ORDER: + if msg_name in settings.CAS_INFO_MESSAGES: + if not isinstance(settings.CAS_INFO_MESSAGES[msg_name], dict): + continue + msg = settings.CAS_INFO_MESSAGES[msg_name].copy() + if "message" in msg: + msg["name"] = msg_name + # use info as default infox type + msg["type"] = msg.get("type", "info") + # make box discardable by default + msg["discardable"] = msg.get("discardable", True) + msg_hash = ( + six.text_type(msg["message"]).encode("utf-8") + + msg["type"].encode("utf-8") + ) + # hash depend of the rendering language + msg["hash"] = hashlib.md5(msg_hash).hexdigest() + params["CAS_INFO_RENDER"].append(msg) return params @@ -700,3 +725,19 @@ def logout_request(ticket): 'datetime': timezone.now().isoformat(), 'ticket': ticket } + + +def regexpr_validator(value): + """ + Test that ``value`` is a valid regular expression + + :param unicode value: A regular expression to test + :raises ValidationError: if ``value`` is not a valid regular expression + """ + try: + re.compile(value) + except re.error: + raise ValidationError( + _('"%(value)s" is not a valid regular expression'), + params={'value': value} + ) diff --git a/cas_server/views.py b/cas_server/views.py index f9be770..c2b18b4 100644 --- a/cas_server/views.py +++ b/cas_server/views.py @@ -727,21 +727,21 @@ class LoginView(View, LogoutMixin): messages.add_message( self.request, messages.ERROR, - _(u'Service %(url)s non allowed.') % {'url': self.service} + _(u'Service %(url)s not allowed.') % {'url': self.service} ) except models.BadUsername: error = 2 messages.add_message( self.request, messages.ERROR, - _(u"Username non allowed") + _(u"Username not allowed") ) except models.BadFilter: error = 3 messages.add_message( self.request, messages.ERROR, - _(u"User characteristics non allowed") + _(u"User characteristics not allowed") ) except models.UserFieldNotDefined: error = 4 @@ -852,7 +852,7 @@ class LoginView(View, LogoutMixin): messages.add_message( self.request, messages.ERROR, - _(u'Service %s non allowed') % self.service + _(u'Service %s not allowed') % self.service ) if self.ajax: data = { diff --git a/docs/CHANGELOG.rst b/docs/CHANGELOG.rst new file mode 100644 index 0000000..565b052 --- /dev/null +++ b/docs/CHANGELOG.rst @@ -0,0 +1 @@ +.. include:: ../CHANGELOG.rst diff --git a/docs/index.rst b/docs/index.rst index 7062ab0..7ad7ed1 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -14,6 +14,11 @@ Contents: README package/cas_server +.. toctree:: + :maxdepth: 2 + + CHANGELOG + Indices and tables ================== diff --git a/tox.ini b/tox.ini index 401c249..4ab3f52 100644 --- a/tox.ini +++ b/tox.ini @@ -101,6 +101,7 @@ deps= skip_install=True commands= rst2html.py --strict {toxinidir}/README.rst /dev/null + rst2html.py --halt=warning {toxinidir}/CHANGELOG.rst /dev/null {[post_cmd]commands} whitelist_externals={[post_cmd]whitelist_externals}