From 4a5a5b1d59bd41d1e567cef48c24d591aaab0f2f Mon Sep 17 00:00:00 2001
From: Bill Fenner <fenner@fenron.net>
Date: Wed, 23 May 2007 16:10:32 +0000
Subject: [PATCH] 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
---
 ietf/ietfauth/.gitignore  |  2 ++
 ietf/ietfauth/__init__.py |  0
 ietf/ietfauth/auth.py     | 42 ++++++++++++++++++++++++++++++++
 ietf/ietfauth/models.py   | 50 +++++++++++++++++++++++++++++++++++++++
 ietf/ietfauth/views.py    |  1 +
 ietf/settings.py          | 10 ++++++++
 6 files changed, 105 insertions(+)
 create mode 100644 ietf/ietfauth/.gitignore
 create mode 100644 ietf/ietfauth/__init__.py
 create mode 100644 ietf/ietfauth/auth.py
 create mode 100644 ietf/ietfauth/models.py
 create mode 100644 ietf/ietfauth/views.py

diff --git a/ietf/ietfauth/.gitignore b/ietf/ietfauth/.gitignore
new file mode 100644
index 000000000..8ca2ec2c8
--- /dev/null
+++ b/ietf/ietfauth/.gitignore
@@ -0,0 +1,2 @@
+/*.swp
+/*.pyc
diff --git a/ietf/ietfauth/__init__.py b/ietf/ietfauth/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/ietf/ietfauth/auth.py b/ietf/ietfauth/auth.py
new file mode 100644
index 000000000..13f9fdf28
--- /dev/null
+++ b/ietf/ietfauth/auth.py
@@ -0,0 +1,42 @@
+from django.contrib.auth.backends import ModelBackend
+from django.core.validators import email_re
+from django.contrib.auth.models import User
+
+def crypt_check_password(user, raw_password):
+    """
+    Returns a boolean of whether the raw_password was correct. Handles
+    crypt format only, and updates the password to the hashed version
+    on first use.  This is like User.check_password().
+    """
+    enc_password = user.password
+    algo, salt, hsh = enc_password.split('$')
+    if algo == 'crypt':
+        import crypt
+        is_correct = ( salt + hsh == crypt.crypt(raw_password, salt) )
+	if is_correct:
+	    user.set_password(raw_password)
+	    user.save()
+	return is_correct
+    return user.check_password(raw_password)
+
+# Based on http://www.djangosnippets.org/snippets/74/
+#  but modified to use crypt_check_password for all users.
+class EmailBackend(ModelBackend):
+    def authenticate(self, username=None, password=None):
+	try:
+	    if email_re.search(username):
+                user = User.objects.get(email=username)
+	    else:
+		user = User.objects.get(username=username)
+	except User.DoesNotExist:
+	    return None
+	if crypt_check_password(user, password):
+	    return user
+        return None
+
+    def get_user(self, user_id):
+	try:
+	    return User.objects.get(pk=user_id)
+	except User.DoesNotExist:
+	    return None
+
diff --git a/ietf/ietfauth/models.py b/ietf/ietfauth/models.py
new file mode 100644
index 000000000..6329e0d91
--- /dev/null
+++ b/ietf/ietfauth/models.py
@@ -0,0 +1,50 @@
+from django.db import models
+from django.contrib.auth.models import User
+from ietf.idtracker.models import PersonOrOrgInfo
+
+class UserMap(models.Model):
+    """
+    This is effectively a many:1 mapping of django-user -> IETF user.
+    It'd ideally be 1:1, but for testing some users have multiple
+    accounts with different privilege levels.
+    """
+    user = models.ForeignKey(User, raw_id_admin=True, core=True, unique=True)
+    person = models.ForeignKey(PersonOrOrgInfo, edit_inline=models.STACKED, max_num_in_admin=1)
+    def __str__(self):
+	return "Mapping django user %s to IETF person %s" % ( self.user, self.person )
+
+
+######################################################
+# legacy per-tool access tables.
+# ietf.idtracker.models.IESGLogin is in the same vein.
+
+class LiaisonUser(models.Model):
+    person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag', primary_key=True, raw_id_admin=True)
+    login_name = models.CharField(maxlength=255)
+    password = models.CharField(maxlength=25)
+    user_level = models.IntegerField(null=True, blank=True)
+    comment = models.TextField(blank=True)
+    def __str__(self):
+	return self.login_name
+    class Meta:
+        db_table = 'users'
+	ordering = ['login_name']
+    class Admin:
+	pass
+
+class WgPassword(models.Model):
+    person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag', primary_key=True, raw_id_admin=True)
+    password = models.CharField(blank=True, maxlength=255)
+    secrete_question_id = models.IntegerField(null=True, blank=True)
+    secrete_answer = models.CharField(blank=True, maxlength=255)
+    is_tut_resp = models.IntegerField(null=True, blank=True)
+    irtf_id = models.IntegerField(null=True, blank=True)
+    comment = models.TextField(blank=True)
+    login_name = models.CharField(blank=True, maxlength=100)
+    def __str__(self):
+	return self.login_name
+    class Meta:
+        db_table = 'wg_password'
+	ordering = ['login_name']
+    class Admin:
+	pass
diff --git a/ietf/ietfauth/views.py b/ietf/ietfauth/views.py
new file mode 100644
index 000000000..60f00ef0e
--- /dev/null
+++ b/ietf/ietfauth/views.py
@@ -0,0 +1 @@
+# Create your views here.
diff --git a/ietf/settings.py b/ietf/settings.py
index 5a6892ec3..8f4613d7b 100644
--- a/ietf/settings.py
+++ b/ietf/settings.py
@@ -57,6 +57,15 @@ MEDIA_URL = ''
 # Examples: "http://foo.com/media/", "/media/".
 ADMIN_MEDIA_PREFIX = '/media/'
 
+# Link django user to IETF user
+AUTH_PROFILE_MODULE = 'ietfauth.UserMap'
+
+# Allow specification of email address as username,
+# and handle htpasswd crypt() format passwords.
+AUTHENTICATION_BACKENDS = (
+    "ietf.ietfauth.auth.EmailBackend",
+)
+
 # List of callables that know how to import templates from various sources.
 TEMPLATE_LOADERS = (
     'django.template.loaders.filesystem.load_template_source',
@@ -92,6 +101,7 @@ INSTALLED_APPS = (
     'ietf.announcements',
     'ietf.idindex',
     'ietf.idtracker',
+    'ietf.ietfauth',
     'ietf.iesg',
     'ietf.ipr',
     'ietf.liaisons',