Skip to content
This repository was archived by the owner on Jun 23, 2023. It is now read-only.

Commit fca92e7

Browse files
committed
Fix id tokens claims
1 parent ccd7234 commit fca92e7

3 files changed

Lines changed: 137 additions & 47 deletions

File tree

src/oidcop/token/id_token.py

Lines changed: 50 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@
1212
from oidcop.session.claims import claims_match
1313
from oidcop.token import is_expired
1414
from oidcop.token.exception import InvalidToken
15+
16+
from ..util import get_logout_id
1517
from . import Token
1618
from . import UnknownToken
17-
from ..util import get_logout_id
1819

1920
logger = logging.getLogger(__name__)
2021

@@ -131,7 +132,13 @@ def __init__(
131132
self.provider_info = construct_endpoint_info(self.default_capabilities, **kwargs)
132133

133134
def payload(
134-
self, session_id, alg="RS256", code=None, access_token=None, extra_claims=None,
135+
self,
136+
session_id,
137+
alg="RS256",
138+
code=None,
139+
access_token=None,
140+
extra_claims=None,
141+
user_info=None,
135142
):
136143
"""
137144
Collect payload for the ID Token.
@@ -155,16 +162,18 @@ def payload(
155162
if _val:
156163
_args[attr] = _val
157164

158-
_claims_restriction = grant.claims.get("id_token")
159-
if _claims_restriction == {}:
160-
user_info = None
161-
else:
162-
user_info = _context.claims_interface.get_user_claims(
163-
user_id=session_information["user_id"], claims_restriction=_claims_restriction,
164-
)
165-
if _claims_restriction and "acr" in _claims_restriction and "acr" in _args:
166-
if claims_match(_args["acr"], _claims_restriction["acr"]) is False:
167-
raise ValueError("Could not match expected 'acr'")
165+
if not user_info:
166+
_claims_restriction = grant.claims.get("id_token")
167+
if _claims_restriction == {}:
168+
user_info = None
169+
else:
170+
user_info = _context.claims_interface.get_user_claims(
171+
user_id=session_information["user_id"],
172+
claims_restriction=_claims_restriction,
173+
)
174+
if _claims_restriction and "acr" in _claims_restriction and "acr" in _args:
175+
if claims_match(_args["acr"], _claims_restriction["acr"]) is False:
176+
raise ValueError("Could not match expected 'acr'")
168177

169178
if user_info:
170179
try:
@@ -203,15 +212,16 @@ def payload(
203212
return _args
204213

205214
def sign_encrypt(
206-
self,
207-
session_id,
208-
client_id,
209-
code=None,
210-
access_token=None,
211-
sign=True,
212-
encrypt=False,
213-
lifetime=None,
214-
extra_claims=None,
215+
self,
216+
session_id,
217+
client_id,
218+
code=None,
219+
access_token=None,
220+
sign=True,
221+
encrypt=False,
222+
lifetime=None,
223+
extra_claims=None,
224+
user_info=None,
215225
) -> str:
216226
"""
217227
Signed and or encrypt a IDToken
@@ -240,6 +250,7 @@ def sign_encrypt(
240250
code=code,
241251
access_token=access_token,
242252
extra_claims=extra_claims,
253+
user_info=user_info,
243254
)
244255

245256
if lifetime is None:
@@ -249,7 +260,15 @@ def sign_encrypt(
249260

250261
return _jwt.pack(_payload, recv=client_id)
251262

252-
def __call__(self, session_id: Optional[str] = "", ttype: Optional[str] = "", **kwargs) -> str:
263+
def __call__(
264+
self,
265+
session_id: Optional[str] = "",
266+
ttype: Optional[str] = "",
267+
encrypt=False,
268+
code=None,
269+
access_token=None,
270+
**kwargs,
271+
) -> str:
253272
_context = self.server_get("endpoint_context")
254273

255274
user_id, client_id, grant_id = _context.session_manager.decrypt_session_id(session_id)
@@ -265,11 +284,16 @@ def __call__(self, session_id: Optional[str] = "", ttype: Optional[str] = "", **
265284

266285
lifetime = self.lifetime
267286

268-
# Weed out stuff that doesn't belong here
269-
kwargs = {k: v for k, v in kwargs.items() if k in ["encrypt", "code", "access_token"]}
270-
271287
id_token = self.sign_encrypt(
272-
session_id, client_id, sign=True, lifetime=lifetime, extra_claims=xargs, **kwargs
288+
session_id,
289+
client_id,
290+
sign=True,
291+
lifetime=lifetime,
292+
extra_claims=xargs,
293+
encrypt=encrypt,
294+
code=code,
295+
access_token=access_token,
296+
user_info=kwargs,
273297
)
274298

275299
return id_token

tests/test_05_id_token.py

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,11 @@ def test_id_token_payload_0(self):
235235
"nonce",
236236
"iat",
237237
"exp",
238+
"email",
239+
"email_verified",
240+
"jti",
241+
"scope",
242+
"client_id",
238243
"iss",
239244
}
240245

@@ -252,6 +257,11 @@ def test_id_token_payload_with_code(self):
252257
"auth_time",
253258
"aud",
254259
"exp",
260+
"email",
261+
"email_verified",
262+
"jti",
263+
"scope",
264+
"client_id",
255265
"c_hash",
256266
"iss",
257267
"iat",
@@ -277,6 +287,11 @@ def test_id_token_payload_with_access_token(self):
277287
"auth_time",
278288
"aud",
279289
"exp",
290+
"email",
291+
"email_verified",
292+
"jti",
293+
"scope",
294+
"client_id",
280295
"iss",
281296
"iat",
282297
"nonce",
@@ -300,6 +315,11 @@ def test_id_token_payload_with_code_and_access_token(self):
300315
"auth_time",
301316
"aud",
302317
"exp",
318+
"email",
319+
"email_verified",
320+
"jti",
321+
"scope",
322+
"client_id",
303323
"iss",
304324
"iat",
305325
"nonce",
@@ -308,9 +328,10 @@ def test_id_token_payload_with_code_and_access_token(self):
308328
}
309329

310330
def test_id_token_payload_with_userinfo(self):
311-
session_id = self._create_session(AREQ)
331+
req = dict(AREQ)
332+
req["claims"] = {"id_token": {"given_name": None}}
333+
session_id = self._create_session(req)
312334
grant = self.session_manager[session_id]
313-
grant.claims = {"id_token": {"given_name": None}}
314335

315336
id_token = self._mint_id_token(grant, session_id)
316337

@@ -320,6 +341,11 @@ def test_id_token_payload_with_userinfo(self):
320341
"nonce",
321342
"iat",
322343
"iss",
344+
"email",
345+
"email_verified",
346+
"jti",
347+
"scope",
348+
"client_id",
323349
"given_name",
324350
"aud",
325351
"exp",
@@ -328,9 +354,10 @@ def test_id_token_payload_with_userinfo(self):
328354
}
329355

330356
def test_id_token_payload_many_0(self):
331-
session_id = self._create_session(AREQ)
357+
req = dict(AREQ)
358+
req["claims"] = {"id_token": {"given_name": None}}
359+
session_id = self._create_session(req)
332360
grant = self.session_manager[session_id]
333-
grant.claims = {"id_token": {"given_name": None}}
334361
code = self._mint_code(grant, session_id)
335362
access_token = self._mint_access_token(grant, session_id, code)
336363

@@ -344,6 +371,11 @@ def test_id_token_payload_many_0(self):
344371
"nonce",
345372
"c_hash",
346373
"at_hash",
374+
"email",
375+
"email_verified",
376+
"jti",
377+
"scope",
378+
"client_id",
347379
"sub",
348380
"auth_time",
349381
"given_name",
@@ -391,9 +423,10 @@ def test_get_sign_algorithm(self):
391423
}
392424

393425
def test_available_claims(self):
394-
session_id = self._create_session(AREQ)
426+
req = dict(AREQ)
427+
req["claims"] = {"id_token": {"nickname": {"essential": True}}}
428+
session_id = self._create_session(req)
395429
grant = self.session_manager[session_id]
396-
grant.claims = {"id_token": {"nickname": {"essential": True}}}
397430

398431
id_token = self._mint_id_token(grant, session_id)
399432

@@ -497,11 +530,7 @@ def test_client_claims_scopes(self):
497530
grant = self.session_manager[session_id]
498531

499532
self.session_manager.token_handler["id_token"].kwargs["add_claims_by_scope"] = True
500-
501-
_claims = self.endpoint_context.claims_interface.get_claims(
502-
session_id=session_id, scopes=AREQS["scope"], claims_release_point="id_token"
503-
)
504-
grant.claims = {"id_token": _claims}
533+
grant.scope = AREQS["scope"]
505534

506535
id_token = self._mint_id_token(grant, session_id)
507536

@@ -519,11 +548,7 @@ def test_client_claims_scopes_and_request_claims_no_match(self):
519548
grant = self.session_manager[session_id]
520549

521550
self.session_manager.token_handler["id_token"].kwargs["add_claims_by_scope"] = True
522-
523-
_claims = self.endpoint_context.claims_interface.get_claims(
524-
session_id=session_id, scopes=AREQRC["scope"], claims_release_point="id_token"
525-
)
526-
grant.claims = {"id_token": _claims}
551+
grant.scope = AREQRC["scope"]
527552

528553
id_token = self._mint_id_token(grant, session_id)
529554

@@ -546,11 +571,7 @@ def test_client_claims_scopes_and_request_claims_one_match(self):
546571
grant = self.session_manager[session_id]
547572

548573
self.session_manager.token_handler["id_token"].kwargs["add_claims_by_scope"] = True
549-
550-
_claims = self.endpoint_context.claims_interface.get_claims(
551-
session_id=session_id, scopes=_req["scope"], claims_release_point="id_token"
552-
)
553-
grant.claims = {"id_token": _claims}
574+
grant.scope = _req["scope"]
554575

555576
id_token = self._mint_id_token(grant, session_id)
556577

tests/test_35_oidc_token_endpoint.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ def create_endpoint(self, conf):
197197
"response_types": ["code", "token", "code id_token", "id_token"],
198198
}
199199
endpoint_context.keyjar.import_jwks(CLIENT_KEYJAR.export_jwks(), "client_1")
200+
endpoint_context.userinfo = USERINFO
200201
self.session_manager = endpoint_context.session_manager
201202
self.token_endpoint = server.server_get("endpoint", "token")
202203
self.user_id = "diana"
@@ -591,6 +592,50 @@ def test_refresh_more_scopes_2(self):
591592

592593
assert at.scope == rt.scope == _request["scope"]
593594

595+
def test_refresh_less_scopes(self):
596+
areq = AUTH_REQ.copy()
597+
areq["scope"] = ["openid", "offline_access", "email"]
598+
599+
self.session_manager.token_handler.handler["id_token"].kwargs["add_claims_by_scope"] = True
600+
session_id = self._create_session(areq)
601+
grant = self.endpoint_context.authz(session_id, areq)
602+
code = self._mint_code(grant, areq["client_id"])
603+
604+
_token_request = TOKEN_REQ_DICT.copy()
605+
_token_request["code"] = code.value
606+
_req = self.token_endpoint.parse_request(_token_request)
607+
_resp = self.token_endpoint.process_request(request=_req)
608+
idtoken = AuthorizationResponse().from_jwt(
609+
_resp["response_args"]["id_token"],
610+
self.endpoint_context.keyjar,
611+
sender="",
612+
)
613+
614+
assert "email" in idtoken
615+
616+
_request = REFRESH_TOKEN_REQ.copy()
617+
_request["refresh_token"] = _resp["response_args"]["refresh_token"]
618+
_request["scope"] = ["openid", "offline_access"]
619+
620+
_token_value = _resp["response_args"]["refresh_token"]
621+
_session_info = self.session_manager.get_session_info_by_token(_token_value)
622+
_token = self.session_manager.find_token(_session_info["session_id"], _token_value)
623+
_token.usage_rules["supports_minting"] = [
624+
"access_token",
625+
"refresh_token",
626+
"id_token",
627+
]
628+
629+
_req = self.token_endpoint.parse_request(_request.to_json())
630+
_resp = self.token_endpoint.process_request(request=_req)
631+
idtoken = AuthorizationResponse().from_jwt(
632+
_resp["response_args"]["id_token"],
633+
self.endpoint_context.keyjar,
634+
sender="",
635+
)
636+
637+
assert "email" not in idtoken
638+
594639
def test_refresh_no_openid_scope(self):
595640
areq = AUTH_REQ.copy()
596641
areq["scope"] = ["openid", "offline_access"]

0 commit comments

Comments
 (0)