Client only return the asked scopes

This commit is contained in:
Éloi Rivard 2022-07-07 16:05:34 +02:00
parent c8281969d8
commit 21a2c306ac
9 changed files with 130 additions and 20 deletions

View file

@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file.
The format is based on `Keep a Changelog <https://keepachangelog.com/en/1.0.0/>`_,
and this project adheres to `Semantic Versioning <https://semver.org/spec/v2.0.0.html>`_.
[0.0.10] - Unreleased
=====================
Fixed
*****
- The consent page was displaying scopes not supported by clients. :pr:`56`
[0.0.9] - 2022-06-05
====================

View file

@ -42,7 +42,9 @@ class Client(LDAPObject, ClientMixin):
return self.redirect_uris[0]
def get_allowed_scope(self, scope):
return util.list_to_scope(self.scope)
return util.list_to_scope(
[scope_piece for scope_piece in self.scope if scope_piece in scope]
)
def check_redirect_uri(self, redirect_uri):
return redirect_uri in self.redirect_uris
@ -99,7 +101,7 @@ class AuthorizationCode(LDAPObject, AuthorizationCodeMixin):
return self.redirect_uri
def get_scope(self):
return self.scope
return self.scope[0].split(" ")
def get_nonce(self):
return self.nonce

View file

@ -70,7 +70,9 @@ def authorize():
abort(400)
user = current_user()
scopes = request.args.get("scope", "").split(" ")
scopes = client.get_allowed_scope(request.args.get("scope", "").split(" ")).split(
" "
)
# LOGIN
@ -143,9 +145,10 @@ def authorize():
grant_user = user.dn
if consent:
consent.scope = list(set(scopes + consents[0].scope))
consent.scope = client.get_allowed_scope(
list(set(scopes + consents[0].scope))
).split(" ")
else:
consent = Consent(
client=client.dn,
subject=user.dn,

View file

@ -107,13 +107,14 @@ def generate_user_claims(user, claims, jwt_mapping_config=None):
def save_authorization_code(code, request):
nonce = request.data.get("nonce")
now = datetime.datetime.now()
scope = request.client.get_allowed_scope(request.scope)
code = AuthorizationCode(
authorization_code_id=gen_salt(48),
code=code,
subject=request.user,
client=request.client.dn,
redirect_uri=request.redirect_uri or request.client.redirect_uris[0],
scope=request.scope,
scope=scope,
nonce=nonce,
issue_date=now,
lifetime=str(84000),

View file

@ -33,7 +33,7 @@ def client(testclient, other_client, slapd_connection):
"refresh_token",
],
response_type=["code", "token", "id_token"],
scope=["openid", "profile", "groups"],
scope=["openid", "email", "profile", "groups", "address", "phone"],
tos_uri="https://mydomain.tld/tos",
policy_uri="https://mydomain.tld/policy",
jwk_uri="https://mydomain.tld/jwk",

View file

@ -22,7 +22,7 @@ def test_authorization_code_flow(
params=dict(
response_type="code",
client_id=client.client_id,
scope="openid profile",
scope="openid profile email groups address phone",
nonce="somenonce",
),
status=200,
@ -35,16 +35,31 @@ def test_authorization_code_flow(
code = params["code"][0]
authcode = AuthorizationCode.get(code=code)
assert authcode is not None
assert set(authcode.scope[0].split(" ")) == {
"openid",
"profile",
"email",
"groups",
"address",
"phone",
}
consents = Consent.filter(client=client.dn, subject=logged_user.dn)
assert "profile" in consents[0].scope
assert set(consents[0].scope) == {
"openid",
"profile",
"email",
"groups",
"address",
"phone",
}
res = testclient.post(
"/oauth/token",
params=dict(
grant_type="authorization_code",
code=code,
scope="openid profile",
scope="openid profile email groups address phone",
redirect_uri=client.redirect_uris[0],
),
headers={"Authorization": f"Basic {client_credentials(client)}"},
@ -55,6 +70,14 @@ def test_authorization_code_flow(
token = Token.get(access_token=access_token)
assert token.client == client.dn
assert token.subject == logged_user.dn
assert set(token.scope[0].split(" ")) == {
"openid",
"profile",
"email",
"groups",
"address",
"phone",
}
id_token = res.json["id_token"]
claims = jwt.decode(id_token, keypair[1])
@ -70,6 +93,7 @@ def test_authorization_code_flow(
assert {
"name": "John (johnny) Doe",
"family_name": "Doe",
"email": "john@doe.com",
"sub": "user",
"groups": [],
} == res.json
@ -138,9 +162,11 @@ def test_authorization_code_flow_preconsented(
"name": "John (johnny) Doe",
"family_name": "Doe",
"sub": "user",
"groups": [],
} == res.json
for consent in consents:
consent.delete()
def test_logout_login(testclient, logged_user, client):
assert not Consent.all()
@ -206,7 +232,6 @@ def test_logout_login(testclient, logged_user, client):
"name": "John (johnny) Doe",
"family_name": "Doe",
"sub": "user",
"groups": [],
} == res.json
for consent in consents:
@ -288,7 +313,6 @@ def test_refresh_token(testclient, user, client):
"name": "John (johnny) Doe",
"family_name": "Doe",
"sub": "user",
"groups": [],
} == res.json
for consent in consents:
@ -355,7 +379,6 @@ def test_code_challenge(testclient, logged_user, client):
"name": "John (johnny) Doe",
"family_name": "Doe",
"sub": "user",
"groups": [],
} == res.json
client.token_endpoint_auth_method = "client_secret_basic"
@ -595,6 +618,7 @@ def test_nonce_required_in_oidc_requests(testclient, logged_user, client):
def test_nonce_not_required_in_oauth_requests(testclient, logged_user, client):
assert not Consent.all()
testclient.app.config["REQUIRE_NONCE"] = False
res = testclient.get(
@ -610,3 +634,79 @@ def test_nonce_not_required_in_oauth_requests(testclient, logged_user, client):
res = res.form.submit(name="answer", value="accept", status=302)
assert res.location.startswith(client.redirect_uris[0])
for consent in Consent.all():
consent.delete()
def test_authorization_code_request_scope_too_large(
testclient, logged_user, keypair, other_client
):
assert not Consent.all()
assert "email" not in other_client.scope
res = testclient.get(
"/oauth/authorize",
params=dict(
response_type="code",
client_id=other_client.client_id,
scope="openid profile email",
nonce="somenonce",
),
status=200,
)
res = res.form.submit(name="answer", value="accept", status=302)
params = parse_qs(urlsplit(res.location).query)
code = params["code"][0]
authcode = AuthorizationCode.get(code=code)
assert set(authcode.scope[0].split(" ")) == {
"openid",
"profile",
}
consents = Consent.filter(client=other_client.dn, subject=logged_user.dn)
assert set(consents[0].scope) == {
"openid",
"profile",
}
res = testclient.post(
"/oauth/token",
params=dict(
grant_type="authorization_code",
code=code,
scope="openid profile email groups address phone",
redirect_uri=other_client.redirect_uris[0],
),
headers={"Authorization": f"Basic {client_credentials(other_client)}"},
status=200,
)
access_token = res.json["access_token"]
token = Token.get(access_token=access_token)
assert token.client == other_client.dn
assert token.subject == logged_user.dn
assert set(token.scope[0].split(" ")) == {
"openid",
"profile",
}
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"]
res = testclient.get(
"/oauth/userinfo",
headers={"Authorization": f"Bearer {access_token}"},
status=200,
)
assert {
"name": "John (johnny) Doe",
"family_name": "Doe",
"sub": "user",
} == res.json
for consent in consents:
consent.delete()

View file

@ -50,7 +50,6 @@ def test_oauth_hybrid(testclient, slapd_connection, user, client):
"name": "John (johnny) Doe",
"family_name": "Doe",
"sub": "user",
"groups": [],
} == res.json
@ -96,5 +95,4 @@ def test_oidc_hybrid(
"name": "John (johnny) Doe",
"family_name": "Doe",
"sub": "user",
"groups": [],
} == res.json

View file

@ -46,7 +46,6 @@ def test_oauth_implicit(testclient, user, client):
"name": "John (johnny) Doe",
"sub": "user",
"family_name": "Doe",
"groups": [],
} == res.json
client.grant_type = ["code"]
@ -103,7 +102,6 @@ def test_oidc_implicit(testclient, keypair, user, client, other_client):
"name": "John (johnny) Doe",
"sub": "user",
"family_name": "Doe",
"groups": [],
} == res.json
client.grant_type = ["code"]

View file

@ -10,7 +10,7 @@ def test_password_flow_basic(testclient, user, client):
grant_type="password",
username="John (johnny) Doe",
password="correct horse battery staple",
scope="profile",
scope="openid profile groups",
),
headers={"Authorization": f"Basic {client_credentials(client)}"},
status=200,
@ -46,7 +46,7 @@ def test_password_flow_post(testclient, user, client):
grant_type="password",
username="John (johnny) Doe",
password="correct horse battery staple",
scope="profile",
scope="openid profile groups",
client_id=client.client_id,
client_secret=client.secret,
),