Skip to content

Commit 4d033df

Browse files
committed
Convert into Kerberos backend for PSA
See: python-social-auth/social-core#296
1 parent d381dad commit 4d033df

File tree

5 files changed

+154
-143
lines changed

5 files changed

+154
-143
lines changed

README.rst

Lines changed: 29 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,43 @@
1-
Kerberos authentication backend for Kiwi TCMS
2-
=============================================
1+
Kerberos authentication backend for Python Social Auth
2+
======================================================
33

4-
.. image:: https://travis-ci.org/kiwitcms/kiwitcms-auth-kerberos.svg?branch=master
5-
:target: https://travis-ci.org/kiwitcms/kiwitcms-auth-kerberos
4+
.. image:: https://travis-ci.org/kiwitcms/python-social-auth-kerberos.svg?branch=master
5+
:target: https://travis-ci.org/kiwitcms/python-social-auth-kerberos
66

7-
.. image:: https://coveralls.io/repos/github/kiwitcms/kiwitcms-auth-kerberos/badge.svg?branch=master
8-
:target: https://coveralls.io/github/kiwitcms/kiwitcms-auth-kerberos?branch=master
7+
.. image:: https://coveralls.io/repos/github/kiwitcms/python-social-auth-kerberos/badge.svg?branch=master
8+
:target: https://coveralls.io/github/kiwitcms/python-social-auth-kerberos?branch=master
99

1010
Introduction
1111
------------
1212

13-
TODO:
13+
This package provides Kerberos backend for Python Social Auth. It can be used to
14+
enable passwordless authentication inside a Django app or any other application
15+
that supports Python Social Auth. This is a pure Python implementation which doesn't
16+
depend on Apache ``mod_auth_kerb``.
1417

15-
- package this backend and publish to PyPI
16-
- enable testing
17-
- why there are 2 classes for kerberos backend? which one if the real deal
18-
maybe 'tcms.auth.kerberos.ModAuthKerbBackend' ????
19-
- check-out https://github.com/kiwitcms/Kiwi/issues/240
18+
First
19+
`configure PSA <https://python-social-auth.readthedocs.io/en/latest/configuration/index.html>`_
20+
and then the following settings::
2021

2122

22-
This package provides passwordless authentication for Kiwi TCMS via Kerberos.
23-
This is turned off by default because most organizations do not use it. To enable
24-
configure the following settings::
25-
26-
MIDDLEWARE += [
27-
'django.contrib.auth.middleware.RemoteUserMiddleware',
28-
]
29-
30-
AUTHENTICATION_BACKENDS += [
31-
'tcms.auth.kerberos.ModAuthKerbBackend',
23+
AUTHENTICATION_BACKENDS = [
24+
'social_auth_kerberos.backend.KerberosAuth',
25+
'django.contrib.auth.backends.ModelBackend',
3226
]
27+
28+
SOCIAL_AUTH_KRB5_KEYTAB = '/tmp/your-application.keytab'
3329

34-
KRB5_REALM='YOUR-DOMAIN.COM'
35-
36-
37-
Also modify Kiwi TCMS ``Dockerfile`` to include the following lines::
38-
39-
RUN yum -y install krb5-devel mod_auth_kerb
40-
RUN pip install kerberos
41-
COPY ./auth_kerb.conf /etc/httpd/conf.d/
42-
43-
Where ``auth_kerb.conf`` is your Kerberos configuration file for Apache!
44-
More information about it can be found
45-
`here <https://access.redhat.com/documentation/en-US/Red_Hat_JBoss_Web_Server/2/html/HTTP_Connectors_Load_Balancing_Guide/ch10s02s03.html>`_.
30+
For more information about Kerberos see:
31+
* `How to configure Firefox for kerberos <https://people.redhat.com/mikeb/negotiate/>`_
32+
* `How to configure kerberos on Fedora <https://fedoraproject.org/wiki/Kerberos_KDC_Quickstart_Guide>`_
33+
* `How to generate a keytab file
34+
<https://docs.tibco.com/pub/spotfire_server/7.6.1/doc/html/tsas_admin_help/GUID-27726F6E-569C-4704-8433-5CCC0232EC79.html>`_
4635

4736
.. warning::
4837

49-
Unless Kerberos authentication is configured and fully-operational the
50-
XML-RPC method `Auth.login_krbv()` will not work!
38+
USE AT YOUR OWN RISK!
39+
40+
This module has been tested manually with Kiwi TCMS. Automated tests
41+
do not exist because we can't quite figure out how to use
42+
`gssapi-console <https://github.com/pythongssapi/gssapi-console>`_ as part of
43+
unit tests! If you do figure it out a pull request will be greatly appreciated!

setup.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# pylint: disable=missing-docstring
2+
3+
from setuptools import setup, find_packages
4+
5+
6+
def get_long_description():
7+
with open('README.rst', 'r') as file:
8+
return file.read()
9+
10+
11+
setup(
12+
name='social-auth-kerberos',
13+
version='0.2.1',
14+
description='Kerberos authentication backend for Python Social Auth',
15+
long_description=get_long_description(),
16+
author='Kiwi TCMS',
17+
author_email='info@kiwitcms.org',
18+
url='https://github.com/kiwitcms/python-social-auth-kerberos/',
19+
license='GPLv2+',
20+
keywords='social auth, kerberos',
21+
install_requires=['social-auth-core', 'gssapi'],
22+
packages=['social_auth_kerberos'],
23+
classifiers=[
24+
'Development Status :: 4 - Beta',
25+
'Topic :: Internet',
26+
'Environment :: Web Environment',
27+
'Intended Audience :: Developers',
28+
'License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)',
29+
'Programming Language :: Python :: 3',
30+
'Programming Language :: Python :: 3.6',
31+
],
32+
)

social_auth_kerberos/__init__.py

Whitespace-only changes.

social_auth_kerberos/backend.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import base64
2+
import gssapi
3+
4+
from social_core.backends.base import BaseAuth
5+
from social_core.exceptions import AuthException
6+
7+
8+
class KerberosAuth(BaseAuth):
9+
_krb5 = {}
10+
name = 'kerberos'
11+
12+
def start(self):
13+
response = self.strategy.html('Authorization Required')
14+
response.status_code = 401
15+
response['WWW-Authenticate'] = 'Negotiate'
16+
17+
# browser didn't send negotiation token
18+
if 'HTTP_AUTHORIZATION' not in self.strategy.request.META:
19+
# this will keep a reference to the current user b/c
20+
# kerberos auth is a stateful protocol while HTTP is stateless.
21+
context_id = "%d@%d" % (hash(repr(self.strategy.request.META)),
22+
id(self.strategy.request))
23+
self.strategy.request.session['_krb5'] = context_id
24+
self._krb5[context_id] = None
25+
return response
26+
27+
token = self.strategy.request.META['HTTP_AUTHORIZATION']
28+
token = token.split('Negotiate')[-1].strip()
29+
token = base64.b64decode(token)
30+
31+
context_id = self.strategy.request.session.get('_krb5', None)
32+
if self._krb5[context_id] is None:
33+
keytab_path = self.strategy.setting('SOCIAL_AUTH_KRB5_KEYTAB')
34+
creds = gssapi.Credentials(usage='accept',
35+
store={'keytab': keytab_path})
36+
self._krb5[context_id] = gssapi.SecurityContext(creds=creds)
37+
38+
server_token = self._krb5[context_id].step(token)
39+
response = self.strategy.redirect(self.redirect_uri)
40+
41+
if server_token is not None:
42+
server_token_hex = base64.b64encode(server_token).decode()
43+
response['WWW-Authenticate'] = 'Negotiate %s' % server_token_hex
44+
45+
return response
46+
47+
def auth_complete(self, *args, **kwargs):
48+
"""Completes loging process, must return user instance"""
49+
try:
50+
context_id = self.strategy.request.session.get('_krb5', None)
51+
52+
if context_id not in self._krb5:
53+
raise AuthException(self.name, 'Authentication failed. context_id not found!')
54+
55+
if not self._krb5[context_id].complete:
56+
raise AuthException(self.name, 'Authentication failed')
57+
58+
kwargs.update({
59+
'response': {
60+
'krb5_initiator': str(self._krb5[context_id].initiator_name),
61+
},
62+
'backend': self,
63+
})
64+
finally:
65+
if context_id in self._krb5:
66+
del self._krb5[context_id]
67+
68+
if '_krb5' in self.strategy.request.session:
69+
del self.strategy.request.session['_krb5']
70+
71+
return self.strategy.authenticate(*args, **kwargs)
72+
73+
def auth_allowed(self, response, details):
74+
"""Return True if the user should be allowed to authenticate"""
75+
return 'krb5_initiator' in response
76+
77+
def get_user_id(self, details, response):
78+
"""Return a unique ID for the current user, by default from server
79+
response."""
80+
return response['krb5_initiator']
81+
82+
def get_user_details(self, response):
83+
"""Return user details"""
84+
email = response['krb5_initiator'].split('/')[-1].lower()
85+
username = email.split('@')[0]
86+
87+
return {
88+
'username': username,
89+
'email': email,
90+
'fullname': '',
91+
'first_name': '',
92+
'last_name': ''
93+
}

tcms/auth/kerberos.py

Lines changed: 0 additions & 107 deletions
This file was deleted.

0 commit comments

Comments
 (0)