Projet

Général

Profil

0001-add-a-switch-user-feature-fixes-8142.patch

Benjamin Dauvergne, 01 septembre 2015 10:18

Télécharger (11,4 ko)

Voir les différences:

Subject: [PATCH 1/2] add a switch-user feature (fixes #8142)

The new switch-user button in the manager allow any super-user to become
another user for debugging purpose.
 src/authentic2/backends/models_backend.py          |  6 +++
 src/authentic2/constants.py                        |  2 +
 src/authentic2/context_processors.py               |  4 +-
 src/authentic2/manager/user_views.py               |  9 ++++
 src/authentic2/profile_urls.py                     |  1 +
 src/authentic2/profile_views.py                    |  4 ++
 src/authentic2/settings.py                         |  1 +
 src/authentic2/templates/authentic2/base.html      |  2 +-
 .../templates/idp/account_management.html          |  3 ++
 src/authentic2/utils.py                            | 57 ++++++++++++++++++++--
 10 files changed, 83 insertions(+), 6 deletions(-)
src/authentic2/backends/models_backend.py
61 61
                return user
62 62
            else:
63 63
                user_login_failure(user.get_username())
64

  
65

  
66
class DummyModelBackend(ModelBackend):
67
    def authenticate(self, user=None):
68
        if user is not None:
69
            return user
src/authentic2/constants.py
2 2
NONCE_FIELD_NAME = 'nonce'
3 3
CANCEL_FIELD_NAME = 'cancel'
4 4
AUTHENTICATION_EVENTS_SESSION_KEY = 'authentication-events'
5
SWITCH_USER_SESSION_KEY = '_switch_user'
6
LAST_LOGIN_SESSION_KEY = '_last_login'
src/authentic2/context_processors.py
3 3
from pkg_resources import get_distribution
4 4
from django.conf import settings
5 5

  
6
from . import utils, app_settings
6
from . import utils, app_settings, constants
7 7

  
8 8
class UserFederations(object):
9 9
    '''Provide access to all federations of the current user'''
......
41 41
            __AUTHENTIC2_DISTRIBUTION = str(get_distribution('authentic2'))
42 42
    variables['AUTHENTIC2_VERSION'] = __AUTHENTIC2_DISTRIBUTION
43 43
    variables['add_to_blocks'] = defaultdict(lambda:[])
44
    variables['LAST_LOGIN'] = request.session.get(constants.LAST_LOGIN_SESSION_KEY)
45
    variables['USER_SWITCHED'] = constants.SWITCH_USER_SESSION_KEY in request.session
44 46
    return variables
src/authentic2/manager/user_views.py
8 8
from django.contrib.auth import get_user_model
9 9
from django.contrib import messages
10 10
from django.http import HttpResponseRedirect
11
from django.views.generic.detail import SingleObjectMixin
12
from django.views.generic import View
11 13

  
14
from authentic2.constants import SWITCH_USER_SESSION_KEY
12 15
from authentic2.models import Attribute, PasswordReset
16
from authentic2.utils import switch_user
13 17

  
14 18
from .views import BaseTableView, BaseAddView, PassRequestToFormMixin, \
15 19
    BaseEditView, ActionMixin, OtherActionsMixin, Action, ExportMixin, \
......
104 108
                         _('Delete'),
105 109
                         _('Do you really want to delete "%s" ?') %
106 110
                         self.object.username)
111
        if self.request.user.is_superuser:
112
            yield Action('switch_user', _('Impersonate this user'))
107 113

  
108 114
    def action_force_password_change(self, request, *args, **kwargs):
109 115
        PasswordReset.objects.get_or_create(user=self.object)
......
156 162
    def action_delete_password_reset(self, request, *args, **kwargs):
157 163
        PasswordReset.objects.filter(user=self.object).delete()
158 164

  
165
    def action_switch_user(self, request, *args, **kwargs):
166
        return switch_user(request, self.object)
167

  
159 168
    # Copied from PasswordResetForm implementation
160 169
    def send_mail(self, subject_template_name, email_template_name,
161 170
                  context, to_email):
src/authentic2/profile_urls.py
69 69
    url(r'^password/reset/done/$',
70 70
        auth_views.password_reset_done,
71 71
        name='auth_password_reset_done'),
72
    url(r'^switch-back/$', profile_views.switch_back, name='a2-switch-back'),
72 73
)
src/authentic2/profile_views.py
114 114
        return utils.login(self.request, self.user, 'email')
115 115

  
116 116
password_reset_confirm = PasswordResetConfirmView.as_view()
117

  
118

  
119
def switch_back(request):
120
    return utils.switch_back(request)
src/authentic2/settings.py
134 134
    'authentic2.backends.ldap_backend.LDAPBackend',
135 135
    'authentic2.backends.ldap_backend.LDAPBackendPasswordLost',
136 136
    'authentic2.backends.models_backend.ModelBackend',
137
    'authentic2.backends.models_backend.DummyModelBackend',
137 138
    'django_rbac.backends.DjangoRBACBackend',
138 139
)
139 140
AUTHENTICATION_BACKENDS = plugins.register_plugins_authentication_backends(
src/authentic2/templates/authentic2/base.html
26 26
        {% if request.user.is_authenticated %}
27 27
        <div id="user">
28 28
          {% block user %}
29
            <p title="{% blocktrans with last_login=request.session.last_login %}Last login {{ last_login }}{% endblocktrans %}">
29
             <p title="{% blocktrans %}Last login {{ LAST_LOGIN }}{% endblocktrans %}">
30 30
              {% blocktrans with request.user.get_full_name as username %}Hello {{ username }}.{% endblocktrans %}
31 31
                <a id="logout" href="{% url 'auth_logout' %}">{% trans "Logout" %}</a>
32 32
            </p>
src/authentic2/templates/idp/account_management.html
34 34
{% if allow_account_deletion %}
35 35
<p><a href="{% url 'delete_account' %}">{% trans "Delete profile" %}</a></p>
36 36
{% endif %}
37
{% if USER_SWITCHED %}
38
<p><a href="{% url 'a2-switch-back' %}">{% trans "Switch back" %}</a></p>
39
{% endif %}
37 40
</div>
38 41
<h3>{% trans "Credentials" %}</h3>
39 42
  {% for html_block in frontends_block %}
src/authentic2/utils.py
13 13
import django
14 14
from django.conf import settings
15 15
from django.http import HttpResponseRedirect
16
from django.core.exceptions import ImproperlyConfigured
16
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
17 17
from django.core import urlresolvers
18 18
from django.http.request import QueryDict
19
from django.contrib.auth import REDIRECT_FIELD_NAME, login as auth_login
19
from django.contrib.auth import (REDIRECT_FIELD_NAME, login as auth_login,
20
    SESSION_KEY, HASH_SESSION_KEY, BACKEND_SESSION_KEY, authenticate)
20 21
from django import forms
21 22
from django.forms.util import ErrorList
22 23
from django.forms.utils import to_current_timezone
......
28 29
from django.core import signing
29 30
from django.core.urlresolvers import reverse
30 31
from django.utils.formats import localize
32
from django.contrib import messages
33
from django.utils.functional import empty
31 34

  
32 35
try:
33 36
    from django.core.exceptions import FieldDoesNotExist
......
303 306
       URL or settings.LOGIN_REDIRECT_URL.'''
304 307
    last_login = user.last_login
305 308
    auth_login(request, user)
306
    if 'last_login' not in request.session:
307
        request.session['last_login'] = localize(to_current_timezone(last_login), True)
309
    if constants.LAST_LOGIN_SESSION_KEY not in request.session:
310
        request.session[constants.LAST_LOGIN_SESSION_KEY] = \
311
                localize(to_current_timezone(last_login), True)
308 312
    record_authentication_event(request, how)
309 313
    return continue_to_next_url(request, **kwargs)
310 314

  
......
567 571
def to_dict_of_set(d):
568 572
    '''Convert a dictionary of sequence into a dictionary of sets'''
569 573
    return dict((k, set(v)) for k, v in d.iteritems())
574

  
575

  
576
def switch_user(request, new_user):
577
    '''Switch to another user and remember currently logged in user in the
578
       session. Reserved to superusers.'''
579
    logger = logging.getLogger(__name__)
580
    if constants.SWITCH_USER_SESSION_KEY in request.session:
581
        messages.error(request, _('Your user is already switched, go to your '
582
                                  'account page and come back to your original '
583
                                  'user to do it again.'))
584
    else:
585
        if not request.user.is_superuser:
586
            raise PermissionDenied
587
        switched = {}
588
        for key in (SESSION_KEY, BACKEND_SESSION_KEY, HASH_SESSION_KEY,
589
                    constants.LAST_LOGIN_SESSION_KEY):
590
            switched[key] = request.session[key]
591
        user = authenticate(user=new_user)
592
        auth_login(request, user)
593
        request.session[constants.SWITCH_USER_SESSION_KEY] = switched
594
        if constants.LAST_LOGIN_SESSION_KEY not in request.session:
595
            request.session[constants.LAST_LOGIN_SESSION_KEY] = \
596
                    localize(to_current_timezone(new_user.last_login), True)
597
        messages.info(request, _('Successfully switched to user %s') %
598
                      new_user.get_full_name())
599
        logging.info('switched to user %s', new_user)
600
        return continue_to_next_url(request)
601

  
602

  
603
def switch_back(request):
604
    '''Switch back to original superuser after a user switch'''
605
    logger = logging.getLogger(__name__)
606
    if constants.SWITCH_USER_SESSION_KEY in request.session:
607
        switched = request.session[constants.SWITCH_USER_SESSION_KEY]
608
        for key in switched:
609
            request.session[key] = switched[key]
610
        del request.session[constants.SWITCH_USER_SESSION_KEY]
611
        del request._cached_user
612
        request.user._wrapped = empty
613
        messages.info(request, _('Successfully switched back to user %s') % 
614
                      request.user.get_full_name())
615
        logger.info('switched back to user %s', request.user)
616
    else:
617
        messages.warning(request, _('No user to switch back to'))
618
    return continue_to_next_url(request)
570
-