Project

General

Profile

0005-views-add-credentials-management-display-view-33550.patch

Valentin Deniaud, 19 Jun 2019 05:33 PM

Download (11.6 KB)

View differences:

Subject: [PATCH] views: add credentials management display view (#33550)

Split ProfileView in half, creating AuthenticatorsView which allows to
display only credentials.
Use it to display a page with only authenticators used as supplementary
authentication factors.
 .../templates/authentic2/accounts.html        |  5 +
 .../templates/authentic2/authenticators.html  | 10 ++
 src/authentic2/urls.py                        |  5 +
 src/authentic2/utils.py                       | 12 ++-
 src/authentic2/views.py                       | 96 +++++++++++--------
 5 files changed, 87 insertions(+), 41 deletions(-)
 create mode 100644 src/authentic2/templates/authentic2/authenticators.html
src/authentic2/templates/authentic2/accounts.html
67 67
          </ul>
68 68
        </div>
69 69
      {% endif %}
70
      {% if multifactor_available %}
71
        <p><a href="{% url 'authenticators_profile' %}">
72
          {% trans "Configure multi-factor authentication" %}
73
        </a></p>
74
      {% endif %}
70 75
    </div>
71 76
  </div>
72 77
{% endblock %}
src/authentic2/templates/authentic2/authenticators.html
1
{% extends "authentic2/base-page.html" %}
2

  
3
{% block content %}
4
  {% for id, block in frontends_block_by_id.items %}
5
  <div class="block" id="account-management-{{ id }}">
6
    <h2>{{ block.name }}</h2>
7
    {{ block.content|safe }}
8
  </div>
9
  {% endfor %}
10
{% endblock %}
src/authentic2/urls.py
83 83
        views.switch_back,
84 84
        name='a2-switch-back'),
85 85

  
86
    # Multi-factor authentication
87
    url(r'^authenticators/$',
88
        views.authenticators,
89
        name='authenticators_profile'),
90

  
86 91
    # Legacy, only there to provide old view names to resolver
87 92
    url(r'^password/change/$',
88 93
        views.notimplemented_view,
src/authentic2/utils.py
170 170
    '''Return the list of enabled cleaned backends.
171 171

  
172 172
    required_auth_level param can be specified in order to filter backends on
173
    the authentication levels they provide. When set to 0, filtering is
174
    disabled.
173
    the authentication levels they provide. The default value of 1 selects only
174
    level 1 backends, which is also the default level for a backend that do not
175
    specify one. Conversely, the special value 0 will exclude those backends,
176
    ie it will only select backend specifying an auth level that is more than 1.
175 177
    '''
176 178
    backends = []
177 179
    for backend_path in getattr(app_settings, setting_name):
......
182 184
        # If no enabled method is defined on the backend, backend enabled by default.
183 185
        if hasattr(backend, 'enabled') and not backend.enabled():
184 186
            continue
187
        auth_level = getattr(backend, 'auth_level', 1)
185 188
        if required_auth_level:
186
            if not required_auth_level == getattr(backend, 'auth_level', 1):
189
            if not required_auth_level == auth_level:
187 190
                continue
191
        elif auth_level == 1:
192
            # required_auth_level null value filters out primary auth factors
193
            continue
188 194
        kwargs_settings = getattr(app_settings, setting_name + '_KWARGS', {})
189 195
        if backend_path in kwargs_settings:
190 196
            kwargs.update(kwargs_settings[backend_path])
src/authentic2/views.py
418 418
homepage = Homepage.as_view()
419 419

  
420 420

  
421
class ProfileView(cbv.TemplateNamesMixin, TemplateView):
422
    template_names = ['idp/account_management.html', 'authentic2/accounts.html']
423
    title = _('Your account')
424

  
425
    def dispatch(self, request, *args, **kwargs):
426
        if app_settings.A2_ACCOUNTS_URL:
427
            return utils.redirect(request, app_settings.A2_ACCOUNTS_URL)
428
        return super(ProfileView, self).dispatch(request, *args, **kwargs)
421
class AuthenticatorsView(cbv.TemplateNamesMixin, TemplateView):
422
    template_name = 'authentic2/authenticators.html'
423
    title = _('Authenticators')
424
    authenticators_level = 0
429 425

  
430 426
    def get_context_data(self, **kwargs):
431
        context = super(ProfileView, self).get_context_data(**kwargs)
427
        context = super(AuthenticatorsView, self).get_context_data(**kwargs)
432 428
        request = self.request
433
        auth_level = int(request.GET.get('auth_level', 0))
434
        frontends = utils.get_backends('AUTH_FRONTENDS', required_auth_level=auth_level)
429

  
430
        auth_level = self.authenticators_level or int(request.GET.get('auth_level', 0))
431
        frontends = utils.get_backends('AUTH_FRONTENDS', auth_level)
435 432

  
436 433
        if request.method == "POST":
437 434
            for frontend in frontends:
......
441 438
                        if request.session.test_cookie_worked():
442 439
                            request.session.delete_test_cookie()
443 440
                        return frontend.post(request, form, None, '/profile')
444
        # User attributes management
441

  
442
        parameters = {'request': self.request,
443
                      'context': context}
444
        profiles = [utils.get_authenticator_method(frontend, 'profile', parameters) for frontend in frontends]
445
        # Old frontends data structure for templates
446
        blocks = [block['content'] for block in profiles if block]
447
        # New frontends data structure for templates
448
        blocks_by_id = collections.OrderedDict((block['id'], block) for block in profiles if block)
449

  
450
        context.update({
451
            'frontends_block': blocks,
452
            'frontends_block_by_id': blocks_by_id,
453
        })
454
        hooks.call_hooks('modify_context_data', self, context)
455
        return context
456

  
457
authenticators = AuthenticatorsView.as_view()
458

  
459

  
460
class ProfileView(AuthenticatorsView):
461
    template_names = ['idp/account_management.html', 'authentic2/accounts.html']
462
    title = _('Your account')
463
    authenticators_level = 1
464

  
465
    def dispatch(self, request, *args, **kwargs):
466
        if app_settings.A2_ACCOUNTS_URL:
467
            return utils.redirect(request, app_settings.A2_ACCOUNTS_URL)
468
        return super(ProfileView, self).dispatch(request, *args, **kwargs)
469

  
470
    def get_context_data(self, **kwargs):
471
        context = super(ProfileView, self).get_context_data(**kwargs)
472
        user = self.request.user
473

  
445 474
        profile = []
446 475
        field_names = app_settings.A2_PROFILE_FIELDS
447 476
        if not field_names:
448 477
            field_names = list(app_settings.A2_REGISTRATION_FIELDS)
449
            for field_name in getattr(request.user, 'USER_PROFILE', []):
478
            for field_name in getattr(user, 'USER_PROFILE', []):
450 479
                if field_name not in field_names:
451 480
                    field_names.append(field_name)
452 481
            qs = models.Attribute.objects.filter(Q(user_editable=True) | Q(user_visible=True))
......
471 500
                if not attribute.user_visible:
472 501
                    continue
473 502
                html_value = attribute.get_kind().get('html_value', lambda a, b: b)
474
                qs = models.AttributeValue.objects.with_owner(request.user)
503
                qs = models.AttributeValue.objects.with_owner(user)
475 504
                qs = qs.filter(attribute=attribute)
476 505
                qs = qs.select_related()
477 506
                value = [at_value.to_python() for at_value in qs]
......
482 511
            else:
483 512
                # fallback to model attributes
484 513
                try:
485
                    field = request.user._meta.get_field(field_name)
514
                    field = user._meta.get_field(field_name)
486 515
                except FieldDoesNotExist:
487 516
                    continue
488 517
                if not title:
489 518
                    title = field.verbose_name
490
                value = getattr(self.request.user, field_name, None)
519
                value = getattr(user, field_name, None)
491 520
                attribute = models.Attribute(name=field_name, label=title)
492 521

  
493 522
            raw_value = None
......
501 530
            if value or app_settings.A2_PROFILE_DISPLAY_EMPTY_FIELDS:
502 531
                profile.append((title, value))
503 532
                attributes.append({'attribute': attribute, 'values': raw_value})
504

  
505
        # Credentials management
506
        parameters = {'request': request,
507
                      'context': context}
508
        profiles = [utils.get_authenticator_method(frontend, 'profile', parameters) for frontend in frontends]
509
        # Old frontends data structure for templates
510
        blocks = [block['content'] for block in profiles if block]
511
        # New frontends data structure for templates
512
        blocks_by_id = collections.OrderedDict((block['id'], block) for block in profiles if block)
513

  
514
        idp_backends = utils.get_backends()
515
        # Get actions for federation management
516
        federation_management = []
517
        if app_settings.A2_PROFILE_CAN_MANAGE_FEDERATION:
518
            for idp_backend in idp_backends:
519
                if hasattr(idp_backend, 'federation_management'):
520
                    federation_management.extend(idp_backend.federation_management(request))
521 533
        context.update({
522
            'frontends_block': blocks,
523
            'frontends_block_by_id': blocks_by_id,
524 534
            'profile': profile,
525 535
            'attributes': attributes,
526 536
            'allow_account_deletion': app_settings.A2_REGISTRATION_CAN_DELETE_ACCOUNT,
527 537
            'allow_profile_edit': EditProfile.can_edit_profile(),
528 538
            'allow_email_change': app_settings.A2_PROFILE_CAN_CHANGE_EMAIL,
539
            'multifactor_available': bool(utils.get_backends('AUTH_FRONTENDS', 0)),
529 540
            # TODO: deprecated should be removed when publik-base-theme is updated
530
            'allow_password_change': utils.user_can_change_password(request=request),
531
            'federation_management': federation_management,
532
            'auth_level': auth_level,
541
            'allow_password_change': utils.user_can_change_password(request=self.request),
533 542
        })
543
        self.set_federation_management_context(context)
534 544
        hooks.call_hooks('modify_context_data', self, context)
535 545
        return context
536 546

  
547
    def set_federation_management_context(self, context):
548
        idp_backends = utils.get_backends()
549
        # Get actions for federation management
550
        federation_management = []
551
        if app_settings.A2_PROFILE_CAN_MANAGE_FEDERATION:
552
            for idp_backend in idp_backends:
553
                if hasattr(idp_backend, 'federation_management'):
554
                    federation_management.extend(idp_backend.federation_management(self.request))
555
        context['federation_management'] = federation_management
556

  
537 557
profile = login_required(ProfileView.as_view())
538 558

  
539 559

  
540
-