Drop dependancies django-picklefield and django-bootstrap3

This commit is contained in:
Valentin Samir 2016-07-24 01:49:03 +02:00
parent ff9566289d
commit 3ff4bb16a9
21 changed files with 248 additions and 110 deletions

1
.gitignore vendored
View file

@ -4,7 +4,6 @@
*.swp
build/
bootstrap3
cas/
dist/
db.sqlite3

View file

@ -44,7 +44,7 @@ test_venv/cas/manage.py: test_venv
mkdir -p test_venv/cas
test_venv/bin/django-admin startproject cas test_venv/cas
ln -s ../../cas_server test_venv/cas/cas_server
sed -i "s/'django.contrib.staticfiles',/'django.contrib.staticfiles',\n 'bootstrap3',\n 'cas_server',/" test_venv/cas/cas/settings.py
sed -i "s/'django.contrib.staticfiles',/'django.contrib.staticfiles',\n 'cas_server',/" test_venv/cas/cas/settings.py
sed -i "s/'django.middleware.clickjacking.XFrameOptionsMiddleware',/'django.middleware.clickjacking.XFrameOptionsMiddleware',\n 'django.middleware.locale.LocaleMiddleware',/" test_venv/cas/cas/settings.py
sed -i 's/from django.conf.urls import url/from django.conf.urls import url, include/' test_venv/cas/cas/urls.py
sed -i "s@url(r'^admin/', admin.site.urls),@url(r'^admin/', admin.site.urls),\n url(r'^', include('cas_server.urls', namespace='cas_server')),@" test_venv/cas/cas/urls.py

View file

@ -9,13 +9,6 @@ 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)
The default login/logout template use `django-bootstrap3 <https://github.com/dyve/django-bootstrap3>`__
but you can use your own templates using settings variables.
Note that for Django 1.7 compatibility, you need a version of
`django-bootstrap3 <https://github.com/dyve/django-bootstrap3>`__ < 7.0.0
like the 6.2.2 version.
.. contents:: Table of Contents
Features
@ -39,8 +32,6 @@ Dependencies
* Django >= 1.7 < 1.10
* requests >= 2.4
* requests_futures >= 0.9.5
* django-picklefield >= 0.3.1
* django-bootstrap3 >= 5.4 (< 7.0.0 if using django 1.7)
* lxml >= 3.4
* six >= 1
@ -55,7 +46,7 @@ The recommended installation mode is to use a virtualenv with ``--system-site-pa
On debian like systems::
$ sudo apt-get install python-django python-requests python-django-picklefield python-six python-lxml
$ sudo apt-get install python-django python-requests python-six python-lxml python-requests-futures
On debian jessie, you can use the version of python-django available in the
`backports <https://backports.debian.org/Instructions/>`_.
@ -105,7 +96,6 @@ Quick start
INSTALLED_APPS = (
'django.contrib.admin',
...
'bootstrap3',
'cas_server',
)
@ -173,6 +163,17 @@ Template settings
* ``CAS_LOGO_URL``: URL to the logo showed in the up left corner on the default
templates. Set it to ``False`` to disable it.
* ``CAS_COMPONENT_URLS``: URLs to css and javascript external components. It is a dictionnary
and it must have the five following keys: ``"bootstrap3_css"``, ``"bootstrap3_js"``,
``"html5shiv"``, ``"respond"``, ``"jquery"``. The default is::
{
"bootstrap3_css": "//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css",
"bootstrap3_js": "//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js",
"html5shiv": "//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js",
"respond": "//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js",
"jquery": "//code.jquery.com/jquery.min.js",
}
* ``CAS_LOGIN_TEMPLATE``: Path to the template showed on ``/login`` then the user
is not autenticated. The default is ``"cas_server/login.html"``.

View file

@ -18,6 +18,14 @@ from importlib import import_module
#: URL to the logo showed in the up left corner on the default templates.
CAS_LOGO_URL = static("cas_server/logo.png")
#: URLs to css and javascript external components.
CAS_COMPONENT_URLS = {
"bootstrap3_css": "//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css",
"bootstrap3_js": "//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js",
"html5shiv": "//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js",
"respond": "//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js",
"jquery": "//code.jquery.com/jquery.min.js",
}
#: Path to the template showed on /login then the user is not autenticated.
CAS_LOGIN_TEMPLATE = 'cas_server/login.html'
#: Path to the template showed on /login?service=... then the user is authenticated and has asked

View file

@ -84,7 +84,7 @@ class CASFederateValidateUser(object):
if username is not None:
if attributs is None:
attributs = {}
attributs["provider"] = self.provider
attributs["provider"] = self.provider.suffix
self.username = username
self.attributs = attributs
user = FederatedUser.objects.update_or_create(

View file

@ -18,7 +18,28 @@ import cas_server.utils as utils
import cas_server.models as models
class WarnForm(forms.Form):
class BootsrapForm(forms.Form):
"""Form base class to use boostrap then rendering the form fields"""
def __init__(self, *args, **kwargs):
super(BootsrapForm, self).__init__(*args, **kwargs)
for (name, field) in self.fields.items():
# Only tweak the fiel if it will be displayed
if not isinstance(field.widget, forms.HiddenInput):
# tell to display the field (used in form.html)
self[name].display = True
attrs = {}
if isinstance(field.widget, forms.CheckboxInput):
self[name].checkbox = True
else:
attrs['class'] = "form-control"
if field.label:
attrs["placeholder"] = field.label
if field.required:
attrs["required"] = "required"
field.widget.attrs.update(attrs)
class WarnForm(BootsrapForm):
"""
Bases: :class:`django.forms.Form`
@ -38,7 +59,7 @@ class WarnForm(forms.Form):
lt = forms.CharField(widget=forms.HiddenInput(), required=False)
class FederateSelect(forms.Form):
class FederateSelect(BootsrapForm):
"""
Bases: :class:`django.forms.Form`
@ -66,7 +87,7 @@ class FederateSelect(forms.Form):
renew = forms.BooleanField(widget=forms.HiddenInput(), required=False)
class UserCredential(forms.Form):
class UserCredential(BootsrapForm):
"""
Bases: :class:`django.forms.Form`

View file

@ -4,7 +4,6 @@ from __future__ import unicode_literals
from django.db import models, migrations
import django.db.models.deletion
import cas_server.utils
import picklefield.fields
class Migration(migrations.Migration):
@ -31,7 +30,7 @@ class Migration(migrations.Migration):
name='ProxyGrantingTicket',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('attributs', picklefield.fields.PickledObjectField(editable=False)),
('attributs', models.TextField(blank=True, default=None, null=True)),
('validate', models.BooleanField(default=False)),
('service', models.TextField()),
('creation', models.DateTimeField(auto_now_add=True)),
@ -47,7 +46,7 @@ class Migration(migrations.Migration):
name='ProxyTicket',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('attributs', picklefield.fields.PickledObjectField(editable=False)),
('attributs', models.TextField(blank=True, default=None, null=True)),
('validate', models.BooleanField(default=False)),
('service', models.TextField()),
('creation', models.DateTimeField(auto_now_add=True)),
@ -80,7 +79,7 @@ class Migration(migrations.Migration):
name='ServiceTicket',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('attributs', picklefield.fields.PickledObjectField(editable=False)),
('attributs', models.TextField(blank=True, default=None, null=True)),
('validate', models.BooleanField(default=False)),
('service', models.TextField()),
('creation', models.DateTimeField(auto_now_add=True)),

View file

@ -3,7 +3,6 @@
from __future__ import unicode_literals
from django.db import migrations, models
import picklefield.fields
import django.db.models.deletion
@ -41,7 +40,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('username', models.CharField(max_length=124)),
('provider', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cas_server.FederatedIendityProvider')),
('attributs', picklefield.fields.PickledObjectField(editable=False)),
('attributs', models.TextField(blank=True, default=None, null=True)),
('ticket', models.CharField(max_length=255)),
('last_update', models.DateTimeField(auto_now=True)),
],

View file

@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.8 on 2016-07-23 22:52
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cas_server', '0006_auto_20160706_1727'),
]
operations = [
migrations.RemoveField(
model_name='federateduser',
name='attributs',
),
migrations.RemoveField(
model_name='proxygrantingticket',
name='attributs',
),
migrations.RemoveField(
model_name='proxyticket',
name='attributs',
),
migrations.RemoveField(
model_name='serviceticket',
name='attributs',
),
migrations.AddField(
model_name='federateduser',
name='_attributs',
field=models.TextField(blank=True, default=None, null=True),
),
migrations.AddField(
model_name='proxygrantingticket',
name='_attributs',
field=models.TextField(blank=True, default=None, null=True),
),
migrations.AddField(
model_name='proxyticket',
name='_attributs',
field=models.TextField(blank=True, default=None, null=True),
),
migrations.AddField(
model_name='serviceticket',
name='_attributs',
field=models.TextField(blank=True, default=None, null=True),
),
migrations.AlterField(
model_name='federatediendityprovider',
name='suffix',
field=models.CharField(help_text='Suffix append to backend CAS returned username: ``returned_username`` @ ``suffix``.', max_length=30, unique=True, verbose_name='suffix'),
),
]

View file

@ -18,7 +18,6 @@ from django.contrib import messages
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from django.utils.encoding import python_2_unicode_compatible
from picklefield.fields import PickledObjectField
import re
import sys
@ -140,8 +139,8 @@ class FederatedUser(models.Model):
username = models.CharField(max_length=124)
#: A foreign key to :class:`FederatedIendityProvider`
provider = models.ForeignKey(FederatedIendityProvider, on_delete=models.CASCADE)
#: The user attributes returned by the CAS backend on successful ticket validation
attributs = PickledObjectField()
#: The user attributes json encoded
_attributs = models.TextField(default=None, null=True, blank=True)
#: The last ticket used to authenticate :attr:`username` against :attr:`provider`
ticket = models.CharField(max_length=255)
#: Last update timespampt. Usually, the last time :attr:`ticket` has been set.
@ -150,6 +149,17 @@ class FederatedUser(models.Model):
def __str__(self):
return self.federated_username
@property
def attributs(self):
"""The user attributes returned by the CAS backend on successful ticket validation"""
if self._attributs is not None:
return utils.json.loads(self._attributs)
@attributs.setter
def attributs(self, value):
"""attributs property setter"""
self._attributs = utils.json_encode(value)
@property
def federated_username(self):
"""The federated username with a suffix for the current :class:`FederatedUser`."""
@ -712,8 +722,8 @@ class Ticket(models.Model):
abstract = True
#: ForeignKey to a :class:`User`.
user = models.ForeignKey(User, related_name="%(class)s")
#: The user attributes to be transmited to the service on successful validation
attributs = PickledObjectField()
#: The user attributes to transmit to the service json encoded
_attributs = models.TextField(default=None, null=True, blank=True)
#: A boolean. ``True`` if the ticket has been validated
validate = models.BooleanField(default=False)
#: The service url for the ticket
@ -736,6 +746,17 @@ class Ticket(models.Model):
#: requests.
TIMEOUT = settings.CAS_TICKET_TIMEOUT
@property
def attributs(self):
"""The user attributes to be transmited to the service on successful validation"""
if self._attributs is not None:
return utils.json.loads(self._attributs)
@attributs.setter
def attributs(self, value):
"""attributs property setter"""
self._attributs = utils.json_encode(value)
class DoesNotExist(Exception):
"""raised in :meth:`Ticket.get` then ticket prefix and ticket classes mismatch"""
pass

View file

@ -1,36 +1,63 @@
{% extends 'bootstrap3/bootstrap3.html' %}
{% load i18n %}
{% block bootstrap3_title %}{% block title %}{% trans "Central Authentication Service" %}{% endblock %}{% endblock %}
{% load staticfiles %}
{% load bootstrap3 %}
{% block bootstrap3_extra_head %}
<link rel="shortcut icon" href="/static/cas_server/favicon.ico?v=1" />
<link href="{% static "cas_server/login.css" %}" rel="stylesheet">
{% endblock %}
{% block bootstrap3_content %}
<div class="container">
{% if auto_submit %}<noscript>{% endif %}
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<h1 id="app-name">
{% if settings.CAS_LOGO_URL %}<img src="{{settings.CAS_LOGO_URL}}"></img> {% endif %}
{% trans "Central Authentication Service" %}</h1>
</div>
</div>
{% if auto_submit %}</noscript>{% endif %}
<div class="row">
<div class="col-lg-3 col-md-3 col-sm-2 col-xs-12"></div>
<div class="col-lg-6 col-md-6 col-sm-8 col-xs-12">
{% if auto_submit %}<noscript>{% endif %}
{% bootstrap_messages %}
{% if auto_submit %}</noscript>{% endif %}
{% block content %}
{% endblock %}
</div>
<div class="col-lg-3 col-md-3 col-sm-2 col-xs-0"></div>
</div>
</div> <!-- /container -->
{% endblock %}
<!DOCTYPE html>
<html{% if request.LANGUAGE_CODE %} lang="{{ request.LANGUAGE_CODE }}"{% endif %}>
<head>
<meta charset="utf-8">
<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge" /><![endif]-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}{% trans "Central Authentication Service" %}{% endblock %}</title>
<link href="{{settings.CAS_COMPONENT_URLS.bootstrap3_css}}" rel="stylesheet">
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="{{settings.CAS_COMPONENT_URLS.html5shiv}}"></script>
<script src="{{settings.CAS_COMPONENT_URLS.respond}}"></script>
<![endif]-->
<link rel="shortcut icon" href="{% static "cas_server/favicon.ico?v=1" %}" />
<link href="{% static "cas_server/login.css" %}" rel="stylesheet">
</head>
<body>
<div class="container">
{% if auto_submit %}<noscript>{% endif %}
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<h1 id="app-name">
{% if settings.CAS_LOGO_URL %}<img src="{{settings.CAS_LOGO_URL}}"></img> {% endif %}
{% trans "Central Authentication Service" %}</h1>
</div>
</div>
{% if auto_submit %}</noscript>{% endif %}
<div class="row">
<div class="col-lg-3 col-md-3 col-sm-2 col-xs-12"></div>
<div class="col-lg-6 col-md-6 col-sm-8 col-xs-12">
{% block ante_messages %}{% endblock %}
{% if auto_submit %}<noscript>{% endif %}
{% for message in messages %}
<div {% spaceless %}
{% if message.level == message_levels.DEBUG %}
class="alert alert-warning alert-dismissable"
{% elif message.level == message_levels.INFO %}
class="alert alert-info alert-dismissable"
{% elif message.level == message_levels.SUCCESS %}
class="alert alert-success alert-dismissable"
{% elif message.level == message_levels.WARNING %}
class="alert alert-warning alert-dismissable"
{% else %}
class="alert alert-danger alert-dismissable"
{% endif %}
{% endspaceless %}>
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&#215;</button>
{{ message }}
</div>
{% endfor %}
{% if auto_submit %}</noscript>{% endif %}
{% block content %}{% endblock %}
</div>
<div class="col-lg-3 col-md-3 col-sm-2 col-xs-0"></div>
</div>
</div> <!-- /container -->
<script src="{{settings.CAS_COMPONENT_URLS.jquery}}"></script>
<script src="{{settings.CAS_COMPONENT_URLS.bootstrap3_js}}"></script>
</body>
</html>

View file

@ -0,0 +1,25 @@
{% for error in form.non_field_errors %}
<div class="alert alert-danger alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&#215;</button>
{{error}}
</div>
{% endfor %}
{% for field in form %}{% if field.display %}
<div class="form-group{% spaceless %}
{% if not form.non_field_errors %}
{% if field.errors %} has-error
{% elif form.cleaned_data %} has-success
{% endif %}
{% endif %}"
{% endspaceless %}>{% spaceless %}
{% if field.checkbox %}
<div class="checkbox"><label for="{{field.auto_id}}">{{field}}{{field.label}}</label>
{% else %}
<label class="control-label" for="{{field.auto_id}}">{{field.label}}</label>
{{field}}
{% endif %}
{% for error in field.errors %}
<span class="help-block">{{error}}</span>
{% endfor %}
{% endspaceless %}</div>
{% else %}{{field}}{% endif %}{% endfor %}

View file

@ -1,6 +1,4 @@
{% extends "cas_server/base.html" %}
{% load bootstrap3 %}
{% load staticfiles %}
{% load i18n %}
{% block content %}
<div class="alert alert-success" role="alert">{% trans "Logged" %}</div>
@ -10,7 +8,7 @@
<input type="checkbox" name="all" value="1"> {% trans "Log me out from all my sessions" %}
</label>
</div>
{% bootstrap_button _('Logout') size='lg' button_type="submit" button_class="btn-danger btn-block"%}
<button class="btn btn-danger btn-block btn-lg" type="submit">{% trans "Logout" %}</button>
</form>
{% endblock %}

View file

@ -1,18 +1,19 @@
{% extends "cas_server/base.html" %}
{% load bootstrap3 %}
{% load staticfiles %}
{% load i18n %}
{% block ante_messages %}
{% if auto_submit %}<noscript>{% endif %}
<h2 class="form-signin-heading">{% trans "Please log in" %}</h2>
{% if auto_submit %}</noscript>{% endif %}
{% endblock %}
{% block content %}
<form class="form-signin" method="post" id="login_form"{% if post_url %} action="{{post_url}}"{% endif %}>
{% if auto_submit %}<noscript>{% endif %}
<h2 class="form-signin-heading">{% trans "Please log in" %}</h2>
{% if auto_submit %}</noscript>{% endif %}
{% csrf_token %}
{% bootstrap_form form %}
{% if auto_submit %}<noscript>{% endif %}
{% bootstrap_button _('Login') size='lg' button_type="submit" button_class="btn-primary btn-block"%}
{% if auto_submit %}</noscript>{% endif %}
</form>
<form class="form-signin" method="post" id="login_form"{% if post_url %} action="{{post_url}}"{% endif %}>
{% csrf_token %}
{% include "cas_server/form.html" %}
{% if auto_submit %}<noscript>{% endif %}
<button class="btn btn-primary btn-block btn-lg" type="submit">{% trans "Login" %}</button>
{% if auto_submit %}</noscript>{% endif %}
</form>
{% if auto_submit %}
<script type="text/javascript">
document.getElementById('login_form').submit(); // SUBMIT FORM

View file

@ -1,5 +1,4 @@
{% extends "cas_server/base.html" %}
{% load bootstrap3 %}
{% load staticfiles %}
{% load i18n %}
{% block content %}

View file

@ -1,12 +1,11 @@
{% extends "cas_server/base.html" %}
{% load bootstrap3 %}
{% load staticfiles %}
{% load i18n %}
{% block content %}
<form class="form-signin" method="post">
{% csrf_token %}
{% bootstrap_form form %}
{% bootstrap_button _('Connect to the service') size='lg' button_type="submit" button_class="btn-primary btn-block"%}
{% include "cas_server/form.html" %}
<button class="btn btn-primary btn-block btn-lg" type="submit">{% trans "Connect to the service" %}</button>
</form>
{% endblock %}

View file

@ -37,7 +37,6 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'bootstrap3',
'cas_server',
]

View file

@ -15,6 +15,8 @@ from .default_settings import settings
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect, HttpResponse
from django.contrib import messages
from django.contrib.messages import constants as DEFAULT_MESSAGE_LEVELS
from django.core.serializers.json import DjangoJSONEncoder
import random
import string
@ -29,6 +31,15 @@ from datetime import datetime, timedelta
from six.moves.urllib.parse import urlparse, urlunparse, parse_qsl, urlencode
def json_encode(obj):
"""Encode a python object to json"""
try:
return json_encode.encoder.encode(obj)
except AttributeError:
json_encode.encoder = DjangoJSONEncoder(default=six.text_type)
return json_encode(obj)
def context(params):
"""
Function that add somes variable to the context before template rendering
@ -39,6 +50,7 @@ def context(params):
:rtype: dict
"""
params["settings"] = settings
params["message_levels"] = DEFAULT_MESSAGE_LEVELS
return params

View file

@ -6,7 +6,5 @@ pytest-pythonpath>=0.3
pytest-cov>=2.2.1
requests>=2.4
requests_futures>=0.9.5
django-picklefield>=0.3.1
django-bootstrap3>=5.4
lxml>=3.4
six>=1

View file

@ -2,7 +2,5 @@ Django >= 1.8,<1.10
setuptools>=5.5
requests>=2.4
requests_futures>=0.9.5
django-picklefield>=0.3.1
django-bootstrap3>=5.4
lxml>=3.4
six>=1

View file

@ -11,27 +11,6 @@ if __name__ == '__main__':
# allow setup.py to be run from any path
os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))
# if we have Django 1.8 available, use last version of django-boostrap3
try:
pkg_resources.require('Django >= 1.8')
django_bootstrap3 = 'django-bootstrap3 >= 5.4'
django = 'Django >= 1.8,<1.10'
except pkg_resources.VersionConflict:
# Else if we have django 1.7, we need django-boostrap3 < 7.0.0
try:
pkg_resources.require('Django >= 1.7')
django_bootstrap3 = 'django-bootstrap3 >= 5.4,<7.0.0'
django = 'Django >= 1.7,<1.8'
except (pkg_resources.VersionConflict, pkg_resources.DistributionNotFound):
# Else we need to install Django, assume version will be >= 1.8
django_bootstrap3 = 'django-bootstrap3 >= 5.4'
django = 'Django >= 1.8,<1.10'
# No version of django installed, assume version will be >= 1.8
except pkg_resources.DistributionNotFound:
django_bootstrap3 = 'django-bootstrap3 >= 5.4'
django = 'Django >= 1.8,<1.10'
setup(
name='django-cas-server',
version=VERSION,
@ -80,9 +59,8 @@ if __name__ == '__main__':
},
keywords=['django', 'cas', 'cas3', 'server', 'sso', 'single sign-on', 'authentication', 'auth'],
install_requires=[
django, 'requests >= 2.4', 'requests_futures >= 0.9.5',
'django-picklefield >= 0.3.1', django_bootstrap3, 'lxml >= 3.4',
'six >= 1'
'Django >= 1.7,<1.10', 'requests >= 2.4', 'requests_futures >= 0.9.5',
'lxml >= 3.4', 'six >= 1'
],
url="https://github.com/nitmir/django-cas-server",
download_url="https://github.com/nitmir/django-cas-server/releases",