0001-profile_views-add-a-profile-page-to-manage-oidc-auth.patch
src/authentic2/app_settings.py | ||
---|---|---|
130 | 130 |
A2_PROFILE_DISPLAY_SERVICE_AUTHORIZATIONS_PAGE=Setting( |
131 | 131 |
default=True, |
132 | 132 |
definition='Display page for user to revoke granted services access to its account profile data'), |
133 |
A2_PROFILE_DISPLAY_OU_AUTHORIZATIONS_PAGE=Setting( |
|
134 |
default=True, |
|
135 |
definition='Display page for user to revoke granted services within OUs access to its account profile data'), |
|
133 | 136 |
A2_PROFILE_DISPLAY_EMPTY_FIELDS=Setting( |
134 | 137 |
default=False, |
135 | 138 |
definition='Include empty fields in profile view'), |
src/authentic2/templates/authentic2/accounts.html | ||
---|---|---|
40 | 40 |
{% if allow_authorization_management %} |
41 | 41 |
<p><a href="{% url 'authorized-oauth-services' %}">{% trans "Manage service authorizations" %}</a></p> |
42 | 42 |
{% endif %} |
43 |
{% if allow_authorization_management_by_ou %} |
|
44 |
<p><a href="{% url 'authorized-oauth-ous' %}">{% trans "Manage organizational unit authorizations" %}</a></p> |
|
45 |
{% endif %} |
|
43 | 46 |
{% if allow_account_deletion %} |
44 | 47 |
<p><a href="{% url 'delete_account' %}">{% trans "Delete account" %}</a></p> |
45 | 48 |
{% endif %} |
src/authentic2/templates/authentic2/accounts_authorized_oauth_ous.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-ous-pre %}{% endblock %} |
|
16 |
<div class="authorized-oauth-ous"> |
|
17 |
{% block oidc-authorized-oauth-ous-top %} |
|
18 |
<p class="authorized-oauth-ous--top"> |
|
19 |
{% if authorized_oauth_ous|length_is:0 %} |
|
20 |
{% trans "You have not granted organizational unit access to your account profile data." %} |
|
21 |
{% else %} |
|
22 |
{% blocktrans count counter=authorized_oauth_ous|length %} |
|
23 |
You have granted one organizational unit access to your account profile data. |
|
24 |
{% plural %} |
|
25 |
You have granted {{ counter }} organizational units access to your account profile data. |
|
26 |
{% endblocktrans %} |
|
27 |
{% endif %} |
|
28 |
</p> |
|
29 |
{% endblock %} |
|
30 |
<ul class="authorized-oauth-ous--list"> |
|
31 |
{% for auth in authorized_oauth_ous %} |
|
32 |
<li class="authorized-oauth-ous--item"> |
|
33 |
<form method="post" class="authorized-oauth-ous--form"> |
|
34 |
{% csrf_token %} |
|
35 |
{% block oidc-authorized-oauth-ou %} |
|
36 |
<div class="authorized-oauth-ous--infos"> |
|
37 |
{% block oidc-authorized-oauth-ou-top %}{% endblock %} |
|
38 |
<span class="authorized-oauth-ous--client"> |
|
39 |
{{ auth.client }} |
|
40 |
</span> |
|
41 |
<span class="authorized-oauth-ous--dates"> |
|
42 |
<span class="authorized-oauth-ous-dates--since"> |
|
43 |
<span class="label">{% trans "Allowed since:" %}</span> |
|
44 |
<span class="time">{{ auth.created }}</span> |
|
45 |
</span> |
|
46 |
<span class="authorized-oauth-ous--separator">/</span> |
|
47 |
<span class="authorized-oauth-ous--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-ous--actions"> |
|
54 |
<input type="hidden" id="auth-id" name="auth_id" value="{{ auth.id }}"> |
|
55 |
<button class="authorized-oauth-ous--revoke-button">{% trans 'Revoke' %}</button> |
|
56 |
</div> |
|
57 |
{% block oidc-authorized-oauth-ou-bottom %}{% endblock %} |
|
58 |
{% endblock %} |
|
59 |
</form> |
|
60 |
</li> |
|
61 |
{% endfor %} |
|
62 |
</ul> |
|
63 |
</table> |
|
64 |
{% block oidc-authorized-oauth-ous-bottom %}{% endblock %} |
|
65 |
</div> |
|
66 |
{% block oidc-authorized-oauth-ous-post %}{% endblock %} |
|
67 |
{% endblock %} |
src/authentic2/urls.py | ||
---|---|---|
69 | 69 |
url(r'^change-email/verify/$', |
70 | 70 |
views.email_change_verify, |
71 | 71 |
name='email-change-verify'), |
72 |
url(r'^authorizations/$', |
|
72 |
url(r'^service-authorizations/$',
|
|
73 | 73 |
login_required(views.authorized_oauth_services), |
74 | 74 |
name='authorized-oauth-services'), |
75 |
url(r'^ou-authorizations/$', |
|
76 |
login_required(views.authorized_oauth_ous), |
|
77 |
name='authorized-oauth-ous'), |
|
75 | 78 |
url(r'^$', |
76 | 79 |
views.profile, |
77 | 80 |
name='account_management'), |
src/authentic2/views.py | ||
---|---|---|
513 | 513 |
'federation_management': federation_management, |
514 | 514 |
}) |
515 | 515 | |
516 |
if ('authentic2_idp_oidc' in settings.INSTALLED_APPS and |
|
517 |
app_settings.A2_PROFILE_DISPLAY_SERVICE_AUTHORIZATIONS_PAGE): |
|
518 |
from authentic2_idp_oidc.models import OIDCClient |
|
519 |
context['allow_authorization_management'] = OIDCClient.objects.filter( |
|
520 |
authorization_mode=OIDCClient.AUTHORIZATION_MODE_BY_SERVICE).exists() |
|
516 |
if 'authentic2_idp_oidc' in settings.INSTALLED_APPS: |
|
517 |
if app_settings.A2_PROFILE_DISPLAY_SERVICE_AUTHORIZATIONS_PAGE: |
|
518 |
from authentic2_idp_oidc.models import OIDCClient |
|
519 |
context['allow_authorization_management'] = OIDCClient.objects.filter( |
|
520 |
authorization_mode=OIDCClient.AUTHORIZATION_MODE_BY_SERVICE).exists() |
|
521 |
if app_settings.A2_PROFILE_DISPLAY_OU_AUTHORIZATIONS_PAGE: |
|
522 |
from authentic2_idp_oidc.models import OIDCClient |
|
523 |
context['allow_authorization_management_by_ou'] = OIDCClient.objects.filter( |
|
524 |
authorization_mode=OIDCClient.AUTHORIZATION_MODE_BY_OU).exists() |
|
521 | 525 | |
522 | 526 |
hooks.call_hooks('modify_context_data', self, context) |
523 | 527 |
return context |
... | ... | |
1309 | 1313 | |
1310 | 1314 | |
1311 | 1315 |
authorized_oauth_services = AuthorizedOauthServicesView.as_view() |
1316 | ||
1317 | ||
1318 |
class AuthorizedOauthOusView(TemplateView): |
|
1319 |
template_name = 'authentic2/accounts_authorized_oauth_ous.html' |
|
1320 |
title = _('Consent Management by organizational units') |
|
1321 | ||
1322 |
def get_context_data(self, **kwargs): |
|
1323 |
from authentic2_idp_oidc.models import OIDCAuthorization |
|
1324 |
from django_rbac.utils import get_ou_model |
|
1325 | ||
1326 |
context = super(AuthorizedOauthOusView, self).get_context_data(**kwargs) |
|
1327 |
ou_ct = ContentType.objects.get_for_model(get_ou_model()) |
|
1328 |
context['authorized_oauth_ous'] = OIDCAuthorization.objects.filter( |
|
1329 |
user=self.request.user, client_ct=ou_ct) |
|
1330 |
return context |
|
1331 | ||
1332 |
def post(self, request, *args, **kwargs): |
|
1333 |
from authentic2_idp_oidc.models import OIDCAuthorization |
|
1334 |
from django_rbac.utils import get_ou_model |
|
1335 | ||
1336 |
ou_ct = ContentType.objects.get_for_model(get_ou_model()) |
|
1337 |
qs = OIDCAuthorization.objects.filter( |
|
1338 |
user=request.user, client_ct=ou_ct) |
|
1339 |
auth_id = request.POST.get('auth_id') |
|
1340 |
if auth_id: |
|
1341 |
qs = qs.filter(id=auth_id) |
|
1342 |
qs.delete() |
|
1343 |
return HttpResponseRedirect(reverse('authorized-oauth-ous')) |
|
1344 | ||
1345 | ||
1346 |
authorized_oauth_ous = AuthorizedOauthOusView.as_view() |
tests/test_idp_oidc.py | ||
---|---|---|
1647 | 1647 |
assert len(response.html.find_all( |
1648 | 1648 |
'button', {'class': 'authorized-oauth-services--revoke-button'})) == 1 |
1649 | 1649 |
assert "You have granted one service access to your account profile data." in response.text |
1650 | ||
1651 | ||
1652 |
def test_oidc_authorized_oauth_ous_view(app, oidc_client, simple_user): |
|
1653 |
url = make_url('authorized-oauth-ous') |
|
1654 |
response = app.get(url, status=302) |
|
1655 |
assert '/login/' in response.location |
|
1656 | ||
1657 |
utils.login(app, simple_user) |
|
1658 |
response = app.get(url, status=200) |
|
1659 |
assert "You have not granted organizational unit access to your account profile data." in response.text |
|
1660 | ||
1661 |
OU = get_ou_model() |
|
1662 |
ou1 = OU.objects.create(name='Orgunit1', slug='orgunit1') |
|
1663 |
ou2 = OU.objects.create(name='Orgunit2', slug='orgunit2') |
|
1664 |
ou3 = OU.objects.create(name='Orgunit3', slug='orgunit3') |
|
1665 | ||
1666 |
oidc_client.authorization_mode = OIDCClient.AUTHORIZATION_MODE_BY_OU |
|
1667 |
oidc_client.ou = ou1 |
|
1668 |
oidc_client.save() |
|
1669 | ||
1670 |
OIDCAuthorization.objects.create( |
|
1671 |
client=ou1, user=simple_user, scopes='openid', |
|
1672 |
expired=now() + datetime.timedelta(days=2)) |
|
1673 |
OIDCAuthorization.objects.create( |
|
1674 |
client=ou2, user=simple_user, scopes='openid profile', |
|
1675 |
expired=now() + datetime.timedelta(days=2)) |
|
1676 |
OIDCAuthorization.objects.create( |
|
1677 |
client=ou3, user=simple_user, scopes='openid profile email', |
|
1678 |
expired=now() + datetime.timedelta(days=2)) |
|
1679 |
# create a service-based authz that should not appear here |
|
1680 |
OIDCAuthorization.objects.create( |
|
1681 |
client=oidc_client, user=simple_user, scopes='openid profile email', |
|
1682 |
expired=now() + datetime.timedelta(days=2)) |
|
1683 | ||
1684 |
response = app.get(url, status=200) |
|
1685 |
assert "You have granted 3 organizationat units access to your account profile data." |
|
1686 |
assert len(response.html.find_all( |
|
1687 |
'button', {'class': 'authorized-oauth-ous--revoke-button'})) == 3 |
|
1688 | ||
1689 |
# revoke two |
|
1690 |
response = response.forms[0].submit() |
|
1691 |
response = response.follow() |
|
1692 |
assert len(response.html.find_all( |
|
1693 |
'button', {'class': 'authorized-oauth-ous--revoke-button'})) == 2 |
|
1694 |
response = response.forms[0].submit() |
|
1695 |
response = response.follow() |
|
1696 |
assert len(response.html.find_all( |
|
1697 |
'button', {'class': 'authorized-oauth-ous--revoke-button'})) == 1 |
|
1698 |
assert "You have granted one organizational unit access to your account profile data." in response.text |
tests/test_profile.py | ||
---|---|---|
231 | 231 |
reverse('delete_account') |
232 | 232 |
] |
233 | 233 | |
234 |
# oidc client defined for ou-based authz management |
|
235 |
client.authorization_mode = OIDCClient.AUTHORIZATION_MODE_BY_OU |
|
236 |
client.save() |
|
237 |
response = app.get(url, status=200) |
|
238 |
assert [x['href'] for x in response.html.find('div', {'id': 'a2-profile'}).find_all('a')] == [ |
|
239 |
reverse('email-change'), |
|
240 |
reverse('profile_edit'), |
|
241 |
reverse('authorized-oauth-ous'), |
|
242 |
reverse('delete_account') |
|
243 |
] |
|
244 | ||
234 | 245 |
# restore authorization mode |
235 | 246 |
client.authorization_mode = OIDCClient.AUTHORIZATION_MODE_BY_SERVICE |
236 | 247 |
client.save() |
237 |
- |