Merge pull request #22 from nitmir/dev
Update version to 0.8.0 Added ----- * Add a test for login with missing parameter (username or password or both) * Add ldap auth using bind method (use the user credentials to bind the the ldap server and let the server check the credentials) * Add CAS_TGT_VALIDITY parameter: Max time after with the user MUST reauthenticate. Fixed ----- * Allow both unicode and bytes dotted string in utils.import_attr * Fix some spelling and grammar on log messages. (thanks to Allie Micka) * Fix froms css class error on success/error due to a scpaless block * Disable pip cache then installing with make install Changed ------- * Update french translation
This commit is contained in:
commit
aa2a35b279
21 changed files with 434 additions and 83 deletions
|
@ -2,24 +2,25 @@
|
|||
BASEDIR="$1"
|
||||
PROJECT_NAME="$2"
|
||||
|
||||
cd "$BASEDIR/htmlcov/"; tar czf "$BASEDIR/coverage.tar.gz" ./
|
||||
|
||||
|
||||
cd "$BASEDIR"
|
||||
TITLE="Coverage report of $PROJECT_NAME"
|
||||
|
||||
# build by gitlab CI
|
||||
if [ -n "$CI_BUILD_REF_NAME" ]; then
|
||||
BRANCH="$CI_BUILD_REF_NAME"
|
||||
TITLE="$TITLE, $BRANCH branch"
|
||||
# build by travis
|
||||
elif [ -n "$TRAVIS_BRANCH" ]; then
|
||||
# if this a pull request ?
|
||||
if [[ "$TRAVIS_PULL_REQUEST" != "false" ]]; then
|
||||
BRANCH="pull-request-$TRAVIS_PULL_REQUEST"
|
||||
TITLE="$TITLE, pull request n°$BRANCH"
|
||||
else
|
||||
BRANCH="$TRAVIS_BRANCH"
|
||||
TITLE="$TITLE, $BRANCH branch"
|
||||
fi
|
||||
else
|
||||
BRANCH="$(git rev-parse --abbrev-ref HEAD)"
|
||||
TITLE="$TITLE, $BRANCH branch"
|
||||
fi
|
||||
|
||||
if [[ "$BRANCH" = "HEAD" ]] || [ -z "$BRANCH" ]; then
|
||||
|
@ -27,7 +28,23 @@ if [[ "$BRANCH" = "HEAD" ]] || [ -z "$BRANCH" ]; then
|
|||
exit 0
|
||||
fi
|
||||
|
||||
curl https://badges.genua.fr/local/coverage/ \
|
||||
|
||||
VENV="$(mktemp -d)"
|
||||
HTMLREPORT="$(mktemp -d)"
|
||||
virtualenv "$VENV"
|
||||
"$VENV/bin/pip" install coverage
|
||||
"$VENV/bin/coverage" html --title "$TITLE" --directory "$HTMLREPORT"
|
||||
rm -rf "$VENV"
|
||||
|
||||
|
||||
cd "$HTMLREPORT"; tar czf "$BASEDIR/coverage.tar.gz" ./
|
||||
|
||||
cd "$BASEDIR"
|
||||
|
||||
rm -rf "$HTMLREPORT"
|
||||
|
||||
|
||||
curl https://badges.genua.fr/coverage/ \
|
||||
-F "secret=$COVERAGE_TOKEN" \
|
||||
-F "tar=@$BASEDIR/coverage.tar.gz" \
|
||||
-F "project=$PROJECT_NAME" \
|
||||
|
|
|
@ -6,6 +6,28 @@ All notable changes to this project will be documented in this file.
|
|||
.. contents:: Table of Contents
|
||||
:depth: 2
|
||||
|
||||
v0.8.0 - 2017-03-08
|
||||
===================
|
||||
|
||||
Added
|
||||
-----
|
||||
* Add a test for login with missing parameter (username or password or both)
|
||||
* Add ldap auth using bind method (use the user credentials to bind the the ldap server and let the
|
||||
server check the credentials)
|
||||
* Add CAS_TGT_VALIDITY parameter: Max time after with the user MUST reauthenticate.
|
||||
|
||||
Fixed
|
||||
-----
|
||||
* Allow both unicode and bytes dotted string in utils.import_attr
|
||||
* Fix some spelling and grammar on log messages. (thanks to Allie Micka)
|
||||
* Fix froms css class error on success/error due to a scpaless block
|
||||
* Disable pip cache then installing with make install
|
||||
|
||||
Changed
|
||||
-------
|
||||
* Update french translation
|
||||
|
||||
|
||||
v0.7.4 - 2016-09-07
|
||||
===================
|
||||
|
||||
|
|
2
Makefile
2
Makefile
|
@ -6,7 +6,7 @@ build:
|
|||
|
||||
install: dist
|
||||
pip -V
|
||||
pip install --no-deps --upgrade --force-reinstall --find-links ./dist/django-cas-server-${VERSION}.tar.gz django-cas-server
|
||||
pip install --no-cache-dir --no-deps --upgrade --force-reinstall --find-links ./dist/django-cas-server-${VERSION}.tar.gz django-cas-server
|
||||
|
||||
uninstall:
|
||||
pip uninstall django-cas-server || true
|
||||
|
|
21
README.rst
21
README.rst
|
@ -268,6 +268,11 @@ Authentication settings
|
|||
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_TGT_VALIDITY``: Max time after with the user MUST reauthenticate. Let it to `None` for no
|
||||
max time.This can be used to force refreshing cached informations only available upon user
|
||||
authentication like the user attributes in federation mode or with the ldap auth in bind mode.
|
||||
The default is ``None``.
|
||||
|
||||
* ``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
|
||||
tell requests to use its internal certificat authorities. Settings it to ``False`` should
|
||||
|
@ -416,6 +421,14 @@ Only usefull if you are using the ldap authentication backend:
|
|||
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.
|
||||
* ``"bind``, the user credentials are used to bind to the ldap database and retreive the user
|
||||
attribute. In this mode, the settings ``CAS_LDAP_PASSWORD_ATTR`` and ``CAS_LDAP_PASSWORD_CHARSET``
|
||||
are ignored, and it is the ldap server that perform password check. The counterpart is that
|
||||
the user attributes are only available upon user password check and so are cached for later
|
||||
use. All the other modes directly fetch the user attributes from the database whenever there
|
||||
are needed. This mean that is you use this mode, they can be some difference between the
|
||||
attributes in database and the cached ones if changes happend in the database after the user
|
||||
authentiate. See the parameter ``CAS_TGT_VALIDITY`` to force user to reauthenticate periodically.
|
||||
|
||||
The default is ``"ldap"``.
|
||||
* ``CAS_LDAP_PASSWORD_CHARSET``: Charset the LDAP users passwords was hash with. This is needed to
|
||||
|
@ -585,6 +598,10 @@ to the provider CAS to authenticate. This provider transmit to ``django-cas-serv
|
|||
username and attributes. The user is now logged in on ``django-cas-server`` and can use
|
||||
services using ``django-cas-server`` as CAS.
|
||||
|
||||
In federation mode, the user attributes are cached upon user authentication. See the settings
|
||||
``CAS_TGT_VALIDITY`` to force users to reauthenticate periodically and allow ``django-cas-server``
|
||||
to refresh cached attributes.
|
||||
|
||||
The list of allowed identity providers is defined using the django admin application.
|
||||
With the development server started, visit http://127.0.0.1:8000/admin/ to add identity providers.
|
||||
|
||||
|
@ -638,8 +655,8 @@ You could for example do as bellow::
|
|||
.. |codacy| image:: https://badges.genua.fr/codacy/grade/255c21623d6946ef8802fa7995b61366/master.svg
|
||||
:target: https://www.codacy.com/app/valentin-samir/django-cas-server
|
||||
|
||||
.. |coverage| image:: https://badges.genua.fr/local/coverage/?project=django-cas-server&branch=master
|
||||
:target: https://badges.genua.fr/local/coverage/django-cas-server/master
|
||||
.. |coverage| image:: https://intranet.genua.fr/coverage/badge/django-cas-server/master.svg
|
||||
:target: https://badges.genua.fr/coverage/django-cas-server/master
|
||||
|
||||
.. |doc| image:: https://badges.genua.fr/local/readthedocs/?version=latest
|
||||
:target: http://django-cas-server.readthedocs.io
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
"""A django CAS server application"""
|
||||
|
||||
#: version of the application
|
||||
VERSION = '0.7.4'
|
||||
VERSION = '0.8.0'
|
||||
|
||||
#: path the the application configuration class
|
||||
default_app_config = 'cas_server.apps.CasAppConfig'
|
||||
|
|
|
@ -9,10 +9,12 @@
|
|||
#
|
||||
# (c) 2015-2016 Valentin Samir
|
||||
"""module for the admin interface of the app"""
|
||||
from .default_settings import settings
|
||||
|
||||
from django.contrib import admin
|
||||
from .models import ServiceTicket, ProxyTicket, ProxyGrantingTicket, User, ServicePattern
|
||||
from .models import Username, ReplaceAttributName, ReplaceAttributValue, FilterAttributValue
|
||||
from .models import FederatedIendityProvider
|
||||
from .models import FederatedIendityProvider, FederatedUser, UserAttributes
|
||||
from .forms import TicketForm
|
||||
|
||||
|
||||
|
@ -167,6 +169,33 @@ class FederatedIendityProviderAdmin(admin.ModelAdmin):
|
|||
list_display = ('verbose_name', 'suffix', 'display')
|
||||
|
||||
|
||||
admin.site.register(User, UserAdmin)
|
||||
class FederatedUserAdmin(admin.ModelAdmin):
|
||||
"""
|
||||
Bases: :class:`django.contrib.admin.ModelAdmin`
|
||||
|
||||
:class:`FederatedUser<cas_server.models.FederatedUser>` in admin
|
||||
interface
|
||||
"""
|
||||
#: Fields to display on a object.
|
||||
fields = ('username', 'provider', 'last_update')
|
||||
#: Fields to display on the list of class:`FederatedUserAdmin` objects.
|
||||
list_display = ('username', 'provider', 'last_update')
|
||||
|
||||
|
||||
class UserAttributesAdmin(admin.ModelAdmin):
|
||||
"""
|
||||
Bases: :class:`django.contrib.admin.ModelAdmin`
|
||||
|
||||
:class:`UserAttributes<cas_server.models.UserAttributes>` in admin
|
||||
interface
|
||||
"""
|
||||
#: Fields to display on a object.
|
||||
fields = ('username', '_attributs')
|
||||
|
||||
|
||||
admin.site.register(ServicePattern, ServicePatternAdmin)
|
||||
admin.site.register(FederatedIendityProvider, FederatedIendityProviderAdmin)
|
||||
if settings.DEBUG: # pragma: no branch (we always test with DEBUG True)
|
||||
admin.site.register(User, UserAdmin)
|
||||
admin.site.register(FederatedUser, FederatedUserAdmin)
|
||||
admin.site.register(UserAttributes, UserAttributesAdmin)
|
||||
|
|
|
@ -30,7 +30,7 @@ try: # pragma: no cover
|
|||
except ImportError:
|
||||
ldap3 = None
|
||||
|
||||
from .models import FederatedUser
|
||||
from .models import FederatedUser, UserAttributes
|
||||
from .utils import check_password, dictfetchall
|
||||
|
||||
|
||||
|
@ -49,7 +49,7 @@ class AuthUser(object):
|
|||
|
||||
def test_password(self, password):
|
||||
"""
|
||||
Tests ``password`` agains the user password.
|
||||
Tests ``password`` against the user-supplied password.
|
||||
|
||||
:raises NotImplementedError: always. The method need to be implemented by subclasses
|
||||
"""
|
||||
|
@ -74,7 +74,7 @@ class DummyAuthUser(AuthUser): # pragma: no cover
|
|||
|
||||
def test_password(self, password):
|
||||
"""
|
||||
Tests ``password`` agains the user password.
|
||||
Tests ``password`` against the user-supplied password.
|
||||
|
||||
:param unicode password: a clear text password as submited by the user.
|
||||
:return: always ``False``
|
||||
|
@ -102,7 +102,7 @@ class TestAuthUser(AuthUser):
|
|||
|
||||
def test_password(self, password):
|
||||
"""
|
||||
Tests ``password`` agains the user password.
|
||||
Tests ``password`` against the user-supplied password.
|
||||
|
||||
:param unicode password: a clear text password as submited by the user.
|
||||
:return: ``True`` if :attr:`username<AuthUser.username>` is valid and
|
||||
|
@ -149,7 +149,7 @@ class MysqlAuthUser(DBAuthUser): # pragma: no cover
|
|||
"""
|
||||
DEPRECATED, use :class:`SqlAuthUser` instead.
|
||||
|
||||
A mysql authentication class: authenticate user agains a mysql database
|
||||
A mysql authentication class: authenticate user against 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
|
||||
|
@ -188,7 +188,7 @@ class MysqlAuthUser(DBAuthUser): # pragma: no cover
|
|||
|
||||
def test_password(self, password):
|
||||
"""
|
||||
Tests ``password`` agains the user password.
|
||||
Tests ``password`` against the user-supplied 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
|
||||
|
@ -208,7 +208,7 @@ class MysqlAuthUser(DBAuthUser): # pragma: no cover
|
|||
|
||||
class SqlAuthUser(DBAuthUser): # pragma: no cover
|
||||
"""
|
||||
A SQL authentication class: authenticate user agains a SQL database. The SQL database
|
||||
A SQL authentication class: authenticate user against 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>`
|
||||
|
@ -238,7 +238,7 @@ class SqlAuthUser(DBAuthUser): # pragma: no cover
|
|||
|
||||
def test_password(self, password):
|
||||
"""
|
||||
Tests ``password`` agains the user password.
|
||||
Tests ``password`` against the user-supplied 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
|
||||
|
@ -284,6 +284,10 @@ class LdapAuthUser(DBAuthUser): # pragma: no cover
|
|||
def __init__(self, username):
|
||||
if not ldap3:
|
||||
raise RuntimeError("Please install ldap3 before using the LdapAuthUser backend")
|
||||
if not settings.CAS_LDAP_BASE_DN:
|
||||
raise ValueError(
|
||||
"You must define CAS_LDAP_BASE_DN for using the ldap authentication backend"
|
||||
)
|
||||
# in case we got deconnected from the database, retry to connect 2 times
|
||||
for retry_nb in range(3):
|
||||
try:
|
||||
|
@ -294,6 +298,8 @@ class LdapAuthUser(DBAuthUser): # pragma: no cover
|
|||
attributes=ldap3.ALL_ATTRIBUTES
|
||||
) and len(conn.entries) == 1:
|
||||
user = conn.entries[0].entry_get_attributes_dict()
|
||||
# store the user dn
|
||||
user["dn"] = conn.entries[0].entry_get_dn()
|
||||
if user.get(settings.CAS_LDAP_USERNAME_ATTR):
|
||||
self.user = user
|
||||
super(LdapAuthUser, self).__init__(user[settings.CAS_LDAP_USERNAME_ATTR][0])
|
||||
|
@ -308,14 +314,41 @@ class LdapAuthUser(DBAuthUser): # pragma: no cover
|
|||
|
||||
def test_password(self, password):
|
||||
"""
|
||||
Tests ``password`` agains the user password.
|
||||
Tests ``password`` against the user-supplied 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):
|
||||
if settings.CAS_LDAP_PASSWORD_CHECK == "bind":
|
||||
try:
|
||||
conn = ldap3.Connection(
|
||||
settings.CAS_LDAP_SERVER,
|
||||
self.user["dn"],
|
||||
password,
|
||||
auto_bind=True
|
||||
)
|
||||
try:
|
||||
# fetch the user attribute
|
||||
if conn.search(
|
||||
settings.CAS_LDAP_BASE_DN,
|
||||
settings.CAS_LDAP_USER_QUERY % ldap3.utils.conv.escape_bytes(self.username),
|
||||
attributes=ldap3.ALL_ATTRIBUTES
|
||||
) and len(conn.entries) == 1:
|
||||
attributes = conn.entries[0].entry_get_attributes_dict()
|
||||
attributes["dn"] = conn.entries[0].entry_get_dn()
|
||||
# cache the attributes locally as we wont have access to the user password
|
||||
# later.
|
||||
user = UserAttributes.objects.get_or_create(username=self.username)[0]
|
||||
user.attributs = attributes
|
||||
user.save()
|
||||
finally:
|
||||
conn.unbind()
|
||||
return True
|
||||
except (ldap3.LDAPBindError, ldap3.LDAPCommunicationError):
|
||||
return False
|
||||
elif self.user and self.user.get(settings.CAS_LDAP_PASSWORD_ATTR):
|
||||
return check_password(
|
||||
settings.CAS_LDAP_PASSWORD_CHECK,
|
||||
password,
|
||||
|
@ -325,6 +358,22 @@ class LdapAuthUser(DBAuthUser): # 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
|
||||
:raises NotImplementedError: if the password check method in `CAS_LDAP_PASSWORD_CHECK`
|
||||
do not allow to fetch the attributes without the user credentials.
|
||||
"""
|
||||
if settings.CAS_LDAP_PASSWORD_CHECK == "bind":
|
||||
raise NotImplementedError()
|
||||
else:
|
||||
return super(LdapAuthUser, self).attributs()
|
||||
|
||||
|
||||
class DjangoAuthUser(AuthUser): # pragma: no cover
|
||||
"""
|
||||
|
@ -347,7 +396,7 @@ class DjangoAuthUser(AuthUser): # pragma: no cover
|
|||
|
||||
def test_password(self, password):
|
||||
"""
|
||||
Tests ``password`` agains the user password.
|
||||
Tests ``password`` against the user-supplied password.
|
||||
|
||||
:param unicode password: a clear text password as submited by the user.
|
||||
:return: ``True`` if :attr:`user` is valid and ``password`` is
|
||||
|
@ -426,7 +475,7 @@ class CASFederateAuth(AuthUser):
|
|||
|
||||
def test_password(self, ticket):
|
||||
"""
|
||||
Tests ``password`` agains the user password.
|
||||
Tests ``password`` against the user-supplied password.
|
||||
|
||||
:param unicode password: The CAS tickets just used to validate the user authentication
|
||||
against its CAS backend.
|
||||
|
|
|
@ -58,6 +58,10 @@ CAS_SLO_MAX_PARALLEL_REQUESTS = 10
|
|||
CAS_SLO_TIMEOUT = 5
|
||||
#: Shared to transmit then using the view :class:`cas_server.views.Auth`
|
||||
CAS_AUTH_SHARED_SECRET = ''
|
||||
#: Max time after with the user MUST reauthenticate. Let it to `None` for no max time.
|
||||
#: This can be used to force refreshing cached informations only available upon user authentication
|
||||
#: like the user attributes in federation mode or with the ldap auth in bind mode.
|
||||
CAS_TGT_VALIDITY = None
|
||||
|
||||
|
||||
#: Number of seconds the service tickets and proxy tickets are valid. This is the maximal time
|
||||
|
|
|
@ -69,7 +69,7 @@ class CASFederateValidateUser(object):
|
|||
|
||||
def verify_ticket(self, ticket):
|
||||
"""
|
||||
test ``ticket`` agains the CAS provider, if valid, create a
|
||||
test ``ticket`` against the CAS provider, if valid, create a
|
||||
:class:`FederatedUser<cas_server.models.FederatedUser>` matching provider returned
|
||||
username and attributes.
|
||||
|
||||
|
|
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-08-24 17:18+0200\n"
|
||||
"PO-Revision-Date: 2016-08-24 17:18+0200\n"
|
||||
"POT-Creation-Date: 2016-09-18 11:29+0200\n"
|
||||
"PO-Revision-Date: 2016-09-18 11:30+0200\n"
|
||||
"Last-Translator: Valentin Samir <valentin.samir@crans.org>\n"
|
||||
"Language-Team: django <LL@li.org>\n"
|
||||
"Language: fr\n"
|
||||
|
@ -16,7 +16,7 @@ msgstr ""
|
|||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
"X-Generator: Poedit 1.8.8\n"
|
||||
"X-Generator: Poedit 1.8.9\n"
|
||||
|
||||
#: apps.py:25 templates/cas_server/base.html:7
|
||||
#: templates/cas_server/base.html:26
|
||||
|
@ -34,37 +34,37 @@ msgstr ""
|
|||
"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
|
||||
#: forms.py:85
|
||||
msgid "Identity provider"
|
||||
msgstr "fournisseur d'identité"
|
||||
|
||||
#: forms.py:88 forms.py:107
|
||||
#: forms.py:89 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:92
|
||||
#: forms.py:93
|
||||
msgid "Remember the identity provider"
|
||||
msgstr "Se souvenir du fournisseur d'identité"
|
||||
|
||||
#: forms.py:102 models.py:594
|
||||
#: forms.py:104 models.py:594
|
||||
msgid "username"
|
||||
msgstr "nom d'utilisateur"
|
||||
|
||||
#: forms.py:104
|
||||
#: forms.py:108
|
||||
msgid "password"
|
||||
msgstr "mot de passe"
|
||||
|
||||
#: forms.py:126
|
||||
#: forms.py:131
|
||||
msgid "The credentials you provided cannot be determined to be authentic."
|
||||
msgstr "Les informations transmises n'ont pas permis de vous authentifier."
|
||||
|
||||
#: forms.py:178
|
||||
#: forms.py:183
|
||||
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:192
|
||||
#: forms.py:197
|
||||
msgid "service"
|
||||
msgstr "service"
|
||||
|
||||
|
@ -331,7 +331,7 @@ msgstr "Connexion"
|
|||
msgid "Connect to the service"
|
||||
msgstr "Se connecter au service"
|
||||
|
||||
#: utils.py:736
|
||||
#: utils.py:744
|
||||
#, python-format
|
||||
msgid "\"%(value)s\" is not a valid regular expression"
|
||||
msgstr "\"%(value)s\" n'est pas une expression rationnelle valide"
|
||||
|
@ -339,7 +339,7 @@ msgstr "\"%(value)s\" n'est pas une expression rationnelle valide"
|
|||
#: views.py:185
|
||||
msgid ""
|
||||
"<h3>Logout successful</h3>You have successfully logged out from the Central "
|
||||
"Authentication Service. For security reasons, exit your web browser."
|
||||
"Authentication Service. For security reasons, close your web browser."
|
||||
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 "
|
||||
|
@ -349,7 +349,7 @@ msgstr ""
|
|||
#, 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 "
|
||||
"of the Central Authentication Service. For security reasons, close your web "
|
||||
"browser."
|
||||
msgstr ""
|
||||
"<h3>Déconnexion réussie</h3>Vous vous êtes déconnecté(e) de %s sessions du "
|
||||
|
@ -359,7 +359,7 @@ msgstr ""
|
|||
#: views.py:198
|
||||
msgid ""
|
||||
"<h3>Logout successful</h3>You were already logged out from the Central "
|
||||
"Authentication Service. For security reasons, exit your web browser."
|
||||
"Authentication Service. For security reasons, close your web browser."
|
||||
msgstr ""
|
||||
"<h3>Déconnexion réussie</h3>Vous étiez déjà déconnecté(e) du Service Central "
|
||||
"d'Authentification. Pour des raisons de sécurité, veuillez fermer votre "
|
||||
|
@ -375,7 +375,7 @@ msgstr ""
|
|||
"ticket %(ticket)s: %(error)r"
|
||||
|
||||
#: views.py:500
|
||||
msgid "Invalid login ticket, please retry to login"
|
||||
msgid "Invalid login ticket, please try to log in again"
|
||||
msgstr "Ticket de connexion invalide, merci de réessayé de vous connecter"
|
||||
|
||||
#: views.py:692
|
||||
|
|
|
@ -23,4 +23,5 @@ class Command(BaseCommand):
|
|||
|
||||
def handle(self, *args, **options):
|
||||
models.User.clean_deleted_sessions()
|
||||
models.UserAttributes.clean_old_entries()
|
||||
models.NewVersionWarning.send_mails()
|
||||
|
|
38
cas_server/migrations/0011_auto_20161007_1258.py
Normal file
38
cas_server/migrations/0011_auto_20161007_1258.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.1 on 2016-10-07 12:58
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cas_server', '0010_auto_20160824_2112'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='UserAttributes',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('_attributs', models.TextField(blank=True, default=None, null=True)),
|
||||
('username', models.CharField(max_length=155, unique=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'User attributes cache',
|
||||
'verbose_name_plural': 'User attributes caches',
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='federateduser',
|
||||
options={'verbose_name': 'Federated user', 'verbose_name_plural': 'Federated users'},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='last_login',
|
||||
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
|
@ -163,6 +163,8 @@ class FederatedUser(JsonAttributes):
|
|||
"""
|
||||
class Meta:
|
||||
unique_together = ("username", "provider")
|
||||
verbose_name = _("Federated user")
|
||||
verbose_name_plural = _("Federated users")
|
||||
#: The user username returned by the CAS backend on successful ticket validation
|
||||
username = models.CharField(max_length=124)
|
||||
#: A foreign key to :class:`FederatedIendityProvider`
|
||||
|
@ -233,6 +235,30 @@ class FederateSLO(models.Model):
|
|||
federate_slo.delete()
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class UserAttributes(JsonAttributes):
|
||||
"""
|
||||
Bases: :class:`JsonAttributes`
|
||||
|
||||
Local cache of the user attributes, used then needed
|
||||
"""
|
||||
class Meta:
|
||||
verbose_name = _("User attributes cache")
|
||||
verbose_name_plural = _("User attributes caches")
|
||||
#: The username of the user for which we cache attributes
|
||||
username = models.CharField(max_length=155, unique=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.username
|
||||
|
||||
@classmethod
|
||||
def clean_old_entries(cls):
|
||||
"""Remove :class:`UserAttributes` for which no more :class:`User` exists."""
|
||||
for user in cls.objects.all():
|
||||
if User.objects.filter(username=user.username).count() == 0:
|
||||
user.delete()
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class User(models.Model):
|
||||
"""
|
||||
|
@ -250,6 +276,8 @@ class User(models.Model):
|
|||
username = models.CharField(max_length=30)
|
||||
#: Last time the authenticated user has do something (auth, fetch ticket, etc…)
|
||||
date = models.DateTimeField(auto_now=True)
|
||||
#: last time the user logged
|
||||
last_login = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
"""
|
||||
|
@ -269,9 +297,12 @@ class User(models.Model):
|
|||
Remove :class:`User` objects inactive since more that
|
||||
:django:setting:`SESSION_COOKIE_AGE` and send corresponding SingleLogOut requests.
|
||||
"""
|
||||
users = cls.objects.filter(
|
||||
date__lt=(timezone.now() - timedelta(seconds=settings.SESSION_COOKIE_AGE))
|
||||
filter = Q(date__lt=(timezone.now() - timedelta(seconds=settings.SESSION_COOKIE_AGE)))
|
||||
if settings.CAS_TGT_VALIDITY is not None:
|
||||
filter |= Q(
|
||||
last_login__lt=(timezone.now() - timedelta(seconds=settings.CAS_TGT_VALIDITY))
|
||||
)
|
||||
users = cls.objects.filter(filter)
|
||||
for user in users:
|
||||
user.logout()
|
||||
users.delete()
|
||||
|
@ -288,9 +319,22 @@ class User(models.Model):
|
|||
def attributs(self):
|
||||
"""
|
||||
Property.
|
||||
A fresh :class:`dict` for the user attributes, using ``settings.CAS_AUTH_CLASS``
|
||||
A fresh :class:`dict` for the user attributes, using ``settings.CAS_AUTH_CLASS`` if
|
||||
possible, and if not, try to fallback to cached attributes (actually only used for ldap
|
||||
auth class with bind password check mthode).
|
||||
"""
|
||||
try:
|
||||
return utils.import_attr(settings.CAS_AUTH_CLASS)(self.username).attributs()
|
||||
except NotImplementedError:
|
||||
try:
|
||||
user = UserAttributes.objects.get(username=self.username)
|
||||
attributes = user.attributs
|
||||
if attributes is not None:
|
||||
return attributes
|
||||
else:
|
||||
return {}
|
||||
except UserAttributes.DoesNotExist:
|
||||
return {}
|
||||
|
||||
def __str__(self):
|
||||
return u"%s - %s" % (self.username, self.session_key)
|
||||
|
@ -433,7 +477,7 @@ class ServicePattern(models.Model):
|
|||
"""
|
||||
Bases: :class:`django.db.models.Model`
|
||||
|
||||
Allowed services pattern agains services are tested to
|
||||
Allowed services pattern against services are tested to
|
||||
"""
|
||||
class Meta:
|
||||
ordering = ("pos", )
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
</div>
|
||||
{% endfor %}
|
||||
{% for field in form %}{% if not field|is_hidden %}
|
||||
<div class="form-group{% spaceless %}
|
||||
<div class="form-group
|
||||
{% if not form.non_field_errors %}
|
||||
{% if field.errors %} has-error
|
||||
{% elif form.cleaned_data %} has-success
|
||||
{% endif %}
|
||||
{% endif %}"
|
||||
{% endspaceless %}>{% spaceless %}
|
||||
>{% spaceless %}
|
||||
{% if field|is_checkbox %}
|
||||
<div class="checkbox"><label for="{{field.auto_id}}">{{field}}{{field.label}}</label></div>
|
||||
{% else %}
|
||||
|
|
29
cas_server/tests/auth.py
Normal file
29
cas_server/tests/auth.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License version 3 for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License version 3
|
||||
# along with this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# (c) 2016 Valentin Samir
|
||||
"""Some test authentication classes for the CAS"""
|
||||
from cas_server import auth
|
||||
|
||||
|
||||
class TestCachedAttributesAuthUser(auth.TestAuthUser):
|
||||
"""
|
||||
A test authentication class only working for one unique user.
|
||||
|
||||
:param unicode username: A username, stored in the :attr:`username<AuthUser.username>`
|
||||
class attribute. The uniq valid value is ``settings.CAS_TEST_USER``.
|
||||
"""
|
||||
def attributs(self):
|
||||
"""
|
||||
The user attributes.
|
||||
|
||||
:raises NotImplementedError: as this class do not support fetching user attributes
|
||||
"""
|
||||
raise NotImplementedError()
|
|
@ -185,6 +185,17 @@ class UserModels(object):
|
|||
).update(date=new_date)
|
||||
return client
|
||||
|
||||
@staticmethod
|
||||
def tgt_expired_user(sec):
|
||||
"""return a user logged since sec seconds"""
|
||||
client = get_auth_client()
|
||||
new_date = timezone.now() - timedelta(seconds=(sec))
|
||||
models.User.objects.filter(
|
||||
username=settings.CAS_TEST_USER,
|
||||
session_key=client.session.session_key
|
||||
).update(last_login=new_date)
|
||||
return client
|
||||
|
||||
@staticmethod
|
||||
def get_user(client):
|
||||
"""return the user associated with an authenticated client"""
|
||||
|
|
|
@ -114,6 +114,24 @@ class FederateSLOTestCase(TestCase, UserModels):
|
|||
models.FederateSLO.objects.get(username="test1@example.com")
|
||||
|
||||
|
||||
@override_settings(CAS_AUTH_CLASS='cas_server.auth.TestAuthUser')
|
||||
class UserAttributesTestCase(TestCase, UserModels):
|
||||
"""test for the user attributes cache model"""
|
||||
def test_clean_old_entries(self):
|
||||
"""test the clean_old_entries methode"""
|
||||
client = get_auth_client()
|
||||
user = self.get_user(client)
|
||||
models.UserAttributes.objects.create(username=settings.CAS_TEST_USER)
|
||||
|
||||
# test that attribute cache is removed for non existant users
|
||||
self.assertEqual(len(models.UserAttributes.objects.all()), 1)
|
||||
models.UserAttributes.clean_old_entries()
|
||||
self.assertEqual(len(models.UserAttributes.objects.all()), 1)
|
||||
user.delete()
|
||||
models.UserAttributes.clean_old_entries()
|
||||
self.assertEqual(len(models.UserAttributes.objects.all()), 0)
|
||||
|
||||
|
||||
@override_settings(CAS_AUTH_CLASS='cas_server.auth.TestAuthUser')
|
||||
class UserTestCase(TestCase, UserModels):
|
||||
"""tests for the user models"""
|
||||
|
@ -144,6 +162,24 @@ class UserTestCase(TestCase, UserModels):
|
|||
# assert the user has being well delete
|
||||
self.assertEqual(len(models.User.objects.all()), 0)
|
||||
|
||||
@override_settings(CAS_TGT_VALIDITY=3600)
|
||||
def test_clean_old_entries_tgt_expired(self):
|
||||
"""test clean_old_entiers with CAS_TGT_VALIDITY set"""
|
||||
# get an authenticated client
|
||||
client = self.tgt_expired_user(settings.CAS_TGT_VALIDITY + 60)
|
||||
# assert the user exists before being cleaned
|
||||
self.assertEqual(len(models.User.objects.all()), 1)
|
||||
# assert the last lofin date is before the expiry date
|
||||
self.assertTrue(
|
||||
self.get_user(client).last_login < (
|
||||
timezone.now() - timedelta(seconds=settings.CAS_TGT_VALIDITY)
|
||||
)
|
||||
)
|
||||
# delete old inactive users
|
||||
models.User.clean_old_entries()
|
||||
# assert the user has being well delete
|
||||
self.assertEqual(len(models.User.objects.all()), 0)
|
||||
|
||||
def test_clean_deleted_sessions(self):
|
||||
"""test clean_deleted_sessions"""
|
||||
# get an authenticated client
|
||||
|
@ -177,6 +213,24 @@ class UserTestCase(TestCase, UserModels):
|
|||
self.assertFalse(models.ServiceTicket.objects.all())
|
||||
self.assertTrue(client2.session.get("authenticated"))
|
||||
|
||||
@override_settings(CAS_AUTH_CLASS='cas_server.tests.auth.TestCachedAttributesAuthUser')
|
||||
def test_cached_attributs(self):
|
||||
"""
|
||||
Test gettting user attributes from cache for auth method that do not support direct
|
||||
fetch (link the ldap bind auth methode)
|
||||
"""
|
||||
client = get_auth_client()
|
||||
user = self.get_user(client)
|
||||
# if no cache is defined, the attributes are empty
|
||||
self.assertEqual(user.attributs, {})
|
||||
user_attr = models.UserAttributes.objects.create(username=settings.CAS_TEST_USER)
|
||||
# if a cache is defined but without atrributes, also empty
|
||||
self.assertEqual(user.attributs, {})
|
||||
user_attr.attributs = settings.CAS_TEST_ATTRIBUTES
|
||||
user_attr.save()
|
||||
# attributes are what is found in the cache
|
||||
self.assertEqual(user.attributs, settings.CAS_TEST_ATTRIBUTES)
|
||||
|
||||
|
||||
@override_settings(CAS_AUTH_CLASS='cas_server.auth.TestAuthUser')
|
||||
class TicketTestCase(TestCase, UserModels, BaseServicePattern):
|
||||
|
|
|
@ -132,6 +132,37 @@ class LoginTestCase(TestCase, BaseServicePattern, CanLogin):
|
|||
# The LoginTicket is conssumed and should no longer be valid
|
||||
self.assertTrue(params['lt'] not in client.session['lt'])
|
||||
|
||||
def test_login_post_missing_params(self):
|
||||
"""Test a login attempt with missing POST parameters (username or password or both)"""
|
||||
# we get a client who fetch a frist time the login page and the login form default
|
||||
# parameters
|
||||
client, params = get_login_page_params()
|
||||
# we set only set username
|
||||
params["username"] = settings.CAS_TEST_USER
|
||||
# we post a login attempt
|
||||
response = client.post('/login', params)
|
||||
# as the LT is not valid, login should fail
|
||||
self.assert_login_failed(client, response)
|
||||
|
||||
# we get a client who fetch a frist time the login page and the login form default
|
||||
# parameters
|
||||
client, params = get_login_page_params()
|
||||
# we set only set password
|
||||
params["password"] = settings.CAS_TEST_PASSWORD
|
||||
# we post a login attempt
|
||||
response = client.post('/login', params)
|
||||
# as the LT is not valid, login should fail
|
||||
self.assert_login_failed(client, response)
|
||||
|
||||
# we get a client who fetch a frist time the login page and the login form default
|
||||
# parameters
|
||||
client, params = get_login_page_params()
|
||||
# we set neither username nor password
|
||||
# we post a login attempt
|
||||
response = client.post('/login', params)
|
||||
# as the LT is not valid, login should fail
|
||||
self.assert_login_failed(client, response)
|
||||
|
||||
def test_login_view_post_goodpass_goodlt_warn(self):
|
||||
"""Test a successul login requesting to be warned before creating services tickets"""
|
||||
# get a client and initial login params
|
||||
|
@ -794,7 +825,7 @@ class LogoutTestCase(TestCase):
|
|||
@override_settings(CAS_ENABLE_AJAX_AUTH=True)
|
||||
def test_ajax_logout(self):
|
||||
"""
|
||||
test ajax logout. These methode are here, but I do not really see an use case for
|
||||
test ajax logout. These methods are here, but I do not really see an use case for
|
||||
javascript logout
|
||||
"""
|
||||
# get a client that is authenticated
|
||||
|
@ -1697,7 +1728,7 @@ class ProxyTestCase(TestCase, BaseServicePattern, XmlContent):
|
|||
self.assert_error(
|
||||
response,
|
||||
"UNAUTHORIZED_SERVICE",
|
||||
'the service %s do not allow proxy ticket' % params['service']
|
||||
'the service %s does not allow proxy tickets' % params['service']
|
||||
)
|
||||
|
||||
self.service_pattern.proxy = True
|
||||
|
@ -1943,7 +1974,7 @@ class SamlValidateTestCase(TestCase, BaseServicePattern, XmlContent):
|
|||
self.assert_error(
|
||||
response,
|
||||
"AuthnFailed",
|
||||
'TARGET %s do not match ticket service' % bad_target
|
||||
'TARGET %s does not match ticket service' % bad_target
|
||||
)
|
||||
|
||||
def test_saml_bad_xml(self):
|
||||
|
|
|
@ -117,14 +117,18 @@ def import_attr(path):
|
|||
transform a python dotted path to the attr
|
||||
|
||||
:param path: A dotted path to a python object or a python object
|
||||
:type path: :obj:`unicode` or anything
|
||||
:type path: :obj:`unicode` or :obj:`str` or anything
|
||||
:return: The python object pointed by the dotted path or the python object unchanged
|
||||
"""
|
||||
if not isinstance(path, str):
|
||||
# if we got a str, decode it to unicode (normally it should only contain ascii)
|
||||
if isinstance(path, six.binary_type):
|
||||
path = path.decode("utf-8")
|
||||
# if path is not an unicode, return it unchanged (may be it is already the attribute to import)
|
||||
if not isinstance(path, six.text_type):
|
||||
return path
|
||||
if "." not in path:
|
||||
if u"." not in path:
|
||||
ValueError("%r should be of the form `module.attr` and we just got `attr`" % path)
|
||||
module, attr = path.rsplit('.', 1)
|
||||
module, attr = path.rsplit(u'.', 1)
|
||||
try:
|
||||
return getattr(import_module(module), attr)
|
||||
except ImportError:
|
||||
|
|
|
@ -61,7 +61,7 @@ class LogoutMixin(object):
|
|||
username = self.request.session.get("username")
|
||||
if username:
|
||||
if all_session:
|
||||
logger.info("Logging out user %s from all of they sessions." % username)
|
||||
logger.info("Logging out user %s from all sessions." % username)
|
||||
else:
|
||||
logger.info("Logging out user %s." % username)
|
||||
users = []
|
||||
|
@ -143,7 +143,7 @@ class LogoutView(View, LogoutMixin):
|
|||
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""
|
||||
methode called on GET request on this view
|
||||
method called on GET request on this view
|
||||
|
||||
:param django.http.HttpRequest request: The current request object
|
||||
"""
|
||||
|
@ -184,20 +184,20 @@ class LogoutView(View, LogoutMixin):
|
|||
logout_msg = _(
|
||||
"<h3>Logout successful</h3>"
|
||||
"You have successfully logged out from the Central Authentication Service. "
|
||||
"For security reasons, exit your web browser."
|
||||
"For security reasons, close your web browser."
|
||||
)
|
||||
elif session_nb > 1:
|
||||
logout_msg = _(
|
||||
"<h3>Logout successful</h3>"
|
||||
"You have successfully logged out from %s sessions of the Central "
|
||||
"Authentication Service. "
|
||||
"For security reasons, exit your web browser."
|
||||
"For security reasons, close your web browser."
|
||||
) % session_nb
|
||||
else:
|
||||
logout_msg = _(
|
||||
"<h3>Logout successful</h3>"
|
||||
"You were already logged out from the Central Authentication Service. "
|
||||
"For security reasons, exit your web browser."
|
||||
"For security reasons, close your web browser."
|
||||
)
|
||||
|
||||
# depending of settings, redirect to the login page with a logout message or display
|
||||
|
@ -229,7 +229,7 @@ class LogoutView(View, LogoutMixin):
|
|||
|
||||
class FederateAuth(CsrfExemptView):
|
||||
"""
|
||||
view to authenticated user agains a backend CAS then CAS_FEDERATE is True
|
||||
view to authenticated user against a backend CAS then CAS_FEDERATE is True
|
||||
|
||||
csrf is disabled for allowing SLO requests reception.
|
||||
"""
|
||||
|
@ -261,7 +261,7 @@ class FederateAuth(CsrfExemptView):
|
|||
"""
|
||||
# if settings.CAS_FEDERATE is not True redirect to the login page
|
||||
if not settings.CAS_FEDERATE:
|
||||
logger.warning("CAS_FEDERATE is False, set it to True to use the federated mode")
|
||||
logger.warning("CAS_FEDERATE is False, set it to True to use federation")
|
||||
return redirect("cas_server:login")
|
||||
# POST with a provider suffix, this is probably an SLO request. csrf is disabled for
|
||||
# allowing SLO requests reception
|
||||
|
@ -305,13 +305,13 @@ class FederateAuth(CsrfExemptView):
|
|||
"""
|
||||
# if settings.CAS_FEDERATE is not True redirect to the login page
|
||||
if not settings.CAS_FEDERATE:
|
||||
logger.warning("CAS_FEDERATE is False, set it to True to use the federated mode")
|
||||
logger.warning("CAS_FEDERATE is False, set it to True to use federation")
|
||||
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") and not renew:
|
||||
logger.warning("User already authenticated, dropping federate authentication request")
|
||||
logger.warning("User already authenticated, dropping federated authentication request")
|
||||
return redirect("cas_server:login")
|
||||
try:
|
||||
# get the identity provider from its suffix
|
||||
|
@ -320,7 +320,7 @@ class FederateAuth(CsrfExemptView):
|
|||
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)
|
||||
logger.info("Trying to authenticate %s again" % auth.provider.server_url)
|
||||
return HttpResponseRedirect(auth.get_login_url())
|
||||
else:
|
||||
ticket = request.GET['ticket']
|
||||
|
@ -360,8 +360,8 @@ class FederateAuth(CsrfExemptView):
|
|||
else:
|
||||
logger.info(
|
||||
(
|
||||
"Got a invalid ticket %s from %s for service %s. "
|
||||
"Retrying to authenticate"
|
||||
"Got an invalid ticket %s from %s for service %s. "
|
||||
"Retrying authentication"
|
||||
) % (
|
||||
ticket,
|
||||
auth.provider.server_url,
|
||||
|
@ -485,7 +485,7 @@ class LoginView(View, LogoutMixin):
|
|||
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""
|
||||
methode called on POST request on this view
|
||||
method called on POST request on this view
|
||||
|
||||
:param django.http.HttpRequest request: The current request object
|
||||
"""
|
||||
|
@ -497,7 +497,7 @@ class LoginView(View, LogoutMixin):
|
|||
messages.add_message(
|
||||
self.request,
|
||||
messages.ERROR,
|
||||
_(u"Invalid login ticket, please retry to login")
|
||||
_(u"Invalid login ticket, please try to log in again")
|
||||
)
|
||||
elif ret == self.USER_LOGIN_OK:
|
||||
# On successful login, update the :class:`models.User<cas_server.models.User>` ``date``
|
||||
|
@ -506,6 +506,7 @@ class LoginView(View, LogoutMixin):
|
|||
username=self.request.session['username'],
|
||||
session_key=self.request.session.session_key
|
||||
)[0]
|
||||
self.user.last_login = timezone.now()
|
||||
self.user.save()
|
||||
elif ret == self.USER_LOGIN_FAILURE: # bad user login
|
||||
if settings.CAS_FEDERATE:
|
||||
|
@ -554,7 +555,7 @@ class LoginView(View, LogoutMixin):
|
|||
"""
|
||||
if not self.check_lt():
|
||||
self.init_form(self.request.POST)
|
||||
logger.warning("Receive an invalid login ticket")
|
||||
logger.warning("Received an invalid login ticket")
|
||||
return self.INVALID_LOGIN_TICKET
|
||||
elif not self.request.session.get("authenticated") or self.renew:
|
||||
# authentication request receive, initialize the form to use
|
||||
|
@ -569,10 +570,10 @@ class LoginView(View, LogoutMixin):
|
|||
logger.info("User %s successfully authenticated" % self.request.session["username"])
|
||||
return self.USER_LOGIN_OK
|
||||
else:
|
||||
logger.warning("A logging attemps failed")
|
||||
logger.warning("A login attempt failed")
|
||||
return self.USER_LOGIN_FAILURE
|
||||
else:
|
||||
logger.warning("Receuve a logging attempt whereas the user is already logged")
|
||||
logger.warning("Received a login attempt for an already-active user")
|
||||
return self.USER_ALREADY_LOGGED
|
||||
|
||||
def init_get(self, request):
|
||||
|
@ -600,7 +601,7 @@ class LoginView(View, LogoutMixin):
|
|||
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""
|
||||
methode called on GET request on this view
|
||||
method called on GET request on this view
|
||||
|
||||
:param django.http.HttpRequest request: The current request object
|
||||
"""
|
||||
|
@ -667,7 +668,7 @@ class LoginView(View, LogoutMixin):
|
|||
|
||||
def service_login(self):
|
||||
"""
|
||||
Perform login agains a service
|
||||
Perform login against a service
|
||||
|
||||
:return:
|
||||
* The rendering of the ``settings.CAS_WARN_TEMPLATE`` if the user asked to be
|
||||
|
@ -801,7 +802,7 @@ class LoginView(View, LogoutMixin):
|
|||
else:
|
||||
return utils.redirect_params("cas_server:login", params=self.request.GET)
|
||||
|
||||
# if login agains a service
|
||||
# if login against a service
|
||||
if self.service:
|
||||
return self.service_login()
|
||||
# else display the logged template
|
||||
|
@ -949,7 +950,7 @@ class Auth(CsrfExemptView):
|
|||
@staticmethod
|
||||
def post(request):
|
||||
"""
|
||||
methode called on POST request on this view
|
||||
method called on POST request on this view
|
||||
|
||||
:param django.http.HttpRequest request: The current request object
|
||||
:return: ``HttpResponse(u"yes\\n")`` if the POSTed tuple (username, password, service)
|
||||
|
@ -1005,7 +1006,7 @@ class Validate(View):
|
|||
@staticmethod
|
||||
def get(request):
|
||||
"""
|
||||
methode called on GET request on this view
|
||||
method called on GET request on this view
|
||||
|
||||
:param django.http.HttpRequest request: The current request object
|
||||
:return:
|
||||
|
@ -1116,7 +1117,7 @@ class ValidateService(View):
|
|||
|
||||
def get(self, request):
|
||||
"""
|
||||
methode called on GET request on this view
|
||||
method called on GET request on this view
|
||||
|
||||
:param django.http.HttpRequest request: The current request object:
|
||||
:return: The rendering of ``cas_server/serviceValidate.xml`` if no errors is raised,
|
||||
|
@ -1284,7 +1285,7 @@ class Proxy(View):
|
|||
|
||||
def get(self, request):
|
||||
"""
|
||||
methode called on GET request on this view
|
||||
method called on GET request on this view
|
||||
|
||||
:param django.http.HttpRequest request: The current request object:
|
||||
:return: The returned value of :meth:`process_proxy` if no error is raised,
|
||||
|
@ -1323,7 +1324,7 @@ class Proxy(View):
|
|||
if not pattern.proxy:
|
||||
raise ValidateError(
|
||||
u'UNAUTHORIZED_SERVICE',
|
||||
u'the service %s do not allow proxy ticket' % self.target_service
|
||||
u'the service %s does not allow proxy tickets' % self.target_service
|
||||
)
|
||||
# is the proxy granting ticket valid
|
||||
ticket = ProxyGrantingTicket.get(self.pgt)
|
||||
|
@ -1387,7 +1388,7 @@ class SamlValidate(CsrfExemptView):
|
|||
|
||||
def post(self, request):
|
||||
"""
|
||||
methode called on POST request on this view
|
||||
method called on POST request on this view
|
||||
|
||||
:param django.http.HttpRequest request: The current request object
|
||||
:return: the rendering of ``cas_server/samlValidate.xml`` if no error is raised,
|
||||
|
@ -1417,7 +1418,7 @@ class SamlValidate(CsrfExemptView):
|
|||
)
|
||||
)
|
||||
logger.debug(
|
||||
"SamlValidate: User attributs are:\n%s" % pprint.pformat(self.ticket.attributs)
|
||||
"SamlValidate: User attributes are:\n%s" % pprint.pformat(self.ticket.attributs)
|
||||
)
|
||||
|
||||
return render(
|
||||
|
@ -1446,7 +1447,7 @@ class SamlValidate(CsrfExemptView):
|
|||
if ticket.service != self.target:
|
||||
raise SamlValidateError(
|
||||
u'AuthnFailed',
|
||||
u'TARGET %s do not match ticket service' % self.target
|
||||
u'TARGET %s does not match ticket service' % self.target
|
||||
)
|
||||
return ticket
|
||||
except (IndexError, KeyError):
|
||||
|
|
Loading…
Reference in a new issue