summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBenjamin Dauvergne <bdauvergne@entrouvert.com>2019-02-02 10:04:09 (GMT)
committerBenjamin Dauvergne <bdauvergne@entrouvert.com>2019-02-05 08:38:49 (GMT)
commite8a1a7221c2bb102b083272168e9d393edcaaa5e (patch)
tree37f4bc08ff5deead9fd4f1762d8303bd99f95a3a
parent61a8167e9e810f995b3a6b3a540ac36d9fd0bc47 (diff)
downloaddjango-kerberos-e8a1a7221c2bb102b083272168e9d393edcaaa5e.zip
django-kerberos-e8a1a7221c2bb102b083272168e9d393edcaaa5e.tar.gz
django-kerberos-e8a1a7221c2bb102b083272168e9d393edcaaa5e.tar.bz2
add Python3 and django 1.11 support (fixes #30321)v1.4.3
Also remove the sample application.
-rw-r--r--Jenkinsfile50
-rw-r--r--README2
-rw-r--r--debian/control13
-rwxr-xr-xpylint.sh13
-rw-r--r--sample/README1
-rw-r--r--sample/config.py.example3
-rwxr-xr-xsample/manage.py10
-rw-r--r--sample/sample/__init__.py0
-rw-r--r--sample/sample/settings.py98
-rw-r--r--sample/sample/templates/base.html7
-rw-r--r--sample/sample/templates/index.html14
-rw-r--r--sample/sample/templates/registration/login.html9
-rw-r--r--sample/sample/urls.py16
-rw-r--r--sample/sample/views.py4
-rw-r--r--sample/sample/wsgi.py14
-rwxr-xr-xsetup.py27
-rw-r--r--src/django_kerberos/backends.py20
-rw-r--r--src/django_kerberos/urls.py6
-rw-r--r--src/django_kerberos/views.py6
-rw-r--r--tests/settings.py42
-rw-r--r--tests/test_login.py146
-rw-r--r--tox.ini48
22 files changed, 319 insertions, 230 deletions
diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 0000000..ccb6310
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,50 @@
+@Library('eo-jenkins-lib@master') import eo.Utils
+
+pipeline {
+ agent any
+ options { disableConcurrentBuilds() }
+ stages {
+ stage('Unit Tests') {
+ steps {
+ sh """
+rm -rf htmlcov* .coverage* coverage* junit*.xml
+rm -rf venv
+virtualenv -p python3 venv
+. venv/bin/activate
+pip install tox"""
+ sh './venv/bin/tox -rv'
+ }
+ post {
+ always {
+ script {
+ utils = new Utils()
+ utils.publish_coverage('coverage.xml')
+ utils.publish_coverage_native('index.html')
+ utils.publish_pylint('pylint.out')
+ }
+ junit 'junit*.xml'
+ }
+ }
+ }
+ stage('Packaging') {
+ steps {
+ script {
+ if (env.JOB_NAME == 'django-kerberos' && env.GIT_BRANCH == 'origin/master') {
+ sh 'sudo -H -u eobuilder /usr/local/bin/eobuilder django-kerberos'
+ }
+ }
+ }
+ }
+ }
+ post {
+ always {
+ script {
+ utils = new Utils()
+ utils.mail_notify(currentBuild, env, 'admin+jenkins-django-kerberos@entrouvert.com')
+ }
+ }
+ success {
+ cleanWs()
+ }
+ }
+}
diff --git a/README b/README
index 1492f1b..482074a 100644
--- a/README
+++ b/README
@@ -3,6 +3,8 @@ Kerberos authentication for Django
Provide Kerberos authentication to Django applications.
+Python 2 and 3, Django >1.8 are supported.
+
Basic usage
===========
diff --git a/debian/control b/debian/control
index a7d371a..deb20b1 100644
--- a/debian/control
+++ b/debian/control
@@ -1,5 +1,5 @@
Source: python-django-kerberos
-Maintainer: Benjamin Dauvergne <info@entrouvert.com>
+Maintainer: Benjamin Dauvergne <bdauvergne@entrouvert.com>
Section: python
Priority: optional
Build-Depends: python-setuptools (>= 0.6b3), python-all (>= 2.6), debhelper (>= 7.4.3),
@@ -10,7 +10,16 @@ X-Python-Version: >= 2.6
Package: python-django-kerberos
Architecture: all
Depends: ${misc:Depends}, ${python:Depends},
- python-django (>= 1.5),
+ python-six,
+ python-django (>= 1.8),
python-kerberos
Description: Kerberos authentication frontend for Authentic2
+Package: python3-django-kerberos
+Architecture: all
+Depends: ${misc:Depends}, ${python3:Depends},
+ python3-six,
+ python3-django (>= 1.8),
+ python3-kerberos
+Description: Kerberos authentication frontend for Authentic2
+
diff --git a/pylint.sh b/pylint.sh
new file mode 100755
index 0000000..d7295cc
--- /dev/null
+++ b/pylint.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+set -e -x
+env
+if [ -f /var/lib/jenkins/pylint.django.rc ]; then
+ PYLINT_RC=/var/lib/jenkins/pylint.django.rc
+elif [ -f pylint.django.rc ]; then
+ PYLINT_RC=pylint.django.rc
+else
+ echo No pylint RC found
+ exit 0
+fi
+pylint -f parseable --rcfile ${PYLINT_RC} "$@" | tee pylint.out || /bin/true
diff --git a/sample/README b/sample/README
deleted file mode 100644
index e883b1b..0000000
--- a/sample/README
+++ /dev/null
@@ -1 +0,0 @@
-Rename config.py.example to config.py, and update default settings.
diff --git a/sample/config.py.example b/sample/config.py.example
deleted file mode 100644
index d2c2c7b..0000000
--- a/sample/config.py.example
+++ /dev/null
@@ -1,3 +0,0 @@
-KERBEROS_SERVICE_PRINCIPAL = 'HTTP/fenouil.entrouvert.org@ENTROUVERT.ORG'
-KERBEROS_DEFAULT_REALM = 'ENTROUVERT.ORG'
-KERBEROS_BACKEND_ADMIN_REGEXP = '^.*/admin@.*$'
diff --git a/sample/manage.py b/sample/manage.py
deleted file mode 100755
index e30e858..0000000
--- a/sample/manage.py
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/usr/bin/env python
-import os
-import sys
-
-if __name__ == "__main__":
- os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sample.settings")
-
- from django.core.management import execute_from_command_line
-
- execute_from_command_line(sys.argv)
diff --git a/sample/sample/__init__.py b/sample/sample/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/sample/sample/__init__.py
+++ /dev/null
diff --git a/sample/sample/settings.py b/sample/sample/settings.py
deleted file mode 100644
index 6edad4a..0000000
--- a/sample/sample/settings.py
+++ /dev/null
@@ -1,98 +0,0 @@
-"""
-Django settings for sample project.
-
-For more information on this file, see
-https://docs.djangoproject.com/en/1.6/topics/settings/
-
-For the full list of settings and their values, see
-https://docs.djangoproject.com/en/1.6/ref/settings/
-"""
-
-# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
-import os
-BASE_DIR = os.path.dirname(os.path.dirname(__file__))
-
-
-# Quick-start development settings - unsuitable for production
-# See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/
-
-# SECURITY WARNING: keep the secret key used in production secret!
-SECRET_KEY = 's0xb*2mi0#pi48tri&x6cwr96k30mmdu%e6pa28_=n9^4eh-3='
-
-# SECURITY WARNING: don't run with debug turned on in production!
-DEBUG = True
-
-TEMPLATE_DEBUG = True
-
-ALLOWED_HOSTS = []
-
-
-# Application definition
-
-INSTALLED_APPS = (
- 'django.contrib.admin',
- 'django.contrib.auth',
- 'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.messages',
- 'django.contrib.staticfiles',
- 'django_kerberos',
-)
-
-MIDDLEWARE_CLASSES = (
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.common.CommonMiddleware',
- 'django.middleware.csrf.CsrfViewMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django.contrib.messages.middleware.MessageMiddleware',
- 'django.middleware.clickjacking.XFrameOptionsMiddleware',
-)
-
-AUTHENTICATION_BACKENDS = (
- 'django_kerberos.backends.KerberosBackend',
- 'django_kerberos.backends.KerberosPasswordBackend',
-)
-
-ROOT_URLCONF = 'sample.urls'
-
-WSGI_APPLICATION = 'sample.wsgi.application'
-
-TEMPLATE_DIRS = ( os.path.join(BASE_DIR, 'sample', 'templates'), )
-
-
-# Database
-# https://docs.djangoproject.com/en/1.6/ref/settings/#databases
-
-DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.sqlite3',
- 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
- }
-}
-
-# Internationalization
-# https://docs.djangoproject.com/en/1.6/topics/i18n/
-
-LANGUAGE_CODE = 'en-us'
-
-TIME_ZONE = 'UTC'
-
-USE_I18N = True
-
-USE_L10N = True
-
-USE_TZ = True
-
-
-# Static files (CSS, JavaScript, Images)
-# https://docs.djangoproject.com/en/1.6/howto/static-files/
-
-STATIC_URL = '/static/'
-
-LOGIN_URL = 'kerberos-login'
-LOGIN_REDIRECT_URL = '/'
-
-KERBEROS_BACKEND_CREATE = True
-KERBEROS_BACKEND_ADMIN_REGEXP = r'^.*/admin$'
-
-execfile('config.py', globals())
diff --git a/sample/sample/templates/base.html b/sample/sample/templates/base.html
deleted file mode 100644
index b5d6e62..0000000
--- a/sample/sample/templates/base.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<!DOCTYPE html>
-<html>
- <body>
- {% block content %}
- {% endblock %}
- </body>
-</html>
diff --git a/sample/sample/templates/index.html b/sample/sample/templates/index.html
deleted file mode 100644
index 527538b..0000000
--- a/sample/sample/templates/index.html
+++ /dev/null
@@ -1,14 +0,0 @@
-{% extends "base.html" %}
-
-{% block content %}
- {% include "django_kerberos/autologin.html" %}
- {% if user.is_authenticated %}
- <p>Hello {{ user }}</p>
- {% if user.is_superuser %}
- <p><a href="/admin">Admin</a></p>
- {% endif %}
- <p><a href="{% url "logout" %}?next=/">Logout</a></p>
- {% else %}
- <p>Please <a href="{% url "kerberos-login" %}">login with kerberos</a> or with your <a href="{% url "login" %}">password</a></p>
- {% endif %}
-{% endblock %}
diff --git a/sample/sample/templates/registration/login.html b/sample/sample/templates/registration/login.html
deleted file mode 100644
index 10f8cba..0000000
--- a/sample/sample/templates/registration/login.html
+++ /dev/null
@@ -1,9 +0,0 @@
-{% extends "base.html" %}
-
-{% block content %}
-<form method="post">
- {% csrf_token %}
- {{ form.as_p }}
- <button>Login</button>
-</form>
-{% endblock %}
diff --git a/sample/sample/urls.py b/sample/sample/urls.py
deleted file mode 100644
index a81f8d9..0000000
--- a/sample/sample/urls.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from django.conf.urls import patterns, include, url
-
-from django.contrib import admin
-admin.autodiscover()
-
-from . import views
-
-urlpatterns = patterns('',
- # Examples:
- url(r'^$', views.home, name='home'),
- # url(r'^blog/', include('blog.urls')),
-
- url('^accounts/kerberos/', include('django_kerberos.urls')),
- url('^accounts/', include('django.contrib.auth.urls')),
- url(r'^admin/', include(admin.site.urls)),
-)
diff --git a/sample/sample/views.py b/sample/sample/views.py
deleted file mode 100644
index 61d8045..0000000
--- a/sample/sample/views.py
+++ /dev/null
@@ -1,4 +0,0 @@
-from django.shortcuts import render
-
-def home(request):
- return render(request, 'index.html')
diff --git a/sample/sample/wsgi.py b/sample/sample/wsgi.py
deleted file mode 100644
index 1301c9c..0000000
--- a/sample/sample/wsgi.py
+++ /dev/null
@@ -1,14 +0,0 @@
-"""
-WSGI config for sample project.
-
-It exposes the WSGI callable as a module-level variable named ``application``.
-
-For more information on this file, see
-https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/
-"""
-
-import os
-os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sample.settings")
-
-from django.core.wsgi import get_wsgi_application
-application = get_wsgi_application()
diff --git a/setup.py b/setup.py
index 4698360..2c1edc5 100755
--- a/setup.py
+++ b/setup.py
@@ -9,7 +9,7 @@ from setuptools.command.sdist import sdist
class eo_sdist(sdist):
def run(self):
- print "creating VERSION file"
+ print("creating VERSION file")
if os.path.exists('VERSION'):
os.remove('VERSION')
version = get_version()
@@ -17,35 +17,41 @@ class eo_sdist(sdist):
version_file.write(version)
version_file.close()
sdist.run(self)
- print "removing VERSION file"
+ print("removing VERSION file")
if os.path.exists('VERSION'):
os.remove('VERSION')
def get_version():
'''Use the VERSION, if absent generates a version with git describe, if not
- tag exists, take 0.0.0- and add the length of the commit log.
+ tag exists, take 0.0- and add the length of the commit log.
'''
if os.path.exists('VERSION'):
with open('VERSION', 'r') as v:
return v.read()
if os.path.exists('.git'):
- p = subprocess.Popen(['git', 'describe', '--dirty', '--match=v*'], stdout=subprocess.PIPE,
+ p = subprocess.Popen(['git', 'describe', '--dirty=.dirty','--match=v*'], stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
result = p.communicate()[0]
if p.returncode == 0:
- result = result.split()[0][1:]
+ result = result.decode('ascii').strip()[1:] # strip spaces/newlines and initial v
+ if '-' in result: # not a tagged version
+ real_number, commit_count, commit_hash = result.split('-', 2)
+ version = '%s.post%s+%s' % (real_number, commit_count, commit_hash)
+ else:
+ version = result
+ return version
else:
- result = '0.0.0-%s' % len(subprocess.check_output(
- ['git', 'rev-list', 'HEAD']).splitlines())
- return result.replace('-', '.').replace('.g', '+g')
- return '0.0.0'
+ return '0.0.post%s' % len(
+ subprocess.check_output(
+ ['git', 'rev-list', 'HEAD']).splitlines())
+ return '0.0'
setup(name="django-kerberos",
version=get_version(),
license="AGPLv3 or later",
description="Kerberos authentication for Django",
- long_description=file('README').read(),
+ long_description=open('README').read(),
url="http://dev.entrouvert.org/projects/authentic/",
author="Entr'ouvert",
author_email="info@entrouvert.org",
@@ -53,6 +59,7 @@ setup(name="django-kerberos",
maintainer_email="bdauvergne@entrouvert.com",
packages=find_packages('src'),
install_requires=[
+ 'six',
'django>1.5',
'pykerberos',
],
diff --git a/src/django_kerberos/backends.py b/src/django_kerberos/backends.py
index 1cac582..0fa7bae 100644
--- a/src/django_kerberos/backends.py
+++ b/src/django_kerberos/backends.py
@@ -17,7 +17,7 @@
import re
import logging
-from . import app_settings
+import six
from django.core.exceptions import ImproperlyConfigured
from django.utils.encoding import force_bytes
@@ -27,6 +27,8 @@ from django.contrib.auth.backends import ModelBackend
import kerberos
+from . import app_settings
+
class KerberosBackend(ModelBackend):
def __init__(self):
@@ -74,7 +76,7 @@ class KerberosBackend(ModelBackend):
self.provision_user(principal, user)
return user
- def authenticate(self, principal=None, **kwargs):
+ def authenticate(self, request=None, principal=None):
if principal and self.authorize_principal(principal):
return self.lookup_user(principal)
@@ -102,22 +104,26 @@ class KerberosPasswordBackend(KerberosBackend):
' set')
return app_settings.SERVICE_PRINCIPAL
- def authenticate(self, username=None, password=None, **kwargs):
+ def authenticate(self, request=None, username=None, password=None):
'''Verify username and password using Kerberos'''
if not username:
return
- principal = force_bytes(self.principal_from_username(username))
- password = force_bytes(password)
+ kerb_principal = principal = self.principal_from_username(username)
+ kerb_password = password
+
+ if six.PY2:
+ kerb_principal = force_bytes(kerb_principal)
+ kerb_password = force_bytes(kerb_principal)
try:
- if not kerberos.checkPassword(principal, password,
+ if not kerberos.checkPassword(kerb_principal, kerb_password,
self.service_principal(),
self.default_realm()):
return
except kerberos.KrbError as e:
logging.getLogger(__name__).error(
- 'password validation forprincipal %r failed %s', principal, e)
+ 'password validation for principal %r failed %s', principal, e)
return
else:
if principal and self.authorize_principal(principal):
diff --git a/src/django_kerberos/urls.py b/src/django_kerberos/urls.py
index 8fe5228..1eed5f1 100644
--- a/src/django_kerberos/urls.py
+++ b/src/django_kerberos/urls.py
@@ -14,10 +14,10 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-from django.conf.urls import patterns, url
+from django.conf.urls import url
from . import views
-urlpatterns = patterns('',
+urlpatterns = [
url(r'^login/$', views.login, name='kerberos-login'),
-)
+]
diff --git a/src/django_kerberos/views.py b/src/django_kerberos/views.py
index 9403e7b..235b4a1 100644
--- a/src/django_kerberos/views.py
+++ b/src/django_kerberos/views.py
@@ -52,7 +52,7 @@ class NegotiateView(View):
'''Do something with the principal we received'''
self.logger.info(u'got ticket for principal %s', self.principal)
user = authenticate(principal=self.principal)
- next_url = request.REQUEST.get(self.NEXT_URL_FIELD) or settings.LOGIN_REDIRECT_URL
+ next_url = request.POST.get(self.NEXT_URL_FIELD) or request.GET.get(self.NEXT_URL_FIELD) or settings.LOGIN_REDIRECT_URL
if user:
self.login_user(request, user)
if request.is_ajax():
@@ -88,8 +88,8 @@ class NegotiateView(View):
# ensure context is finalized
try:
if result != 1:
- self.logger.warning(u'authGSSServerInit result is non-zero: %s', result)
- details = u'authGSSServerInit result is non-zero: %s' % result
+ self.logger.warning(u'authGSSServerInit result is non-one: %s', result)
+ details = u'authGSSServerInit result is non-one: %s' % result
return TemplateResponse(request, self.error_template_name,
context={'details': details}, status=500)
try:
diff --git a/tests/settings.py b/tests/settings.py
index 27f9439..c36ae6d 100644
--- a/tests/settings.py
+++ b/tests/settings.py
@@ -1,5 +1,42 @@
+import django
import os.path
+DATABASES = {
+ 'default': {
+ 'ENGINE': os.environ.get('DB_ENGINE', 'django.db.backends.sqlite3'),
+ }
+}
+
+TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [os.path.join(os.path.dirname(__file__), 'templates')],
+ 'APP_DIRS': True,
+ },
+]
+
+if django.VERSION < (1, 10):
+ MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.middleware.http.ConditionalGetMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.middleware.locale.LocaleMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ )
+else:
+ MIDDLEWARE = [
+ 'django.middleware.security.SecurityMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ ]
+
INSTALLED_APPS = (
'django.contrib.contenttypes',
'django.contrib.auth',
@@ -9,3 +46,8 @@ INSTALLED_APPS = (
TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), 'templates'),)
ROOT_URLCONF = 'django_kerberos.urls'
SECRET_KEY = 'xxx'
+
+AUTHENTICATION_BACKENDS = (
+ 'django_kerberos.backends.KerberosBackend',
+ 'django_kerberos.backends.KerberosPasswordBackend',
+)
diff --git a/tests/test_login.py b/tests/test_login.py
index 707b8f7..9f32b92 100644
--- a/tests/test_login.py
+++ b/tests/test_login.py
@@ -1,6 +1,9 @@
+import logging
import pytest
+import json
import kerberos
+from django.contrib.auth.models import User
@pytest.fixture
@@ -11,7 +14,8 @@ def kerberos_mock(request, mocker):
'kerberos.authGSSServerStep',
'kerberos.authGSSServerResponse',
'kerberos.authGSSServerUserName',
- 'kerberos.authGSSServerClean'
+ 'kerberos.authGSSServerClean',
+ 'kerberos.checkPassword'
)
for name in d:
if hasattr(request, 'param') and name in request.param:
@@ -29,9 +33,143 @@ def test_login_no_header(client, settings, kerberos_mock):
@pytest.mark.parametrize('kerberos_mock', ['kerberos.authGSSServerInit'], indirect=True)
def test_login_missing_keytab(client, settings, kerberos_mock, caplog):
resp = client.get('/login/', HTTP_AUTHORIZATION='Negotiate coin')
- for key, mock in kerberos_mock.iteritems():
+ for key, mock in kerberos_mock.items():
assert mock.call_count == 0
- assert 'keytab problem' in resp.content
- assert 'keytab problem' in caplog.text()
+ assert b'keytab problem' in resp.content
+ assert 'keytab problem' in caplog.text
+def test_login(client, db, settings, kerberos_mock, caplog):
+ caplog.set_level(logging.INFO)
+ kerberos_mock['kerberos.authGSSServerInit'].side_effect = kerberos.KrbError('coin')
+ response = client.get('/login/', HTTP_AUTHORIZATION='Negotiate xxxx')
+ assert response.status_code == 500
+ assert b'exception during authGSSServerInit' in response.content
+ assert 'exception during authGSSServerInit' in caplog.text
+ assert b'coin' in response.content
+ kerberos_mock['kerberos.authGSSServerInit'].side_effect = None
+ caplog.clear()
+
+ kerberos_mock['kerberos.authGSSServerInit'].return_value = 0, None
+ response = client.get('/login/', HTTP_AUTHORIZATION='Negotiate xxxx')
+ assert response.status_code == 500
+ assert b'authGSSServerInit result is non-one' in response.content
+ assert 'authGSSServerInit result is non-one' in caplog.text
+ caplog.clear()
+
+ kerberos_mock['kerberos.authGSSServerInit'].return_value = 1, None
+ kerberos_mock['kerberos.authGSSServerStep'].side_effect = kerberos.KrbError('coin')
+ response = client.get('/login/', HTTP_AUTHORIZATION='Negotiate xxxx')
+ assert response.status_code == 500
+ assert b'exception during authGSSServerStep' in response.content
+ assert 'exception during authGSSServerStep' in caplog.text
+ assert b'coin' in response.content
+ kerberos_mock['kerberos.authGSSServerStep'].side_effect = None
+ caplog.clear()
+
+ kerberos_mock['kerberos.authGSSServerStep'].return_value = 0
+ response = client.get('/login/', HTTP_AUTHORIZATION='Negotiate xxxx')
+ assert response.status_code == 401
+
+ kerberos_mock['kerberos.authGSSServerStep'].return_value = 1
+ kerberos_mock['kerberos.authGSSServerUserName'].side_effect = kerberos.KrbError('coin')
+ response = client.get('/login/', HTTP_AUTHORIZATION='Negotiate xxxx')
+ assert response.status_code == 500
+ assert b'exception during authGSSServerUserName' in response.content
+ assert 'exception during authGSSServerUserName' in caplog.text
+ assert b'coin' in response.content
+ kerberos_mock['kerberos.authGSSServerUserName'].side_effect = None
+ caplog.clear()
+
+ kerberos_mock['kerberos.authGSSServerUserName'].return_value = 'john.doe@EXAMPLE.COM'
+ kerberos_mock['kerberos.authGSSServerResponse'].return_value = 'yyyy'
+ response = client.get('/login/', HTTP_AUTHORIZATION='Negotiate xxxx')
+ assert response.status_code == 302
+ assert 'principal john.doe@EXAMPLE.COM has no local user' in caplog.text
+ caplog.clear()
+
+ user = User.objects.create(username='john.doe@example.com')
+ assert '_auth_user_id' not in client.session
+ response = client.get('/login/', HTTP_AUTHORIZATION='Negotiate xxxx')
+ assert response.status_code == 302
+ assert response['WWW-Authenticate'] == 'Negotiate yyyy'
+ assert 'principal john.doe@EXAMPLE.COM has no local user' not in caplog.text
+ assert client.session['_auth_user_id'] == str(user.id)
+ client.logout()
+ user.delete()
+ assert User.objects.count() == 0
+ caplog.clear()
+
+ settings.KERBEROS_BACKEND_CREATE = True
+ assert '_auth_user_id' not in client.session
+ response = client.get('/login/', HTTP_AUTHORIZATION='Negotiate xxxx')
+ assert response.status_code == 302
+ assert 'principal john.doe@EXAMPLE.COM has no local user' not in caplog.text
+ assert User.objects.count() == 1
+ user = User.objects.get()
+ assert not user.is_staff
+ assert not user.is_superuser
+ assert client.session['_auth_user_id'] == str(user.id)
+ assert 'got ticket for principal john.doe@EXAMPLE.COM' in caplog.text
+ client.logout()
+ caplog.clear()
+
+ settings.KERBEROS_BACKEND_ADMIN_REGEXP = 'john.doe'
+ assert '_auth_user_id' not in client.session
+ response = client.get('/login/', HTTP_AUTHORIZATION='Negotiate xxxx')
+ assert response.status_code == 302
+ assert 'principal john.doe@EXAMPLE.COM has no local user' not in caplog.text
+ assert User.objects.count() == 1
+ user = User.objects.get()
+ assert user.is_staff
+ assert user.is_superuser
+ assert client.session['_auth_user_id'] == str(user.id)
+ assert 'got ticket for principal john.doe@EXAMPLE.COM' in caplog.text
+ assert 'giving superuser power to principal \'john.doe@EXAMPLE.COM\'' in caplog.text
+ client.logout()
+ caplog.clear()
+
+ assert '_auth_user_id' not in client.session
+ response = client.get('/login/', HTTP_AUTHORIZATION='Negotiate xxxx', HTTP_X_REQUESTED_WITH='XMLHttpRequest')
+ assert response.status_code == 200
+ assert json.loads(response.content.decode('ascii')) is True
+
+
+def test_password_backend(db, settings, kerberos_mock, caplog):
+ from django.contrib.auth import authenticate
+
+ settings.KERBEROS_DEFAULT_REALM = 'EXAMPLE.COM'
+ settings.KERBEROS_SERVICE_PRINCIPAL = 'HTTP/SERVICE.EXAMPLE.COM@EXAMPLE.COM'
+
+ m = kerberos_mock['kerberos.checkPassword']
+ m.return_value = False
+ assert authenticate(username='john.doe', password='password') is None
+ assert User.objects.count() == 0
+
+ m.return_value = True
+ assert authenticate(username='john.doe', password='password') is None
+ assert User.objects.count() == 0
+
+ user = User.objects.create(username='john.doe@example.com')
+ assert authenticate(username='john.doe', password='password') == user
+ user.delete()
+
+ assert User.objects.count() == 0
+ settings.KERBEROS_BACKEND_CREATE = True
+ new_user = authenticate(username='john.doe', password='password')
+ assert new_user
+ assert User.objects.count() == 1
+ assert new_user.username == 'john.doe@example.com'
+ assert not new_user.has_usable_password()
+
+ settings.KERBEROS_KEEP_PASSWORD = True
+ new_user = authenticate(username='john.doe', password='password')
+ assert User.objects.count() == 1
+ assert new_user.username == 'john.doe@example.com'
+ assert new_user.has_usable_password()
+ assert new_user.check_password('password')
+
+ caplog.clear()
+ m.side_effect = kerberos.KrbError('coin')
+ assert authenticate(username='john.doe', password='password') is None
+ assert 'password validation for principal %r failed coin' % u'john.doe@EXAMPLE.COM' in caplog.text
diff --git a/tox.ini b/tox.ini
index 83a1f0a..99eefe4 100644
--- a/tox.ini
+++ b/tox.ini
@@ -3,40 +3,38 @@
# test suite on all supported python versions. To use it, "pip install tox"
# and then run "tox" from this directory.
-[testenv:coverage]
+[tox]
+toxworkdir = {env:TMPDIR:/tmp}/tox-{env:USER}/django-kerberos/{env:BRANCH_NAME:}
+envlist = py27-coverage-{dj18,dj111}-{pg,sqlite},py3-coverage-{dj18,dj111,dj20,djlast}-{pg,sqlite},pylint
+
+[testenv]
+whitelist_externals =
+ /bin/mv
+ /bin/rm
setenv =
DJANGO_SETTINGS_MODULE=settings
PYTHONPATH=tests
+ coverage: COVERAGE=--cov-branch --cov-append --cov=src/ --cov-report=html --cov-report=xml --cov-config .coveragerc
+ sqlite: DB_ENGINE=django.db.backends.sqlite3
+ pg: DB_ENGINE=django.db.backends.postgresql_psycopg2
usedevelop = true
deps =
- pytest
+ dj18: django>1.8,<1.9
+ dj18: django-tables2<1.1
+ dj111: django<2.0
+ dj20: django<2.1
+ djlast: django
+ pg: psycopg2-binary
+ pytest<4.2
pytest-mock
pytest-django
pytest-cov
- pytest-capturelog
commands =
- py.test {posargs:--junit-xml=junit.xml --cov=src --cov-report xml --nomigrations tests}
+ py.test {env:COVERAGE:} --junit-xml=junit-{envname}.xml {posargs:tests}
-[testenv:nocoverage]
-setenv =
- DJANGO_SETTINGS_MODULE=settings
- PYTHONPATH=tests
+[testenv:pylint]
deps =
- pytest
- pytest-mock
- pytest-django
- pytest-cov
- pytest-capturelog
-commands =
- py.test {posargs:--nomigrations tests}
-
-[testenv:package]
-# eobuilder is not on pypi, too bad
-deps = setuptools
- pip<8
- pyasn1
- ndg-httpsclient
- pyopenssl
+ pylint<1.8
+ pylint-django<0.8.1
commands =
- pip install -U --find-links https://jenkins.entrouvert.org/packages/ eobuilder
- sh -c "sudo -u eobuilder -E env HOME=/var/lib/eobuilder PATH=$PATH $VIRTUAL_ENV/bin/eobuilder-ctl -d wheezy,jessie {posargs:django-kerberos}"
+ pylint: ./pylint.sh src/django_kerberos/