0001-profile_views-add-a-profil-page-to-manage-authorized.patch
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 |
- |