0001-idp_oidc-revoke-oidc-claims-authorization-45200.patch
src/authentic2/views.py | ||
---|---|---|
50 | 50 |
from django.http import Http404 |
51 | 51 |
from django.utils.http import urlsafe_base64_decode |
52 | 52 |
from django.views.generic.edit import CreateView |
53 | 53 |
from django.forms import CharField |
54 | 54 |
from django.http import HttpResponseBadRequest |
55 | 55 |
from django.template import loader |
56 | 56 | |
57 | 57 |
from authentic2.compat.misc import default_token_generator |
58 |
from authentic2_idp_oidc.models import OIDCAuthorization |
|
58 | 59 |
from . import (utils, app_settings, decorators, constants, |
59 | 60 |
models, cbv, hooks, validators) |
60 | 61 |
from .utils import switch_user |
61 | 62 |
from .a2_rbac.utils import get_default_ou |
62 | 63 |
from .a2_rbac.models import OrganizationalUnit as OU |
63 | 64 |
from .forms import ( |
64 | 65 |
passwords as passwords_forms, |
65 | 66 |
registration as registration_forms, |
... | ... | |
501 | 502 |
context.update({ |
502 | 503 |
'frontends_block': blocks, |
503 | 504 |
'frontends_block_by_id': blocks_by_id, |
504 | 505 |
'profile': profile, |
505 | 506 |
'attributes': attributes, |
506 | 507 |
'allow_account_deletion': app_settings.A2_REGISTRATION_CAN_DELETE_ACCOUNT, |
507 | 508 |
'allow_profile_edit': EditProfile.can_edit_profile(), |
508 | 509 |
'allow_email_change': app_settings.A2_PROFILE_CAN_CHANGE_EMAIL, |
510 |
'oidc_authorizations': OIDCAuthorization.objects.filter(user=self.request.user), |
|
509 | 511 |
# TODO: deprecated should be removed when publik-base-theme is updated |
510 | 512 |
'allow_password_change': utils.user_can_change_password(request=request), |
511 | 513 |
'federation_management': federation_management, |
512 | 514 |
}) |
513 | 515 |
hooks.call_hooks('modify_context_data', self, context) |
514 | 516 |
return context |
515 | 517 | |
516 | 518 |
profile = login_required(ProfileView.as_view()) |
src/authentic2_idp_oidc/templates/authentic2_idp_oidc/authorized_oauth_apps.html | ||
---|---|---|
1 |
{% extends "authentic2/base-page.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block content %} |
|
5 |
{% block oidc-authorized-oauth-apps-pre %}{% endblock %} |
|
6 |
<div id="oidc-authorized-oauth-apps" class="authorized-oauth-apps"> |
|
7 |
{% block oidc-authorized-oauth-apps-top %}{% endblock %} |
|
8 |
{% if authorized_oauth_apps|length_is:0 %} |
|
9 |
{% trans "You have no granted application access to your account." %} |
|
10 |
{% else %} |
|
11 |
<form method="post"> |
|
12 |
{% blocktrans with len=authorized_oauth_apps|length %} |
|
13 |
You have granted {{ len }} applications access to your account. |
|
14 |
{% endblocktrans %} |
|
15 |
{% csrf_token %} |
|
16 |
<input type="hidden" id="auth-id" name="auth_id" value=""> |
|
17 |
<input type="submit" value="{% trans 'Revoke all' %}"> |
|
18 |
</form> |
|
19 |
<table> |
|
20 |
<thead> |
|
21 |
<tr> |
|
22 |
<td>{% trans "Application" %}</td> |
|
23 |
<td>{% trans "Attributs" %}</td> |
|
24 |
<td>{% trans "From" %}</td> |
|
25 |
<td>{% trans "To" %}</td> |
|
26 |
<td></td> |
|
27 |
</tr> |
|
28 |
</thead> |
|
29 |
{% for auth in authorized_oauth_apps %} |
|
30 |
<tr> |
|
31 |
<td>{{ auth.client }}</td> |
|
32 |
<td>{{ auth.scopes }}</td> |
|
33 |
<td>{{ auth.created }}</td> |
|
34 |
<td>{{ auth.expired }}</td> |
|
35 |
<td> |
|
36 |
<form method="post"> |
|
37 |
{% csrf_token %} |
|
38 |
<input type="hidden" id="auth-id" name="auth_id" value="{{ auth.id }}"> |
|
39 |
<input type="submit" value="{% trans 'Revoke' %}"> |
|
40 |
</form> |
|
41 |
</td> |
|
42 |
</tr> |
|
43 |
{% endfor %} |
|
44 |
</table> |
|
45 |
{% endif %} |
|
46 |
{% block oidc-authorized-oauth-apps-bottom %}{% endblock %} |
|
47 |
</div> |
|
48 |
{% block oidc-authorized-oauth-apps-post %}{% endblock %} |
|
49 |
{% endblock %} |
src/authentic2_idp_oidc/urls.py | ||
---|---|---|
24 | 24 |
views.openid_configuration, |
25 | 25 |
name='oidc-openid-configuration'), |
26 | 26 |
url(r'^idp/oidc/certs/$', |
27 | 27 |
views.certs, |
28 | 28 |
name='oidc-certs'), |
29 | 29 |
url(r'^idp/oidc/authorize/$', |
30 | 30 |
views.authorize, |
31 | 31 |
name='oidc-authorize'), |
32 |
url(r'^idp/oidc/authorized_oauth_apps/$', |
|
33 |
views.authorized_oauth_apps, |
|
34 |
name='oidc-authorized-oauth-apps'), |
|
32 | 35 |
url(r'^idp/oidc/token/$', |
33 | 36 |
views.token, |
34 | 37 |
name='oidc-token'), |
35 | 38 |
url(r'^idp/oidc/user_info/$', |
36 | 39 |
views.user_info, |
37 | 40 |
name='oidc-user-info'), |
38 | 41 |
url(r'^idp/oidc/logout/$', |
39 | 42 |
views.logout, |
src/authentic2_idp_oidc/views.py | ||
---|---|---|
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 | 17 |
import logging |
18 | 18 |
import math |
19 | 19 |
import datetime |
20 | 20 |
import base64 |
21 | 21 |
import time |
22 | 22 | |
23 |
from django.http import (HttpResponse, HttpResponseNotAllowed, JsonResponse) |
|
23 |
from django.http import (HttpResponse, HttpResponseNotAllowed, HttpResponseRedirect, JsonResponse)
|
|
24 | 24 |
from django.urls import reverse |
25 | 25 |
from django.utils import six |
26 | 26 |
from django.utils.encoding import force_text |
27 | 27 |
from django.utils.timezone import now, utc |
28 | 28 |
from django.utils.http import urlencode |
29 | 29 |
from django.shortcuts import render |
30 | 30 |
from django.views.decorators.csrf import csrf_exempt |
31 |
from django.views.generic import TemplateView |
|
31 | 32 |
from django.contrib import messages |
32 | 33 |
from django.contrib.auth import authenticate |
34 |
from django.contrib.auth.decorators import login_required |
|
33 | 35 |
from django.conf import settings |
34 | 36 |
from django.utils.translation import ugettext as _ |
35 | 37 |
from ratelimit.utils import is_ratelimited |
36 | 38 | |
37 | 39 |
from authentic2 import app_settings as a2_app_settings |
38 | 40 |
from authentic2.compat.misc import Base64Error |
39 | 41 |
from authentic2.decorators import setting_enabled |
40 | 42 |
from authentic2.exponential_retry_timeout import ExponentialRetryTimeout |
... | ... | |
361 | 363 |
}) |
362 | 364 |
# query is transfered through the hashtag |
363 | 365 |
response = redirect(request, redirect_uri + '#%s' % urlencode(params), resolve=False) |
364 | 366 |
hooks.call_hooks('event', name='sso-success', idp='oidc', service=client, user=request.user) |
365 | 367 |
utils.add_oidc_session(request, client) |
366 | 368 |
return response |
367 | 369 | |
368 | 370 | |
371 |
class AuthorizedOauthAppsView(TemplateView): |
|
372 |
template_name = 'authentic2_idp_oidc/authorized_oauth_apps.html' |
|
373 |
title = _('Granted Applications') |
|
374 | ||
375 |
def dispatch(self, request, *args, **kwargs): |
|
376 |
return login_required( |
|
377 |
super(AuthorizedOauthAppsView, self).dispatch)(request, *args, **kwargs) |
|
378 | ||
379 |
def get_context_data(self, **kwargs): |
|
380 |
context = super(AuthorizedOauthAppsView, self).get_context_data(**kwargs) |
|
381 |
context['authorized_oauth_apps'] = models.OIDCAuthorization.objects.filter( |
|
382 |
user=self.request.user) |
|
383 |
return context |
|
384 | ||
385 |
def post(self, request, *args, **kwargs): |
|
386 |
if request.user: |
|
387 |
qs = models.OIDCAuthorization.objects.filter(user=request.user) |
|
388 |
auth_id = request.POST.get('auth_id') |
|
389 |
if auth_id: |
|
390 |
qs = qs.filter(id=auth_id) |
|
391 |
qs.delete() |
|
392 |
return HttpResponseRedirect(reverse('oidc-authorized-oauth-apps')) |
|
393 | ||
394 | ||
395 |
authorized_oauth_apps = AuthorizedOauthAppsView.as_view() |
|
396 | ||
397 | ||
369 | 398 |
def authenticate_client(request, client=None): |
370 | 399 |
'''Authenticate client on the token endpoint''' |
371 | 400 | |
372 | 401 |
if 'HTTP_AUTHORIZATION' in request.META: |
373 | 402 |
authorization = request.META['HTTP_AUTHORIZATION'].split() |
374 | 403 |
if authorization[0] != 'Basic' or len(authorization) != 2: |
375 | 404 |
return None |
376 | 405 |
try: |
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_apps_views(app, oidc_client, simple_user): |
|
1610 |
url = make_url('oidc-authorized-oauth-apps') |
|
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 no granted application access to your account." 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 applications access to your account." in response.text |
|
1630 |
assert len(response.html.find_all('form')) == 4 |
|
1631 | ||
1632 |
revoke_all_form = response.html.find_all('form')[0] |
|
1633 |
assert revoke_all_form.find('input', {"type": "submit"}).attrs['value'] == 'Revoke all' |
|
1634 |
assert revoke_all_form.find('input', {"name": "auth_id"}).attrs['value'] == "" |
|
1635 | ||
1636 |
revoke_one_forms = response.html.find_all('form')[1:] |
|
1637 |
assert [x.find('input', {"type": "submit"}).attrs['value'] |
|
1638 |
for x in revoke_one_forms] == ['Revoke'] * 3 |
|
1639 | ||
1640 |
# revoke one |
|
1641 |
response = response.forms[1].submit() |
|
1642 |
response = response.follow() |
|
1643 |
assert "You have granted 2 applications access to your account." in response.text |
|
1644 |
assert len(response.html.find_all('form')) == 3 |
|
1645 | ||
1646 |
# revoke all |
|
1647 |
response = response.forms[0].submit() |
|
1648 |
response = response.follow() |
|
1649 |
assert "You have no granted application access to your account." in response.text |
|
1607 |
- |