Projet

Général

Profil

0001-profile_views-add-a-profil-page-to-manage-authorized.patch

Nicolas Roche, 27 juillet 2020 18:42

Télécharger (13,1 ko)

Voir les différences:

Subject: [PATCH] profile_views: add a profil page to manage authorized oauth
 services (#45200)

 src/authentic2/app_settings.py                |  3 +
 .../templates/authentic2/accounts.html        |  3 +
 .../accounts_authorized_oauth_services.html   | 67 +++++++++++++++++++
 src/authentic2/urls.py                        |  3 +
 src/authentic2/views.py                       | 29 ++++++++
 tests/test_idp_oidc.py                        | 36 ++++++++++
 tests/test_profile.py                         | 31 +++++++++
 7 files changed, 172 insertions(+)
 create mode 100644 src/authentic2/templates/authentic2/accounts_authorized_oauth_services.html
src/authentic2/app_settings.py
122 122
        default=True,
123 123
        definition='Can user self change their email'),
124 124
    A2_PROFILE_CAN_EDIT_PROFILE=Setting(
125 125
        default=True,
126 126
        definition='Can user self edit their profile'),
127 127
    A2_PROFILE_CAN_MANAGE_FEDERATION=Setting(
128 128
        default=True,
129 129
        definition='Can user manage its federations'),
130
    A2_PROFILE_CAN_MANAGE_SERVICE_AUTHORIZATIONS=Setting(
131
        default=True,
132
        definition='Allow user to revoke granted services access to its account profile data'),
130 133
    A2_PROFILE_DISPLAY_EMPTY_FIELDS=Setting(
131 134
        default=False,
132 135
        definition='Include empty fields in profile view'),
133 136
    A2_HOMEPAGE_URL=Setting(
134 137
        default=None,
135 138
        definition='IdP has no homepage, redirect to this one.'),
136 139
    A2_USER_CAN_RESET_PASSWORD=Setting(
137 140
        default=None,
src/authentic2/templates/authentic2/accounts.html
32 32
        </dl>
33 33
      {% endif %}
34 34
      {% if allow_email_change %}
35 35
        <p><a href="{% url 'email-change' %}">{% trans "Change email" %}</a></p>
36 36
      {% endif %}
37 37
      {% if allow_profile_edit %}
38 38
        <p><a href="{% url 'profile_edit' %}">{% trans "Edit account data" %}</a></p>
39 39
      {% endif %}
40
      {% if allow_authorization_management %}
41
        <p><a href="{% url 'authorized-oauth-services' %}">{% trans "Manage service authorizations" %}</a></p>
42
      {% endif %}
40 43
      {% if allow_account_deletion %}
41 44
        <p><a href="{% url 'delete_account' %}">{% trans "Delete account" %}</a></p>
42 45
      {% endif %}
43 46
    </div>
44 47
    <div id="a2-credentials" class="a2-profile-block">
45 48
      <h3>{% trans "Credentials" %}</h3>
46 49
      {% for html_block in frontends_block %}
47 50
        {{ html_block|safe }}
src/authentic2/templates/authentic2/accounts_authorized_oauth_services.html
1
{% extends "authentic2/base-page.html" %}
2
{% load i18n %}
3

  
4
{% block page-title %}
5
{{ block.super }} - {{ view.title }}
6
{% endblock %}
7

  
8
{% block breadcrumb %}
9
{{ block.super }}
10
<a href="..">{% trans "Your account" %}</a>
11
<a href="">{{ view.title }}</a>
12
{% endblock %}
13

  
14
{% block content %}
15
{% block oidc-authorized-oauth-services-pre %}{% endblock %}
16
<div class="authorized-oauth-services">
17
  {% block oidc-authorized-oauth-services-top %}
18
  <p class="authorized-oauth-services--top">
19
    {% if authorized_oauth_services|length_is:0 %}
20
    {% trans "You have not granted service access to your account profile data." %}
21
    {% else %}
22
    {% blocktrans count counter=authorized_oauth_services|length %}
23
    You have granted one service access to your account profile data.
24
    {% plural %}
25
    You have granted {{ counter }} services access to your account profile data.
26
    {% endblocktrans %}
27
    {% endif %}
28
  </p>
29
  {% endblock %}
30
  <ul class="authorized-oauth-services--list">
31
    {% for auth in authorized_oauth_services %}
32
    <li class="authorized-oauth-services--item">
33
      <form method="post" class="authorized-oauth-services--form">
34
        {% csrf_token %}
35
        {% block oidc-authorized-oauth-service %}
36
        <div class="authorized-oauth-services--infos">
37
          {% block oidc-authorized-oauth-service-top %}{% endblock %}
38
          <span class="authorized-oauth-services--client">
39
            {{ auth.client }}
40
          </span>
41
          <span class="authorized-oauth-services--dates">
42
            <span class="authorized-oauth-services-dates--since">
43
              <span class="label">{% trans "Allowed since:" %}</span>
44
              <span class="time">{{ auth.created }}</span>
45
            </span>
46
            <span class="authorized-oauth-services--separator">/</span>
47
            <span class="authorized-oauth-services--expired">
48
              <span class="label">{% trans "Expire on:" %}</span>
49
              <span class="time">{{ auth.expired }}</span>
50
            </span>
51
          </span>
52
        </div>
53
        <div class="authorized-oauth-services--actions">
54
          <input type="hidden" id="auth-id" name="auth_id" value="{{ auth.id }}">
55
          <button class="authorized-oauth-services--revoke-button">{% trans 'Revoke' %}</button>
56
        </div>
57
        {% block oidc-authorized-oauth-service-bottom %}{% endblock %}
58
        {% endblock %}
59
      </form>
60
    </li>
61
    {% endfor %}
62
  </ul>
63
</table>
64
{% block oidc-authorized-oauth-services-bottom %}{% endblock %}
65
</div>
66
{% block oidc-authorized-oauth-services-post %}{% endblock %}
67
{% endblock %}
src/authentic2/urls.py
64 64
        views.edit_profile,
65 65
        name='profile_edit_with_scope'),
66 66
    url(r'^change-email/$',
67 67
        views.email_change,
68 68
        name='email-change'),
69 69
    url(r'^change-email/verify/$',
70 70
        views.email_change_verify,
71 71
        name='email-change-verify'),
72
    url(r'^authorizations/$',
73
        login_required(views.authorized_oauth_services),
74
        name='authorized-oauth-services'),
72 75
    url(r'^$',
73 76
        views.profile,
74 77
        name='account_management'),
75 78

  
76 79
    # Password change
77 80
    url(r'^password/change/$',
78 81
        views.password_change,
79 82
        name='password_change'),
src/authentic2/views.py
501 501
        context.update({
502 502
            'frontends_block': blocks,
503 503
            'frontends_block_by_id': blocks_by_id,
504 504
            'profile': profile,
505 505
            'attributes': attributes,
506 506
            'allow_account_deletion': app_settings.A2_REGISTRATION_CAN_DELETE_ACCOUNT,
507 507
            'allow_profile_edit': EditProfile.can_edit_profile(),
508 508
            'allow_email_change': app_settings.A2_PROFILE_CAN_CHANGE_EMAIL,
509
            'allow_authorization_management': (
510
                app_settings.A2_PROFILE_CAN_MANAGE_SERVICE_AUTHORIZATIONS
511
                and 'authentic2_idp_oidc' in settings.INSTALLED_APPS),
509 512
            # TODO: deprecated should be removed when publik-base-theme is updated
510 513
            'allow_password_change': utils.user_can_change_password(request=request),
511 514
            'federation_management': federation_management,
512 515
        })
513 516
        hooks.call_hooks('modify_context_data', self, context)
514 517
        return context
515 518

  
516 519
profile = login_required(ProfileView.as_view())
......
1264 1267
class SuView(View):
1265 1268
    def get(self, request, uuid):
1266 1269
        user = switch_user.resolve_token(uuid)
1267 1270
        if not user:
1268 1271
            raise Http404
1269 1272
        return utils.simulate_authentication(request, user, 'su')
1270 1273

  
1271 1274
su = SuView.as_view()
1275

  
1276

  
1277
class AuthorizedOauthServicesView(TemplateView):
1278
    template_name = 'authentic2/accounts_authorized_oauth_services.html'
1279
    title = _('Consent Management')
1280

  
1281
    def get_context_data(self, **kwargs):
1282
        from authentic2_idp_oidc.models import OIDCAuthorization
1283

  
1284
        context = super(AuthorizedOauthServicesView, self).get_context_data(**kwargs)
1285
        context['authorized_oauth_services'] = OIDCAuthorization.objects.filter(
1286
            user=self.request.user)
1287
        return context
1288

  
1289
    def post(self, request, *args, **kwargs):
1290
        from authentic2_idp_oidc.models import OIDCAuthorization
1291

  
1292
        qs = OIDCAuthorization.objects.filter(user=request.user)
1293
        auth_id = request.POST.get('auth_id')
1294
        if auth_id:
1295
            qs = qs.filter(id=auth_id)
1296
        qs.delete()
1297
        return HttpResponseRedirect(reverse('authorized-oauth-services'))
1298

  
1299

  
1300
authorized_oauth_services = AuthorizedOauthServicesView.as_view()
tests/test_idp_oidc.py
1599 1599
    with pytest.raises(ValidationError, match=r'same domain'):
1600 1600
        OIDCClient(
1601 1601
            redirect_uris='https://example.com/ https://example2.com/',
1602 1602
            identifier_policy=OIDCClient.POLICY_PAIRWISE).clean()
1603 1603

  
1604 1604
    OIDCClient(
1605 1605
        redirect_uris='https://example.com/ https://example2.com/',
1606 1606
        sector_identifier_uri='https://example.com/').clean()
1607

  
1608

  
1609
def test_oidc_authorized_oauth_services_view(app, oidc_client, simple_user):
1610
    url = make_url('authorized-oauth-services')
1611
    response = app.get(url, status=302)
1612
    assert '/login/' in response.location
1613

  
1614
    utils.login(app, simple_user)
1615
    response = app.get(url, status=200)
1616
    assert "You have not granted service access to your account profile data." in response.text
1617

  
1618
    OIDCAuthorization.objects.create(
1619
        client=oidc_client, user=simple_user, scopes='openid',
1620
        expired=now() + datetime.timedelta(days=2))
1621
    OIDCAuthorization.objects.create(
1622
        client=oidc_client, user=simple_user, scopes='openid profile',
1623
        expired=now() + datetime.timedelta(days=2))
1624
    OIDCAuthorization.objects.create(
1625
        client=oidc_client, user=simple_user, scopes='openid profile email',
1626
        expired=now() + datetime.timedelta(days=2))
1627

  
1628
    response = app.get(url, status=200)
1629
    assert "You have granted 3 services access to your account profile data."
1630
    assert len(response.html.find_all(
1631
        'button', {'class': 'authorized-oauth-services--revoke-button'})) == 3
1632

  
1633
    # revoke two
1634
    response = response.forms[0].submit()
1635
    response = response.follow()
1636
    assert len(response.html.find_all(
1637
        'button', {'class': 'authorized-oauth-services--revoke-button'})) == 2
1638
    response = response.forms[0].submit()
1639
    response = response.follow()
1640
    assert len(response.html.find_all(
1641
        'button', {'class': 'authorized-oauth-services--revoke-button'})) == 1
1642
    assert "You have granted one service access to your account profile data." in response.text
tests/test_profile.py
187 187
    assert len(response.pyquery('input[type="radio"][name="edit-profile-title"][readonly="true"]')) == 0
188 188
    assert len(response.pyquery('select[name="edit-profile-title"]')) == 0
189 189

  
190 190
    simple_user.verified_attributes.title = 'Monsieur'
191 191

  
192 192
    response = app.get(url, status=200)
193 193
    assert len(response.pyquery('input[type="radio"][name="edit-profile-title"]')) == 0
194 194
    assert len(response.pyquery('input[type="text"][name="edit-profile-title@disabled"][readonly]')) == 1
195

  
196

  
197
def test_acount_view(app, simple_user, settings):
198
    utils.login(app, simple_user)
199
    url = reverse('account_management')
200
    response = app.get(url, status=200)
201
    assert [x['href'] for x in response.html.find('div', {'id': 'a2-profile'}).find_all('a')] == [
202
        reverse('email-change'),
203
        reverse('profile_edit'),
204
        reverse('authorized-oauth-services'),
205
        reverse('delete_account')
206
    ]
207

  
208
    settings.INSTALLED_APPS = tuple(x for x in settings.INSTALLED_APPS if x != 'authentic2_idp_oidc')
209
    url = reverse('account_management')
210
    response = app.get(url, status=200)
211
    assert [x['href'] for x in response.html.find('div', {'id': 'a2-profile'}).find_all('a')] == [
212
        reverse('email-change'),
213
        reverse('profile_edit'),
214
        reverse('delete_account')
215
    ]
216
    settings.INSTALLED_APPS += ('authentic2_idp_oidc',)
217

  
218
    settings.A2_PROFILE_CAN_CHANGE_EMAIL = False
219
    settings.A2_PROFILE_CAN_MANAGE_SERVICE_AUTHORIZATIONS = False
220
    settings.A2_REGISTRATION_CAN_DELETE_ACCOUNT = False
221
    url = reverse('account_management')
222
    response = app.get(url, status=200)
223
    assert [x['href'] for x in response.html.find('div', {'id': 'a2-profile'}).find_all('a')] == [
224
        reverse('profile_edit'),
225
    ]
195
-