first commit

This commit is contained in:
Benjamin Dauvergne 2014-08-09 01:19:09 +02:00
commit 778f0df7b7
34 changed files with 1068 additions and 0 deletions

2
COPYING Normal file
View File

@ -0,0 +1,2 @@
cmsplugin-blurp is entirely under the copyright of Entr'ouvert and distributed
under the license AGPLv3 or later.

51
Jenkinsfile vendored Normal file
View File

@ -0,0 +1,51 @@
@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')
}
sh './merge-junit-results.py junit-*.xml >junit.xml'
junit 'junit.xml'
}
}
}
stage('Packaging') {
steps {
script {
if (env.JOB_NAME == 'django-gssapi' && env.GIT_BRANCH == 'origin/master') {
sh 'sudo -H -u eobuilder /usr/local/bin/eobuilder django-gssapi'
}
}
}
}
}
post {
always {
script {
utils = new Utils()
utils.mail_notify(currentBuild, env, 'admin+jenkins-django-gssapi@entrouvert.com')
}
}
success {
cleanWs()
}
}
}

8
MANIFEST.in Normal file
View File

@ -0,0 +1,8 @@
include COPYING
include NEWS
include VERSION
include tox.ini
recursive-include tests *.py *.html
recursive-include sample *.py *.html README *.py.example
recursive-include src/django_gssapi/templates *.html
recursive-include src/django_gssapi/static *.js

4
NEWS Normal file
View File

@ -0,0 +1,4 @@
0.1
===
- first release

91
README Normal file
View File

@ -0,0 +1,91 @@
GSSAPI authentication for Django
==================================
Provide GSSAPI (SPNEGO) authentication to Django applications.
It's a rewrite of django-kerberos using python-gssapi.
It's only tested with MIT Kerberos 5 using package k5test.
Python 2 and 3, Django >1.8 are supported.
Basic usage
===========
Add this to your project `urls.py`::
url('^auth/gssapi/', include('django_gssapi.urls')),
And use the default authentication backend, by adding that to your `settings.py` file::
AUTHENTICATION_BACKENDS = (
'django_gssapi.backends.GSSAPIBackend',
)
View
====
django-gssapi provide a base LoginView that you can subclass to get the
behaviour your need, the main extension points are:
- `challenge()` returns the 401 response with the challenge, you should override it
to show a template explaining the failure,
- `success(user)` it should log the given user and redirect to REDIRECT_FIELD_NAME,
- `get_service_name()` it should return a gssapi.Name for your service, by
default it returns None, so GSSAPI will match any name available (for example
with Kerberos it will match any name in your keytab, like
@HTTP/my.domain.com@).
Settings
========
To make your application use GSSAPI as its main login method::
LOGIN_URL = 'gssapi-login'
Your application need an environment where the GSSAPI mechanism like Kerberos
will work, for Kerberos it means having a default keytab of creating one and
setting its path in KRB5_KTNAME or you can use `GSSAPI_STORE` with MIT Kerberos
5 and credential store extension to indicate a keytab::
GSSAPI_STORE = {'keytab': 'FILE:/var/lib/mykeytab'}
You can also force a GSSAPI name for you service with::
import gssapi
GSSAPI_NAME = gssapi.Name('HTTP/my.service.com', gssapi.MechType.hostbased_service)
GSSAPI authentication backend
=============================
A dummy backend is provided in `django_gssapi.backends.GSSAPIBackend` it looks
up user with the same username as the GSSAPI name. You should implement it for
your use case.
A custom authentication backend must have the following signature::
class CustomGSSAPIBackend(object):
def authenticate(self, request, gssapi_name):
pass
The parameter `gssapi_name` is a `gssapi.Name` object, it can be casted to
string to get the raw name.
Kerberos username/password backend
==================================
If your users does not have their browser configured for SPNEGO HTTP
authentication you can also provide a classic login/password form which check
passwords using Kerberos. For this use
`django_gssapi.backends.KerberosPasswordBackend`, the username is used as the
raw principal name.
django-rest-framework authentication backend
============================================
To authenticate users with GSSAPI you can use
`django_gssapi.drf.GSSAPIAuthentication`, it uses the configured GSSAPI
authentication backend to find an user and returns the GSSAPI name in
`request.auth`.

5
changelog Normal file
View File

@ -0,0 +1,5 @@
python-django-gssapi (0.1) stable; urgency=low
* New upstream release
-- Benjamin Dauvergne <bdauvergne@entrouvert.org> Thu, 23 Aug 2019 19:00:29 +0100

1
compat Normal file
View File

@ -0,0 +1 @@
9

33
control Normal file
View File

@ -0,0 +1,33 @@
Source: python-django-gssapi
Maintainer: Benjamin Dauvergne <bdauvergne@entrouvert.com>
Section: python
Priority: optional
Build-Depends: dh-python,
debhelper (>= 9),
python-setuptools (>= 0.6b3),
python-all (>= 2.6),
python-django (>= 1.8),
python3-all,
python3-setuptools,
python3-django (>= 1.8)
Standards-Version: 3.9.1
X-Python-Version: >= 2.7
X-Python3-Version: >= 3.4
Package: python-django-gssapi
Architecture: all
Depends: ${misc:Depends}, ${python:Depends},
python-six,
python-django (>= 1.8),
python-gssapi
Description: GSSAPI authentication for Django
Package: python3-django-gssapi
Architecture: all
Depends: ${misc:Depends}, ${python:Depends},
python3-six,
python3-django (>= 1.8),
python3-gssapi
Description: GSSAPI authentication for Django
.
This is the Python 3 version of the package.

5
debian/changelog vendored Normal file
View File

@ -0,0 +1,5 @@
python-django-gssapi (0.1) stable; urgency=low
* New upstream release
-- Benjamin Dauvergne <bdauvergne@entrouvert.org> Thu, 23 Aug 2019 19:00:29 +0100

1
debian/compat vendored Normal file
View File

@ -0,0 +1 @@
9

33
debian/control vendored Normal file
View File

@ -0,0 +1,33 @@
Source: python-django-gssapi
Maintainer: Benjamin Dauvergne <bdauvergne@entrouvert.com>
Section: python
Priority: optional
Build-Depends: dh-python,
debhelper (>= 9),
python-setuptools (>= 0.6b3),
python-all (>= 2.6),
python-django (>= 1.8),
python3-all,
python3-setuptools,
python3-django (>= 1.8)
Standards-Version: 3.9.1
X-Python-Version: >= 2.7
X-Python3-Version: >= 3.4
Package: python-django-gssapi
Architecture: all
Depends: ${misc:Depends}, ${python:Depends},
python-six,
python-django (>= 1.8),
python-gssapi
Description: GSSAPI authentication for Django
Package: python3-django-gssapi
Architecture: all
Depends: ${misc:Depends}, ${python:Depends},
python3-six,
python3-django (>= 1.8),
python3-gssapi
Description: GSSAPI authentication for Django
.
This is the Python 3 version of the package.

2
debian/pydist-overrides vendored Normal file
View File

@ -0,0 +1,2 @@
django python-django
gssapi python-gssapi

7
debian/rules vendored Executable file
View File

@ -0,0 +1,7 @@
#!/usr/bin/make -f
export PYBUILD_NAME=django-gssapi
%:
dh $@ --with python2,python3 --buildsystem=pybuild

1
debian/source/format vendored Normal file
View File

@ -0,0 +1 @@
3.0 (quilt)

65
merge-junit-results.py Executable file
View File

@ -0,0 +1,65 @@
#!/usr/bin/env python
#
# Corey Goldberg, Dec 2012
#
import os
import sys
import xml.etree.ElementTree as ET
"""Merge multiple JUnit XML files into a single results file.
Output dumps to sdtdout.
example usage:
$ python merge_junit_results.py results1.xml results2.xml > results.xml
"""
def main():
args = sys.argv[1:]
if not args:
usage()
sys.exit(2)
if '-h' in args or '--help' in args:
usage()
sys.exit(2)
merge_results(args[:])
def merge_results(xml_files):
failures = 0
tests = 0
errors = 0
time = 0.0
cases = []
for file_name in xml_files:
tree = ET.parse(file_name)
test_suite = tree.getroot()
failures += int(test_suite.attrib['failures'])
tests += int(test_suite.attrib['tests'])
errors += int(test_suite.attrib['errors'])
time += float(test_suite.attrib['time'])
name = test_suite.attrib.get('name', '')
for child in test_suite.getchildren():
child.attrib['classname'] = '%s-%s' % (name, child.attrib.get('classname', ''))
cases.append(test_suite.getchildren())
new_root = ET.Element('testsuite')
new_root.attrib['failures'] = '%s' % failures
new_root.attrib['tests'] = '%s' % tests
new_root.attrib['errors'] = '%s' % errors
new_root.attrib['time'] = '%s' % time
for case in cases:
new_root.extend(case)
new_tree = ET.ElementTree(new_root)
ET.dump(new_tree)
def usage():
this_file = os.path.basename(__file__)
print('Usage: %s results1.xml results2.xml' % this_file)
if __name__ == '__main__':
main()

13
pylint.sh Executable file
View File

@ -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

7
rules Executable file
View File

@ -0,0 +1,7 @@
#!/usr/bin/make -f
export PYBUILD_NAME=django-gssapi
%:
dh $@ --with python2,python3 --buildsystem=pybuild

77
setup.py Executable file
View File

@ -0,0 +1,77 @@
#! /usr/bin/env python
import subprocess
import os
from setuptools import setup, find_packages
from setuptools.command.sdist import sdist
class eo_sdist(sdist):
def run(self):
print("creating VERSION file")
if os.path.exists('VERSION'):
os.remove('VERSION')
version = get_version()
version_file = open('VERSION', 'w')
version_file.write(version)
version_file.close()
sdist.run(self)
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- 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=.dirty','--match=v*'], stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
result = p.communicate()[0]
if p.returncode == 0:
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:
return '0.0.post%s' % len(
subprocess.check_output(
['git', 'rev-list', 'HEAD']).splitlines())
return '0.0'
setup(name="django-gssapi",
version=get_version(),
license="AGPLv3 or later",
description="GSSAPI authentication for Django",
long_description=open('README').read(),
url="http://dev.entrouvert.org/projects/authentic/",
author="Entr'ouvert",
author_email="info@entrouvert.org",
maintainer="Benjamin Dauvergne",
maintainer_email="bdauvergne@entrouvert.com",
packages=find_packages('src'),
zip_safe=False,
include_package_data=True,
install_requires=[
'six',
'django>1.8',
'gssapi',
],
package_dir={
'': 'src',
},
package_data={
'django_gssapi': [
'templates/django_gssapi/*.html',
'static/js/*.js',
],
},
cmdclass={'sdist': eo_sdist})

1
source/format Normal file
View File

@ -0,0 +1 @@
3.0 (quilt)

View File

View File

@ -0,0 +1,78 @@
# django-gssapi - SPNEGO/Kerberos authentication for Django applications
# Copyright (C) 2014-2019 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# 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 __future__ import unicode_literals
import logging
import warnings
from django.contrib.auth import get_user_model
from django.utils.encoding import force_bytes
import gssapi
import gssapi.exceptions
import gssapi.raw as gb
logger = logging.getLogger('django_gssapi')
class GSSAPIBackend(object):
def authenticate(self, request, gssapi_name):
warnings.warn('example backend do not use in production!')
User = get_user_model()
try:
user = User.objects.get(username=str(gssapi_name))
except User.DoesNotExist:
logger.debug('GSSAPI no user found for name %s', gssapi_name)
else:
if user.is_active:
return user
class KerberosPasswordBackend(object):
def principal_from_user(self, user):
return user.username
def authenticate(self, request, username=None, password=None, **kwargs):
'''Verify username and password using Kerberos'''
warnings.warn('Kerberos: example backend do not use in production!')
User = get_user_model()
if username is None:
username = kwargs.get(User.USERNAME_FIELD)
try:
user = User._default_manager.get_by_natural_key(username)
except User.DoesNotExist:
logger.debug('Kerberos: no user for username %s', username)
return
else:
if not user.is_active:
return
principal = self.principal_from_user(user)
try:
name = gb.import_name(force_bytes(principal), gb.NameType.kerberos_principal)
if gb.acquire_cred_with_password(name, force_bytes(password)):
if not user.check_password(password):
user.set_password(password)
user.save()
return user
except gssapi.exceptions.GSSError as e:
logger.debug('Kerberos: password check failed for principal %s: %s', principal, e)

68
src/django_gssapi/drf.py Normal file
View File

@ -0,0 +1,68 @@
# django-gssapi - SPNEGO/Kerberos authentication for Django applications
# Copyright (C) 2014-2019 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# 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 import settings
import rest_framework.authentication
from . import utils
__all__ = ['GSSAPIAuthentication', 'add_gssapi_mutual_auth']
class GSSAPIAuthentication(rest_framework.authentication.BaseAuthentication):
def get_gssapi_store(self):
return getattr(settings, 'GSSAPI_STORE', None)
def get_gssapi_name(self):
# force a service name, ex. :
# server_name = 'HTTP@%s' % self.request.get_host()
# return gssapi.Name(server_name, name_type=gssapi.NameType.hostbased_service)
# without one, any service name in keytab will do
return getattr(settings, 'GSSAPI_NAME', None)
def authenticate(self, request):
try:
user, gss_name, token = utils.negotiate_and_auth(
request,
name=self.get_gssapi_name(),
store=self.get_gssapi_store())
except utils.NegotiateContinue as e:
token = e.token
if user is None:
return None
# DRF authentication does not allow to implement
# natively mutual GSSAPI authentication as we
# cannot modify the response, if needed views can retrieve the result
# token and set it on their response
request._drf_gssapi_token = token
return user, gss_name
def authenticate_header(self, request):
return utils.authenticate_header(token=get_gssapi_token(request))
def add_gssapi_mutual_auth(request, response):
token = get_gssapi_token(request)
if token is not None:
response['WWW-Authenticate'] = utils.authenticate_header(token=token)
def get_gssapi_token(request):
return getattr(request, '_drf_gssapi_token', None)

View File

23
src/django_gssapi/urls.py Normal file
View File

@ -0,0 +1,23 @@
# django-gssapi - SPNEGO/Kerberos authentication for Django applications
# Copyright (C) 2014-2019 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# 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 url
from . import views
urlpatterns = [
url(r'^login/$', views.login, name='gssapi-login'),
]

View File

@ -0,0 +1,89 @@
# django-gssapi - SPNEGO/Kerberos authentication for Django applications
# Copyright (C) 2014-2019 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# 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 __future__ import unicode_literals
import base64
import logging
import gssapi
import gssapi.exceptions
from django import http
from django.contrib.auth import authenticate
logger = logging.getLogger('django_kerberos')
class NegotiateContinue(Exception):
def __init__(self, token):
self.token = token
def negotiate(request, name=None, store=None):
'''Try to authenticate the user using SPNEGO and Kerberos'''
if name:
logger.debug(u'GSSAPI negotiate using name %s', name)
try:
server_creds = gssapi.Credentials(usage='accept', name=name, store=store)
except gssapi.exceptions.GSSError as e:
logging.debug('GSSAPI credentials failure: %s', e)
return None, None
if not request.META.get('HTTP_AUTHORIZATION', '').startswith('Negotiate '):
return None, None
authstr = request.META['HTTP_AUTHORIZATION'][10:]
try:
in_token = base64.b64decode(authstr)
except ValueError:
return None, None
server_ctx = gssapi.SecurityContext(creds=server_creds, usage='accept')
try:
out_token = server_ctx.step(in_token)
except gssapi.exceptions.GSSError as e:
logging.debug('GSSAPI security context failure: %s', e)
if not server_ctx.complete:
raise NegotiateContinue(out_token)
return server_ctx.initiator_name, out_token
def negotiate_and_auth(request, name=None, store=None):
gssapi_name, token = negotiate(request, name=name, store=store)
if gssapi_name is None:
return None, None, token
return authenticate(gssapi_name=gssapi_name), gssapi_name, token
def challenge_response():
'''Send negotiate challenge'''
response = http.HttpResponse('GSSAPI authentication failed', status=401)
response_add_www_authenticate(response)
return response
def authenticate_header(token=None):
return 'Negotiate%s' % (' ' + base64.b64encode(token).decode('ascii') if token else '')
def response_add_www_authenticate(response, token=None):
response['WWW-Authenticate'] = authenticate_header(token)

102
src/django_gssapi/views.py Normal file
View File

@ -0,0 +1,102 @@
# django-gssapi - SPNEGO/Kerberos authentication for Django applications
# Copyright (C) 2014-2019 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# 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 __future__ import unicode_literals
import base64
import logging
from django import http
from django.conf import settings
from django.utils.http import is_safe_url
from django.views.generic.base import View
from django.contrib.auth import authenticate, login as auth_login, REDIRECT_FIELD_NAME
from . import utils
logger = logging.getLogger('django_kerberos')
class NegotiateFailed(Exception):
pass
class LoginView(View):
redirect_field_name = REDIRECT_FIELD_NAME
def get_success_url_allowed_hosts(self):
return {self.request.get_host()}
def get_redirect_url(self):
"""Return the user-originating redirect URL if it's safe."""
redirect_to = self.request.POST.get(
self.redirect_field_name,
self.request.GET.get(self.redirect_field_name, '')
)
url_is_safe = is_safe_url(
url=redirect_to,
allowed_hosts=self.get_success_url_allowed_hosts(),
require_https=self.request.is_secure(),
)
return redirect_to if url_is_safe else settings.LOGIN_REDIRECT_URL
def get_gssapi_store(self):
return getattr(settings, 'GSSAPI_STORE', None)
def get_gssapi_name(self):
# force a service name, ex. :
# server_name = 'HTTP@%s' % self.request.get_host()
# return gssapi.Name(server_name, name_type=gssapi.NameType.hostbased_service)
# without one, any service name in keytab will do
return getattr(settings, 'GSSAPI_NAME', None)
def challenge(self):
'''Send negotiate challenge'''
return utils.challenge_response()
def success(self, user):
'''Do something with the user we found'''
auth_login(self.request, user)
return http.HttpResponseRedirect(self.get_redirect_url())
def negotiate(self):
'''Try to authenticate the user using SPNEGO'''
try:
user, gss_name, token = utils.negotiate_and_auth(
self.request,
name=self.get_gssapi_name(),
store=self.get_gssapi_store())
except utils.NegotiateContinue as e:
token = e.token
if user is None:
response = self.challenge()
else:
logger.debug('GSSAPI found user %s for name %s', user, gss_name)
response = self.success(user)
utils.response_add_www_authenticate(response, token)
return response
def get(self, request, *args, **kwargs):
return self.negotiate()
def post(self, request, *args, **kwargs):
return self.negotiate()
login = LoginView.as_view()

62
tests/conftest.py Normal file
View File

@ -0,0 +1,62 @@
# django-gssapi - SPNEGO/Kerberos authentication for Django applications
# Copyright (C) 2014-2019 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# 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 __future__ import unicode_literals
import base64
import os
import pytest
import gssapi
import k5test
import k5test._utils
from django.contrib.auth import get_user_model
User = get_user_model()
@pytest.fixture
def k5env():
k5realm = k5test.K5Realm()
old_environ = os.environ.copy()
try:
os.environ.update(k5realm.env)
k5realm.http_princ = 'HTTP/testserver@%s' % k5realm.realm
k5realm.addprinc(k5realm.http_princ)
k5realm.extract_keytab(k5realm.http_princ, k5realm.keytab)
def spnego():
service_name = gssapi.Name(k5realm.http_princ)
service_name.canonicalize(gssapi.MechType.kerberos)
# first attempt
ctx = gssapi.SecurityContext(usage='initiate', name=service_name)
return 'Negotiate %s' % base64.b64encode(ctx.step()).decode('ascii')
k5realm.spnego = spnego
yield k5realm
finally:
os.environ.clear()
os.environ.update(old_environ)
k5realm.stop()
@pytest.fixture
def user_princ(k5env):
return User.objects.create(username=k5env.user_princ)

53
tests/settings.py Normal file
View File

@ -0,0 +1,53 @@
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',
'django.contrib.sessions',
'django_gssapi',
)
TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), 'templates'),)
ROOT_URLCONF = 'django_gssapi.urls'
SECRET_KEY = 'xxx'
AUTHENTICATION_BACKENDS = (
'django_gssapi.backends.GSSAPIBackend',
'django_gssapi.backends.KerberosPasswordBackend',
)

View File

@ -0,0 +1,2 @@
{% block content %}
{% endblock %}

31
tests/test_backends.py Normal file
View File

@ -0,0 +1,31 @@
# django-gssapi - SPNEGO/Kerberos authentication for Django applications
# Copyright (C) 2014-2019 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# 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/>.
import os
from django.contrib.auth import get_user_model, authenticate
User = get_user_model()
def test_kerberos_password(k5env, db):
user = User.objects.create(username=k5env.user_princ)
k5env.run(['kdestroy'])
assert authenticate(username=k5env.user_princ, password='nogood') is None
assert authenticate(username=k5env.user_princ, password=k5env.password('user')) == user
assert not os.path.exists(k5env.ccache)

59
tests/test_drf.py Normal file
View File

@ -0,0 +1,59 @@
# django-gssapi - SPNEGO/Kerberos authentication for Django applications
# Copyright (C) 2014-2019 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# 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 rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from django_gssapi.drf import GSSAPIAuthentication, add_gssapi_mutual_auth
class RestView(APIView):
authentication_classes = [GSSAPIAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request):
response = Response({
'user': str(request.user),
'auth': str(request.auth),
})
add_gssapi_mutual_auth(request, response)
return response
view = RestView.as_view()
def test_gssapi_authentication_no_auth(k5env, db, rf):
request = rf.get('/')
response = view(request)
assert response.status_code == 401
assert response['WWW-Authenticate'] == 'Negotiate'
def test_gssapi_authentication_no_user(k5env, db, rf):
request = rf.get('/', HTTP_AUTHORIZATION=k5env.spnego())
response = view(request)
assert response.status_code == 401
assert response['WWW-Authenticate'] == 'Negotiate'
def test_gssapi_authentication_passing(k5env, db, rf, user_princ):
request = rf.get('/', HTTP_AUTHORIZATION=k5env.spnego())
response = view(request)
assert response.status_code == 200
assert response.data['user'] == k5env.user_princ
assert response.data['auth'] == k5env.user_princ
assert response['WWW-Authenticate'].startswith('Negotiate ')

49
tests/test_views.py Normal file
View File

@ -0,0 +1,49 @@
# django-gssapi - SPNEGO/Kerberos authentication for Django applications
# Copyright (C) 2014-2019 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# 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/>.
import logging
import gssapi
from django.contrib.auth import get_user_model
User = get_user_model()
def test_login(k5env, client, caplog, db, settings):
caplog.set_level(logging.DEBUG)
response = client.get('/login/')
assert response.status_code == 401
response = client.get('/login/', HTTP_AUTHORIZATION=k5env.spnego())
assert response.status_code == 401
assert '_auth_user_id' not in client.session
# create an user...
User.objects.create(username=k5env.user_princ)
# and retry.
response = client.get('/login/', HTTP_AUTHORIZATION=k5env.spnego())
assert response.status_code == 302
assert client.session['_auth_user_id']
# break service name resolution
settings.GSSAPI_NAME = gssapi.Name('HTTP@localhost', gssapi.NameType.hostbased_service)
response = client.get('/login/', HTTP_AUTHORIZATION=k5env.spnego())
assert response.status_code == 401

0
tests/urls.py Normal file
View File

45
tox.ini Normal file
View File

@ -0,0 +1,45 @@
# Tox (http://tox.testrun.org/) is a tool for running tests
# in multiple virtualenvs. This configuration file will run the
# test suite on all supported python versions. To use it, "pip install tox"
# and then run "tox" from this directory.
[tox]
toxworkdir = {env:TMPDIR:/tmp}/tox-{env:USER}/django-gssapi/{env:BRANCH_NAME:}
envlist = py27-coverage-dj111-{stretch,},py3-coverage-{dj111,dj20,djlast},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
DB_ENGINE=django.db.backends.sqlite3
usedevelop = true
deps =
stretch: gssapi<1.2.3
dj18: django>1.8,<1.9
dj18: django-tables2<1.1
dj111: django<2.0
dj20: django<2.1
djlast: django
pytest<4.2
pytest-mock
pytest-django
pytest-cov
k5test
django-rest-framework
commands =
py.test {env:COVERAGE:} -o junit_suite_name={envname} --junit-xml=junit-{envname}.xml {posargs:tests}
[testenv:pylint]
deps =
pylint<1.8
pylint-django<0.8.1
commands =
pylint: ./pylint.sh src/django_gssapi/
[pytest]
filterwarnings =
ignore:Kerberos. example backend.*