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

Commit 9e03268

Browse files
committed
Introduce grant and session for token exchange
1 parent 10ed5ac commit 9e03268

File tree

4 files changed

+117
-24
lines changed

4 files changed

+117
-24
lines changed

docs/source/contents/conf.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -670,7 +670,7 @@ There are two possible ways to configure Token Exchange in OIDC-OP, globally and
670670
For the first case the configuration is passed in the Token Exchange handler throught the
671671
`urn:ietf:params:oauth:grant-type:token-exchange` dictionary in token's `grant_types_supported`.
672672

673-
If present, the token exchange configuration must contain a `policy` object that describes a default
673+
If present, the token exchange configuration may contain a `policy` object that describes a default
674674
policy `callable` and its `kwargs` through the `""` key. Different callables can be optionally
675675
defined for each token type supported.
676676

src/oidcop/oidc/token.py

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ def __init__(self, endpoint, config=None):
381381
],
382382
"policy": {
383383
"": {
384-
"callable": "oidcop.oidc.token.default_token_exchange_policy",
384+
"callable": default_token_exchange_policy,
385385
"kwargs": {
386386
"scope": ["openid"]
387387
}
@@ -491,17 +491,16 @@ def _enforce_policy(self, request, token, config):
491491
)
492492

493493
try:
494-
if (
495-
"requested_token_type" in request
496-
and request["requested_token_type"] in config["policy"]
497-
):
498-
callable = config["policy"][request["requested_token_type"]]["callable"]
499-
kwargs = config["policy"][request["requested_token_type"]]["kwargs"]
494+
subject_token_type = request.get("subject_token_type", "")
495+
if subject_token_type not in config["policy"]:
496+
subject_token_type = ""
497+
callable = config["policy"][subject_token_type]["callable"]
498+
kwargs = config["policy"][subject_token_type]["kwargs"]
499+
500+
if isinstance(callable, str):
501+
fn = importer(callable)
500502
else:
501-
callable = config["policy"][""]["callable"]
502-
kwargs = config["policy"][""]["kwargs"]
503-
504-
fn = importer(callable)
503+
fn = callable
505504
return fn(request, context=_context, subject_token=token, **kwargs)
506505

507506
except Exception:
@@ -515,7 +514,7 @@ def token_exchange_response(self, token):
515514
response_args["access_token"] = token.value
516515
response_args["scope"] = token.scope
517516
response_args["issued_token_type"] = token.token_class
518-
response_args["expires_in"] = token.usage_rules["expires_in"]
517+
response_args["expires_in"] = token.usage_rules.get("expires_in", 0)
519518
response_args["token_type"] = "bearer"
520519

521520
return TokenExchangeResponse(**response_args)
@@ -541,9 +540,7 @@ def process_request(self, request, **kwargs):
541540
)
542541

543542
token = _mngr.find_token(_session_info["session_id"], request["subject_token"])
544-
545543
grant = _session_info["grant"]
546-
547544
_requested_token_type = request.get("requested_token_type",
548545
"urn:ietf:params:oauth:token-type:access_token")
549546

@@ -555,24 +552,30 @@ def process_request(self, request, **kwargs):
555552

556553
sid = _session_info["session_id"]
557554
if request["client_id"] != _session_info["client_id"]:
558-
authn_event = _session_info["grant"].authentication_event
559555
_token_usage_rules = _context.authz.usage_rules(request["client_id"])
560-
_exp_in = _token_usage_rules["refresh_token"].get("expires_in")
561-
if _exp_in and "valid_until" in authn_event:
562-
authn_event["valid_until"] = utc_time_sans_frac() + _exp_in
563556

564-
sid = _mngr.create_session(
565-
authn_event=authn_event,
566-
auth_req=request,
557+
sid = _mngr.create_exchange_session(
558+
exchange_request=request,
559+
original_session_id=sid,
567560
user_id=_session_info["user_id"],
568561
client_id=request["client_id"],
569562
token_usage_rules=_token_usage_rules,
570563
)
571564

565+
try:
566+
_session_info = _mngr.get_session_info(
567+
session_id=sid, grant=True)
568+
except Exception:
569+
logger.error("Error retrieving token exchabge session information")
570+
return self.error_cls(
571+
error="server_error",
572+
error_description="Internal server error"
573+
)
574+
572575
try:
573576
new_token = self._mint_token(
574577
token_class=_token_class,
575-
grant=grant,
578+
grant=_session_info["grant"],
576579
session_id=sid,
577580
client_id=request["client_id"],
578581
based_on=token,

src/oidcop/session/grant.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -449,12 +449,14 @@ def __init__(
449449
authorization_details: Optional[dict] = None,
450450
issued_token: Optional[list] = None,
451451
usage_rules: Optional[dict] = None,
452+
exchange_request: str = "",
453+
original_session_id: str = "",
452454
issued_at: int = 0,
453455
expires_in: int = 0,
454456
expires_at: int = 0,
455457
revoked: bool = False,
456458
token_map: Optional[dict] = None,
457-
users: list = None,
459+
users: list = None
458460
):
459461
Grant.__init__(
460462
self,
@@ -475,3 +477,5 @@ def __init__(
475477
self.usage_rules = {
476478
"access_token": {"supports_minting": ["access_token"], "expires_in": 60}
477479
}
480+
self.exchange_request=exchange_request
481+
self.original_session_id=original_session_id

src/oidcop/session/manager.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import uuid
77

88
from oidcmsg.oauth2 import AuthorizationRequest
9+
from oidcmsg.oauth2 import TokenExchangeRequest
910

1011
from oidcop import rndstr
1112
from oidcop.authn_event import AuthnEvent
@@ -15,6 +16,7 @@
1516
from oidcop.session.database import NoSuchClientSession
1617
from .database import Database
1718
from .grant import Grant
19+
from .grant import ExchangeGrant
1820
from .grant import SessionToken
1921
from .info import ClientSessionInfo
2022
from .info import UserSessionInfo
@@ -198,6 +200,41 @@ def create_grant(
198200

199201
return self.encrypted_session_id(user_id, client_id, grant.id)
200202

203+
def create_exchange_grant(
204+
self,
205+
exchange_request: TokenExchangeRequest,
206+
original_session_id: str,
207+
user_id: str,
208+
client_id: Optional[str] = "",
209+
sub_type: Optional[str] = "public",
210+
token_usage_rules: Optional[dict] = None,
211+
scopes: Optional[list] = None,
212+
) -> str:
213+
"""
214+
215+
:param scopes: Scopes
216+
:param exchange_req:
217+
:param user_id:
218+
:param client_id:
219+
:param sub_type:
220+
:param token_usage_rules:
221+
:return:
222+
"""
223+
sector_identifier = exchange_request.get("sector_identifier_uri", "")
224+
225+
_claims = exchange_request.get("claims", {})
226+
227+
grant = ExchangeGrant(
228+
usage_rules=token_usage_rules,
229+
scope=scopes,
230+
claims=_claims,
231+
original_session_id=original_session_id,
232+
exchange_request=exchange_request
233+
)
234+
self.set([user_id, client_id, grant.id], grant)
235+
236+
return self.encrypted_session_id(user_id, client_id, grant.id)
237+
201238
def create_session(
202239
self,
203240
authn_event: AuthnEvent,
@@ -247,6 +284,55 @@ def create_session(
247284
scopes=scopes,
248285
)
249286

287+
def create_exchange_session(
288+
self,
289+
exchange_request: TokenExchangeRequest,
290+
original_session_id: str,
291+
user_id: str,
292+
client_id: Optional[str] = "",
293+
sub_type: Optional[str] = "public",
294+
token_usage_rules: Optional[dict] = None,
295+
scopes: Optional[list] = None,
296+
) -> str:
297+
"""
298+
Create part of a user session. The parts added are user- and client
299+
information and a grant.
300+
301+
:param scopes:
302+
:param authn_event: Authentication Event information
303+
:param auth_req: Authorization Request
304+
:param client_id: Client ID
305+
:param user_id: User ID
306+
:param sub_type: What kind of subject will be assigned
307+
:param token_usage_rules: Rules for how tokens can be used
308+
:return: Session key
309+
"""
310+
311+
try:
312+
_usi = self.get([user_id])
313+
except KeyError:
314+
_usi = UserSessionInfo(user_id=user_id)
315+
self.set([user_id], _usi)
316+
317+
if not client_id:
318+
client_id = exchange_request["client_id"]
319+
320+
try:
321+
self.get([user_id, client_id])
322+
except (NoSuchClientSession, ValueError):
323+
client_info = ClientSessionInfo(client_id=client_id)
324+
self.set([user_id, client_id], client_info)
325+
326+
return self.create_exchange_grant(
327+
exchange_request=exchange_request,
328+
original_session_id = original_session_id,
329+
user_id=user_id,
330+
client_id=client_id,
331+
sub_type=sub_type,
332+
token_usage_rules=token_usage_rules,
333+
scopes=scopes,
334+
)
335+
250336
def __getitem__(self, session_id: str):
251337
return self.get(self.decrypt_session_id(session_id))
252338

0 commit comments

Comments
 (0)