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',