import re from django.core.exceptions import ImproperlyConfigured, PermissionDenied from django.utils.importlib import import_module from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed SESSION_KEY = '_auth_user_id' BACKEND_SESSION_KEY = '_auth_user_backend' REDIRECT_FIELD_NAME = 'next' def load_backend(path): i = path.rfind('.') module, attr = path[:i], path[i + 1:] try: mod = import_module(module) except ImportError as e: raise ImproperlyConfigured('Error importing authentication backend %s: "%s"' % (path, e)) except ValueError: raise ImproperlyConfigured('Error importing authentication backends. Is AUTHENTICATION_BACKENDS a correctly defined list or tuple?') 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)) if not backends: raise ImproperlyConfigured('No authentication backends have been defined. Does AUTHENTICATION_BACKENDS contain anything?') return backends def _clean_credentials(credentials): """ Cleans a dictionary of credentials of potentially sensitive info before sending to less secure functions. Not comprehensive - intended for user_login_failed signal """ SENSITIVE_CREDENTIALS = re.compile('api|token|key|secret|password|signature', re.I) CLEANSED_SUBSTITUTE = '********************' for key in credentials: if SENSITIVE_CREDENTIALS.search(key): credentials[key] = CLEANSED_SUBSTITUTE return credentials def authenticate(**credentials): """ If the given credentials are valid, 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 except PermissionDenied: # This backend says to stop in our tracks - this user should not be allowed in at all. return None if user is None: continue # Annotate the user object with the path of the backend. user.backend = "%s.%s" % (backend.__module__, backend.__class__.__name__) return user # The credentials supplied are invalid to all backends, fire signal user_login_failed.send(sender=__name__, credentials=_clean_credentials(credentials)) 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. Note that data set during the anonymous session is retained when the user logs in. """ if user is None: user = request.user # TODO: It would be nice to support different login methods, like signed cookies. if SESSION_KEY in request.session: if request.session[SESSION_KEY] != user.id: # To avoid reusing another user's session, create a new, empty # session if the existing session corresponds to a different # authenticated user. request.session.flush() else: request.session.cycle_key() request.session[SESSION_KEY] = user.id request.session[BACKEND_SESSION_KEY] = user.backend if hasattr(request, 'user'): request.user = user user_logged_in.send(sender=user.__class__, request=request, user=user) def logout(request): """ Removes the authenticated user's ID from the request and flushes their session data. """ # Dispatch the signal before the user is logged out so the receivers have a # chance to find out *who* logged out. user = getattr(request, 'user', None) if hasattr(user, 'is_authenticated') and not user.is_authenticated(): user = None user_logged_out.send(sender=user.__class__, request=request, user=user) request.session.flush() if hasattr(request, 'user'): from django.contrib.auth.models import AnonymousUser request.user = AnonymousUser() def get_user_model(): "Return the User model that is active in this project" from django.conf import settings from django.db.models import get_model try: app_label, model_name = settings.AUTH_USER_MODEL.split('.') except ValueError: raise ImproperlyConfigured("AUTH_USER_MODEL must be of the form 'app_label.model_name'") user_model = get_model(app_label, model_name) if user_model is None: raise ImproperlyConfigured("AUTH_USER_MODEL refers to model '%s' that has not been installed" % settings.AUTH_USER_MODEL) return user_model 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