Skip to content

Commit 11887ca

Browse files
committed
Replaced old authentication/authorization system with new one (which uses Apache's authentication)
- Legacy-Id: 1877
1 parent 875b97e commit 11887ca

20 files changed

Lines changed: 363 additions & 301 deletions

changelog

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,22 @@
1+
ietfdb (2.39)
2+
3+
* Replaced old authentication/authorization system with a new one
4+
(which uses Apache's authentication). This requires applying
5+
database fixups as follows:
6+
7+
cd /a/www/ietf-datatracker/2.39/ietf
8+
PYTHONPATH=../ python manage.py dbshell < ../test/sql_fixup.sql
9+
PYTHONPATH=../ python manage.py syncdb
10+
11+
And adding something like this to Apache configuration:
12+
13+
<LocationMatch "^/accounts/login/">
14+
AuthType Basic
15+
AuthName "IETF Datatracker"
16+
AuthUserFile /a/www/htpasswd
17+
Require valid-user
18+
</LocationMatch>
19+
120
ietfdb (2.38)
221

322
Miscellaneous minor fixes:

ietf/ietfauth/auth.py

Lines changed: 103 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,111 @@
1+
# Portions Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
2+
# All rights reserved. Contact: Pasi Eronen <pasi.eronen@nokia.com>
3+
#
4+
# Redistribution and use in source and binary forms, with or without
5+
# modification, are permitted provided that the following conditions
6+
# are met:
7+
#
8+
# * Redistributions of source code must retain the above copyright
9+
# notice, this list of conditions and the following disclaimer.
10+
#
11+
# * Redistributions in binary form must reproduce the above
12+
# copyright notice, this list of conditions and the following
13+
# disclaimer in the documentation and/or other materials provided
14+
# with the distribution.
15+
#
16+
# * Neither the name of the Nokia Corporation and/or its
17+
# subsidiary(-ies) nor the names of its contributors may be used
18+
# to endorse or promote products derived from this software
19+
# without specific prior written permission.
20+
#
21+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25+
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32+
133
# Copyright The IETF Trust 2007, All Rights Reserved
234

3-
from django.contrib.auth.backends import ModelBackend
4-
from django.forms.fields import email_re
5-
from django.contrib.auth.models import User
6-
from django.conf import settings
7-
from django.core.exceptions import ObjectDoesNotExist
8-
from ietf.ietfauth.models import UserMap
9-
import md5
35+
from django.contrib.auth.backends import RemoteUserBackend
36+
from django.contrib.auth.models import Group
37+
from ietf.idtracker.models import IESGLogin, Role, PersonOrOrgInfo
38+
from ietf.ietfauth.models import LegacyWgPassword, IetfUserProfile
39+
40+
from ietf.utils import log
41+
42+
class IetfUserBackend(RemoteUserBackend):
43+
44+
def find_groups(username):
45+
"""
46+
Role/Group:
47+
Area_Director currently sitting AD
48+
IETF_Chair currently sitting IETF Chair
49+
IAB_Chair currently sitting IAB Chair
50+
IRTF_Chair currently sitting IRTF Chair
51+
Secretariat secretariat staff
1052
11-
def compat_check_password(user, raw_password):
12-
"""
13-
Returns a boolean of whether the raw_password was correct. Handles
14-
crypt and htdigest formats, and updates the password to htdigest
15-
on first use. This is like User.check_password().
16-
"""
17-
enc_password = user.password
18-
algo, salt, hsh = enc_password.split('$')
19-
if algo == 'crypt':
20-
import crypt
21-
is_correct = ( salt + hsh == crypt.crypt(raw_password, salt) )
22-
if is_correct:
23-
# upgrade to htdigest
24-
set_password(user, raw_password)
25-
return is_correct
26-
if algo == 'htdigest':
27-
# Check username hash.
28-
is_correct = ( hsh == htdigest( user.username, raw_password ) )
29-
if not is_correct:
30-
# Try to check email hash, which we stored in the profile.
31-
# If the profile doesn't exist, that's odd but we shouldn't
32-
# completely fail, so try/except it.
33-
try:
34-
is_correct = ( user.get_profile().email_htdigest == htdigest( user.email, raw_password ) )
35-
except ObjectDoesNotExist:
36-
# no user profile to store the htdigest, so can't check it.
37-
pass
38-
return is_correct
39-
# permit django passwords, but upgrade to htdigest
40-
is_correct = user.check_password(raw_password)
41-
if is_correct:
42-
# upgrade to htdigest
43-
set_password(user, raw_password)
44-
return is_correct
53+
Roles/Groups NOT YET IMPLEMENTED
54+
WG_Chair currently sitting chair of some WG
55+
IESG_Liaison non-ADs on iesg@ietf.org and telechats
56+
Session_Chair chairing a non-WG session in IETF meeting
57+
Ex_Area_Director past AD
58+
"""
59+
groups = []
60+
try:
61+
login = IESGLogin.objects.get(login_name=username)
62+
if login.user_level == 1:
63+
groups.append("Area_Director")
64+
elif login.user_level == 0:
65+
groups.append("Secretariat")
66+
try:
67+
person = login.person
68+
for role in person.role_set.all():
69+
if role.id == Role.IETF_CHAIR:
70+
groups.append("IETF_Chair")
71+
elif role.id == Role.IAB_CHAIR:
72+
groups.append("IAB_Chair")
73+
elif role.id == Role.IRTF_CHAIR:
74+
groups.append("IRTF_Chair")
75+
except PersonOrOrgInfo.DoesNotExist:
76+
pass
77+
except IESGLogin.DoesNotExist:
78+
pass
79+
#
80+
# Additional sources of group memberships:
81+
# - wg_password table
82+
# - other Roles
83+
# - the /etc/.../*.perms files
84+
return groups
4585

46-
# Based on http://www.djangosnippets.org/snippets/74/
47-
# but modified to use compat_check_password for all users.
48-
class EmailBackend(ModelBackend):
49-
def authenticate(self, username=None, password=None):
50-
try:
51-
if email_re.search(username):
52-
user = User.objects.get(email__iexact=username)
53-
else:
54-
user = User.objects.get(username__iexact=username)
55-
except User.DoesNotExist:
56-
#
57-
# See if there's an IETF person with this address:
58-
try:
59-
usermap = UserMap.objects.distinct().get(person__emailaddress__address__iexact=username)
60-
except UserMap.DoesNotExist:
61-
return None
62-
except AssertionError:
63-
# multiple UserMaps, should never happen!
64-
return None
65-
user = usermap.user
66-
if compat_check_password(user, password):
67-
return user
68-
return None
86+
find_groups = staticmethod(find_groups)
6987

70-
def get_user(self, user_id):
71-
try:
72-
return User.objects.get(pk=user_id)
73-
except User.DoesNotExist:
74-
return None
88+
def authenticate(self, remote_user):
89+
user = RemoteUserBackend.authenticate(self, remote_user)
90+
if not user:
91+
return user
7592

76-
def htdigest( username, password, realm=None ):
77-
"""Returns a hashed password in the Apache htdigest format, which
78-
is used in an AuthDigestFile ."""
79-
if realm is None:
80-
try:
81-
realm = settings.DIGEST_REALM
82-
except AttributeError:
83-
realm = 'IETF'
84-
return md5.md5( ':'.join( [ username, realm, password ] ) ).hexdigest()
93+
# Create profile if it doesn't exist
94+
try:
95+
profile = user.get_profile()
96+
except IetfUserProfile.DoesNotExist:
97+
profile = IetfUserProfile(user=user)
98+
profile.save()
8599

86-
def set_password( user, password, realm=None ):
87-
# The username-hashed digest goes in the user database;
88-
# the email-address-hashed digest goes in the userprof.
89-
user.password = '$'.join( [ 'htdigest', '',
90-
htdigest( user.username, password, realm ) ] )
91-
user.save()
92-
( userprof, created ) = UserMap.objects.get_or_create( user=user )
93-
userprof.email_htdigest = htdigest( user.email, password, realm )
94-
userprof.rfced_htdigest = htdigest( user.email, password, 'RFC Editor' )
95-
userprof.save()
100+
# Update group memberships
101+
group_names = IetfUserBackend.find_groups(user.username)
102+
groups = []
103+
for group_name in group_names:
104+
# Create groups as needed
105+
group,created = Group.objects.get_or_create(name=group_name)
106+
if created:
107+
log("IetfUserBackend created Group '%s'" % (group_name,))
108+
groups.append(group)
109+
user.groups = groups
110+
return user
96111

97-
# changes done by convert-096.py:changed email_re import

ietf/ietfauth/models.py

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,68 @@
1+
# Portions Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
2+
# All rights reserved. Contact: Pasi Eronen <pasi.eronen@nokia.com>
3+
#
4+
# Redistribution and use in source and binary forms, with or without
5+
# modification, are permitted provided that the following conditions
6+
# are met:
7+
#
8+
# * Redistributions of source code must retain the above copyright
9+
# notice, this list of conditions and the following disclaimer.
10+
#
11+
# * Redistributions in binary form must reproduce the above
12+
# copyright notice, this list of conditions and the following
13+
# disclaimer in the documentation and/or other materials provided
14+
# with the distribution.
15+
#
16+
# * Neither the name of the Nokia Corporation and/or its
17+
# subsidiary(-ies) nor the names of its contributors may be used
18+
# to endorse or promote products derived from this software
19+
# without specific prior written permission.
20+
#
21+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25+
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32+
133
# Copyright The IETF Trust 2007, All Rights Reserved
234

335
from django.db import models
436
from django.contrib.auth.models import User
5-
from ietf.idtracker.models import PersonOrOrgInfo
37+
from ietf.idtracker.models import PersonOrOrgInfo, IESGLogin
38+
39+
40+
def find_person(username):
41+
try:
42+
person = IESGLogin.objects.get(login_name=username).person
43+
return person
44+
except IESGLogin.DoesNotExist, PersonOrOrgInfo.DoesNotExist:
45+
pass
46+
# TODO: try LegacyWgPassword next
47+
return None
48+
49+
class IetfUserProfile(models.Model):
50+
user = models.ForeignKey(User,unique=True)
51+
52+
def person(self):
53+
return find_person(self.user.username)
654

7-
class UserMap(models.Model):
8-
"""
9-
This is a 1:1 mapping of django-user -> IETF user.
10-
This can't represent the users in the existing tool that
11-
have multiple accounts with multiple privilege levels: they
12-
need extra IETF users.
55+
def iesg_login_id(self):
56+
person = self.person()
57+
if not person:
58+
return None
59+
try:
60+
return person.iesglogin_set.all()[0].id
61+
except:
62+
return None
1363

14-
It also contains a text field for the user's hashed htdigest
15-
password. In order to allow logging in with either username
16-
or email address, we need to store two hashes. One is in the
17-
user model's password field, the other is here. We also store
18-
a hashed version of just the email address for the RFC Editor.
19-
"""
20-
user = models.ForeignKey(User)
21-
# user should have unique=True, but that confuses the
22-
# admin edit_inline interface.
23-
person = models.ForeignKey(PersonOrOrgInfo, unique=True, null=True)
24-
email_htdigest = models.CharField(max_length=32, blank=True, null=True)
25-
rfced_htdigest = models.CharField(max_length=32, blank=True, null=True)
2664
def __str__(self):
27-
return "Mapping django user %s to IETF person %s" % ( self.user, self.person )
65+
return "IetfUserProfile(%s)" % (self.user,)
2866

2967

3068
######################################################

0 commit comments

Comments
 (0)