profile_views: add a profil page to manage authorized oauth services (#45200)

This commit is contained in:
Nicolas Roche 2020-07-16 17:31:03 +02:00
parent 5db2ca57e0
commit 1a3bd4bb05
7 changed files with 172 additions and 0 deletions

View File

@ -127,6 +127,9 @@ default_settings = dict(
A2_PROFILE_CAN_MANAGE_FEDERATION=Setting(
default=True,
definition='Can user manage its federations'),
A2_PROFILE_CAN_MANAGE_SERVICE_AUTHORIZATIONS=Setting(
default=True,
definition='Allow user to revoke granted services access to its account profile data'),
A2_PROFILE_DISPLAY_EMPTY_FIELDS=Setting(
default=False,
definition='Include empty fields in profile view'),

View File

@ -37,6 +37,9 @@
{% if allow_profile_edit %}
<p><a href="{% url 'profile_edit' %}">{% trans "Edit account data" %}</a></p>
{% endif %}
{% if allow_authorization_management %}
<p><a href="{% url 'authorized-oauth-services' %}">{% trans "Manage service authorizations" %}</a></p>
{% endif %}
{% if allow_account_deletion %}
<p><a href="{% url 'delete_account' %}">{% trans "Delete account" %}</a></p>
{% endif %}

View File

@ -0,0 +1,67 @@
{% extends "authentic2/base-page.html" %}
{% load i18n %}
{% block page-title %}
{{ block.super }} - {{ view.title }}
{% endblock %}
{% block breadcrumb %}
{{ block.super }}
<a href="..">{% trans "Your account" %}</a>
<a href="">{{ view.title }}</a>
{% endblock %}
{% block content %}
{% block oidc-authorized-oauth-services-pre %}{% endblock %}
<div class="authorized-oauth-services">
{% block oidc-authorized-oauth-services-top %}
<p class="authorized-oauth-services--top">
{% if authorized_oauth_services|length_is:0 %}
{% trans "You have not granted service access to your account profile data." %}
{% else %}
{% blocktrans count counter=authorized_oauth_services|length %}
You have granted one service access to your account profile data.
{% plural %}
You have granted {{ counter }} services access to your account profile data.
{% endblocktrans %}
{% endif %}
</p>
{% endblock %}
<ul class="authorized-oauth-services--list">
{% for auth in authorized_oauth_services %}
<li class="authorized-oauth-services--item">
<form method="post" class="authorized-oauth-services--form">
{% csrf_token %}
{% block oidc-authorized-oauth-service %}
<div class="authorized-oauth-services--infos">
{% block oidc-authorized-oauth-service-top %}{% endblock %}
<span class="authorized-oauth-services--client">
{{ auth.client }}
</span>
<span class="authorized-oauth-services--dates">
<span class="authorized-oauth-services-dates--since">
<span class="label">{% trans "Allowed since:" %}</span>
<span class="time">{{ auth.created }}</span>
</span>
<span class="authorized-oauth-services--separator">/</span>
<span class="authorized-oauth-services--expired">
<span class="label">{% trans "Expire on:" %}</span>
<span class="time">{{ auth.expired }}</span>
</span>
</span>
</div>
<div class="authorized-oauth-services--actions">
<input type="hidden" id="auth-id" name="auth_id" value="{{ auth.id }}">
<button class="authorized-oauth-services--revoke-button">{% trans 'Revoke' %}</button>
</div>
{% block oidc-authorized-oauth-service-bottom %}{% endblock %}
{% endblock %}
</form>
</li>
{% endfor %}
</ul>
</table>
{% block oidc-authorized-oauth-services-bottom %}{% endblock %}
</div>
{% block oidc-authorized-oauth-services-post %}{% endblock %}
{% endblock %}

View File

@ -69,6 +69,9 @@ accounts_urlpatterns = [
url(r'^change-email/verify/$',
views.email_change_verify,
name='email-change-verify'),
url(r'^authorizations/$',
login_required(views.authorized_oauth_services),
name='authorized-oauth-services'),
url(r'^$',
views.profile,
name='account_management'),

View File

@ -506,6 +506,9 @@ class ProfileView(cbv.TemplateNamesMixin, TemplateView):
'allow_account_deletion': app_settings.A2_REGISTRATION_CAN_DELETE_ACCOUNT,
'allow_profile_edit': EditProfile.can_edit_profile(),
'allow_email_change': app_settings.A2_PROFILE_CAN_CHANGE_EMAIL,
'allow_authorization_management': (
app_settings.A2_PROFILE_CAN_MANAGE_SERVICE_AUTHORIZATIONS
and 'authentic2_idp_oidc' in settings.INSTALLED_APPS),
# TODO: deprecated should be removed when publik-base-theme is updated
'allow_password_change': utils.user_can_change_password(request=request),
'federation_management': federation_management,
@ -1269,3 +1272,29 @@ class SuView(View):
return utils.simulate_authentication(request, user, 'su')
su = SuView.as_view()
class AuthorizedOauthServicesView(TemplateView):
template_name = 'authentic2/accounts_authorized_oauth_services.html'
title = _('Consent Management')
def get_context_data(self, **kwargs):
from authentic2_idp_oidc.models import OIDCAuthorization
context = super(AuthorizedOauthServicesView, self).get_context_data(**kwargs)
context['authorized_oauth_services'] = OIDCAuthorization.objects.filter(
user=self.request.user)
return context
def post(self, request, *args, **kwargs):
from authentic2_idp_oidc.models import OIDCAuthorization
qs = OIDCAuthorization.objects.filter(user=request.user)
auth_id = request.POST.get('auth_id')
if auth_id:
qs = qs.filter(id=auth_id)
qs.delete()
return HttpResponseRedirect(reverse('authorized-oauth-services'))
authorized_oauth_services = AuthorizedOauthServicesView.as_view()

View File

@ -1604,3 +1604,39 @@ def test_oidc_client_clean():
OIDCClient(
redirect_uris='https://example.com/ https://example2.com/',
sector_identifier_uri='https://example.com/').clean()
def test_oidc_authorized_oauth_services_view(app, oidc_client, simple_user):
url = make_url('authorized-oauth-services')
response = app.get(url, status=302)
assert '/login/' in response.location
utils.login(app, simple_user)
response = app.get(url, status=200)
assert "You have not granted service access to your account profile data." in response.text
OIDCAuthorization.objects.create(
client=oidc_client, user=simple_user, scopes='openid',
expired=now() + datetime.timedelta(days=2))
OIDCAuthorization.objects.create(
client=oidc_client, user=simple_user, scopes='openid profile',
expired=now() + datetime.timedelta(days=2))
OIDCAuthorization.objects.create(
client=oidc_client, user=simple_user, scopes='openid profile email',
expired=now() + datetime.timedelta(days=2))
response = app.get(url, status=200)
assert "You have granted 3 services access to your account profile data."
assert len(response.html.find_all(
'button', {'class': 'authorized-oauth-services--revoke-button'})) == 3
# revoke two
response = response.forms[0].submit()
response = response.follow()
assert len(response.html.find_all(
'button', {'class': 'authorized-oauth-services--revoke-button'})) == 2
response = response.forms[0].submit()
response = response.follow()
assert len(response.html.find_all(
'button', {'class': 'authorized-oauth-services--revoke-button'})) == 1
assert "You have granted one service access to your account profile data." in response.text

View File

@ -192,3 +192,34 @@ def test_account_edit_locked_title(app, simple_user):
response = app.get(url, status=200)
assert len(response.pyquery('input[type="radio"][name="edit-profile-title"]')) == 0
assert len(response.pyquery('input[type="text"][name="edit-profile-title@disabled"][readonly]')) == 1
def test_acount_view(app, simple_user, settings):
utils.login(app, simple_user)
url = reverse('account_management')
response = app.get(url, status=200)
assert [x['href'] for x in response.html.find('div', {'id': 'a2-profile'}).find_all('a')] == [
reverse('email-change'),
reverse('profile_edit'),
reverse('authorized-oauth-services'),
reverse('delete_account')
]
settings.INSTALLED_APPS = tuple(x for x in settings.INSTALLED_APPS if x != 'authentic2_idp_oidc')
url = reverse('account_management')
response = app.get(url, status=200)
assert [x['href'] for x in response.html.find('div', {'id': 'a2-profile'}).find_all('a')] == [
reverse('email-change'),
reverse('profile_edit'),
reverse('delete_account')
]
settings.INSTALLED_APPS += ('authentic2_idp_oidc',)
settings.A2_PROFILE_CAN_CHANGE_EMAIL = False
settings.A2_PROFILE_CAN_MANAGE_SERVICE_AUTHORIZATIONS = False
settings.A2_REGISTRATION_CAN_DELETE_ACCOUNT = False
url = reverse('account_management')
response = app.get(url, status=200)
assert [x['href'] for x in response.html.find('div', {'id': 'a2-profile'}).find_all('a')] == [
reverse('profile_edit'),
]