1# Copyright (c) 2012-2016 Seafile Ltd.
2import datetime
3from importlib import import_module
4from warnings import warn
5
6from django.conf import settings
7from django.core.exceptions import ImproperlyConfigured
8
9from seahub.auth.signals import user_logged_in
10
11from constance import config
12
13SESSION_KEY = '_auth_user_name'
14BACKEND_SESSION_KEY = '_auth_user_backend_2'
15REDIRECT_FIELD_NAME = 'next'
16
17def load_backend(path):
18    i = path.rfind('.')
19    module, attr = path[:i], path[i+1:]
20    try:
21        mod = import_module(module)
22    except ImportError as e:
23        raise ImproperlyConfigured('Error importing authentication backend %s: "%s"' % (module, e))
24    except ValueError as e:
25        raise ImproperlyConfigured('Error importing authentication backends. Is AUTHENTICATION_BACKENDS a correctly defined list or tuple?')
26    try:
27        cls = getattr(mod, attr)
28    except AttributeError:
29        raise ImproperlyConfigured('Module "%s" does not define a "%s" authentication backend' % (module, attr))
30    try:
31        getattr(cls, 'supports_object_permissions')
32    except AttributeError:
33        warn("Authentication backends without a `supports_object_permissions` attribute are deprecated. Please define it in %s." % cls,
34             PendingDeprecationWarning)
35        cls.supports_object_permissions = False
36    try:
37        getattr(cls, 'supports_anonymous_user')
38    except AttributeError:
39        warn("Authentication backends without a `supports_anonymous_user` attribute are deprecated. Please define it in %s." % cls,
40             PendingDeprecationWarning)
41        cls.supports_anonymous_user = False
42    return cls()
43
44def get_backends():
45    backends = []
46    for backend_path in settings.AUTHENTICATION_BACKENDS:
47        backends.append(load_backend(backend_path))
48    return backends
49
50def authenticate(**credentials):
51    """
52    If the given credentials are valid, return a User object.
53    """
54    for backend in get_backends():
55        try:
56            user = backend.authenticate(**credentials)
57        except TypeError:
58            # This backend doesn't accept these credentials as arguments. Try the next one.
59            continue
60        if user is None:
61            continue
62        # Annotate the user object with the path of the backend.
63        user.backend = "%s.%s" % (backend.__module__, backend.__class__.__name__)
64        return user
65
66def login(request, user):
67    """
68    Persist a user id and a backend in the request. This way a user doesn't
69    have to reauthenticate on every request.
70    """
71    if user is None:
72        user = request.user
73    # TODO: It would be nice to support different login methods, like signed cookies.
74    user.last_login = datetime.datetime.now()
75
76    if SESSION_KEY in request.session:
77        if request.session[SESSION_KEY] != user.username:
78            # To avoid reusing another user's session, create a new, empty
79            # session if the existing session corresponds to a different
80            # authenticated user.
81            request.session.flush()
82    else:
83        request.session.cycle_key()
84
85    request.session[SESSION_KEY] = user.username
86    request.session[BACKEND_SESSION_KEY] = user.backend
87
88    if request.session.get('remember_me', False):
89        request.session.set_expiry(config.LOGIN_REMEMBER_DAYS * 24 * 60 * 60)
90    if hasattr(request, 'user'):
91        request.user = user
92    user_logged_in.send(sender=user.__class__, request=request, user=user)
93
94def logout(request):
95    """
96    Removes the authenticated user's ID from the request and flushes their
97    session data.
98    Also remove all passwords used to decrypt repos.
99    """
100    request.session.flush()
101    if hasattr(request, 'user'):
102        from seahub.base.accounts import User
103        if isinstance(request.user, User):
104            # Do not directly/indirectly import models in package root level.
105            from seahub.utils import is_org_context
106            if is_org_context(request):
107                org_id = request.user.org.org_id
108                request.user.remove_org_repo_passwds(org_id)
109            else:
110                request.user.remove_repo_passwds()
111        from seahub.auth.models import AnonymousUser
112        request.user = AnonymousUser()
113
114def get_user(request):
115    from seahub.auth.models import AnonymousUser
116    try:
117        username = request.session[SESSION_KEY]
118        backend_path = request.session[BACKEND_SESSION_KEY]
119        backend = load_backend(backend_path)
120        user = backend.get_user(username) or AnonymousUser()
121    except KeyError:
122        user = AnonymousUser()
123    return user
124