Skip to content

Commit 4a5a5b1

Browse files
committed
Introduce initial authentication/authorization linkage. This has a
couple of aspects: - ietfauth.auth.EmailBackEnd is a django.contrib.auth backend to allow two modified authentication methods: - using email address (stored in django user table) as login username - using htpasswd-style "crypt" passwords (for compatability with existing user database). On the first successful login, the password will be re-hashed to the django-hash style password. - ietfauth.models.UserMap: a mapping from django user to IETF person. This is configured as the profile table, meaning that if you have a django user (e.g., from the RequestContext), you can use user.get_profile.person to get to the IETF person. - ietfauth.models has models for the "legacy" username/person mapping tables (LiaisonUser aka "users" and WgPassword aka "wg_password"). This is to allow mapping of legacy permissions to django permissions by walking these tables and applying permissions to users. The plan is to discard these tables eventually. - Legacy-Id: 155
1 parent d4d3b47 commit 4a5a5b1

6 files changed

Lines changed: 105 additions & 0 deletions

File tree

ietf/ietfauth/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/*.swp
2+
/*.pyc

ietf/ietfauth/__init__.py

Whitespace-only changes.

ietf/ietfauth/auth.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from django.contrib.auth.backends import ModelBackend
2+
from django.core.validators import email_re
3+
from django.contrib.auth.models import User
4+
5+
def crypt_check_password(user, raw_password):
6+
"""
7+
Returns a boolean of whether the raw_password was correct. Handles
8+
crypt format only, and updates the password to the hashed version
9+
on first use. This is like User.check_password().
10+
"""
11+
enc_password = user.password
12+
algo, salt, hsh = enc_password.split('$')
13+
if algo == 'crypt':
14+
import crypt
15+
is_correct = ( salt + hsh == crypt.crypt(raw_password, salt) )
16+
if is_correct:
17+
user.set_password(raw_password)
18+
user.save()
19+
return is_correct
20+
return user.check_password(raw_password)
21+
22+
# Based on http://www.djangosnippets.org/snippets/74/
23+
# but modified to use crypt_check_password for all users.
24+
class EmailBackend(ModelBackend):
25+
def authenticate(self, username=None, password=None):
26+
try:
27+
if email_re.search(username):
28+
user = User.objects.get(email=username)
29+
else:
30+
user = User.objects.get(username=username)
31+
except User.DoesNotExist:
32+
return None
33+
if crypt_check_password(user, password):
34+
return user
35+
return None
36+
37+
def get_user(self, user_id):
38+
try:
39+
return User.objects.get(pk=user_id)
40+
except User.DoesNotExist:
41+
return None
42+

ietf/ietfauth/models.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from django.db import models
2+
from django.contrib.auth.models import User
3+
from ietf.idtracker.models import PersonOrOrgInfo
4+
5+
class UserMap(models.Model):
6+
"""
7+
This is effectively a many:1 mapping of django-user -> IETF user.
8+
It'd ideally be 1:1, but for testing some users have multiple
9+
accounts with different privilege levels.
10+
"""
11+
user = models.ForeignKey(User, raw_id_admin=True, core=True, unique=True)
12+
person = models.ForeignKey(PersonOrOrgInfo, edit_inline=models.STACKED, max_num_in_admin=1)
13+
def __str__(self):
14+
return "Mapping django user %s to IETF person %s" % ( self.user, self.person )
15+
16+
17+
######################################################
18+
# legacy per-tool access tables.
19+
# ietf.idtracker.models.IESGLogin is in the same vein.
20+
21+
class LiaisonUser(models.Model):
22+
person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag', primary_key=True, raw_id_admin=True)
23+
login_name = models.CharField(maxlength=255)
24+
password = models.CharField(maxlength=25)
25+
user_level = models.IntegerField(null=True, blank=True)
26+
comment = models.TextField(blank=True)
27+
def __str__(self):
28+
return self.login_name
29+
class Meta:
30+
db_table = 'users'
31+
ordering = ['login_name']
32+
class Admin:
33+
pass
34+
35+
class WgPassword(models.Model):
36+
person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag', primary_key=True, raw_id_admin=True)
37+
password = models.CharField(blank=True, maxlength=255)
38+
secrete_question_id = models.IntegerField(null=True, blank=True)
39+
secrete_answer = models.CharField(blank=True, maxlength=255)
40+
is_tut_resp = models.IntegerField(null=True, blank=True)
41+
irtf_id = models.IntegerField(null=True, blank=True)
42+
comment = models.TextField(blank=True)
43+
login_name = models.CharField(blank=True, maxlength=100)
44+
def __str__(self):
45+
return self.login_name
46+
class Meta:
47+
db_table = 'wg_password'
48+
ordering = ['login_name']
49+
class Admin:
50+
pass

ietf/ietfauth/views.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Create your views here.

ietf/settings.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,15 @@
5757
# Examples: "http://foo.com/media/", "/media/".
5858
ADMIN_MEDIA_PREFIX = '/media/'
5959

60+
# Link django user to IETF user
61+
AUTH_PROFILE_MODULE = 'ietfauth.UserMap'
62+
63+
# Allow specification of email address as username,
64+
# and handle htpasswd crypt() format passwords.
65+
AUTHENTICATION_BACKENDS = (
66+
"ietf.ietfauth.auth.EmailBackend",
67+
)
68+
6069
# List of callables that know how to import templates from various sources.
6170
TEMPLATE_LOADERS = (
6271
'django.template.loaders.filesystem.load_template_source',
@@ -92,6 +101,7 @@
92101
'ietf.announcements',
93102
'ietf.idindex',
94103
'ietf.idtracker',
104+
'ietf.ietfauth',
95105
'ietf.iesg',
96106
'ietf.ipr',
97107
'ietf.liaisons',

0 commit comments

Comments
 (0)