forked from Github-Mirrors/canaille
Merge branch 'issue-69-preconsent-details' into 'main'
Issue 69 preconsent details Closes #69 See merge request yaal/canaille!104
This commit is contained in:
commit
8b7a72b26c
9 changed files with 419 additions and 58 deletions
|
@ -8,6 +8,9 @@ Added
|
|||
|
||||
- Display TOS and policy URI on the consent list page. :pr:`102`
|
||||
- Admin token deletion :pr:`100` :pr:`101`
|
||||
- Revoked consents can be restored. :pr:`103`
|
||||
- Pre-consented clients are displayed in the user consent list,
|
||||
and their consents can be revoked. :issue:`69` :pr:`103`
|
||||
|
||||
Fixed
|
||||
*****
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import datetime
|
||||
import uuid
|
||||
|
||||
from canaille.flaskutils import user_needed
|
||||
from canaille.oidc.models import Client
|
||||
from canaille.oidc.models import Consent
|
||||
|
@ -5,7 +8,7 @@ from flask import Blueprint
|
|||
from flask import flash
|
||||
from flask import redirect
|
||||
from flask import url_for
|
||||
from flask_babel import gettext
|
||||
from flask_babel import gettext as _
|
||||
from flask_themer import render_template
|
||||
|
||||
from .utils import SCOPE_DETAILS
|
||||
|
@ -18,9 +21,14 @@ bp = Blueprint("consents", __name__, url_prefix="/consent")
|
|||
@user_needed()
|
||||
def consents(user):
|
||||
consents = Consent.filter(subject=user.dn)
|
||||
consents = [c for c in consents if not c.revokation_date]
|
||||
client_dns = list({t.client for t in consents})
|
||||
client_dns = {t.client for t in consents}
|
||||
clients = {dn: Client.get(dn) for dn in client_dns}
|
||||
preconsented = [
|
||||
client
|
||||
for client in Client.filter()
|
||||
if client.preconsent and client.dn not in clients
|
||||
]
|
||||
|
||||
return render_template(
|
||||
"oidc/user/consent_list.html",
|
||||
consents=consents,
|
||||
|
@ -28,19 +36,69 @@ def consents(user):
|
|||
menuitem="consents",
|
||||
scope_details=SCOPE_DETAILS,
|
||||
ignored_scopes=["openid"],
|
||||
preconsented=preconsented,
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/delete/<consent_id>")
|
||||
@bp.route("/revoke/<consent_id>")
|
||||
@user_needed()
|
||||
def delete(user, consent_id):
|
||||
def revoke(user, consent_id):
|
||||
consent = Consent.get(consent_id)
|
||||
|
||||
if not consent or consent.subject != user.dn:
|
||||
flash(gettext("Could not delete this access"), "error")
|
||||
flash(_("Could not revoke this access"), "error")
|
||||
|
||||
elif consent.revokation_date:
|
||||
flash(_("The access is already revoked"), "error")
|
||||
|
||||
else:
|
||||
consent.revoke()
|
||||
flash(gettext("The access has been revoked"), "success")
|
||||
flash(_("The access has been revoked"), "success")
|
||||
|
||||
return redirect(url_for("oidc.consents.consents"))
|
||||
|
||||
|
||||
@bp.route("/restore/<consent_id>")
|
||||
@user_needed()
|
||||
def restore(user, consent_id):
|
||||
consent = Consent.get(consent_id)
|
||||
|
||||
if not consent or consent.subject != user.dn:
|
||||
flash(_("Could not restore this access"), "error")
|
||||
|
||||
elif not consent.revokation_date:
|
||||
flash(_("The access is not revoked"), "error")
|
||||
|
||||
else:
|
||||
consent.restore()
|
||||
if not consent.issue_date:
|
||||
consent.issue_date = datetime.datetime.now()
|
||||
consent.save()
|
||||
flash(_("The access has been restored"), "success")
|
||||
|
||||
return redirect(url_for("oidc.consents.consents"))
|
||||
|
||||
|
||||
@bp.route("/revoke-preconsent/<client_id>")
|
||||
@user_needed()
|
||||
def revoke_preconsent(user, client_id):
|
||||
client = Client.get(client_id)
|
||||
|
||||
if not client or not client.preconsent:
|
||||
flash(_("Could not revoke this access"), "error")
|
||||
|
||||
elif consent := Consent.get(client=client.dn, subject=user.dn):
|
||||
return redirect(url_for("oidc.consents.revoke", consent_id=consent.cn[0]))
|
||||
|
||||
else:
|
||||
consent = Consent(
|
||||
cn=str(uuid.uuid4()),
|
||||
client=client.dn,
|
||||
subject=user.dn,
|
||||
scope=client.scope,
|
||||
)
|
||||
consent.revoke()
|
||||
consent.save()
|
||||
flash(_("The access has been revoked"), "success")
|
||||
|
||||
return redirect(url_for("oidc.consents.consents"))
|
||||
|
|
|
@ -93,12 +93,13 @@ def authorize():
|
|||
client=client.dn,
|
||||
subject=user.dn,
|
||||
)
|
||||
consents = [c for c in consents if not c.revokation_date]
|
||||
consent = consents[0] if consents else None
|
||||
|
||||
if request.method == "GET":
|
||||
if client.preconsent or (
|
||||
consent and all(scope in set(consent.scope) for scope in scopes)
|
||||
if (
|
||||
(client.preconsent and (not consent or not consent.revoked))
|
||||
or (consent and all(scope in set(consent.scope) for scope in scopes))
|
||||
and not consent.revoked
|
||||
):
|
||||
return authorization.create_authorization_response(grant_user=user.dn)
|
||||
|
||||
|
@ -137,6 +138,8 @@ def authorize():
|
|||
grant_user = user.dn
|
||||
|
||||
if consent:
|
||||
if consent.revoked:
|
||||
consent.restore()
|
||||
consent.scope = client.get_allowed_scope(
|
||||
list(set(scopes + consents[0].scope))
|
||||
).split(" ")
|
||||
|
|
|
@ -222,6 +222,10 @@ class Consent(LDAPObject):
|
|||
"revokation_date": "oauthRevokationDate",
|
||||
}
|
||||
|
||||
@property
|
||||
def revoked(self):
|
||||
return bool(self.revokation_date)
|
||||
|
||||
def revoke(self):
|
||||
self.revokation_date = datetime.datetime.now()
|
||||
self.save()
|
||||
|
@ -230,10 +234,11 @@ class Consent(LDAPObject):
|
|||
oauthClient=self.client,
|
||||
oauthSubject=self.subject,
|
||||
)
|
||||
tokens = [token for token in tokens if not token.revoked]
|
||||
for t in tokens:
|
||||
different_scope = any(scope not in t.scope[0] for scope in self.scope)
|
||||
if t.revoked or different_scope:
|
||||
continue
|
||||
|
||||
t.revokation_date = self.revokation_date
|
||||
t.save()
|
||||
|
||||
def restore(self):
|
||||
self.revokation_date = None
|
||||
self.save()
|
||||
|
|
|
@ -36,12 +36,20 @@
|
|||
{% else %}
|
||||
<div class="header">{{ client.client_name }}</div>
|
||||
{% endif %}
|
||||
{% if consent.issue_date %}
|
||||
<div class="meta">{% trans %}From:{% endtrans %} {{ consent.issue_date.strftime("%d/%m/%Y %H:%M:%S") }}</div>
|
||||
{% endif %}
|
||||
{% if consent.revokation_date %}
|
||||
<div class="meta">{% trans %}Revoked:{% endtrans %} {{ consent.revokation_date.strftime("%d/%m/%Y %H:%M:%S") }}</div>
|
||||
{% endif %}
|
||||
<div class="description">
|
||||
<p>{% trans %}Has access to:{% endtrans %}</p>
|
||||
<p>
|
||||
{% if consent.revokation_date %}
|
||||
{% trans %}Had access to:{% endtrans %}
|
||||
{% else %}
|
||||
{% trans %}Has access to:{% endtrans %}
|
||||
{% endif %}
|
||||
</p>
|
||||
<div class="ui list">
|
||||
{% for scope in consent.scope %}
|
||||
{% if scope not in ignored_scopes %}
|
||||
|
@ -74,10 +82,17 @@
|
|||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<a class="ui bottom attached button" href="{{ url_for('oidc.consents.delete', consent_id=consent.cn[0] ) }}">
|
||||
<i class="remove icon"></i>
|
||||
{% trans %}Remove access{% endtrans %}
|
||||
{% if consent.revokation_date %}
|
||||
<a class="ui bottom attached button" href="{{ url_for('oidc.consents.restore', consent_id=consent.cn[0] ) }}">
|
||||
<i class="reply icon"></i>
|
||||
{% trans %}Restore access{% endtrans %}
|
||||
</a>
|
||||
{% else %}
|
||||
<a class="ui bottom attached button" href="{{ url_for('oidc.consents.revoke', consent_id=consent.cn[0] ) }}">
|
||||
<i class="remove icon"></i>
|
||||
{% trans %}Revoke access{% endtrans %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
@ -90,5 +105,72 @@
|
|||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if preconsented %}
|
||||
<h2 class="ui center aligned header">
|
||||
<div class="content">
|
||||
{{ _("Pre-authorized applications") }}
|
||||
</div>
|
||||
<div class="sub header">
|
||||
{% trans %}Those applications automatically have authorizations to access you data.{% endtrans %}
|
||||
</div>
|
||||
</h2>
|
||||
<div class="ui centered cards">
|
||||
{% for client in preconsented %}
|
||||
<div class="ui card">
|
||||
<div class="content">
|
||||
{% if client.logo_uri %}
|
||||
<img class="right floated mini ui image" src="{{ client.logo_uri }}">
|
||||
{% endif %}
|
||||
{% if client.client_uri %}
|
||||
<a href="{{ client.client_uri }}" class="header">{{ client.client_name }}</a>
|
||||
{% else %}
|
||||
<div class="header">{{ client.client_name }}</div>
|
||||
{% endif %}
|
||||
<div class="description">
|
||||
<p>
|
||||
{% trans %}Has access to:{% endtrans %}
|
||||
</p>
|
||||
<div class="ui list">
|
||||
{% for scope in client.scope %}
|
||||
{% if scope not in ignored_scopes %}
|
||||
{% if scope not in scope_details %}
|
||||
<div class="item" title="{{ scope }}">{{ scope }}</div>
|
||||
{% else %}
|
||||
<div class="item" title="{{ scope }}">
|
||||
<i class="{{ scope_details[scope][0] }} icon"></i>
|
||||
<div class="content">{{ scope_details[scope][1] }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if client.tos_uri or client.policy_uri %}
|
||||
<div class="extra content">
|
||||
{% if client.policy_uri %}
|
||||
<span class="right floated">
|
||||
<i class="mask icon"></i>
|
||||
<a href="{{ client.policy_uri }}">{% trans %}Policy{% endtrans %}</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if client.tos_uri %}
|
||||
<span>
|
||||
<i class="file signature icon"></i>
|
||||
<a href="{{ client.tos_uri }}">{% trans %}Terms of service{% endtrans %}</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<a class="ui bottom attached button" href="{{ url_for('oidc.consents.revoke_preconsent', client_id=client.client_id ) }}">
|
||||
<i class="remove icon"></i>
|
||||
{% trans %}Revoke access{% endtrans %}
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2023-02-10 09:52+0100\n"
|
||||
"POT-Creation-Date: 2023-02-14 21:56+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
@ -388,27 +388,43 @@ msgstr ""
|
|||
msgid "The client has been deleted."
|
||||
msgstr ""
|
||||
|
||||
#: canaille/oidc/consents.py:40
|
||||
msgid "Could not delete this access"
|
||||
#: canaille/oidc/consents.py:49 canaille/oidc/consents.py:88
|
||||
msgid "Could not revoke this access"
|
||||
msgstr ""
|
||||
|
||||
#: canaille/oidc/consents.py:44
|
||||
#: canaille/oidc/consents.py:52
|
||||
msgid "The access is already revoked"
|
||||
msgstr ""
|
||||
|
||||
#: canaille/oidc/consents.py:56 canaille/oidc/consents.py:102
|
||||
msgid "The access has been revoked"
|
||||
msgstr ""
|
||||
|
||||
#: canaille/oidc/endpoints.py:130
|
||||
#: canaille/oidc/consents.py:67
|
||||
msgid "Could not restore this access"
|
||||
msgstr ""
|
||||
|
||||
#: canaille/oidc/consents.py:70
|
||||
msgid "The access is not revoked"
|
||||
msgstr ""
|
||||
|
||||
#: canaille/oidc/consents.py:77
|
||||
msgid "The access has been restored"
|
||||
msgstr ""
|
||||
|
||||
#: canaille/oidc/endpoints.py:131
|
||||
msgid "You have been successfully logged out."
|
||||
msgstr ""
|
||||
|
||||
#: canaille/oidc/endpoints.py:329
|
||||
#: canaille/oidc/endpoints.py:332
|
||||
msgid "You have been disconnected"
|
||||
msgstr ""
|
||||
|
||||
#: canaille/oidc/endpoints.py:337
|
||||
#: canaille/oidc/endpoints.py:340
|
||||
msgid "An error happened during the logout"
|
||||
msgstr ""
|
||||
|
||||
#: canaille/oidc/endpoints.py:349
|
||||
#: canaille/oidc/endpoints.py:352
|
||||
msgid "You have not been disconnected"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1200,26 +1216,54 @@ msgstr ""
|
|||
msgid "Consult and revoke the authorization you gave to websites."
|
||||
msgstr ""
|
||||
|
||||
#: canaille/templates/oidc/user/consent_list.html:39
|
||||
#: canaille/templates/oidc/user/consent_list.html:40
|
||||
msgid "From:"
|
||||
msgstr ""
|
||||
|
||||
#: canaille/templates/oidc/user/consent_list.html:41
|
||||
#: canaille/templates/oidc/user/consent_list.html:43
|
||||
msgid "Revoked:"
|
||||
msgstr ""
|
||||
|
||||
#: canaille/templates/oidc/user/consent_list.html:44
|
||||
#: canaille/templates/oidc/user/consent_list.html:48
|
||||
msgid "Had access to:"
|
||||
msgstr ""
|
||||
|
||||
#: canaille/templates/oidc/user/consent_list.html:50
|
||||
#: canaille/templates/oidc/user/consent_list.html:132
|
||||
msgid "Has access to:"
|
||||
msgstr ""
|
||||
|
||||
#: canaille/templates/oidc/user/consent_list.html:63
|
||||
msgid "Remove access"
|
||||
#: canaille/templates/oidc/user/consent_list.html:74
|
||||
#: canaille/templates/oidc/user/consent_list.html:155
|
||||
msgid "Policy"
|
||||
msgstr ""
|
||||
|
||||
#: canaille/templates/oidc/user/consent_list.html:72
|
||||
#: canaille/templates/oidc/user/consent_list.html:80
|
||||
#: canaille/templates/oidc/user/consent_list.html:161
|
||||
msgid "Terms of service"
|
||||
msgstr ""
|
||||
|
||||
#: canaille/templates/oidc/user/consent_list.html:88
|
||||
msgid "Restore access"
|
||||
msgstr ""
|
||||
|
||||
#: canaille/templates/oidc/user/consent_list.html:93
|
||||
#: canaille/templates/oidc/user/consent_list.html:168
|
||||
msgid "Revoke access"
|
||||
msgstr ""
|
||||
|
||||
#: canaille/templates/oidc/user/consent_list.html:103
|
||||
msgid "You did not authorize applications yet."
|
||||
msgstr ""
|
||||
|
||||
#: canaille/templates/oidc/user/consent_list.html:112
|
||||
msgid "Pre-authorized applications"
|
||||
msgstr ""
|
||||
|
||||
#: canaille/templates/oidc/user/consent_list.html:115
|
||||
msgid "Those applications automatically have authorizations to access you data."
|
||||
msgstr ""
|
||||
|
||||
#: canaille/templates/oidc/user/logout.html:9
|
||||
#: canaille/themes/default/base.html:90
|
||||
msgid "Log out"
|
||||
|
|
|
@ -36,7 +36,7 @@ Then you have access to:
|
|||
|
||||
- A canaille server at http://localhost:5000
|
||||
- A dummy client at http://localhost:5001
|
||||
- Another dummy client at http://localhost:5002
|
||||
- Another dummy client at http://localhost:5002, for which consent is already granted for users
|
||||
|
||||
The canaille server has some default users:
|
||||
|
||||
|
|
|
@ -45,3 +45,4 @@ oauthResponseType: code
|
|||
oauthResponseType: id_token
|
||||
oauthTokenEndpointAuthMethod: client_secret_basic
|
||||
oauthAudience: oauthClientID=gn4yFN7GDykL7QP8v8gS9YfV,ou=clients,ou=oauth,dc=mydomain,dc=tld
|
||||
oauthPreconsent: TRUE
|
||||
|
|
|
@ -1,52 +1,217 @@
|
|||
import datetime
|
||||
from urllib.parse import parse_qs
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
from canaille.oidc.models import Consent
|
||||
from canaille.oidc.models import Token
|
||||
|
||||
from . import client_credentials
|
||||
|
||||
|
||||
def test_no_logged_no_access(testclient):
|
||||
testclient.get("/consent", status=403)
|
||||
|
||||
|
||||
def test_delete(testclient, client, consent, logged_user, token):
|
||||
def test_revokation(testclient, client, consent, logged_user, token):
|
||||
res = testclient.get("/consent", status=200)
|
||||
assert client.client_name in res.text
|
||||
assert "Revoke access" in res.text
|
||||
assert "Restore access" not in res.text
|
||||
assert not consent.revoked
|
||||
assert not token.revoked
|
||||
|
||||
res = testclient.get(f"/consent/delete/{consent.cn[0]}", status=302)
|
||||
res = testclient.get(f"/consent/revoke/{consent.cn[0]}", status=302)
|
||||
assert ("success", "The access has been revoked") in res.flashes
|
||||
res = res.follow(status=200)
|
||||
assert client.client_name not in res.text
|
||||
assert "Revoke access" not in res.text
|
||||
assert "Restore access" in res.text
|
||||
|
||||
consent.reload()
|
||||
assert consent.revoked
|
||||
token.reload()
|
||||
assert token.revoked
|
||||
|
||||
|
||||
def test_delete_token_already_revoked(testclient, client, consent, logged_user, token):
|
||||
revokation_date = datetime.datetime.utcnow().replace(
|
||||
microsecond=0
|
||||
) - datetime.timedelta(days=7)
|
||||
token.revokation_date = revokation_date
|
||||
token.save()
|
||||
def test_revokation_already_revoked(testclient, client, consent, logged_user):
|
||||
consent.revoke()
|
||||
|
||||
token.reload()
|
||||
assert token.revoked
|
||||
assert token.revokation_date == revokation_date
|
||||
consent.reload()
|
||||
assert consent.revoked
|
||||
|
||||
res = testclient.get(f"/consent/delete/{consent.cn[0]}", status=302)
|
||||
assert ("success", "The access has been revoked") in res.flashes
|
||||
res = testclient.get(f"/consent/revoke/{consent.cn[0]}", status=302)
|
||||
assert ("error", "The access is already revoked") in res.flashes
|
||||
res = res.follow(status=200)
|
||||
assert client.client_name not in res.text
|
||||
|
||||
consent.reload()
|
||||
assert consent.revoked
|
||||
|
||||
|
||||
def test_restoration(testclient, client, consent, logged_user, token):
|
||||
consent.revoke()
|
||||
|
||||
consent.reload()
|
||||
assert consent.revoked
|
||||
token.reload()
|
||||
assert token.revoked
|
||||
|
||||
res = testclient.get(f"/consent/restore/{consent.cn[0]}", status=302)
|
||||
assert ("success", "The access has been restored") in res.flashes
|
||||
res = res.follow(status=200)
|
||||
|
||||
consent.reload()
|
||||
assert not consent.revoked
|
||||
token.reload()
|
||||
assert token.revoked
|
||||
assert token.revokation_date == revokation_date
|
||||
|
||||
|
||||
def test_invalid_consent_delete(testclient, client, logged_user):
|
||||
res = testclient.get(f"/consent/delete/invalid", status=302)
|
||||
def test_restoration_already_restored(testclient, client, consent, logged_user, token):
|
||||
assert not consent.revoked
|
||||
|
||||
res = testclient.get(f"/consent/restore/{consent.cn[0]}", status=302)
|
||||
assert ("error", "The access is not revoked") in res.flashes
|
||||
res = res.follow(status=200)
|
||||
|
||||
|
||||
def test_invalid_consent_revokation(testclient, client, logged_user):
|
||||
res = testclient.get(f"/consent/revoke/invalid", status=302)
|
||||
assert ("success", "The access has been revoked") not in res.flashes
|
||||
assert ("error", "Could not delete this access") in res.flashes
|
||||
assert ("error", "Could not revoke this access") in res.flashes
|
||||
|
||||
|
||||
def test_someone_else_consent_delete(testclient, client, consent, logged_moderator):
|
||||
res = testclient.get(f"/consent/delete/{consent.cn[0]}", status=302)
|
||||
def test_someone_else_consent_revokation(testclient, client, consent, logged_moderator):
|
||||
res = testclient.get(f"/consent/revoke/{consent.cn[0]}", status=302)
|
||||
assert ("success", "The access has been revoked") not in res.flashes
|
||||
assert ("error", "Could not delete this access") in res.flashes
|
||||
assert ("error", "Could not revoke this access") in res.flashes
|
||||
|
||||
|
||||
def test_invalid_consent_restoration(testclient, client, logged_user):
|
||||
res = testclient.get(f"/consent/restore/invalid", status=302)
|
||||
assert ("success", "The access has been restored") not in res.flashes
|
||||
assert ("error", "Could not restore this access") in res.flashes
|
||||
|
||||
|
||||
def test_someone_else_consent_restoration(
|
||||
testclient, client, consent, logged_moderator
|
||||
):
|
||||
res = testclient.get(f"/consent/restore/{consent.cn[0]}", status=302)
|
||||
assert ("success", "The access has been restore") not in res.flashes
|
||||
assert ("error", "Could not restore this access") in res.flashes
|
||||
|
||||
|
||||
def test_oidc_authorization_after_revokation(
|
||||
testclient, logged_user, client, keypair, consent
|
||||
):
|
||||
consent.revoke()
|
||||
|
||||
consent.reload()
|
||||
assert consent.revoked
|
||||
|
||||
res = testclient.get(
|
||||
"/oauth/authorize",
|
||||
params=dict(
|
||||
response_type="code",
|
||||
client_id=client.client_id,
|
||||
scope="openid profile",
|
||||
nonce="somenonce",
|
||||
),
|
||||
status=200,
|
||||
)
|
||||
|
||||
res = res.form.submit(name="answer", value="accept", status=302)
|
||||
|
||||
Consent.all()
|
||||
consents = Consent.filter(client=client.dn, subject=logged_user.dn)
|
||||
assert consents[0].dn == consent.dn
|
||||
consent.reload()
|
||||
assert not consent.revoked
|
||||
|
||||
params = parse_qs(urlsplit(res.location).query)
|
||||
code = params["code"][0]
|
||||
res = testclient.post(
|
||||
"/oauth/token",
|
||||
params=dict(
|
||||
grant_type="authorization_code",
|
||||
code=code,
|
||||
scope="openid profile",
|
||||
redirect_uri=client.redirect_uris[0],
|
||||
),
|
||||
headers={"Authorization": f"Basic {client_credentials(client)}"},
|
||||
status=200,
|
||||
)
|
||||
|
||||
access_token = res.json["access_token"]
|
||||
token = Token.get(access_token=access_token)
|
||||
assert token.client == client.dn
|
||||
assert token.subject == logged_user.dn
|
||||
|
||||
|
||||
def test_preconsented_client_appears_in_consent_list(testclient, client, logged_user):
|
||||
assert not client.preconsent
|
||||
res = testclient.get("/consent")
|
||||
assert client.client_name not in res.text
|
||||
|
||||
client.preconsent = True
|
||||
client.save()
|
||||
|
||||
res = testclient.get("/consent")
|
||||
assert client.client_name in res.text
|
||||
|
||||
|
||||
def test_revoke_preconsented_client(testclient, client, logged_user, token):
|
||||
client.preconsent = True
|
||||
client.save()
|
||||
assert not Consent.get()
|
||||
assert not token.revoked
|
||||
|
||||
res = testclient.get(f"/consent/revoke-preconsent/{client.client_id}", status=302)
|
||||
assert ("success", "The access has been revoked") in res.flashes
|
||||
|
||||
consent = Consent.get()
|
||||
assert consent.client == client.dn
|
||||
assert consent.subject == logged_user.dn
|
||||
assert consent.scope == ["openid", "email", "profile", "groups", "address", "phone"]
|
||||
assert not consent.issue_date
|
||||
token.reload()
|
||||
assert token.revoked
|
||||
|
||||
res = testclient.get(f"/consent/restore/{consent.cn[0]}", status=302)
|
||||
assert ("success", "The access has been restored") in res.flashes
|
||||
|
||||
consent.reload()
|
||||
assert not consent.revoked
|
||||
assert consent.issue_date
|
||||
token.reload()
|
||||
assert token.revoked
|
||||
|
||||
res = testclient.get(f"/consent/revoke/{consent.cn[0]}", status=302)
|
||||
assert ("success", "The access has been revoked") in res.flashes
|
||||
consent.reload()
|
||||
assert consent.revoked
|
||||
assert consent.issue_date
|
||||
|
||||
|
||||
def test_revoke_invalid_preconsented_client(testclient, logged_user):
|
||||
res = testclient.get("/consent/revoke-preconsent/invalid", status=302)
|
||||
assert ("error", "Could not revoke this access") in res.flashes
|
||||
|
||||
|
||||
def test_revoke_preconsented_client_with_manual_consent(
|
||||
testclient, logged_user, client, consent
|
||||
):
|
||||
client.preconsent = True
|
||||
client.save()
|
||||
res = testclient.get(f"/consent/revoke-preconsent/{client.client_id}", status=302)
|
||||
res = res.follow()
|
||||
assert ("success", "The access has been revoked") in res.flashes
|
||||
|
||||
|
||||
def test_revoke_preconsented_client_with_manual_revokation(
|
||||
testclient, logged_user, client, consent
|
||||
):
|
||||
client.preconsent = True
|
||||
client.save()
|
||||
consent.revoke()
|
||||
consent.save()
|
||||
|
||||
res = testclient.get(f"/consent/revoke-preconsent/{client.client_id}", status=302)
|
||||
res = res.follow()
|
||||
assert ("error", "The access is already revoked") in res.flashes
|
||||
|
|
Loading…
Reference in a new issue