forked from Github-Mirrors/canaille
Merge branch 'issue-62-multiple-audiences' into 'master'
tokens can have multiple audiences Closes #62 See merge request yaal/canaille!9
This commit is contained in:
commit
94a2c36cef
11 changed files with 200 additions and 30 deletions
|
@ -18,6 +18,12 @@ def index(user):
|
|||
return render_template("admin/client_list.html", clients=clients, menuitem="admin")
|
||||
|
||||
|
||||
def client_audiences():
|
||||
return [
|
||||
(client.dn, client.oauthClientName) for client in Client.filter()
|
||||
]
|
||||
|
||||
|
||||
class ClientAdd(FlaskForm):
|
||||
oauthClientName = wtforms.StringField(
|
||||
_("Name"),
|
||||
|
@ -73,6 +79,12 @@ class ClientAdd(FlaskForm):
|
|||
],
|
||||
default="client_secret_basic",
|
||||
)
|
||||
oauthAudience = wtforms.SelectMultipleField(
|
||||
_("Token audiences"),
|
||||
validators=[wtforms.validators.Optional()],
|
||||
choices=client_audiences,
|
||||
validate_choice=False,
|
||||
)
|
||||
oauthLogoURI = wtforms.URLField(
|
||||
_("Logo URI"),
|
||||
validators=[wtforms.validators.Optional()],
|
||||
|
@ -120,7 +132,8 @@ def add(user):
|
|||
|
||||
if not form.validate():
|
||||
flash(
|
||||
_("The client has not been added. Please check your information."), "error",
|
||||
_("The client has not been added. Please check your information."),
|
||||
"error",
|
||||
)
|
||||
return render_template("admin/client_add.html", form=form, menuitem="admin")
|
||||
|
||||
|
@ -148,9 +161,11 @@ def add(user):
|
|||
if form["oauthTokenEndpointAuthMethod"].data == "none"
|
||||
else gen_salt(48),
|
||||
)
|
||||
client.oauthAudience = [client.dn]
|
||||
client.save()
|
||||
flash(
|
||||
_("The client has been created."), "success",
|
||||
_("The client has been created."),
|
||||
"success",
|
||||
)
|
||||
|
||||
return redirect(url_for("admin_clients.edit", client_id=client_id))
|
||||
|
@ -203,10 +218,12 @@ def client_edit(client_id):
|
|||
oauthSoftwareVersion=form["oauthSoftwareVersion"].data,
|
||||
oauthJWK=form["oauthJWK"].data,
|
||||
oauthJWKURI=form["oauthJWKURI"].data,
|
||||
oauthAudience=form["oauthAudience"].data,
|
||||
)
|
||||
client.save()
|
||||
flash(
|
||||
_("The client has been edited."), "success",
|
||||
_("The client has been edited."),
|
||||
"success",
|
||||
)
|
||||
|
||||
return render_template(
|
||||
|
@ -217,7 +234,8 @@ def client_edit(client_id):
|
|||
def client_delete(client_id):
|
||||
client = Client.get(client_id) or abort(404)
|
||||
flash(
|
||||
_("The client has been deleted."), "success",
|
||||
_("The client has been deleted."),
|
||||
"success",
|
||||
)
|
||||
client.delete()
|
||||
return redirect(url_for("admin_clients.index"))
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import datetime
|
||||
import ldap
|
||||
import uuid
|
||||
from authlib.common.encoding import json_loads, json_dumps
|
||||
from authlib.oauth2.rfc6749 import (
|
||||
ClientMixin,
|
||||
TokenMixin,
|
||||
|
|
|
@ -125,6 +125,10 @@ class OpenIDCode(_OpenIDCode):
|
|||
def generate_user_info(self, user, scope):
|
||||
return generate_user_info(user, scope)
|
||||
|
||||
def get_audiences(self, request):
|
||||
client = request.client
|
||||
return [Client.get(aud).oauthClientID for aud in client.oauthAudience]
|
||||
|
||||
|
||||
class PasswordGrant(_ResourceOwnerPasswordCredentialsGrant):
|
||||
def authenticate_user(self, username, password):
|
||||
|
@ -157,6 +161,10 @@ class OpenIDImplicitGrant(_OpenIDImplicitGrant):
|
|||
def generate_user_info(self, user, scope):
|
||||
return generate_user_info(user, scope)
|
||||
|
||||
def get_audiences(self, request):
|
||||
client = request.client
|
||||
return [Client.get(aud).oauthClientID for aud in client.oauthAudience]
|
||||
|
||||
|
||||
class OpenIDHybridGrant(_OpenIDHybridGrant):
|
||||
def save_authorization_code(self, code, request):
|
||||
|
@ -171,6 +179,11 @@ class OpenIDHybridGrant(_OpenIDHybridGrant):
|
|||
def generate_user_info(self, user, scope):
|
||||
return generate_user_info(user, scope)
|
||||
|
||||
def get_audiences(self, request):
|
||||
client = request.client
|
||||
print(client)
|
||||
return [Client.get(aud).oauthClientID for aud in client.oauthAudience]
|
||||
|
||||
|
||||
def query_client(client_id):
|
||||
return Client.get(client_id)
|
||||
|
@ -187,6 +200,7 @@ def save_token(token, request):
|
|||
oauthClient=request.client.dn,
|
||||
oauthRefreshToken=token.get("refresh_token"),
|
||||
oauthSubject=request.user,
|
||||
oauthAudience=request.client.oauthAudience,
|
||||
)
|
||||
t.save()
|
||||
|
||||
|
@ -244,6 +258,7 @@ class IntrospectionEndpoint(_IntrospectionEndpoint):
|
|||
def introspect_token(self, token):
|
||||
client_id = Client.get(token.oauthClient).oauthClientID
|
||||
user = User.get(dn=token.oauthSubject)
|
||||
audience = [Client.get(aud).oauthClientID for aud in token.oauthAudience]
|
||||
return {
|
||||
"active": True,
|
||||
"client_id": client_id,
|
||||
|
@ -251,7 +266,7 @@ class IntrospectionEndpoint(_IntrospectionEndpoint):
|
|||
"username": user.name,
|
||||
"scope": token.get_scope(),
|
||||
"sub": user.uid[0],
|
||||
"aud": client_id,
|
||||
"aud": audience,
|
||||
"iss": authorization.metadata["issuer"],
|
||||
"exp": token.get_expires_at(),
|
||||
"iat": token.get_issued_at(),
|
||||
|
|
|
@ -62,7 +62,7 @@ olcAttributeTypes: ( 1.3.6.1.4.1.56207.1.1.7 NAME 'oauthAuthorizationDate'
|
|||
USAGE userApplications
|
||||
X-ORIGIN 'OAuth 2.0' )
|
||||
olcAttributeTypes: ( 1.3.6.1.4.1.56207.1.1.8 NAME 'oauthCodeChallenge'
|
||||
DESC 'OAuth 2.0 nonce'
|
||||
DESC 'OAuth 2.0 code challenge'
|
||||
EQUALITY caseExactMatch
|
||||
ORDERING caseExactOrderingMatch
|
||||
SUBSTR caseExactSubstringsMatch
|
||||
|
@ -71,7 +71,7 @@ olcAttributeTypes: ( 1.3.6.1.4.1.56207.1.1.8 NAME 'oauthCodeChallenge'
|
|||
USAGE userApplications
|
||||
X-ORIGIN 'OAuth 2.0' )
|
||||
olcAttributeTypes: ( 1.3.6.1.4.1.56207.1.1.9 NAME 'oauthCodeChallengeMethod'
|
||||
DESC 'OAuth 2.0 nonce'
|
||||
DESC 'OAuth 2.0 code challenge method'
|
||||
EQUALITY caseExactMatch
|
||||
ORDERING caseExactOrderingMatch
|
||||
SUBSTR caseExactSubstringsMatch
|
||||
|
@ -295,6 +295,13 @@ olcAttributeTypes: ( 1.3.6.1.4.1.56207.1.1.34 NAME 'oauthClient'
|
|||
SINGLE-VALUE
|
||||
USAGE userApplications
|
||||
X-ORIGIN 'OAuth 2.0' )
|
||||
olcAttributeTypes: ( 1.3.6.1.4.1.56207.1.1.35 NAME 'oauthAudience'
|
||||
DESC 'Token audience'
|
||||
EQUALITY distinguishedNameMatch
|
||||
SUBSTR caseExactSubstringsMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
|
||||
USAGE userApplications
|
||||
X-ORIGIN 'OAuth 2.0' )
|
||||
olcObjectClasses: ( 1.3.6.1.4.1.56207.1.2.1 NAME 'oauthClient'
|
||||
DESC 'OAuth 2.0 Authorization Code'
|
||||
SUP top
|
||||
|
@ -318,7 +325,8 @@ olcObjectClasses: ( 1.3.6.1.4.1.56207.1.2.1 NAME 'oauthClient'
|
|||
oauthJWK $
|
||||
oauthTokenEndpointAuthMethod $
|
||||
oauthSoftwareID $
|
||||
oauthSoftwareVersion )
|
||||
oauthSoftwareVersion $
|
||||
oauthAudience )
|
||||
)
|
||||
X-ORIGIN 'OAuth 2.0' )
|
||||
olcObjectClasses: ( 1.3.6.1.4.1.56207.1.2.2 NAME 'oauthAuthorizationCode'
|
||||
|
@ -352,7 +360,8 @@ olcObjectClasses: ( 1.3.6.1.4.1.56207.1.2.3 NAME 'oauthToken'
|
|||
oauthScope $
|
||||
oauthIssueDate $
|
||||
oauthTokenLifetime $
|
||||
oauthRevokationDate )
|
||||
oauthRevokationDate $
|
||||
oauthAudience )
|
||||
X-ORIGIN 'OAuth 2.0' )
|
||||
olcObjectClasses: ( 1.3.6.1.4.1.56207.1.2.4 NAME 'oauthConsent'
|
||||
DESC 'OAuth 2.0 User consents'
|
||||
|
|
|
@ -59,7 +59,7 @@ attributetype ( 1.3.6.1.4.1.56207.1.1.7 NAME 'oauthAuthorizationDate'
|
|||
USAGE userApplications
|
||||
X-ORIGIN 'OAuth 2.0' )
|
||||
attributetype ( 1.3.6.1.4.1.56207.1.1.8 NAME 'oauthCodeChallenge'
|
||||
DESC 'OAuth 2.0 nonce'
|
||||
DESC 'OAuth 2.0 code challenge'
|
||||
EQUALITY caseExactMatch
|
||||
ORDERING caseExactOrderingMatch
|
||||
SUBSTR caseExactSubstringsMatch
|
||||
|
@ -68,7 +68,7 @@ attributetype ( 1.3.6.1.4.1.56207.1.1.8 NAME 'oauthCodeChallenge'
|
|||
USAGE userApplications
|
||||
X-ORIGIN 'OAuth 2.0' )
|
||||
attributetype ( 1.3.6.1.4.1.56207.1.1.9 NAME 'oauthCodeChallengeMethod'
|
||||
DESC 'OAuth 2.0 nonce'
|
||||
DESC 'OAuth 2.0 code challenge method'
|
||||
EQUALITY caseExactMatch
|
||||
ORDERING caseExactOrderingMatch
|
||||
SUBSTR caseExactSubstringsMatch
|
||||
|
@ -292,6 +292,13 @@ attributetypes ( 1.3.6.1.4.1.56207.1.1.34 NAME 'oauthClient'
|
|||
SINGLE-VALUE
|
||||
USAGE userApplications
|
||||
X-ORIGIN 'OAuth 2.0' )
|
||||
attributetypes ( 1.3.6.1.4.1.56207.1.1.35 NAME 'oauthAudience'
|
||||
DESC 'Token Audience'
|
||||
EQUALITY distinguishedNameMatch
|
||||
SUBSTR caseExactSubstringsMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
|
||||
USAGE userApplications
|
||||
X-ORIGIN 'OAuth 2.0' )
|
||||
objectclass ( 1.3.6.1.4.1.56207.1.2.1 NAME 'oauthClient'
|
||||
DESC 'OAuth 2.0 Authorization Code'
|
||||
SUP top
|
||||
|
@ -315,7 +322,8 @@ objectclass ( 1.3.6.1.4.1.56207.1.2.1 NAME 'oauthClient'
|
|||
oauthJWK $
|
||||
oauthTokenEndpointAuthMethod $
|
||||
oauthSoftwareID $
|
||||
oauthSoftwareVersion )
|
||||
oauthSoftwareVersion $
|
||||
oauthAudience)
|
||||
)
|
||||
X-ORIGIN 'OAuth 2.0' )
|
||||
objectclass ( 1.3.6.1.4.1.56207.1.2.2 NAME 'oauthAuthorizationCode'
|
||||
|
@ -349,7 +357,8 @@ objectclass ( 1.3.6.1.4.1.56207.1.2.3 NAME 'oauthToken'
|
|||
oauthScope $
|
||||
oauthIssueDate $
|
||||
oauthTokenLifetime $
|
||||
oauthRevokationDate )
|
||||
oauthRevokationDate $
|
||||
oauthAudience )
|
||||
X-ORIGIN 'OAuth 2.0' )
|
||||
objectclass ( 1.3.6.1.4.1.56207.1.2.4 NAME 'oauthConsent'
|
||||
DESC 'OAuth 2.0 User consents'
|
||||
|
|
|
@ -197,7 +197,7 @@ def testclient(app):
|
|||
|
||||
|
||||
@pytest.fixture
|
||||
def client(app, slapd_connection):
|
||||
def client(app, slapd_connection, other_client):
|
||||
Client.ocs_by_name(slapd_connection)
|
||||
c = Client(
|
||||
oauthClientID=gen_salt(24),
|
||||
|
@ -225,6 +225,42 @@ def client(app, slapd_connection):
|
|||
oauthJWKURI="https://mydomain.tld/jwk",
|
||||
oauthTokenEndpointAuthMethod="client_secret_basic",
|
||||
)
|
||||
c.oauthAudience = [c.dn, other_client.dn]
|
||||
c.save(slapd_connection)
|
||||
|
||||
return c
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def other_client(app, slapd_connection):
|
||||
Client.ocs_by_name(slapd_connection)
|
||||
c = Client(
|
||||
oauthClientID=gen_salt(24),
|
||||
oauthClientName="Some other client",
|
||||
oauthClientContact="contact@myotherdomain.tld",
|
||||
oauthClientURI="https://myotherdomain.tld",
|
||||
oauthRedirectURIs=[
|
||||
"https://myotherdomain.tld/redirect1",
|
||||
"https://myotherdomain.tld/redirect2",
|
||||
],
|
||||
oauthLogoURI="https://myotherdomain.tld/logo.png",
|
||||
oauthIssueDate=datetime.datetime.now().strftime("%Y%m%d%H%S%MZ"),
|
||||
oauthClientSecret=gen_salt(48),
|
||||
oauthGrantType=[
|
||||
"password",
|
||||
"authorization_code",
|
||||
"implicit",
|
||||
"hybrid",
|
||||
"refresh_token",
|
||||
],
|
||||
oauthResponseType=["code", "token", "id_token"],
|
||||
oauthScope=["openid", "profile", "groups"],
|
||||
oauthTermsOfServiceURI="https://myotherdomain.tld/tos",
|
||||
oauthPolicyURI="https://myotherdomain.tld/policy",
|
||||
oauthJWKURI="https://myotherdomain.tld/jwk",
|
||||
oauthTokenEndpointAuthMethod="client_secret_basic",
|
||||
)
|
||||
c.oauthAudience = [c.dn]
|
||||
c.save(slapd_connection)
|
||||
|
||||
return c
|
||||
|
@ -301,6 +337,7 @@ def token(slapd_connection, client, user):
|
|||
Token.ocs_by_name(slapd_connection)
|
||||
t = Token(
|
||||
oauthAccessToken=gen_salt(48),
|
||||
oauthAudience=[client.dn],
|
||||
oauthClient=client.dn,
|
||||
oauthSubject=user.dn,
|
||||
oauthTokenType=None,
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
from . import client_credentials
|
||||
from authlib.jose import jwt
|
||||
from authlib.oauth2.rfc7636 import create_s256_code_challenge
|
||||
from urllib.parse import urlsplit, parse_qs
|
||||
from canaille.models import AuthorizationCode, Token, Consent
|
||||
from werkzeug.security import gen_salt
|
||||
|
||||
|
||||
def test_authorization_code_flow(testclient, slapd_connection, logged_user, client):
|
||||
def test_authorization_code_flow(testclient, slapd_connection, logged_user, client, keypair, other_client):
|
||||
res = testclient.get(
|
||||
"/oauth/authorize",
|
||||
params=dict(
|
||||
|
@ -36,12 +37,18 @@ def test_authorization_code_flow(testclient, slapd_connection, logged_user, clie
|
|||
headers={"Authorization": f"Basic {client_credentials(client)}"},
|
||||
status=200,
|
||||
)
|
||||
access_token = res.json["access_token"]
|
||||
|
||||
access_token = res.json["access_token"]
|
||||
token = Token.get(access_token, conn=slapd_connection)
|
||||
assert token.oauthClient == client.dn
|
||||
assert token.oauthSubject == logged_user.dn
|
||||
|
||||
id_token = res.json["id_token"]
|
||||
claims = jwt.decode(id_token, keypair[1])
|
||||
assert logged_user.uid[0] == claims["sub"]
|
||||
assert logged_user.cn[0] == claims["name"]
|
||||
assert [client.oauthClientID, other_client.oauthClientID] == claims["aud"]
|
||||
|
||||
res = testclient.get(
|
||||
"/oauth/userinfo",
|
||||
headers={"Authorization": f"Bearer {access_token}"},
|
||||
|
@ -99,8 +106,8 @@ def test_logout_login(testclient, slapd_connection, logged_user, client):
|
|||
headers={"Authorization": f"Basic {client_credentials(client)}"},
|
||||
status=200,
|
||||
)
|
||||
access_token = res.json["access_token"]
|
||||
|
||||
access_token = res.json["access_token"]
|
||||
token = Token.get(access_token, conn=slapd_connection)
|
||||
assert token.oauthClient == client.dn
|
||||
assert token.oauthSubject == logged_user.dn
|
||||
|
|
|
@ -32,15 +32,17 @@ def test_client_add(testclient, logged_admin, slapd_connection):
|
|||
"oauthSoftwareVersion": "1",
|
||||
"oauthJWK": "jwk",
|
||||
"oauthJWKURI": "https://foo.bar/jwks.json",
|
||||
"oauthAudience": [],
|
||||
}
|
||||
for k, v in data.items():
|
||||
res.form[k] = v
|
||||
res.form[k].force_value(v)
|
||||
|
||||
res = res.form.submit(status=302, name="action", value="edit")
|
||||
res = res.follow(status=200)
|
||||
|
||||
client_id = res.forms["readonly"]["oauthClientID"].value
|
||||
client = Client.get(client_id, conn=slapd_connection)
|
||||
data["oauthAudience"] = [client.dn]
|
||||
for k, v in data.items():
|
||||
client_value = getattr(client, k)
|
||||
if k == "oauthScope":
|
||||
|
@ -49,7 +51,7 @@ def test_client_add(testclient, logged_admin, slapd_connection):
|
|||
assert v == client_value
|
||||
|
||||
|
||||
def test_client_edit(testclient, client, logged_admin, slapd_connection):
|
||||
def test_client_edit(testclient, client, logged_admin, slapd_connection, other_client):
|
||||
res = testclient.get("/admin/client/edit/" + client.oauthClientID)
|
||||
data = {
|
||||
"oauthClientName": "foobar",
|
||||
|
@ -67,12 +69,17 @@ def test_client_edit(testclient, client, logged_admin, slapd_connection):
|
|||
"oauthSoftwareVersion": "1",
|
||||
"oauthJWK": "jwk",
|
||||
"oauthJWKURI": "https://foo.bar/jwks.json",
|
||||
"oauthAudience": [client.dn, other_client.dn],
|
||||
}
|
||||
for k, v in data.items():
|
||||
res.forms["clientadd"][k] = v
|
||||
res.forms["clientadd"][k].force_value(v)
|
||||
res = res.forms["clientadd"].submit(status=200, name="action", value="edit")
|
||||
|
||||
client.reload(conn=slapd_connection)
|
||||
assert (
|
||||
"The client has not been edited. Please check your information." not in res.text
|
||||
)
|
||||
|
||||
client = Client.get(client.dn, conn=slapd_connection)
|
||||
for k, v in data.items():
|
||||
client_value = getattr(client, k)
|
||||
if k == "oauthScope":
|
||||
|
|
|
@ -50,7 +50,9 @@ def test_oauth_hybrid(testclient, slapd_connection, user, client):
|
|||
} == res.json
|
||||
|
||||
|
||||
def test_oidc_hybrid(testclient, slapd_connection, logged_user, client, keypair):
|
||||
def test_oidc_hybrid(
|
||||
testclient, slapd_connection, logged_user, client, keypair, other_client
|
||||
):
|
||||
res = testclient.get(
|
||||
"/oauth/authorize",
|
||||
params=dict(
|
||||
|
@ -79,7 +81,7 @@ def test_oidc_hybrid(testclient, slapd_connection, logged_user, client, keypair)
|
|||
claims = jwt.decode(id_token, keypair[1])
|
||||
assert logged_user.uid[0] == claims["sub"]
|
||||
assert logged_user.cn[0] == claims["name"]
|
||||
assert [client.oauthClientID] == claims["aud"]
|
||||
assert [client.oauthClientID, other_client.oauthClientID] == claims["aud"]
|
||||
|
||||
res = testclient.get(
|
||||
"/oauth/userinfo",
|
||||
|
|
|
@ -52,7 +52,9 @@ def test_oauth_implicit(testclient, slapd_connection, user, client):
|
|||
client.save(slapd_connection)
|
||||
|
||||
|
||||
def test_oidc_implicit(testclient, keypair, slapd_connection, user, client):
|
||||
def test_oidc_implicit(
|
||||
testclient, keypair, slapd_connection, user, client, other_client
|
||||
):
|
||||
client.oauthGrantType = ["token id_token"]
|
||||
client.oauthTokenEndpointAuthMethod = "none"
|
||||
|
||||
|
@ -89,7 +91,7 @@ def test_oidc_implicit(testclient, keypair, slapd_connection, user, client):
|
|||
claims = jwt.decode(id_token, keypair[1])
|
||||
assert user.uid[0] == claims["sub"]
|
||||
assert user.cn[0] == claims["name"]
|
||||
assert [client.oauthClientID] == claims["aud"]
|
||||
assert [client.oauthClientID, other_client.oauthClientID] == claims["aud"]
|
||||
|
||||
res = testclient.get(
|
||||
"/oauth/userinfo",
|
||||
|
@ -110,7 +112,7 @@ def test_oidc_implicit(testclient, keypair, slapd_connection, user, client):
|
|||
|
||||
|
||||
def test_oidc_implicit_with_group(
|
||||
testclient, keypair, slapd_connection, user, client, foo_group
|
||||
testclient, keypair, slapd_connection, user, client, foo_group, other_client
|
||||
):
|
||||
client.oauthGrantType = ["token id_token"]
|
||||
client.oauthTokenEndpointAuthMethod = "none"
|
||||
|
@ -148,7 +150,7 @@ def test_oidc_implicit_with_group(
|
|||
claims = jwt.decode(id_token, keypair[1])
|
||||
assert user.uid[0] == claims["sub"]
|
||||
assert user.cn[0] == claims["name"]
|
||||
assert [client.oauthClientID] == claims["aud"]
|
||||
assert [client.oauthClientID, other_client.oauthClientID] == claims["aud"]
|
||||
assert ["foo"] == claims["groups"]
|
||||
|
||||
res = testclient.get(
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
from canaille.models import AuthorizationCode, Token, Client
|
||||
from urllib.parse import urlsplit, parse_qs
|
||||
from . import client_credentials
|
||||
|
||||
|
||||
def test_token_introspection(testclient, user, client, token):
|
||||
res = testclient.post(
|
||||
"/oauth/introspect",
|
||||
params=dict(token=token.oauthAccessToken,),
|
||||
params=dict(
|
||||
token=token.oauthAccessToken,
|
||||
),
|
||||
headers={"Authorization": f"Basic {client_credentials(client)}"},
|
||||
status=200,
|
||||
)
|
||||
|
@ -15,7 +19,7 @@ def test_token_introspection(testclient, user, client, token):
|
|||
"username": user.name,
|
||||
"scope": token.get_scope(),
|
||||
"sub": user.uid[0],
|
||||
"aud": client.oauthClientID,
|
||||
"aud": [client.oauthClientID],
|
||||
"iss": "https://mydomain.tld",
|
||||
"exp": token.get_expires_at(),
|
||||
"iat": token.get_issued_at(),
|
||||
|
@ -30,3 +34,64 @@ def test_token_invalid(testclient, client):
|
|||
status=200,
|
||||
)
|
||||
assert {"active": False} == res.json
|
||||
|
||||
|
||||
def test_full_flow(
|
||||
testclient, slapd_connection, logged_user, client, user, other_client
|
||||
):
|
||||
res = testclient.get(
|
||||
"/oauth/authorize",
|
||||
params=dict(
|
||||
response_type="code",
|
||||
client_id=client.oauthClientID,
|
||||
scope="profile",
|
||||
nonce="somenonce",
|
||||
),
|
||||
status=200,
|
||||
)
|
||||
|
||||
res = res.form.submit(name="answer", value="accept", status=302)
|
||||
|
||||
assert res.location.startswith(client.oauthRedirectURIs[0])
|
||||
params = parse_qs(urlsplit(res.location).query)
|
||||
code = params["code"][0]
|
||||
authcode = AuthorizationCode.get(code, conn=slapd_connection)
|
||||
assert authcode is not None
|
||||
|
||||
res = testclient.post(
|
||||
"/oauth/token",
|
||||
params=dict(
|
||||
grant_type="authorization_code",
|
||||
code=code,
|
||||
scope="profile",
|
||||
redirect_uri=client.oauthRedirectURIs[0],
|
||||
),
|
||||
headers={"Authorization": f"Basic {client_credentials(client)}"},
|
||||
status=200,
|
||||
)
|
||||
access_token = res.json["access_token"]
|
||||
|
||||
token = Token.get(access_token, conn=slapd_connection)
|
||||
assert token.oauthClient == client.dn
|
||||
assert token.oauthSubject == logged_user.dn
|
||||
|
||||
res = testclient.post(
|
||||
"/oauth/introspect",
|
||||
params=dict(
|
||||
token=token.oauthAccessToken,
|
||||
),
|
||||
headers={"Authorization": f"Basic {client_credentials(client)}"},
|
||||
status=200,
|
||||
)
|
||||
assert {
|
||||
"aud": [client.oauthClientID, other_client.oauthClientID],
|
||||
"active": True,
|
||||
"client_id": client.oauthClientID,
|
||||
"token_type": token.oauthTokenType,
|
||||
"username": user.name,
|
||||
"scope": token.get_scope(),
|
||||
"sub": user.uid[0],
|
||||
"iss": "https://mydomain.tld",
|
||||
"exp": token.get_expires_at(),
|
||||
"iat": token.get_issued_at(),
|
||||
} == res.json
|
||||
|
|
Loading…
Reference in a new issue