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

Commit 8927805

Browse files
angelakisctriant
authored andcommitted
Add proper token exchange tests
Fix merge
1 parent d26bdb9 commit 8927805

2 files changed

Lines changed: 595 additions & 61 deletions

File tree

src/oidcop/oidc/token.py

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,173 @@ def post_parse_request(
352352

353353
return request
354354

355+
class TokenExchangeHelper(TokenEndpointHelper):
356+
"""Implements Token Exchange a.k.a. RFC8693"""
357+
358+
def __init__(self, endpoint, config=None):
359+
TokenEndpointHelper.__init__(self, endpoint=endpoint, config=config)
360+
361+
# TODO: should we even have a policy for the simple use cases?
362+
if config is None:
363+
self.policy = {}
364+
else:
365+
self.policy = config.get('policy', {})
366+
367+
# TODO: Make this a part of the policy. Note the distinction between
368+
# requested_token_type, subject_token_type, actor_token_type, issued_token_type
369+
self.token_types_allowed = [
370+
"urn:ietf:params:oauth:token-type:access_token",
371+
"urn:ietf:params:oauth:token-type:jwt",
372+
# "urn:ietf:params:oauth:token-type:id_token",
373+
# "urn:ietf:params:oauth:token-type:refresh_token",
374+
]
375+
376+
def post_parse_request(self, request, client_id="", **kwargs):
377+
request = TokenExchangeRequest(**request.to_dict())
378+
379+
# if "client_id" not in request:
380+
# request["client_id"] = client_id
381+
382+
keyjar = getattr(self.endpoint_context, "keyjar", "")
383+
384+
try:
385+
request.verify(keyjar=keyjar, opponent_id=client_id)
386+
except (
387+
MissingRequiredAttribute,
388+
ValueError,
389+
MissingRequiredValue,
390+
JWKESTException,
391+
) as err:
392+
return self.endpoint.error_cls(
393+
error="invalid_request", error_description="%s" % err
394+
)
395+
396+
397+
error = self.check_for_errors(request=request)
398+
if error is not None:
399+
return error
400+
401+
_mngr = self.endpoint_context.session_manager
402+
try:
403+
_session_info = _mngr.get_session_info_by_token(
404+
request["subject_token"], grant=True
405+
)
406+
except (KeyError, UnknownToken):
407+
logger.error("Subject token invalid.")
408+
return self.error_cls(
409+
error="invalid_request",
410+
error_description="Subject token invalid"
411+
)
412+
413+
token = _mngr.find_token(_session_info["session_id"], request["subject_token"])
414+
415+
if not isinstance(token, AccessToken):
416+
return self.error_cls(
417+
error="invalid_request", error_description="Wrong token type"
418+
)
419+
420+
if token.is_active() is False:
421+
return self.error_cls(
422+
error="invalid_request", error_description="Subject token inactive"
423+
)
424+
425+
return request
426+
427+
def check_for_errors(self, request):
428+
context = self.endpoint.endpoint_context
429+
if "resource" in request:
430+
iss = urlparse(context.issuer)
431+
if any(
432+
urlparse(res).netloc != iss.netloc for res in request["resource"]
433+
):
434+
return TokenErrorResponse(
435+
error="invalid_target", error_description="Unknown resource"
436+
)
437+
438+
if "audience" in request:
439+
if any(
440+
aud != context.issuer for aud in request["audience"]
441+
):
442+
return TokenErrorResponse(
443+
error="invalid_target", error_description="Unknown audience"
444+
)
445+
446+
# TODO: if requested type is jwt make sure our tokens are jwt
447+
if (
448+
"requested_token_type" in request
449+
and request["requested_token_type"] not in self.token_types_allowed
450+
):
451+
return TokenErrorResponse(
452+
error="invalid_target",
453+
error_description="Unsupported requested token type"
454+
)
455+
456+
if "actor_token" in request or "actor_token_type" in request:
457+
return TokenErrorResponse(
458+
error="invalid_request", error_description="Actor token not supported"
459+
)
460+
461+
# TODO: also check if the (valid) subject_token matches subject_token_type
462+
if request["subject_token_type"] not in self.token_types_allowed:
463+
return TokenErrorResponse(
464+
error="invalid_request",
465+
error_description="Unsupported subject token type",
466+
)
467+
468+
def token_exchange_response(self, token):
469+
response_args = {
470+
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
471+
"token_type": token.type,
472+
"access_token": token.value,
473+
"scope": token.scope,
474+
"expires_in": token.usage_rules["expires_in"]
475+
}
476+
return TokenExchangeResponse(**response_args)
477+
478+
def process_request(self, request, **kwargs):
479+
# TODO: should we even have a policy for the simple use cases?
480+
# client_policy = self.policy.get(req["client_id"]) or self.policy.get("default")
481+
# if not client_policy:
482+
# logger.error(
483+
# "TokenExchange policy for client {req['client_id']} or default missing."
484+
# )
485+
# return TokenErrorResponse(
486+
# error="invalid_request", error_description="Not allowed"
487+
# )
488+
_mngr = self.endpoint_context.session_manager
489+
try:
490+
_session_info = _mngr.get_session_info_by_token(
491+
request["subject_token"],
492+
grant=True,
493+
)
494+
except KeyError:
495+
logger.error("Subject token invalid.")
496+
return self.error_cls(
497+
error="invalid_grant",
498+
error_description="Subject token invalid",
499+
)
500+
501+
token = _mngr.find_token(_session_info["session_id"], request["subject_token"])
502+
grant = _session_info["grant"]
503+
504+
try:
505+
new_token = grant.mint_token(
506+
session_id=_session_info["session_id"],
507+
endpoint_context=self.endpoint_context,
508+
token_type='access_token',
509+
token_handler=_mngr.token_handler["access_token"],
510+
based_on=token,
511+
resources=request.get("resource"),
512+
scope=request.get("scope"),
513+
)
514+
except MintingNotAllowed:
515+
logger.error("Minting not allowed for 'access_token'")
516+
return self.error_cls(
517+
error="invalid_grant",
518+
error_description="Token Exchange not allowed with that token",
519+
)
520+
521+
return self.token_exchange_response(token=new_token)
355522

356523
class Token(oauth2.token.Token):
357524
request_cls = Message
@@ -367,4 +534,5 @@ class Token(oauth2.token.Token):
367534
helper_by_grant_type = {
368535
"authorization_code": AccessTokenHelper,
369536
"refresh_token": RefreshTokenHelper,
537+
"urn:ietf:params:oauth:grant-type:token-exchange": TokenExchangeHelper,
370538
}

0 commit comments

Comments
 (0)