Ok, here's a stripped-down solution.

I ended up creating a new SchoolUser user model with a OneToOne relation to my 
LegacyUser, to keep the LegacyUser model uncluttered. The SchoolUser implements 
all methods from AbstractBaseUser and PermissionsMixin but doesn't inherit from 
them, because I don't want the model fields that they contain.

I also kept the SchoolUser independent from the standard Django User (i.e. no 
AUTH_USER_MODEL='SchoolUser' in settings.py), so I can still create superuser 
accounts for myself and my colleagues, that are not connected to a school user.

Here's the code:


settings.py:
[...]
AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend',
    'school_auth.backends.SchoolModelBackend',
)


school_auth/backend.py:
from django.contrib.auth.backends import ModelBackend
from .models import SchoolUser, LegacyUser
class SchoolModelBackend(object):
    def authenticate(self, school_id=None, username=None, password=None, 
**kwargs):
        if LegacyUser.validate(school=school_id, username=username, 
password=password):
            # Password hash validation
            try:
                school_user = SchoolUser.objects.get(user__school=school_id, 
user__name=username)
            except SchoolUser.DoesNotExist:
                school_user = 
SchoolUser.objects.create_user(school_id=school_id, username=username)
            # Annotate the user object with the path of the backend.
            school_user.backend = "%s.%s" % (self.__class__.__module__, 
self.__class__.__name__)
            return school_user
        #
        # if LDAP.validate(school=school_id, username=username, 
password=password):
        #     pass
        # if WAYF.validate(school=school_id, username=username, 
password=password):
        #     pass
        return None

    def get_group_permissions(self, user_obj, obj=None):
        raise NotImplementedError()
    def get_all_permissions(self, user_obj, obj=None):
        raise NotImplementedError()

    def has_perm(self, user_obj, perm, obj=None):
        if not user_obj.is_active:
            return False
        return perm in self.get_all_permissions(user_obj, obj)

    def has_module_perms(self, user_obj, app_label):
        if not user_obj.is_active:
            return False
        for perm in self.get_all_permissions(user_obj):
            if perm[:perm.index('.')] == app_label:
                return True
        return False

    def get_user(self, user_id):
        try:
            return SchoolUser.objects.get(pk=user_id)
        except SchoolUser.DoesNotExist:
            return None


school_auth/forms.py:
from django.contrib.auth.forms import AuthenticationForm, PasswordResetForm, 
PasswordChangeForm
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.utils.text import capfirst
from .models import LegacyUser, School, SchoolUser
from .backends import SchoolModelBackend
class SchoolAuthenticationForm(AuthenticationForm):
    school = forms.ModelChoiceField(queryset=School.objects.active(), 
empty_label=_('Please select a school'))
    username = forms.CharField(max_length=40)
    password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)

    class Meta:
        model = LegacyUser
        fields = ['school', 'name', 'password']

    def __init__(self, request=None, *args, **kwargs):
        """
        The 'request' parameter is set for custom auth use by subclasses.
        The form data comes in via the standard 'data' kwarg.
        """
        self.request = request
        self.user_cache = None
        super().__init__(*args, **kwargs)

        # Set the label for the "username" field.
        self.username_field = 
LegacyUser._meta.get_field(SchoolUser.USERNAME_FIELD)
        if self.fields['username'].label is None:
            self.fields['username'].label = 
capfirst(self.username_field.verbose_name)

    def clean(self):
        school = self.cleaned_data.get('school')
        username = self.cleaned_data.get('username')
        password = self.cleaned_data.get('password')

        if school and username and password:
            self.user_cache = 
SchoolModelBackend().authenticate(school_id=school.pk, username=username,
                                                                  
password=password)
            if self.user_cache is None:
                raise forms.ValidationError(
                    self.error_messages['invalid_login'],
                    code='invalid_login',
                    params={'username': self.username_field.verbose_name},
                )
            else:
                self.confirm_login_allowed(self.user_cache)

        return self.cleaned_data


school_auth/models.py:
from django.contrib.auth.models import PermissionsMixin, BaseUserManager, 
AbstractBaseUser
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from django.utils.crypto import salted_hmac
from ..legacy.models import LegacyUser, School
class SchoolUserManager(BaseUserManager):
    def create_user(self, school_id, username):
        user = LegacyUser.objects.get(school=school_id, name=username)
        school_user = self.model(user=user)
        school_user.save()
        return school_user


class SchoolUser(models.Model):
    """
    Custom User model. We don't inherit AbstractBaseUser because we don't want 
the password field.
    """
    USERNAME_FIELD = 'name'

    user = models.OneToOneField(User, null=False)
    last_login = models.DateTimeField(_('last login'), default=timezone.now)

    objects = SchoolUserManager()
    REQUIRED_FIELDS = []

    @property
    def school(self):
        return self.user.school

    def __str__(self):
        return self.get_username()

    def get_username(self):
        return self.user.name

    @property
    def is_active(self):
        return self.user.active

    @property
    def is_staff(self):
        return self.user.is_admin()

    def natural_key(self):
        return self.user.school_id, self.user.name

    @staticmethod
    def is_anonymous():
        return False

    @staticmethod
    def is_authenticated():
        return True

    def set_password(self, raw_password):
        self.user.set_password(raw_password)

    def check_password(self, raw_password):
        return self.user.validate_password(raw_password)

    def set_unusable_password(self):
        pass

    @staticmethod
    def has_usable_password():
        return True

    def get_full_name(self):
        return self.user.name

    def get_short_name(self):
        return self.user.name

    def get_session_auth_hash(self):
        """
        Returns an HMAC of the password field.
        """
        key_salt = 
"django.contrib.auth.models.AbstractBaseUser.get_session_auth_hash"
        return salted_hmac(key_salt, self.user.password).hexdigest()

    def email_user(self, subject, message, from_email=None, **kwargs):
        """
        Sends an email to this User.
        """
        from django.core.mail import send_mail
        send_mail(subject, message, from_email, [self.email], **kwargs)

    @property
    def is_superuser(self):
        # This can never be a Django superuser
        return False
    def get_group_permissions(self, obj=None):
        raise NotImplementedError()
    def get_all_permissions(self, obj=None):
        raise NotImplementedError()

    def has_perm(self, perm, obj=None):
        [...]  # My custom per-app permissions

    def has_perms(self, perm_list, obj=None):
        for perm in perm_list:
            if not self.has_perm(perm, obj):
                return False
        return True

    def has_module_perms(self, app_label):
        [...]  # My custom per-module permissions



school_auth/urls.py:
from django.conf.urls import url
from django.contrib.auth.views import login, logout
from .forms import SchoolAuthenticationForm
urlpatterns = [
    url(r'^login/$', login, kwargs={'authentication_form': 
SchoolAuthenticationForm, 'template_name': 'school_auth/login.html'}, 
name='school_auth.login'),
    url(r'^logout/$', logout, name='school_auth.logout'),
]

Thanks,
Erik

-- 
You received this message because you are subscribed to the Google Groups 
"Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-users+unsubscr...@googlegroups.com.
To post to this group, send email to django-users@googlegroups.com.
Visit this group at http://groups.google.com/group/django-users.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-users/BC66EBBA-A032-42B1-8DB0-F2CFFEE48BA6%40cederstrand.dk.
For more options, visit https://groups.google.com/d/optout.

Reply via email to