Commit 07a537b4 authored by Valentin Samir's avatar Valentin Samir Committed by GitHub

Update version to 0.6.3

Bugs fixes
----------
* typos in README.rst
* w3c validation

Cleaning
--------
* Code factorisation (models.py, views.py)
* Usage of the documented API for models _meta in auth.DjangoAuthUser

Whats new
---------
* Add powered by footer
* set warn cookie using javascript if possible
* Unfold many to many attributes in auth.DjangoAuthUser attributes
* Add a github version badge
* documents templatetags
parents 1a04b5af 84d0d267
CAS Server CAS Server
########## ##########
|travis| |version| |lisence| |codacy| |coverage| |doc| |travis| |coverage| |licence| |github_version| |pypi_version| |codacy| |doc|
CAS Server is a Django application implementing the `CAS Protocol 3.0 Specification CAS Server is a Django application implementing the `CAS Protocol 3.0 Specification
<https://apereo.github.io/cas/4.2.x/protocol/CAS-Protocol-Specification.html>`_. <https://apereo.github.io/cas/4.2.x/protocol/CAS-Protocol-Specification.html>`_.
...@@ -206,6 +206,7 @@ Template settings ...@@ -206,6 +206,7 @@ Template settings
templates. Set it to ``False`` to disable it. templates. Set it to ``False`` to disable it.
* ``CAS_FAVICON_URL``: URL to the favicon (shortcut icon) used by the default templates. * ``CAS_FAVICON_URL``: URL to the favicon (shortcut icon) used by the default templates.
Default is a key icon. Set it to ``False`` to disable it. 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 * ``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"``, and it must have the five following keys: ``"bootstrap3_css"``, ``"bootstrap3_js"``,
``"html5shiv"``, ``"respond"``, ``"jquery"``. The default is:: ``"html5shiv"``, ``"respond"``, ``"jquery"``. The default is::
...@@ -603,10 +604,13 @@ You could for example do as bellow : ...@@ -603,10 +604,13 @@ You could for example do as bellow :
.. |travis| image:: https://badges.genua.fr/travis/nitmir/django-cas-server/master.svg .. |travis| image:: https://badges.genua.fr/travis/nitmir/django-cas-server/master.svg
:target: https://travis-ci.org/nitmir/django-cas-server :target: https://travis-ci.org/nitmir/django-cas-server
.. |version| image:: https://badges.genua.fr/pypi/v/django-cas-server.svg .. |pypi_version| image:: https://badges.genua.fr/pypi/v/django-cas-server.svg
:target: https://pypi.python.org/pypi/django-cas-server :target: https://pypi.python.org/pypi/django-cas-server
.. |lisence| image:: https://badges.genua.fr/pypi/l/django-cas-server.svg .. |github_version| image:: https://badges.genua.fr/github/tag/nitmir/django-cas-server.svg?label=github
:target: https://github.com/nitmir/django-cas-server/releases/latest
.. |licence| image:: https://badges.genua.fr/pypi/l/django-cas-server.svg
:target: https://www.gnu.org/licenses/gpl-3.0.html :target: https://www.gnu.org/licenses/gpl-3.0.html
.. |codacy| image:: https://badges.genua.fr/codacy/grade/255c21623d6946ef8802fa7995b61366/master.svg .. |codacy| image:: https://badges.genua.fr/codacy/grade/255c21623d6946ef8802fa7995b61366/master.svg
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
"""A django CAS server application""" """A django CAS server application"""
#: version of the application #: version of the application
VERSION = '0.6.2' VERSION = '0.6.3'
#: path the the application configuration class #: path the the application configuration class
default_app_config = 'cas_server.apps.CasAppConfig' default_app_config = 'cas_server.apps.CasAppConfig'
...@@ -369,8 +369,34 @@ class DjangoAuthUser(AuthUser): # pragma: no cover ...@@ -369,8 +369,34 @@ class DjangoAuthUser(AuthUser): # pragma: no cover
""" """
if self.user: if self.user:
attr = {} attr = {}
for field in self.user._meta.fields: # _meta.get_fields() is from the new documented _meta interface in django 1.8
attr[field.attname] = getattr(self.user, field.attname) try:
field_names = [
field.attname for field in self.user._meta.get_fields()
if hasattr(field, "attname")
]
# backward compatibility with django 1.7
except AttributeError: # pragma: no cover (only used by django 1.7)
field_names = self.user._meta.get_all_field_names()
for name in field_names:
attr[name] = getattr(self.user, name)
# unfold user_permissions many to many relation
if 'user_permissions' in attr:
attr['user_permissions'] = [
(
u"%s.%s" % (
perm.content_type.model_class().__module__,
perm.content_type.model_class().__name__
),
perm.codename
) for perm in attr['user_permissions'].filter()
]
# unfold group many to many relation
if 'groups' in attr:
attr['groups'] = [group.name for group in attr['groups'].filter()]
return attr return attr
else: else:
return {} return {}
......
...@@ -20,6 +20,8 @@ from importlib import import_module ...@@ -20,6 +20,8 @@ from importlib import import_module
CAS_LOGO_URL = static("cas_server/logo.png") CAS_LOGO_URL = static("cas_server/logo.png")
#: URL to the favicon (shortcut icon) used by the default templates. Default is a key icon. #: URL to the favicon (shortcut icon) used by the default templates. Default is a key icon.
CAS_FAVICON_URL = static("cas_server/favicon.ico") CAS_FAVICON_URL = static("cas_server/favicon.ico")
#: Show the powered by footer if set to ``True``
CAS_SHOW_POWERED = True
#: URLs to css and javascript external components. #: URLs to css and javascript external components.
CAS_COMPONENT_URLS = { CAS_COMPONENT_URLS = {
"bootstrap3_css": "//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css", "bootstrap3_css": "//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css",
......
This diff is collapsed.
function alert_version(last_version){
jQuery(function( $ ){
$("#alert-version").click(function( e ){
e.preventDefault();
var date = new Date();
date.setTime(date.getTime()+(10*365*24*60*60*1000));
var expires = "; expires="+date.toGMTString();
document.cookie = "cas-alert-version=" + last_version + expires + "; path=/";
});
var nameEQ="cas-alert-version=";
var ca = document.cookie.split(";");
var value;
for(var i=0;i < ca.length;i++) {
var c = ca[i];
while(c.charAt(0) === " "){
c = c.substring(1,c.length);
}
if(c.indexOf(nameEQ) === 0){
value = c.substring(nameEQ.length,c.length);
}
}
if(value === last_version){
$("#alert-version").parent().hide();
}
});
}
function createCookie(name, value, days){
var expires;
var date;
if(days){
date = new Date();
date.setTime(date.getTime()+(days*24*60*60*1000));
expires = "; expires="+date.toGMTString();
}
else{
expires = "";
}
document.cookie = name + "=" + value + expires + "; path=/";
}
function readCookie(name){
var nameEQ = name + "=";
var ca = document.cookie.split(";");
for(var i=0;i < ca.length;i++) {
var c = ca[i];
while (c.charAt(0) === " "){
c = c.substring(1,c.length);
}
if (c.indexOf(nameEQ) === 0){
return c.substring(nameEQ.length,c.length);
}
}
return null;
}
function eraseCookie(name) {
createCookie(name,"",-1);
}
function alert_version(last_version){
jQuery(function( $ ){
$("#alert-version").click(function( e ){
e.preventDefault();
createCookie("cas-alert-version", last_version, 10*365);
});
if(readCookie("cas-alert-version") === last_version){
$("#alert-version").parent().hide();
}
});
}
html, body {
height: 100%;
}
body { body {
padding-top: 40px; padding-top: 40px;
padding-bottom: 40px; padding-bottom: 0;
background-color: #eee; background-color: #eee;
} }
...@@ -41,6 +44,22 @@ body { ...@@ -41,6 +44,22 @@ body {
width:110px; width:110px;
} }
/* Wrapper for page content to push down footer */
#wrap {
min-height: 100%;
height: auto;
height: 100%;
/* Negative indent footer by it's height */
margin: 0 auto -40px;
}
#footer {
height: 40px;
text-align: center;
}
#footer p {
padding-top: 10px;
}
@media screen and (max-width: 680px) { @media screen and (max-width: 680px) {
#app-name { #app-name {
margin: 0; margin: 0;
......
{% load i18n %} {% load i18n %}{% load staticfiles %}<!DOCTYPE html>
{% load staticfiles %}
<!DOCTYPE html>
<html{% if request.LANGUAGE_CODE %} lang="{{ request.LANGUAGE_CODE }}"{% endif %}> <html{% if request.LANGUAGE_CODE %} lang="{{ request.LANGUAGE_CODE }}"{% endif %}>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
...@@ -18,12 +16,13 @@ ...@@ -18,12 +16,13 @@
<link href="{% static "cas_server/styles.css" %}" rel="stylesheet"> <link href="{% static "cas_server/styles.css" %}" rel="stylesheet">
</head> </head>
<body> <body>
<div id="wrap">
<div class="container"> <div class="container">
{% if auto_submit %}<noscript>{% endif %} {% if auto_submit %}<noscript>{% endif %}
<div class="row"> <div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12"> <div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<h1 id="app-name"> <h1 id="app-name">
{% if settings.CAS_LOGO_URL %}<img src="{{settings.CAS_LOGO_URL}}"></img> {% endif %} {% if settings.CAS_LOGO_URL %}<img src="{{settings.CAS_LOGO_URL}}" alt="cas-logo" />{% endif %}
{% trans "Central Authentication Service" %}</h1> {% trans "Central Authentication Service" %}</h1>
</div> </div>
</div> </div>
...@@ -53,7 +52,7 @@ ...@@ -53,7 +52,7 @@
class="alert alert-danger" class="alert alert-danger"
{% endif %} {% endif %}
{% endspaceless %}> {% endspaceless %}>
{{ message }} {{message|safe}}
</div> </div>
{% endfor %} {% endfor %}
{% if auto_submit %}</noscript>{% endif %} {% if auto_submit %}</noscript>{% endif %}
...@@ -62,11 +61,25 @@ ...@@ -62,11 +61,25 @@
<div class="col-lg-3 col-md-3 col-sm-2 col-xs-0"></div> <div class="col-lg-3 col-md-3 col-sm-2 col-xs-0"></div>
</div> </div>
</div> <!-- /container --> </div> <!-- /container -->
<script src="{{settings.CAS_COMPONENT_URLS.jquery}}"></script> </div>
<script src="{{settings.CAS_COMPONENT_URLS.bootstrap3_js}}"></script> <div style="clear: both;"></div>
{% if settings.CAS_NEW_VERSION_HTML_WARNING and upgrade_available %} {% if settings.CAS_SHOW_POWERED %}
<script src="{% static "cas_server/alert-version.js" %}"></script> <div id="footer">
<script>alert_version("{{LAST_VERSION}}")</script> <p><a class="text-muted" href="https://pypi.python.org/pypi/django-cas-server">django-cas-server powered</a></p>
{% endif %} </div>
{% endif %}
<script src="{{settings.CAS_COMPONENT_URLS.jquery}}"></script>
<script src="{{settings.CAS_COMPONENT_URLS.bootstrap3_js}}"></script>
<script src="{% static "cas_server/functions.js" %}"></script>
{% if settings.CAS_NEW_VERSION_HTML_WARNING and upgrade_available %}
<script type="text/javascript">alert_version("{{LAST_VERSION}}")</script>
{% endif %}
{% block javascript %}{% endblock %}
</body> </body>
</html> </html>
<!--
Powered by django-cas-server version {{VERSION}}
Pypi: https://pypi.python.org/pypi/django-cas-server
github: https://github.com/nitmir/django-cas-server
-->
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
{% endif %}" {% endif %}"
{% endspaceless %}>{% spaceless %} {% endspaceless %}>{% spaceless %}
{% if field|is_checkbox %} {% if field|is_checkbox %}
<div class="checkbox"><label for="{{field.auto_id}}">{{field}}{{field.label}}</label> <div class="checkbox"><label for="{{field.auto_id}}">{{field}}{{field.label}}</label></div>
{% else %} {% else %}
<label class="control-label" for="{{field.auto_id}}">{{field.label}}</label> <label class="control-label" for="{{field.auto_id}}">{{field.label}}</label>
{{field}} {{field}}
......
...@@ -14,10 +14,17 @@ ...@@ -14,10 +14,17 @@
<button class="btn btn-primary btn-block btn-lg" type="submit">{% trans "Login" %}</button> <button class="btn btn-primary btn-block btn-lg" type="submit">{% trans "Login" %}</button>
{% if auto_submit %}</noscript>{% endif %} {% if auto_submit %}</noscript>{% endif %}
</form> </form>
{% if auto_submit %}
<script type="text/javascript">
document.getElementById('login_form').submit(); // SUBMIT FORM
</script>
{% endif %}
{% endblock %} {% endblock %}
{% block javascript %}<script type="text/javascript">
jQuery(function( $ ){
$("#id_warn").click(function(e){
if($("#id_warn").is(':checked')){
createCookie("warn", "on", 10 * 365);
} else {
eraseCookie("warn");
}
});
});{% if auto_submit %}
document.getElementById('login_form').submit(); // SUBMIT FORM{% endif %}
</script>{% endblock %}
...@@ -261,7 +261,7 @@ class FederateAuthLoginLogoutTestCase( ...@@ -261,7 +261,7 @@ class FederateAuthLoginLogoutTestCase(
# SLO for an unkown ticket should do nothing # SLO for an unkown ticket should do nothing
response = client.post( response = client.post(
"/federate/%s" % provider.suffix, "/federate/%s" % provider.suffix,
{'logoutRequest': tests_utils.logout_request(utils.gen_st())} {'logoutRequest': utils.logout_request(utils.gen_st())}
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, b"ok") self.assertEqual(response.content, b"ok")
...@@ -288,7 +288,7 @@ class FederateAuthLoginLogoutTestCase( ...@@ -288,7 +288,7 @@ class FederateAuthLoginLogoutTestCase(
# 3 or 'CAS_2_SAML_1_0' # 3 or 'CAS_2_SAML_1_0'
response = client.post( response = client.post(
"/federate/%s" % provider.suffix, "/federate/%s" % provider.suffix,
{'logoutRequest': tests_utils.logout_request(ticket)} {'logoutRequest': utils.logout_request(ticket)}
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, b"ok") self.assertEqual(response.content, b"ok")
......
# -*- 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
"""tests for the customs template tags"""
from django.test import TestCase
from cas_server import forms
from cas_server.templatetags import cas_server
class TemplateTagsTestCase(TestCase):
"""tests for the customs template tags"""
def test_is_checkbox(self):
"""test for the template filter is_checkbox"""
form = forms.UserCredential()
self.assertFalse(cas_server.is_checkbox(form["username"]))
self.assertTrue(cas_server.is_checkbox(form["warn"]))
def test_is_hidden(self):
"""test for the template filter is_hidden"""
form = forms.UserCredential()
self.assertFalse(cas_server.is_hidden(form["username"]))
self.assertTrue(cas_server.is_hidden(form["lt"]))
...@@ -115,8 +115,8 @@ def get_validated_ticket(service): ...@@ -115,8 +115,8 @@ def get_validated_ticket(service):
client = Client() client = Client()
response = client.get('/validate', {'ticket': ticket.value, 'service': service}) response = client.get('/validate', {'ticket': ticket.value, 'service': service})
assert (response.status_code == 200) assert response.status_code == 200
assert (response.content == b'yes\ntest\n') assert response.content == b'yes\ntest\n'
ticket = models.ServiceTicket.objects.get(value=ticket.value) ticket = models.ServiceTicket.objects.get(value=ticket.value)
return (auth_client, ticket) return (auth_client, ticket)
...@@ -222,6 +222,10 @@ class Http404Handler(HttpParamsHandler): ...@@ -222,6 +222,10 @@ class Http404Handler(HttpParamsHandler):
class DummyCAS(BaseHTTPServer.BaseHTTPRequestHandler): class DummyCAS(BaseHTTPServer.BaseHTTPRequestHandler):
"""A dummy CAS that validate for only one (service, ticket) used in federated mode tests""" """A dummy CAS that validate for only one (service, ticket) used in federated mode tests"""
#: dict of the last receive GET parameters
params = None
def test_params(self): def test_params(self):
"""check that internal and provided (service, ticket) matches""" """check that internal and provided (service, ticket) matches"""
if ( if (
...@@ -340,17 +344,3 @@ class DummyCAS(BaseHTTPServer.BaseHTTPRequestHandler): ...@@ -340,17 +344,3 @@ class DummyCAS(BaseHTTPServer.BaseHTTPRequestHandler):
httpd_thread.daemon = True httpd_thread.daemon = True
httpd_thread.start() httpd_thread.start()
return (httpd, host, port) return (httpd, host, port)
def logout_request(ticket):
"""build a SLO request XML, ready to be send"""
return u"""<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
ID="%(id)s" Version="2.0" IssueInstant="%(datetime)s">
<saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"></saml:NameID>
<samlp:SessionIndex>%(ticket)s</samlp:SessionIndex>
</samlp:LogoutRequest>""" % \
{
'id': utils.gen_saml_id(),
'datetime': timezone.now().isoformat(),
'ticket': ticket
}
...@@ -17,6 +17,7 @@ from django.http import HttpResponseRedirect, HttpResponse ...@@ -17,6 +17,7 @@ from django.http import HttpResponseRedirect, HttpResponse
from django.contrib import messages from django.contrib import messages
from django.contrib.messages import constants as DEFAULT_MESSAGE_LEVELS from django.contrib.messages import constants as DEFAULT_MESSAGE_LEVELS
from django.core.serializers.json import DjangoJSONEncoder from django.core.serializers.json import DjangoJSONEncoder
from django.utils import timezone
import random import random
import string import string
...@@ -680,3 +681,22 @@ def dictfetchall(cursor): ...@@ -680,3 +681,22 @@ def dictfetchall(cursor):
dict(zip(columns, row)) dict(zip(columns, row))
for row in cursor.fetchall() for row in cursor.fetchall()
] ]
def logout_request(ticket):
"""
Forge a SLO logout request
:param unicode ticket: A ticket value
:return: A SLO XML body request
:rtype: unicode
"""
return u"""<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
ID="%(id)s" Version="2.0" IssueInstant="%(datetime)s">
<saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"></saml:NameID>
<samlp:SessionIndex>%(ticket)s</samlp:SessionIndex>
</samlp:LogoutRequest>""" % {
'id': gen_saml_id(),
'datetime': timezone.now().isoformat(),
'ticket': ticket
}
...@@ -45,6 +45,7 @@ logger = logging.getLogger(__name__) ...@@ -45,6 +45,7 @@ logger = logging.getLogger(__name__)
class LogoutMixin(object): class LogoutMixin(object):
"""destroy CAS session utils""" """destroy CAS session utils"""
def logout(self, all_session=False): def logout(self, all_session=False):
""" """
effectively destroy a CAS session effectively destroy a CAS session
...@@ -63,43 +64,59 @@ class LogoutMixin(object): ...@@ -63,43 +64,59 @@ class LogoutMixin(object):
logger.info("Logging out user %s from all of they sessions." % username) logger.info("Logging out user %s from all of they sessions." % username)
else: else:
logger.info("Logging out user %s." % username) logger.info("Logging out user %s." % username)
# logout the user from the current session users = []
# try to get the user from the current session
try: try:
user = models.User.objects.get( users.append(
username=username, models.User.objects.get(
session_key=self.request.session.session_key username=username,
session_key=self.request.session.session_key
)
) )
# flush the session except models.User.DoesNotExist:
# if user not found in database, flush the session anyway
self.request.session.flush() self.request.session.flush()
# If all_session is set, search all of the user sessions
if all_session:
users.extend(
models.User.objects.filter(
username=username
).exclude(
session_key=self.request.session.session_key
)
)
# Iterate over all user sessions that have to be logged out
for user in users:
# get the user session
session = SessionStore(session_key=user.session_key)
# flush the session
session.flush()
# send SLO requests # send SLO requests
user.logout(self.request) user.logout(self.request)
# delete the user # delete the user
user.delete() user.delete()
# increment the destroyed session counter # increment the destroyed session counter
session_nb += 1 session_nb += 1
except models.User.DoesNotExist:
# if user not found in database, flush the session anyway
self.request.session.flush()
# If all_session is set logout user from alternative sessions
if all_session:
# Iterate over all user sessions
for user in models.User.objects.filter(username=username):
# get the user session
session = SessionStore(session_key=user.session_key)
# flush the session
session.flush()
# send SLO requests
user.logout(self.request)
# delete the user
user.delete()
# increment the destroyed session counter
session_nb += 1
if username: if username:
logger.info("User %s logged out" % username) logger.info("User %s logged out" % username)
return session_nb return session_nb
class CsrfExemptView(View):
"""base class for csrf exempt class views"""
@method_decorator(csrf_exempt) # csrf is disabled for allowing SLO requests reception
def dispatch(self, request, *args, **kwargs):
"""
dispatch different http request to the methods of the same name
:param django.http.HttpRequest request: The current request object
"""
return super(CsrfExemptView, self).dispatch(request, *args, **kwargs)
class LogoutView(View, LogoutMixin): class LogoutView(View, LogoutMixin):
"""destroy CAS session (logout) view""" """destroy CAS session (logout) view"""
...@@ -210,17 +227,15 @@ class LogoutView(View, LogoutMixin): ...@@ -210,17 +227,15 @@ class LogoutView(View, LogoutMixin):
) )
class FederateAuth(View): class FederateAuth(CsrfExemptView):
"""view to authenticated user agains a backend CAS then CAS_FEDERATE is True""" """
view to authenticated user agains a backend CAS then CAS_FEDERATE is True
@method_decorator(csrf_exempt) # csrf is disabled for allowing SLO requests reception csrf is disabled for allowing SLO requests reception.
def dispatch(self, request, *args, **kwargs): """
"""
dispatch different http request to the methods of the same name
:param django.http.HttpRequest request: The current request object #: current URL used as service URL by the CAS client
""" service_url = None
return super(FederateAuth, self).dispatch(request, *args, **kwargs)
def get_cas_client(self, request, provider, renew=False): def get_cas_client(self, request, provider, renew=False):
""" """
...@@ -285,7 +300,7 @@ class FederateAuth(View): ...@@ -285,7 +300,7 @@ class FederateAuth(View):
""" """
method called on GET request method called on GET request
:param django.http.HttpRequest request: The current request object :param django.http.HttpRequestself. request: The current request object
:param unicode provider: Optional parameter. The user provider suffix. :param unicode provider: Optional parameter. The user provider suffix.
""" """
# if settings.CAS_FEDERATE is not True redirect to the login page # if settings.CAS_FEDERATE is not True redirect to the login page
...@@ -923,18 +938,13 @@ class LoginView(View, LogoutMixin): ...@@ -923,18 +938,13 @@ class LoginView(View, LogoutMixin):
return self.not_authenticated() return self.not_authenticated()
class Auth(View): class Auth(CsrfExemptView):
"""A simple view to validate username/password/service tuple""" """
# csrf is disable as it is intended to be used by programs. Security is assured by a shared A simple view to validate username/password/service tuple
# secret between the programs dans django-cas-server.
@method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
"""
dispatch requests based on method GET, POST, ...
:param django.http.HttpRequest request: The current request object csrf is disable as it is intended to be used by programs. Security is assured by a shared
""" secret between the programs dans django-cas-server.
return super(Auth, self).dispatch(request, *args, **kwargs) """
@staticmethod @staticmethod
def post(request): def post(request):
...@@ -1041,8 +1051,9 @@ class Validate(View): ...@@ -1041,8 +1051,9 @@ class Validate(View):
@python_2_unicode_compatible @python_2_unicode_compatible
class ValidateError(Exception): class ValidationBaseError(Exception):
"""handle service validation error""" """Base class for both saml and cas validation error"""
#: The error code #: The error code
code = None code = None
#: The error message #: The error message
...@@ -1051,7 +1062,7 @@ class ValidateError(Exception): ...@@ -1051,7 +1062,7 @@ class ValidateError(Exception):
def __init__(self, code, msg=""): def __init__(self, code, msg=""):
self.code = code self.code = code
self.msg = msg self.msg = msg
super(ValidateError, self).__init__(code) super(ValidationBaseError, self).__init__(code)
def __str__(self): def __str__(self):
return u"%s" % self.msg return u"%s" % self.msg
...@@ -1066,12 +1077,27 @@ class ValidateError(Exception): ...@@ -1066,12 +1077,27 @@ class ValidateError(Exception):
""" """
return render( return render(
request, request,
"cas_server/serviceValidateError.xml", self.template,
{'code': self.code, 'msg': self.msg}, self.context(), content_type="text/xml; charset=utf-8"
content_type="text/xml; charset=utf-8"
) )
class ValidateError(ValidationBaseError):