Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 46 additions & 18 deletions src/idpyoidc/server/oauth2/token_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def _mint_token(
token_args = meth(_context, client_id, token_args)

if token_args:
_args = {"token_args": token_args}
_args = token_args
else:
_args = {}

Expand Down Expand Up @@ -258,7 +258,6 @@ def process_request(self, req: Union[Message, dict], **kwargs):
if (
issue_refresh
and "refresh_token" in _supports_minting
and "refresh_token" in grant_types_supported
):
try:
refresh_token = self._mint_token(
Expand Down Expand Up @@ -370,7 +369,7 @@ def process_request(self, req: Union[Message, dict], **kwargs):
token_type = "DPoP"

token = _grant.get_token(token_value)
scope = _grant.find_scope(token.based_on)
scope = _grant.find_scope(token)
if "scope" in req:
scope = req["scope"]
access_token = self._mint_token(
Expand Down Expand Up @@ -543,6 +542,27 @@ def post_parse_request(self, request, client_id="", **kwargs):
)

resp = self._enforce_policy(request, token, config)
if isinstance(resp, TokenErrorResponse):
return resp

scopes = resp.get("scope", [])
scopes = _context.scopes_handler.filter_scopes(scopes, client_id=resp["client_id"])

if not scopes:
logger.error("All requested scopes have been filtered out.")
return self.error_cls(
error="invalid_scope", error_description="Invalid requested scopes"
)

_requested_token_type = resp.get(
"requested_token_type", "urn:ietf:params:oauth:token-type:access_token"
)
_token_class = self.token_types_mapping[_requested_token_type]
if _token_class == "refresh_token" and "offline_access" not in scopes:
return TokenErrorResponse(
error="invalid_request",
error_description="Exchanging this subject token to refresh token forbidden",
)

return resp

Expand Down Expand Up @@ -572,7 +592,7 @@ def _enforce_policy(self, request, token, config):
error_description="Unsupported requested token type",
)

request_info = dict(scope=request.get("scope", []))
request_info = dict(scope=request.get("scope", token.scope))
try:
check_unknown_scopes_policy(request_info, request["client_id"], _context)
except UnAuthorizedClientScope:
Expand Down Expand Up @@ -602,11 +622,11 @@ def _enforce_policy(self, request, token, config):
logger.error(f"Error while executing the {fn} policy callable: {e}")
return self.error_cls(error="server_error", error_description="Internal server error")

def token_exchange_response(self, token):
def token_exchange_response(self, token, issued_token_type):
response_args = {}
response_args["access_token"] = token.value
response_args["scope"] = token.scope
response_args["issued_token_type"] = token.token_class
response_args["issued_token_type"] = issued_token_type

if token.expires_at:
response_args["expires_in"] = token.expires_at - utc_time_sans_frac()
Expand Down Expand Up @@ -636,6 +656,7 @@ def process_request(self, request, **kwargs):
error="invalid_request", error_description="Subject token invalid"
)

grant = _session_info["grant"]
token = _mngr.find_token(_session_info["branch_id"], request["subject_token"])
_requested_token_type = request.get(
"requested_token_type", "urn:ietf:params:oauth:token-type:access_token"
Expand All @@ -650,16 +671,19 @@ def process_request(self, request, **kwargs):
if "dpop_signing_alg_values_supported" in _context.provider_info:
if request.get("dpop_jkt"):
_token_type = "DPoP"
scopes = request.get("scope", [])

if request["client_id"] != _session_info["client_id"]:
_token_usage_rules = _context.authz.usage_rules(request["client_id"])

sid = _mngr.create_exchange_session(
exchange_request=request,
original_grant=grant,
original_session_id=sid,
user_id=_session_info["user_id"],
client_id=request["client_id"],
token_usage_rules=_token_usage_rules,
scopes=scopes,
)

try:
Expand All @@ -676,25 +700,30 @@ def process_request(self, request, **kwargs):
else:
resources = request.get("audience")

_token_args = None
if resources:
_token_args = {"resources": resources}

try:
new_token = self._mint_token(
token_class=_token_class,
grant=_session_info["grant"],
session_id=sid,
client_id=request["client_id"],
based_on=token,
scope=request.get("scope"),
token_args={"resources": resources},
scope=scopes,
token_args=_token_args,
token_type=_token_type,
)
new_token.expires_at = token.expires_at
except MintingNotAllowed:
logger.error(f"Minting not allowed for {_token_class}")
return self.error_cls(
error="invalid_grant",
error_description="Token Exchange not allowed with that token",
)

return self.token_exchange_response(token=new_token)
return self.token_exchange_response(new_token, _requested_token_type)

def _validate_configuration(self, config):
if "requested_token_types_supported" not in config:
Expand Down Expand Up @@ -763,14 +792,13 @@ def validate_token_exchange_policy(request, context, subject_token, **kwargs):
f"forbidden",
)

if "scope" in request:
scopes = list(set(request.get("scope")).intersection(kwargs.get("scope")))
if scopes:
request["scope"] = scopes
else:
return TokenErrorResponse(
error="invalid_request",
error_description="No supported scope requested",
)
scopes = request.get("scope", subject_token.scope)
scopes = list(set(scopes).intersection(subject_token.scope))
if kwargs.get("scope"):
scopes = list(set(scopes).intersection(kwargs.get("scope")))
if scopes:
request["scope"] = scopes
else:
request.pop("scope")

return request
85 changes: 84 additions & 1 deletion src/idpyoidc/server/session/grant.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ def payload_arguments(
endpoint_context,
item: SessionToken,
claims_release_point: str,
scope: Optional[dict] = None,
extra_payload: Optional[dict] = None,
secondary_identifier: str = "",
) -> dict:
Expand Down Expand Up @@ -211,6 +212,10 @@ def payload_arguments(

payload["jti"] = uuid1().hex

if scope is None:
scope = self.scope
payload["scope"] = scope

if extra_payload:
payload.update(extra_payload)

Expand Down Expand Up @@ -359,6 +364,7 @@ def mint_token(
endpoint_context,
item=item,
claims_release_point=claims_release_point,
scope=scope,
extra_payload=handler_args,
secondary_identifier=_secondary_identifier,
)
Expand Down Expand Up @@ -474,7 +480,7 @@ def get_usage_rules(token_type, endpoint_context, grant, client_id):

class ExchangeGrant(Grant):
parameter = Grant.parameter.copy()
parameter.update({"users": []})
parameter.update({"exchange_request": TokenExchangeRequest, "original_session_id": ""})
type = "exchange_grant"

def __init__(
Expand All @@ -483,6 +489,8 @@ def __init__(
claims: Optional[dict] = None,
resources: Optional[list] = None,
authorization_details: Optional[dict] = None,
authorization_request: Optional[Message] = None,
authentication_event: Optional[AuthnEvent] = None,
issued_token: Optional[list] = None,
usage_rules: Optional[dict] = None,
exchange_request: Optional[TokenExchangeRequest] = None,
Expand All @@ -501,6 +509,8 @@ def __init__(
claims=claims,
resources=resources,
authorization_details=authorization_details,
authorization_request=authorization_request,
authentication_event=authentication_event,
issued_token=issued_token,
usage_rules=usage_rules,
issued_at=issued_at,
Expand All @@ -517,3 +527,76 @@ def __init__(
}
self.exchange_request = exchange_request
self.original_branch_id = original_branch_id

def payload_arguments(
self,
session_id: str,
endpoint_context,
item: SessionToken,
claims_release_point: str,
scope: Optional[dict] = None,
extra_payload: Optional[dict] = None,
secondary_identifier: str = "",
) -> dict:
"""
:param session_id: Session ID
:param endpoint_context: EndPoint Context
:param claims_release_point: One of "userinfo", "introspection", "id_token", "access_token"
:param scope: scope from the request
:param extra_payload:
:param secondary_identifier: Used if the claims returned are also based on rules for
another release_point
:param item: A SessionToken instance
:type item: SessionToken
:return: dictionary containing information to place in a token value
"""
payload = {}
for _in, _out in [("scope", "scope"), ("resources", "aud")]:
_val = getattr(item, _in)
if _val:
payload[_out] = _val
else:
_val = getattr(self, _in)
if _val:
payload[_out] = _val

payload["jti"] = uuid1().hex

if scope is None:
scope = self.scope

payload = {"scope": scope, "aud": self.resources, "jti": uuid1().hex}

if extra_payload:
payload.update(extra_payload)

_jkt = self.extra.get("dpop_jkt")
if _jkt:
payload["cnf"] = {"jkt": _jkt}

if self.exchange_request:
client_id = self.exchange_request.get("client_id")
if client_id:
payload.update({"client_id": client_id, "sub": self.sub})

if item.claims:
_claims_restriction = item.claims
else:
_claims_restriction = endpoint_context.claims_interface.get_claims(
session_id,
scopes=scope,
claims_release_point=claims_release_point,
secondary_identifier=secondary_identifier,
)

user_id, _, _ = endpoint_context.session_manager.decrypt_session_id(session_id)
user_info = endpoint_context.claims_interface.get_user_claims(user_id, _claims_restriction)
payload.update(user_info)

# Should I add the acr value
if self.add_acr_value(claims_release_point):
payload["acr"] = self.authentication_event["authn_info"]
elif self.add_acr_value(secondary_identifier):
payload["acr"] = self.authentication_event["authn_info"]

return payload
7 changes: 6 additions & 1 deletion src/idpyoidc/server/session/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ def create_grant(
def create_exchange_grant(
self,
exchange_request: TokenExchangeRequest,
original_grant: Grant,
original_session_id: str,
user_id: str,
client_id: Optional[str] = "",
Expand All @@ -241,11 +242,13 @@ def create_exchange_grant(
"""

return self.add_exchange_grant(
authentication_event=original_grant.authentication_event,
authorization_request=original_grant.authorization_request,
exchange_request=exchange_request,
original_branch_id=original_session_id,
path=self.make_path(user_id=user_id, client_id=client_id),
sub=original_grant.sub,
token_usage_rules=token_usage_rules,
sub=self.sub_func[sub_type](user_id, salt=self.get_salt(), sector_identifier=""),
scope=scopes
)

Expand Down Expand Up @@ -286,6 +289,7 @@ def create_session(
def create_exchange_session(
self,
exchange_request: TokenExchangeRequest,
original_grant: Grant,
original_session_id: str,
user_id: str,
client_id: Optional[str] = "",
Expand All @@ -309,6 +313,7 @@ def create_exchange_session(

return self.create_exchange_grant(
exchange_request=exchange_request,
original_grant=original_grant,
original_session_id=original_session_id,
user_id=user_id,
client_id=client_id,
Expand Down
Loading