Ticket #1428: multiauth.diff

File multiauth.diff, 20.0 KB (added by jkocherhans, 9 years ago)

works with post-mr trunk now... rev [2849] to be exact

  • django/conf/global_settings.py

     
    265265# A tuple of IP addresses that have been banned from participating in various
    266266# Django-powered features.
    267267BANNED_IPS = ()
     268
     269##################
     270# AUTHENTICATION #
     271##################
     272
     273AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',)
     274CREDENTIAL_PLUGINS = ('django.contrib.auth.credentials.username_password_form',)
     275
  • django/contrib/auth/backends.py

     
     1from django.conf import settings
     2from django.contrib.auth.models import User
     3from django.contrib.auth.utils import check_password
     4
     5class SettingsBackend:
     6    """
     7    Authenticate against vars in settings.py Use the login name, and a hash
     8    of the password.
     9   
     10    ADMIN_LOGIN = 'admin'
     11    ADMIN_PASSWORD = 'sha1$4e987$afbcf42e21bd417fb71db8c66b321e9fc33051de'
     12    """
     13    def authenticate(self, username=None, password=None):
     14        login_valid = (settings.ADMIN_LOGIN == username)
     15        pwd_valid = check_password(password, settings.ADMIN_PASSWORD)
     16        if login_valid and pwd_valid:
     17            # TODO: This should be abstracted out someplace else.
     18            try:
     19                user = User.objects.get(username=username)
     20            except User.DoesNotExist:
     21                user = User(username=username, password='')
     22                user.is_staff = True
     23                user.is_superuser = True
     24                user.save()
     25            return user
     26        return None
     27
     28    def get_user(self, user_id):
     29        try:
     30            return User.objects.get(pk=user_id)
     31        except User.DoesNotExist:
     32            return None
     33
     34class ModelBackend:
     35    """
     36    Authenticate against django.contrib.auth.models.User
     37    """
     38    # TODO: Model, login attribute name and password attribute name should be
     39    # configurable.
     40    def authenticate(self, username=None, password=None):
     41        try:
     42            user = User.objects.get(username=username)
     43            if user.check_password(password):
     44                return user
     45        except User.DoesNotExist:
     46            return None
     47
     48    def get_user(self, user_id):
     49        try:
     50            return User.objects.get(pk=user_id)
     51        except User.DoesNotExist:
     52            return None
  • django/contrib/auth/middleware.py

     
    44
    55    def __get__(self, request, obj_type=None):
    66        if self._user is None:
    7             from django.contrib.auth.models import User, AnonymousUser, SESSION_KEY
    8             try:
    9                 user_id = request.session[SESSION_KEY]
    10                 self._user = User.objects.get(pk=user_id)
    11             except (KeyError, User.DoesNotExist):
    12                 self._user = AnonymousUser()
     7            from django.contrib.auth import get_current_user
     8            self._user = get_current_user(request)
    139        return self._user
    1410
    1511class AuthenticationMiddleware:
  • django/contrib/auth/views.py

     
    33from django import forms
    44from django.shortcuts import render_to_response
    55from django.template import RequestContext
    6 from django.contrib.auth.models import SESSION_KEY
    76from django.contrib.sites.models import Site
    87from django.http import HttpResponse, HttpResponseRedirect
    98from django.contrib.auth.decorators import login_required
     
    1918            # Light security check -- make sure redirect_to isn't garbage.
    2019            if not redirect_to or '://' in redirect_to or ' ' in redirect_to:
    2120                redirect_to = '/accounts/profile/'
    22             request.session[SESSION_KEY] = manipulator.get_user_id()
     21            from django.contrib.auth import login
     22            login(request, manipulator.get_user())
    2323            request.session.delete_test_cookie()
    2424            return HttpResponseRedirect(redirect_to)
    2525    else:
     
    3333
    3434def logout(request, next_page=None):
    3535    "Logs out the user and displays 'You are logged out' message."
     36    from django.contrib.auth import logout
    3637    try:
    37         del request.session[SESSION_KEY]
     38        logout(request)
    3839    except KeyError:
    3940        return render_to_response('registration/logged_out.html', {'title': 'Logged out'}, context_instance=RequestContext(request))
    4041    else:
  • django/contrib/auth/credentials.py

     
     1def username_password_form(request):
     2    try:
     3        username = request.POST['username']
     4        password = request.POST['password']
     5        return {'username': username, 'password': password}
     6    except KeyError:
     7        return None
     8
     9def token(request):
     10    try:
     11        return request.POST['token']
     12    except KeyError:
     13        return None
  • django/contrib/auth/__init__.py

     
     1from django.core.exceptions import ImproperlyConfigured
     2
     3SESSION_KEY = '_auth_user_id'
     4BACKEND_SESSION_KEY = '_auth_user_backend'
    15LOGIN_URL = '/accounts/login/'
    26REDIRECT_FIELD_NAME = 'next'
     7
     8def load_plugin(path):
     9    i = path.rfind('.')
     10    module, attr = path[:i], path[i+1:]
     11    try:
     12        mod = __import__(module, '', '', [attr])
     13    except ImportError, e:
     14        raise ImproperlyConfigured, 'Error importing credential plugin %s: "%s"' % (module, e)
     15    try:
     16        func = getattr(mod, attr)
     17    except AttributeError:
     18        raise ImproperlyConfigured, 'Module "%s" does not define a "%s" credential plugin' % (module, attr)
     19    return func
     20
     21def load_backend(path):
     22    i = path.rfind('.')
     23    module, attr = path[:i], path[i+1:]
     24    try:
     25        mod = __import__(module, '', '', [attr])
     26    except ImportError, e:
     27        raise ImproperlyConfigured, 'Error importing authentication backend %s: "%s"' % (module, e)
     28    try:
     29        cls = getattr(mod, attr)
     30    except AttributeError:
     31        raise ImproperlyConfigured, 'Module "%s" does not define a "%s" authentication backend' % (module, attr)
     32    return cls()
     33
     34def get_backends():
     35    from django.conf import settings
     36    backends = []
     37    for backend_path in settings.AUTHENTICATION_BACKENDS:
     38        backends.append(load_backend(backend_path))
     39    return backends
     40
     41def get_credential_plugins():
     42    from django.conf import settings
     43    credential_plugins = []
     44    for plugin_path in settings.CREDENTIAL_PLUGINS:
     45        credential_plugins.append(load_plugin(plugin_path))
     46    return credential_plugins
     47       
     48def authenticate_credentials(**credentials):
     49    """
     50    If the given credentials, return a user object.
     51    """
     52    for backend in get_backends():
     53        try:
     54            user = backend.authenticate(**credentials)
     55        except TypeError:
     56            # this backend doesn't accept these credentials as arguments, try the next one.
     57            continue
     58        if user is None:
     59            continue
     60        # annotate the user object with the path of the backend
     61        user.backend = str(backend.__class__)
     62        return user
     63
     64def authenticate_request(request):
     65    """
     66    Use CREDENTIAL_PLUGINS to find credentials in the request and try to
     67    authenticate them.
     68    """
     69    for plugin in get_credential_plugins():
     70        credentials = plugin(request)
     71        if credentials is None:
     72            continue
     73        user = authenticate_credentials(**credentials)
     74        if user is None:
     75            continue
     76        return user
     77
     78def login(request, user=None):
     79    """
     80    Persist a user id and a backend in the request. This way a user doesn't
     81    have to reauthenticate on every request.
     82    """
     83    if user is None:
     84        user = request.user
     85    # TODO: It would be nice to support different login methods, like signed cookies.
     86    request.session[SESSION_KEY] = user.id
     87    request.session[BACKEND_SESSION_KEY] = user.backend
     88
     89def authenticate_request_and_login(request):
     90    """
     91    Convenience function to authenticate a request and log a user in. Returns
     92    the user object, or None if authentication failed.
     93    """
     94    user = authenticate_request(request)
     95    if user is not None:
     96        login(request, user)
     97    return user
     98
     99def logout(request):
     100    """
     101    Remove the authenticated user's id from request.
     102    """
     103    del request.session[SESSION_KEY]
     104    del request.session[BACKEND_SESSION_KEY]
     105
     106def get_current_user(request):
     107    from django.contrib.auth.models import AnonymousUser
     108    try:
     109        user_id = request.session[SESSION_KEY]
     110        backend_path = request.session[BACKEND_SESSION_KEY]
     111        backend = load_backend(backend_path)
     112        user = backend.get_user(user_id) or AnonymousUser()
     113    except KeyError:
     114        user = AnonymousUser()
     115    return user
  • django/contrib/auth/utils.py

     
     1def encrypt_password(raw_password):
     2    import sha, random
     3    algo = 'sha1'
     4    salt = sha.new(str(random.random())).hexdigest()[:5]
     5    hsh = sha.new(salt+raw_password).hexdigest()
     6    return '%s$%s$%s' % (algo, salt, hsh)
     7
     8def check_password(raw_password, enc_password):
     9    """
     10    Returns a boolean of whether the raw_password was correct. Handles
     11    encryption formats behind the scenes.
     12    """
     13    # Backwards-compatibility check. Older passwords won't include the
     14    # algorithm or salt.
     15    if '$' not in enc_password:
     16        import md5
     17        return enc_password == md5.new(raw_password).hexdigest()
     18    algo, salt, hsh = enc_password.split('$')
     19    if algo == 'md5':
     20        import md5
     21        return hsh == md5.new(salt+raw_password).hexdigest()
     22    elif algo == 'sha1':
     23        import sha
     24        return hsh == sha.new(salt+raw_password).hexdigest()
     25    raise ValueError, "Got unknown password algorithm type in password."
     26
  • django/contrib/auth/models.py

     
    44from django.utils.translation import gettext_lazy as _
    55import datetime
    66
    7 SESSION_KEY = '_auth_user_id'
    8 
    97class SiteProfileNotAvailable(Exception):
    108    pass
    119
  • django/contrib/auth/forms.py

     
    11from django.contrib.auth.models import User
     2from django.contrib.auth import authenticate_request
    23from django.contrib.sites.models import Site
    34from django.template import Context, loader
    45from django.core import validators
     
    2021        self.fields = [
    2122            forms.TextField(field_name="username", length=15, maxlength=30, is_required=True,
    2223                validator_list=[self.isValidUser, self.hasCookiesEnabled]),
    23             forms.PasswordField(field_name="password", length=15, maxlength=30, is_required=True,
    24                 validator_list=[self.isValidPasswordForUser]),
     24            forms.PasswordField(field_name="password", length=15, maxlength=30, is_required=True),
    2525        ]
    2626        self.user_cache = None
    2727
     
    3030            raise validators.ValidationError, _("Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in.")
    3131
    3232    def isValidUser(self, field_data, all_data):
    33         try:
    34             self.user_cache = User.objects.get(username=field_data)
    35         except User.DoesNotExist:
     33        self.user_cache = authenticate_request(self.request)
     34        if self.user_cache is None:
    3635            raise validators.ValidationError, _("Please enter a correct username and password. Note that both fields are case-sensitive.")
    3736
    38     def isValidPasswordForUser(self, field_data, all_data):
    39         if self.user_cache is not None and not self.user_cache.check_password(field_data):
    40             self.user_cache = None
    41             raise validators.ValidationError, _("Please enter a correct username and password. Note that both fields are case-sensitive.")
    42 
    4337    def get_user_id(self):
    4438        if self.user_cache:
    4539            return self.user_cache.id
  • django/contrib/comments/views/comments.py

     
    55from django.core.exceptions import ObjectDoesNotExist
    66from django.shortcuts import render_to_response
    77from django.template import RequestContext
    8 from django.contrib.auth.models import SESSION_KEY
    98from django.contrib.comments.models import Comment, FreeComment, PHOTOS_REQUIRED, PHOTOS_OPTIONAL, RATINGS_REQUIRED, RATINGS_OPTIONAL, IS_PUBLIC
    109from django.contrib.contenttypes.models import ContentType
    1110from django.contrib.auth.forms import AuthenticationForm
     
    219218    # If user gave correct username/password and wasn't already logged in, log them in
    220219    # so they don't have to enter a username/password again.
    221220    if manipulator.get_user() and new_data.has_key('password') and manipulator.get_user().check_password(new_data['password']):
    222         request.session[SESSION_KEY] = manipulator.get_user_id()
     221        from django.contrib.auth import login
     222        login(request, manipulator.get_user())
    223223    if errors or request.POST.has_key('preview'):
    224224        class CommentFormWrapper(forms.FormWrapper):
    225225            def __init__(self, manipulator, new_data, errors, rating_choices):
  • django/contrib/admin/views/decorators.py

     
    11from django import http, template
    22from django.conf import settings
    3 from django.contrib.auth.models import User, SESSION_KEY
     3from django.contrib.auth.models import User
     4from django.contrib.auth import authenticate_request, login
    45from django.shortcuts import render_to_response
    56from django.utils.translation import gettext_lazy
    67import base64, datetime, md5
     
    6970            return _display_login_form(request, message)
    7071
    7172        # Check the password.
    72         username = request.POST.get('username', '')
    73         try:
    74             user = User.objects.get(username=username, is_staff=True)
    75         except User.DoesNotExist:
     73        user = authenticate_request(request)
     74        if user is None:
    7675            message = ERROR_MESSAGE
     76            username = request.POST.get('username', '')
    7777            if '@' in username:
    7878                # Mistakenly entered e-mail address instead of username? Look it up.
    7979                try:
     
    8686
    8787        # The user data is correct; log in the user in and continue.
    8888        else:
    89             if user.check_password(request.POST.get('password', '')):
    90                 request.session[SESSION_KEY] = user.id
     89            if user.is_staff:
     90                login(request, user)
     91                # TODO: set last_login with an event.
    9192                user.last_login = datetime.datetime.now()
    9293                user.save()
    9394                if request.POST.has_key('post_data'):
  • docs/authentication.txt

     
    269269
    270270To log a user in, do the following within a view::
    271271
    272     from django.contrib.auth.models import SESSION_KEY
    273     request.session[SESSION_KEY] = some_user.id
     272    from django.contrib.auth import login
     273    login(request)
    274274
    275 Because this uses sessions, you'll need to make sure you have
     275Because the login functions uses sessions, you'll need to make sure you have
    276276``SessionMiddleware`` enabled. See the `session documentation`_ for more
    277277information.
    278278
    279 This assumes ``some_user`` is your ``User`` instance. Depending on your task,
    280 you'll probably want to make sure to validate the user's username and password.
     279Depending on your task, you'll probably want to make sure to validate the
     280user's username and password before you log them in.
    281281
    282282Limiting access to logged-in users
    283283----------------------------------
     
    611611database. To send messages to anonymous users, use the `session framework`_.
    612612
    613613.. _session framework: http://www.djangoproject.com/documentation/sessions/
     614
     615Other Authentication Sources
     616============================
     617
     618Django supports other authentication sources as well. You can even use
     619multiple sources at the same time.
     620
     621Using multiple backends
     622-----------------------
     623
     624The list of backends to use is controlled by the ``AUTHENTICATION_BACKENDS``
     625setting. This should be a tuple of python path names. It defaults to
     626``('django.contrib.auth.backends.ModelBackend',)``. To add additional backends
     627just add them to your settings.py file. Ordering matters, so if the same
     628username and password is valid in multiple backends, the first one in the
     629list will return a user object, and the remaining ones won't even get a chance.
     630
     631
     632Customizing ModelBackend
     633------------------------
     634
     635TODO: write me
     636
     637
     638Writing an authentication backend
     639---------------------------------
     640
     641An authentication backend is a class that implements 2 methods: ``get_user(id)``
     642and ``authenticate(**credentials)``. The ``get_user`` method takes an id, which
     643could be a username, and database id, whatever, and returns a user object. The
     644``authenticate`` method takes credentials as keyword arguments. Many times it
     645will just look like this::
     646
     647    class MyBackend:
     648        def authenticate(username=None, password=None):
     649            # check the username/password and return a user
     650
     651but it could also authenticate a token like so::
     652
     653    class MyBackend:
     654        def authenticate(token=None):
     655            # check the token and return a user
     656
     657Regardless, ``authenticate`` should check the credentials it gets, and if they
     658are valid, it should return a user object that matches those credentials.
     659
     660The Django admin system is tightly coupled to the Django User object described
     661at the beginning of this document. For now, the best way to deal with this is to
     662create a Django User object for each user that exists for your backend (i.e.
     663in your ldap directory, your external sql database, etc.) You can either
     664write a script to do this in advance, or your ``authenticate`` method can do
     665it the first time a user logs in. `django.contrib.auth.backends.SettingsBackend`_
     666is an example of the latter approach. Note that you don't have to save a user's
     667password in the Django User object. Your backend can still check the password
     668against an external source, and return a Django User object.
     669
     670.. _django.contrib.auth.backends.SettingsBackend: http://code.djangoproject.com/browser/django/branches/magic-removal/django/contrib/auth/backends.py
     671
     672Credential Plugins
     673==================
     674
     675Like metaclasses, 99.999% of people should never need to bother with these.
     676I'm not sure they're even worth keeping... at least conceptually. The work
     677they do is probably best done in a few utility functions that get used in
     678decorators or views. Basically, they allow using different types of
     679credentials for the same view or url. The main use I can think of is REST, or
     680changing your whole app from form/cookie auth, to http basic or digest auth. I
     681don't think that will happen very often, if ever.
     682
     683TODO: write more... maybe
     684 No newline at end of file
Back to Top