Skip to content
This repository was archived by the owner on Jun 23, 2023. It is now read-only.
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
35 changes: 31 additions & 4 deletions src/oidcop/oauth2/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def _mint_token(
session_id: str,
client_id: str,
based_on: Optional[SessionToken] = None,
scope: Optional[list] = None,
token_args: Optional[dict] = None,
token_type: Optional[str] = ""
) -> SessionToken:
Expand Down Expand Up @@ -80,6 +81,7 @@ def _mint_token(
token_handler=_mngr.token_handler[token_class],
based_on=based_on,
usage_rules=usage_rules,
scope=scope,
token_type=token_type,
**_args,
)
Expand Down Expand Up @@ -136,7 +138,6 @@ def process_request(self, req: Union[Message, dict], **kwargs):
_log_debug("All checks OK")

issue_refresh = kwargs.get("issue_refresh", False)

_response = {
"token_type": "Bearer",
"scope": grant.scope,
Expand Down Expand Up @@ -225,15 +226,29 @@ def process_request(self, req: Union[Message, dict], **kwargs):

token_value = req["refresh_token"]
_session_info = _mngr.get_session_info_by_token(token_value, grant=True)

_grant = _session_info["grant"]

token_type = "Bearer"

# Is DPOP supported
if "dpop_signing_alg_values_supported" in _context.provider_info:
_dpop_jkt = req.get("dpop_jkt")
if _dpop_jkt:
_grant.extra["dpop_jkt"] = _dpop_jkt
token_type = "DPoP"

token = _grant.get_token(token_value)
scope = _grant.find_scope(token.based_on)
if "scope" in req:
scope = req["scope"]
access_token = self._mint_token(
token_class="access_token",
grant=_grant,
session_id=_session_info["session_id"],
client_id=_session_info["client_id"],
based_on=token,
scope=scope,
token_type=token_type,
)

_resp = {
Expand All @@ -246,13 +261,15 @@ def process_request(self, req: Union[Message, dict], **kwargs):
_resp["expires_in"] = access_token.expires_at - utc_time_sans_frac()

_mints = token.usage_rules.get("supports_minting")
if "refresh_token" in _mints:
issue_refresh = kwargs.get("issue_refresh", False)
if "refresh_token" in _mints and issue_refresh:
refresh_token = self._mint_token(
token_class="refresh_token",
grant=_grant,
session_id=_session_info["session_id"],
client_id=_session_info["client_id"],
based_on=token,
scope=scope,
)
refresh_token.usage_rules = token.usage_rules.copy()
_resp["refresh_token"] = refresh_token.value
Expand Down Expand Up @@ -288,7 +305,8 @@ def post_parse_request(
logger.error("Access Code invalid")
return self.error_cls(error="invalid_grant")

token = _session_info["grant"].get_token(request["refresh_token"])
grant = _session_info["grant"]
token = grant.get_token(request["refresh_token"])

if not isinstance(token, RefreshToken):
return self.error_cls(error="invalid_request", error_description="Wrong token type")
Expand All @@ -298,6 +316,15 @@ def post_parse_request(
error="invalid_request", error_description="Refresh token inactive"
)

if "scope" in request:
req_scopes = set(request["scope"])
scopes = set(grant.find_scope(token.based_on))
if scopes < req_scopes:
return self.error_cls(
error="invalid_request",
error_description="Invalid refresh scopes",
)

return request


Expand Down
30 changes: 26 additions & 4 deletions src/oidcop/oidc/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,13 +202,17 @@ 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)
if "scope" in req:
scope = req["scope"]
access_token = self._mint_token(
token_class="access_token",
grant=_grant,
session_id=_session_info["session_id"],
client_id=_session_info["client_id"],
based_on=token,
token_type=token_type
scope=scope,
token_type=token_type,
)

_resp = {
Expand All @@ -221,25 +225,33 @@ def process_request(self, req: Union[Message, dict], **kwargs):
_resp["expires_in"] = access_token.expires_at - utc_time_sans_frac()

_mints = token.usage_rules.get("supports_minting")
if "refresh_token" in _mints:
issue_refresh = False
if "issue_refresh" in kwargs:
issue_refresh = kwargs["issue_refresh"]
else:
if "offline_access" in scope:
issue_refresh = True
if "refresh_token" in _mints and issue_refresh:
refresh_token = self._mint_token(
token_class="refresh_token",
grant=_grant,
session_id=_session_info["session_id"],
client_id=_session_info["client_id"],
based_on=token,
scope=scope,
)
refresh_token.usage_rules = token.usage_rules.copy()
_resp["refresh_token"] = refresh_token.value

if "id_token" in _mints:
if "id_token" in _mints and "openid" in scope:
try:
_idtoken = self._mint_token(
token_class="refresh_token",
grant=_grant,
session_id=_session_info["session_id"],
client_id=_session_info["client_id"],
based_on=token,
scope=scope,
)
except (JWEException, NoSuitableSigningKeys) as err:
logger.warning(str(err))
Expand Down Expand Up @@ -281,7 +293,8 @@ def post_parse_request(
logger.error("Access Code invalid")
return self.error_cls(error="invalid_grant")

token = _session_info["grant"].get_token(request["refresh_token"])
grant = _session_info["grant"]
token = grant.get_token(request["refresh_token"])

if not isinstance(token, RefreshToken):
return self.error_cls(error="invalid_request", error_description="Wrong token type")
Expand All @@ -291,6 +304,15 @@ def post_parse_request(
error="invalid_request", error_description="Refresh token inactive"
)

if "scope" in request:
req_scopes = set(request["scope"])
scopes = set(grant.find_scope(token.based_on))
if scopes < req_scopes:
return self.error_cls(
error="invalid_request",
error_description="Invalid refresh scopes",
)

return request


Expand Down
134 changes: 124 additions & 10 deletions tests/test_24_oauth2_token_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
AUTH_REQ = AuthorizationRequest(
client_id="client_1",
redirect_uri="https://example.com/cb",
scope=["openid"],
scope=["email"],
state="STATE",
response_type="code",
)
Expand Down Expand Up @@ -302,7 +302,7 @@ def test_process_request_using_private_key_jwt(self):

def test_do_refresh_access_token(self):
areq = AUTH_REQ.copy()
areq["scope"] = ["openid"]
areq["scope"] = ["email"]

session_id = self._create_session(areq)
grant = self.endpoint_context.authz(session_id, areq)
Expand All @@ -324,7 +324,7 @@ def test_do_refresh_access_token(self):
_token.usage_rules["supports_minting"] = ["access_token", "refresh_token"]

_req = self.token_endpoint.parse_request(_request.to_json())
_resp = self.token_endpoint.process_request(request=_req)
_resp = self.token_endpoint.process_request(request=_req, issue_refresh=True)
assert set(_resp.keys()) == {"cookie", "response_args", "http_headers"}
assert set(_resp["response_args"].keys()) == {
"access_token",
Expand All @@ -338,7 +338,7 @@ def test_do_refresh_access_token(self):

def test_do_2nd_refresh_access_token(self):
areq = AUTH_REQ.copy()
areq["scope"] = ["openid", "offline_access"]
areq["scope"] = ["email"]

session_id = self._create_session(areq)
grant = self.endpoint_context.authz(session_id, areq)
Expand All @@ -361,16 +361,15 @@ def test_do_2nd_refresh_access_token(self):
_token.usage_rules["supports_minting"] = [
"access_token",
"refresh_token",
"id_token",
]

_req = self.token_endpoint.parse_request(_request.to_json())
_resp = self.token_endpoint.process_request(request=_req)
_resp = self.token_endpoint.process_request(request=_req, issue_refresh=True)

_2nd_request = REFRESH_TOKEN_REQ.copy()
_2nd_request["refresh_token"] = _resp["response_args"]["refresh_token"]
_2nd_req = self.token_endpoint.parse_request(_request.to_json())
_2nd_resp = self.token_endpoint.process_request(request=_req)
_2nd_resp = self.token_endpoint.process_request(request=_req, issue_refresh=True)

assert set(_2nd_resp.keys()) == {"cookie", "response_args", "http_headers"}
assert set(_2nd_resp["response_args"].keys()) == {
Expand All @@ -393,7 +392,7 @@ def test_new_refresh_token(self, conf):
}

areq = AUTH_REQ.copy()
areq["scope"] = ["openid", "offline_access"]
areq["scope"] = ["email"]

session_id = self._create_session(areq)
grant = self.endpoint_context.authz(session_id, areq)
Expand Down Expand Up @@ -422,9 +421,124 @@ def test_new_refresh_token(self, conf):

assert first_refresh_token != second_refresh_token

def test_refresh_scopes(self):
areq = AUTH_REQ.copy()
areq["scope"] = ["email", "profile"]

session_id = self._create_session(areq)
grant = self.endpoint_context.authz(session_id, areq)
code = self._mint_code(grant, areq["client_id"])

_token_request = TOKEN_REQ_DICT.copy()
_token_request["code"] = code.value
_req = self.token_endpoint.parse_request(_token_request)
_resp = self.token_endpoint.process_request(request=_req, issue_refresh=True)

_request = REFRESH_TOKEN_REQ.copy()
_request["refresh_token"] = _resp["response_args"]["refresh_token"]
_request["scope"] = ["email"]

_req = self.token_endpoint.parse_request(_request.to_json())
_resp = self.token_endpoint.process_request(request=_req, issue_refresh=True)
assert set(_resp.keys()) == {"cookie", "response_args", "http_headers"}
assert set(_resp["response_args"].keys()) == {
"access_token",
"token_type",
"expires_in",
"refresh_token",
"scope",
}

_token_value = _resp["response_args"]["access_token"]
_session_info = self.session_manager.get_session_info_by_token(_token_value)
at = self.session_manager.find_token(
_session_info["session_id"], _token_value
)
rt = self.session_manager.find_token(
_session_info["session_id"], _resp["response_args"]["refresh_token"]
)

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

def test_refresh_more_scopes(self):
areq = AUTH_REQ.copy()
areq["scope"] = ["email"]

session_id = self._create_session(areq)
grant = self.endpoint_context.authz(session_id, areq)
code = self._mint_code(grant, areq["client_id"])

_token_request = TOKEN_REQ_DICT.copy()
_token_request["code"] = code.value
_req = self.token_endpoint.parse_request(_token_request)
_resp = self.token_endpoint.process_request(request=_req, issue_refresh=True)

_request = REFRESH_TOKEN_REQ.copy()
_request["refresh_token"] = _resp["response_args"]["refresh_token"]
_request["scope"] = ["email", "profile"]

_req = self.token_endpoint.parse_request(_request.to_json())
assert isinstance(_req, TokenErrorResponse)
_resp = self.token_endpoint.process_request(request=_req, issue_refresh=True)

assert _resp.to_dict() == {
"error": "invalid_request",
"error_description": "Invalid refresh scopes"
}

def test_refresh_more_scopes_2(self):
areq = AUTH_REQ.copy()
areq["scope"] = ["email", "profile"]

session_id = self._create_session(areq)
grant = self.endpoint_context.authz(session_id, areq)
code = self._mint_code(grant, areq["client_id"])

_token_request = TOKEN_REQ_DICT.copy()
_token_request["code"] = code.value
_req = self.token_endpoint.parse_request(_token_request)
_resp = self.token_endpoint.process_request(request=_req, issue_refresh=True)

_request = REFRESH_TOKEN_REQ.copy()
_request["refresh_token"] = _resp["response_args"]["refresh_token"]
_request["scope"] = ["email"]

_token_value = _resp["response_args"]["refresh_token"]

_req = self.token_endpoint.parse_request(_request.to_json())
_resp = self.token_endpoint.process_request(request=_req, issue_refresh=True)

_token_value = _resp["response_args"]["refresh_token"]
_request["refresh_token"] = _token_value
# We should be able to request the original requests scopes
_request["scope"] = ["email", "profile"]

_req = self.token_endpoint.parse_request(_request.to_json())
_resp = self.token_endpoint.process_request(request=_req, issue_refresh=True)

assert set(_resp.keys()) == {"cookie", "response_args", "http_headers"}
assert set(_resp["response_args"].keys()) == {
"access_token",
"token_type",
"expires_in",
"refresh_token",
"scope",
}

_token_value = _resp["response_args"]["access_token"]
_session_info = self.session_manager.get_session_info_by_token(_token_value)
at = self.session_manager.find_token(
_session_info["session_id"], _token_value
)
rt = self.session_manager.find_token(
_session_info["session_id"], _resp["response_args"]["refresh_token"]
)

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

def test_do_refresh_access_token_not_allowed(self):
areq = AUTH_REQ.copy()
areq["scope"] = ["openid", "offline_access"]
areq["scope"] = ["email"]

session_id = self._create_session(areq)
grant = self.endpoint_context.authz(session_id, areq)
Expand All @@ -448,7 +562,7 @@ def test_do_refresh_access_token_not_allowed(self):

def test_do_refresh_access_token_revoked(self):
areq = AUTH_REQ.copy()
areq["scope"] = ["openid"]
areq["scope"] = ["email"]

session_id = self._create_session(areq)
grant = self.endpoint_context.authz(session_id, areq)
Expand Down
Loading