mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Merged multi-auth branch to trunk. See the authentication docs for the ramifications of this change. Many, many thanks to Joseph Kocherhans for the hard work!
git-svn-id: http://code.djangoproject.com/svn/django/trunk@3226 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
		| @@ -281,3 +281,9 @@ COMMENTS_FIRST_FEW = 0 | ||||
| # A tuple of IP addresses that have been banned from participating in various | ||||
| # Django-powered features. | ||||
| BANNED_IPS = () | ||||
|  | ||||
| ################## | ||||
| # AUTHENTICATION # | ||||
| ################## | ||||
|  | ||||
| AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',) | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| from django import http, template | ||||
| from django.conf import settings | ||||
| from django.contrib.auth.models import User, SESSION_KEY | ||||
| from django.contrib.auth.models import User | ||||
| from django.contrib.auth import authenticate, login | ||||
| from django.shortcuts import render_to_response | ||||
| from django.utils.translation import gettext_lazy | ||||
| import base64, datetime, md5 | ||||
| @@ -69,10 +70,10 @@ def staff_member_required(view_func): | ||||
|             return _display_login_form(request, message) | ||||
|  | ||||
|         # Check the password. | ||||
|         username = request.POST.get('username', '') | ||||
|         try: | ||||
|             user = User.objects.get(username=username, is_staff=True) | ||||
|         except User.DoesNotExist: | ||||
|         username = request.POST.get('username', None) | ||||
|         password = request.POST.get('password', None) | ||||
|         user = authenticate(username=username, password=password) | ||||
|         if user is None: | ||||
|             message = ERROR_MESSAGE | ||||
|             if '@' in username: | ||||
|                 # Mistakenly entered e-mail address instead of username? Look it up. | ||||
| @@ -86,8 +87,9 @@ def staff_member_required(view_func): | ||||
|  | ||||
|         # The user data is correct; log in the user in and continue. | ||||
|         else: | ||||
|             if user.check_password(request.POST.get('password', '')): | ||||
|                 request.session[SESSION_KEY] = user.id | ||||
|             if user.is_staff: | ||||
|                 login(request, user) | ||||
|                 # TODO: set last_login with an event. | ||||
|                 user.last_login = datetime.datetime.now() | ||||
|                 user.save() | ||||
|                 if request.POST.has_key('post_data'): | ||||
|   | ||||
| @@ -1,2 +1,71 @@ | ||||
| from django.core.exceptions import ImproperlyConfigured | ||||
|  | ||||
| SESSION_KEY = '_auth_user_id' | ||||
| BACKEND_SESSION_KEY = '_auth_user_backend' | ||||
| LOGIN_URL = '/accounts/login/' | ||||
| REDIRECT_FIELD_NAME = 'next' | ||||
|  | ||||
| def load_backend(path): | ||||
|     i = path.rfind('.') | ||||
|     module, attr = path[:i], path[i+1:] | ||||
|     try: | ||||
|         mod = __import__(module, '', '', [attr]) | ||||
|     except ImportError, e: | ||||
|         raise ImproperlyConfigured, 'Error importing authentication backend %s: "%s"' % (module, e) | ||||
|     try: | ||||
|         cls = getattr(mod, attr) | ||||
|     except AttributeError: | ||||
|         raise ImproperlyConfigured, 'Module "%s" does not define a "%s" authentication backend' % (module, attr) | ||||
|     return cls() | ||||
|  | ||||
| def get_backends(): | ||||
|     from django.conf import settings | ||||
|     backends = [] | ||||
|     for backend_path in settings.AUTHENTICATION_BACKENDS: | ||||
|         backends.append(load_backend(backend_path)) | ||||
|     return backends | ||||
|  | ||||
| def authenticate(**credentials): | ||||
|     """ | ||||
|     If the given credentials, return a user object. | ||||
|     """ | ||||
|     for backend in get_backends(): | ||||
|         try: | ||||
|             user = backend.authenticate(**credentials) | ||||
|         except TypeError: | ||||
|             # this backend doesn't accept these credentials as arguments, try the next one. | ||||
|             continue | ||||
|         if user is None: | ||||
|             continue | ||||
|         # annotate the user object with the path of the backend | ||||
|         user.backend = str(backend.__class__) | ||||
|         return user | ||||
|  | ||||
| def login(request, user): | ||||
|     """ | ||||
|     Persist a user id and a backend in the request. This way a user doesn't | ||||
|     have to reauthenticate on every request. | ||||
|     """ | ||||
|     if user is None: | ||||
|         user = request.user | ||||
|     # TODO: It would be nice to support different login methods, like signed cookies. | ||||
|     request.session[SESSION_KEY] = user.id | ||||
|     request.session[BACKEND_SESSION_KEY] = user.backend | ||||
|  | ||||
| def logout(request): | ||||
|     """ | ||||
|     Remove the authenticated user's id from request. | ||||
|     """ | ||||
|     del request.session[SESSION_KEY] | ||||
|     del request.session[BACKEND_SESSION_KEY] | ||||
|  | ||||
| def get_user(request): | ||||
|     from django.contrib.auth.models import AnonymousUser | ||||
|     try: | ||||
|         user_id = request.session[SESSION_KEY] | ||||
|         backend_path = request.session[BACKEND_SESSION_KEY] | ||||
|         backend = load_backend(backend_path) | ||||
|         user = backend.get_user(user_id) or AnonymousUser() | ||||
|     except KeyError: | ||||
|         user = AnonymousUser() | ||||
|     return user | ||||
|   | ||||
							
								
								
									
										21
									
								
								django/contrib/auth/backends.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								django/contrib/auth/backends.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| from django.contrib.auth.models import User, check_password | ||||
|  | ||||
| class ModelBackend: | ||||
|     """ | ||||
|     Authenticate against django.contrib.auth.models.User | ||||
|     """ | ||||
|     # TODO: Model, login attribute name and password attribute name should be | ||||
|     # configurable. | ||||
|     def authenticate(self, username=None, password=None): | ||||
|         try: | ||||
|             user = User.objects.get(username=username) | ||||
|             if user.check_password(password): | ||||
|                 return user | ||||
|         except User.DoesNotExist: | ||||
|             return None | ||||
|  | ||||
|     def get_user(self, user_id): | ||||
|         try: | ||||
|             return User.objects.get(pk=user_id) | ||||
|         except User.DoesNotExist: | ||||
|             return None | ||||
| @@ -1,4 +1,5 @@ | ||||
| from django.contrib.auth.models import User | ||||
| from django.contrib.auth import authenticate | ||||
| from django.contrib.sites.models import Site | ||||
| from django.template import Context, loader | ||||
| from django.core import validators | ||||
| @@ -20,8 +21,7 @@ class AuthenticationForm(forms.Manipulator): | ||||
|         self.fields = [ | ||||
|             forms.TextField(field_name="username", length=15, maxlength=30, is_required=True, | ||||
|                 validator_list=[self.isValidUser, self.hasCookiesEnabled]), | ||||
|             forms.PasswordField(field_name="password", length=15, maxlength=30, is_required=True, | ||||
|                 validator_list=[self.isValidPasswordForUser]), | ||||
|             forms.PasswordField(field_name="password", length=15, maxlength=30, is_required=True), | ||||
|         ] | ||||
|         self.user_cache = None | ||||
|  | ||||
| @@ -30,16 +30,10 @@ class AuthenticationForm(forms.Manipulator): | ||||
|             raise validators.ValidationError, _("Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in.") | ||||
|  | ||||
|     def isValidUser(self, field_data, all_data): | ||||
|         try: | ||||
|             self.user_cache = User.objects.get(username=field_data) | ||||
|         except User.DoesNotExist: | ||||
|             raise validators.ValidationError, _("Please enter a correct username and password. Note that both fields are case-sensitive.") | ||||
|  | ||||
|     def isValidPasswordForUser(self, field_data, all_data): | ||||
|         username = field_data | ||||
|         password = all_data.get('password', None) | ||||
|         self.user_cache = authenticate(username=username, password=password) | ||||
|         if self.user_cache is None: | ||||
|             return | ||||
|         if not self.user_cache.check_password(field_data): | ||||
|             self.user_cache = None | ||||
|             raise validators.ValidationError, _("Please enter a correct username and password. Note that both fields are case-sensitive.") | ||||
|         elif not self.user_cache.is_active: | ||||
|             raise validators.ValidationError, _("This account is inactive.") | ||||
|   | ||||
| @@ -4,12 +4,8 @@ class LazyUser(object): | ||||
|  | ||||
|     def __get__(self, request, obj_type=None): | ||||
|         if self._user is None: | ||||
|             from django.contrib.auth.models import User, AnonymousUser, SESSION_KEY | ||||
|             try: | ||||
|                 user_id = request.session[SESSION_KEY] | ||||
|                 self._user = User.objects.get(pk=user_id) | ||||
|             except (KeyError, User.DoesNotExist): | ||||
|                 self._user = AnonymousUser() | ||||
|             from django.contrib.auth import get_user | ||||
|             self._user = get_user(request) | ||||
|         return self._user | ||||
|  | ||||
| class AuthenticationMiddleware(object): | ||||
|   | ||||
| @@ -4,7 +4,19 @@ from django.contrib.contenttypes.models import ContentType | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| import datetime | ||||
|  | ||||
| SESSION_KEY = '_auth_user_id' | ||||
| def check_password(raw_password, enc_password): | ||||
|     """ | ||||
|     Returns a boolean of whether the raw_password was correct. Handles | ||||
|     encryption formats behind the scenes. | ||||
|     """ | ||||
|     algo, salt, hsh = enc_password.split('$') | ||||
|     if algo == 'md5': | ||||
|         import md5 | ||||
|         return hsh == md5.new(salt+raw_password).hexdigest() | ||||
|     elif algo == 'sha1': | ||||
|         import sha | ||||
|         return hsh == sha.new(salt+raw_password).hexdigest() | ||||
|     raise ValueError, "Got unknown password algorithm type in password." | ||||
|  | ||||
| class SiteProfileNotAvailable(Exception): | ||||
|     pass | ||||
| @@ -141,14 +153,7 @@ class User(models.Model): | ||||
|                 self.set_password(raw_password) | ||||
|                 self.save() | ||||
|             return is_correct | ||||
|         algo, salt, hsh = self.password.split('$') | ||||
|         if algo == 'md5': | ||||
|             import md5 | ||||
|             return hsh == md5.new(salt+raw_password).hexdigest() | ||||
|         elif algo == 'sha1': | ||||
|             import sha | ||||
|             return hsh == sha.new(salt+raw_password).hexdigest() | ||||
|         raise ValueError, "Got unknown password algorithm type in password." | ||||
|         return check_password(raw_password, self.password) | ||||
|  | ||||
|     def get_group_permissions(self): | ||||
|         "Returns a list of permission strings that this user has through his/her groups." | ||||
|   | ||||
| @@ -3,7 +3,6 @@ from django.contrib.auth.forms import PasswordResetForm, PasswordChangeForm | ||||
| from django import forms | ||||
| from django.shortcuts import render_to_response | ||||
| from django.template import RequestContext | ||||
| from django.contrib.auth.models import SESSION_KEY | ||||
| from django.contrib.sites.models import Site | ||||
| from django.http import HttpResponse, HttpResponseRedirect | ||||
| from django.contrib.auth.decorators import login_required | ||||
| @@ -19,7 +18,8 @@ def login(request, template_name='registration/login.html'): | ||||
|             # Light security check -- make sure redirect_to isn't garbage. | ||||
|             if not redirect_to or '://' in redirect_to or ' ' in redirect_to: | ||||
|                 redirect_to = '/accounts/profile/' | ||||
|             request.session[SESSION_KEY] = manipulator.get_user_id() | ||||
|             from django.contrib.auth import login | ||||
|             login(request, manipulator.get_user()) | ||||
|             request.session.delete_test_cookie() | ||||
|             return HttpResponseRedirect(redirect_to) | ||||
|     else: | ||||
| @@ -33,8 +33,9 @@ def login(request, template_name='registration/login.html'): | ||||
|  | ||||
| def logout(request, next_page=None, template_name='registration/logged_out.html'): | ||||
|     "Logs out the user and displays 'You are logged out' message." | ||||
|     from django.contrib.auth import logout | ||||
|     try: | ||||
|         del request.session[SESSION_KEY] | ||||
|         logout(request) | ||||
|     except KeyError: | ||||
|         return render_to_response(template_name, {'title': _('Logged out')}, context_instance=RequestContext(request)) | ||||
|     else: | ||||
|   | ||||
| @@ -5,7 +5,6 @@ from django.http import Http404 | ||||
| from django.core.exceptions import ObjectDoesNotExist | ||||
| from django.shortcuts import render_to_response | ||||
| from django.template import RequestContext | ||||
| from django.contrib.auth.models import SESSION_KEY | ||||
| from django.contrib.comments.models import Comment, FreeComment, PHOTOS_REQUIRED, PHOTOS_OPTIONAL, RATINGS_REQUIRED, RATINGS_OPTIONAL, IS_PUBLIC | ||||
| from django.contrib.contenttypes.models import ContentType | ||||
| from django.contrib.auth.forms import AuthenticationForm | ||||
| @@ -219,7 +218,8 @@ def post_comment(request): | ||||
|     # If user gave correct username/password and wasn't already logged in, log them in | ||||
|     # so they don't have to enter a username/password again. | ||||
|     if manipulator.get_user() and new_data.has_key('password') and manipulator.get_user().check_password(new_data['password']): | ||||
|         request.session[SESSION_KEY] = manipulator.get_user_id() | ||||
|         from django.contrib.auth import login | ||||
|         login(request, manipulator.get_user()) | ||||
|     if errors or request.POST.has_key('preview'): | ||||
|         class CommentFormWrapper(forms.FormWrapper): | ||||
|             def __init__(self, manipulator, new_data, errors, rating_choices): | ||||
|   | ||||
| @@ -267,17 +267,25 @@ previous section). You can tell them apart with ``is_anonymous()``, like so:: | ||||
| How to log a user in | ||||
| -------------------- | ||||
|  | ||||
| To log a user in, do the following within a view:: | ||||
| Depending on your task, you'll probably want to make sure to validate the  | ||||
| user's username and password before you log them in. The easiest way to do so  | ||||
| is to use the built-in ``authenticate`` and ``login`` functions from within a  | ||||
| view:: | ||||
|  | ||||
|     from django.contrib.auth.models import SESSION_KEY | ||||
|     request.session[SESSION_KEY] = some_user.id | ||||
|     from django.contrib.auth import authenticate, login | ||||
|     username = request.POST['username'] | ||||
|     password = request.POST['password'] | ||||
|     user = authenticate(username=username, password=password) | ||||
|     if user is not None: | ||||
|         login(request, user) | ||||
|  | ||||
| Because this uses sessions, you'll need to make sure you have | ||||
| ``SessionMiddleware`` enabled. See the `session documentation`_ for more | ||||
| information. | ||||
| ``authenticate`` checks the username and password. If they are valid it  | ||||
| returns a user object, otherwise it returns ``None``. ``login`` makes it so  | ||||
| your users don't have send a username and password for every request. Because  | ||||
| the ``login`` function uses sessions, you'll need to make sure you have  | ||||
| ``SessionMiddleware`` enabled. See the `session documentation`_ for  | ||||
| more information. | ||||
|  | ||||
| This assumes ``some_user`` is your ``User`` instance. Depending on your task, | ||||
| you'll probably want to make sure to validate the user's username and password. | ||||
|  | ||||
| Limiting access to logged-in users | ||||
| ---------------------------------- | ||||
| @@ -672,3 +680,84 @@ Finally, note that this messages framework only works with users in the user | ||||
| database. To send messages to anonymous users, use the `session framework`_. | ||||
|  | ||||
| .. _session framework: http://www.djangoproject.com/documentation/sessions/ | ||||
|  | ||||
| Other Authentication Sources | ||||
| ============================ | ||||
|  | ||||
| Django supports other authentication sources as well. You can even use  | ||||
| multiple sources at the same time. | ||||
|  | ||||
| Using multiple backends | ||||
| ----------------------- | ||||
|  | ||||
| The list of backends to use is controlled by the ``AUTHENTICATION_BACKENDS``  | ||||
| setting. This should be a tuple of python path names. It defaults to  | ||||
| ``('django.contrib.auth.backends.ModelBackend',)``. To add additional backends | ||||
| just add them to your settings.py file. Ordering matters, so if the same | ||||
| username and password is valid in multiple backends, the first one in the | ||||
| list will return a user object, and the remaining ones won't even get a chance. | ||||
|  | ||||
| Writing an authentication backend | ||||
| --------------------------------- | ||||
|  | ||||
| An authentication backend is a class that implements 2 methods:  | ||||
| ``get_user(id)`` and ``authenticate(**credentials)``. The ``get_user`` method  | ||||
| takes an id, which could be a username, and database id, whatever, and returns  | ||||
| a user object. The  ``authenticate`` method takes credentials as keyword  | ||||
| arguments. Many times it will just look like this:: | ||||
|  | ||||
|     class MyBackend: | ||||
|         def authenticate(username=None, password=None): | ||||
|             # check the username/password and return a user | ||||
|  | ||||
| but it could also authenticate a token like so:: | ||||
|  | ||||
|     class MyBackend: | ||||
|         def authenticate(token=None): | ||||
|             # check the token and return a user | ||||
|  | ||||
| Regardless, ``authenticate`` should check the credentials it gets, and if they  | ||||
| are valid, it should return a user object that matches those credentials. | ||||
|  | ||||
| The Django admin system is tightly coupled to the Django User object described  | ||||
| at the beginning of this document. For now, the best way to deal with this is  | ||||
| to create a Django User object for each user that exists for your backend  | ||||
| (i.e. in your LDAP directory, your external SQL database, etc.) You can either  | ||||
| write a script to do this in advance, or your ``authenticate`` method can do  | ||||
| it the first time a user logs in.  Here's an example backend that  | ||||
| authenticates against a username and password variable defined in your  | ||||
| ``settings.py`` file and creates a Django user object the first time they  | ||||
| authenticate:: | ||||
|  | ||||
|     from django.conf import settings | ||||
|     from django.contrib.auth.models import User, check_password | ||||
|  | ||||
|     class SettingsBackend: | ||||
|         """ | ||||
|         Authenticate against vars in settings.py Use the login name, and a hash  | ||||
|         of the password. For example: | ||||
|  | ||||
|         ADMIN_LOGIN = 'admin' | ||||
|         ADMIN_PASSWORD = 'sha1$4e987$afbcf42e21bd417fb71db8c66b321e9fc33051de' | ||||
|         """ | ||||
|         def authenticate(self, username=None, password=None): | ||||
|             login_valid = (settings.ADMIN_LOGIN == username) | ||||
|             pwd_valid = check_password(password, settings.ADMIN_PASSWORD) | ||||
|             if login_valid and pwd_valid: | ||||
|                 try: | ||||
|                     user = User.objects.get(username=username) | ||||
|                 except User.DoesNotExist: | ||||
|                     # Create a new user. Note that we can set password to anything | ||||
|                     # as it won't be checked, the password from settings.py will. | ||||
|                     user = User(username=username, password='get from settings.py') | ||||
|                     user.is_staff = True | ||||
|                     user.is_superuser = True | ||||
|                     user.save() | ||||
|                 return user | ||||
|             return None | ||||
|  | ||||
|         def get_user(self, user_id): | ||||
|             try: | ||||
|                 return User.objects.get(pk=user_id) | ||||
|             except User.DoesNotExist: | ||||
|                 return None | ||||
|   | ||||
		Reference in New Issue
	
	Block a user