Skip to content

Commit 3c5dabd

Browse files
committed
Merge branch 'fedservice' of https://github.com/IdentityPython/idpy-oidc into fedservice
2 parents 4ae2762 + 6cf0d45 commit 3c5dabd

File tree

12 files changed

+769
-16
lines changed

12 files changed

+769
-16
lines changed

src/idpyoidc/client/entity.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,4 +201,3 @@ def import_keys(self, keyspec):
201201

202202
def get_callback_uris(self):
203203
return self.context.claims.callback_uri
204-

src/idpyoidc/defaults.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,3 @@
1212
JWT_BEARER = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
1313

1414
BASECHR = string.ascii_letters + string.digits
15-

src/idpyoidc/message/oauth2/__init__.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from idpyoidc.exception import MissingRequiredAttribute
1111
from idpyoidc.exception import VerificationError
1212
from idpyoidc.message import Message
13+
from idpyoidc.message import msg_ser
1314
from idpyoidc.message import OPTIONAL_LIST_OF_SP_SEP_STRINGS
1415
from idpyoidc.message import OPTIONAL_LIST_OF_STRINGS
1516
from idpyoidc.message import REQUIRED_LIST_OF_SP_SEP_STRINGS
@@ -20,7 +21,6 @@
2021
from idpyoidc.message import SINGLE_REQUIRED_BOOLEAN
2122
from idpyoidc.message import SINGLE_REQUIRED_INT
2223
from idpyoidc.message import SINGLE_REQUIRED_STRING
23-
from idpyoidc.message import msg_ser
2424

2525
logger = logging.getLogger(__name__)
2626

@@ -582,6 +582,33 @@ class JSONWebToken(Message):
582582
}
583583

584584

585+
# RFC 7009
586+
class TokenRevocationRequest(Message):
587+
c_param = {
588+
"token": SINGLE_REQUIRED_STRING,
589+
"token_type_hint": SINGLE_OPTIONAL_STRING,
590+
# The ones below are part of authentication information
591+
"client_id": SINGLE_OPTIONAL_STRING,
592+
"client_secret": SINGLE_OPTIONAL_STRING,
593+
}
594+
595+
596+
class TokenRevocationResponse(Message):
597+
pass
598+
599+
600+
class TokenRevocationErrorResponse(ResponseMessage):
601+
"""
602+
Error response from the revocation endpoint
603+
"""
604+
c_allowed_values = ResponseMessage.c_allowed_values.copy()
605+
c_allowed_values.update({
606+
"error": [
607+
"unsupported_token_type"
608+
]
609+
})
610+
611+
585612
def factory(msgtype, **kwargs):
586613
"""
587614
Factory method that can be used to easily instansiate a class instance

src/idpyoidc/server/authz/__init__.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,31 +61,39 @@ def __call__(
6161
request: Union[dict, Message],
6262
resources: Optional[list] = None,
6363
) -> Grant:
64-
session_info = self.upstream_get("context").session_manager.get_session_info(
64+
_context = self.upstream_get("context")
65+
session_info = _context.session_manager.get_session_info(
6566
session_id=session_id, grant=True
6667
)
6768
grant = session_info["grant"]
69+
_client_id = session_info['client_id']
6870

6971
args = self.grant_config.copy()
7072

7173
for key, val in args.items():
7274
if key == "expires_in":
7375
grant.set_expires_at(val)
7476
elif key == "usage_rules":
75-
setattr(grant, key, self.usage_rules(request.get("client_id")))
77+
setattr(grant, key, self.usage_rules(_client_id))
7678
else:
7779
setattr(grant, key, val)
7880

7981
if resources is None:
80-
grant.resources = [session_info["client_id"]]
82+
grant.resources = [_client_id]
8183
else:
8284
grant.resources = resources
8385

84-
# After this is where user consent should be handled
86+
# Scope handling. If allowed scopes are defined for the client filter using that
8587
scopes = grant.scope
8688
if not scopes:
8789
scopes = request.get("scope", [])
88-
grant.scope = scopes
90+
else:
91+
_allowed = _context.cdb[_client_id].get('allowed_scopes', [])
92+
if _allowed:
93+
scopes = list(set(scopes).intersection(set(_allowed)))
94+
grant.scope = scopes
95+
96+
# After this is where user consent should be handled
8997
grant.claims = self.upstream_get("context").claims_interface.get_claims_all_usage(
9098
session_id=session_id, scopes=scopes
9199
)

src/idpyoidc/server/oauth2/token.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ class Token(Endpoint):
3333
helper_by_grant_type = {
3434
"authorization_code": AccessTokenHelper,
3535
"refresh_token": RefreshTokenHelper,
36+
"urn:ietf:params:oauth:grant-type:token-exchange": TokenExchangeHelper,
3637
}
38+
token_exchange_helper = TokenExchangeHelper
3739

3840
def __init__(self, upstream_get, new_refresh_token=False, **kwargs):
3941
Endpoint.__init__(self, upstream_get, **kwargs)
@@ -132,7 +134,7 @@ def process_request(self, request: Optional[Union[Message, dict]] = None, **kwar
132134
_access_token = response_args["access_token"]
133135
_context = self.upstream_get("context")
134136

135-
if isinstance(_helper, TokenExchangeHelper):
137+
if isinstance(_helper, self.token_exchange_helper):
136138
_handler_key = _helper.get_handler_key(request, _context)
137139
else:
138140
_handler_key = "access_token"

src/idpyoidc/server/oauth2/token_helper.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434

3535
class TokenEndpointHelper(object):
36+
3637
def __init__(self, endpoint, config=None):
3738
self.endpoint = endpoint
3839
self.config = config
@@ -154,6 +155,7 @@ def validate_resource_indicators_policy(request, context, **kwargs):
154155

155156

156157
class AccessTokenHelper(TokenEndpointHelper):
158+
157159
def process_request(self, req: Union[Message, dict], **kwargs):
158160
"""
159161
@@ -341,6 +343,7 @@ def post_parse_request(
341343

342344

343345
class RefreshTokenHelper(TokenEndpointHelper):
346+
344347
def process_request(self, req: Union[Message, dict], **kwargs):
345348
_context = self.endpoint.upstream_get("context")
346349
_mngr = _context.session_manager
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
"""Implements RFC7009"""
2+
3+
import logging
4+
5+
from idpyoidc.exception import ImproperlyConfigured
6+
from idpyoidc.message import oauth2
7+
from idpyoidc.server.endpoint import Endpoint
8+
from idpyoidc.server.token.exception import UnknownToken
9+
from idpyoidc.server.token.exception import WrongTokenClass
10+
from idpyoidc.util import importer
11+
12+
logger = logging.getLogger(__name__)
13+
14+
15+
class TokenRevocation(Endpoint):
16+
"""Implements RFC7009"""
17+
18+
request_cls = oauth2.TokenRevocationRequest
19+
response_cls = oauth2.TokenRevocationResponse
20+
error_cls = oauth2.TokenRevocationErrorResponse
21+
request_format = "urlencoded"
22+
response_format = "json"
23+
endpoint_name = "revocation_endpoint"
24+
name = "token_revocation"
25+
default_capabilities = {
26+
"client_authn_method": [
27+
"client_secret_basic",
28+
"client_secret_post",
29+
"client_secret_jwt",
30+
"bearer_header",
31+
"private_key_jwt",
32+
]
33+
}
34+
35+
token_types_supported = ["authorization_code", "access_token", "refresh_token"]
36+
37+
def __init__(self, upstream_get, **kwargs):
38+
Endpoint.__init__(self, upstream_get, **kwargs)
39+
self.token_revocation_kwargs = kwargs
40+
41+
def get_client_id_from_token(self, endpoint_context, token, request=None):
42+
_info = endpoint_context.session_manager.get_session_info_by_token(
43+
token, handler_key="access_token"
44+
)
45+
return _info["client_id"]
46+
47+
def process_request(self, request=None, **kwargs):
48+
"""
49+
:param request: The revocation request as a dictionary
50+
:param kwargs:
51+
:return:
52+
"""
53+
_revoke_request = self.request_cls(**request)
54+
if "error" in _revoke_request:
55+
return _revoke_request
56+
57+
request_token = _revoke_request["token"]
58+
_resp = self.response_cls()
59+
_context = self.upstream_get("endpoint_context")
60+
logger.debug("Token Revocation")
61+
62+
try:
63+
_session_info = _context.session_manager.get_session_info_by_token(
64+
request_token, grant=True
65+
)
66+
except (UnknownToken, WrongTokenClass):
67+
return {"response_args": _resp}
68+
69+
client_id = _session_info["client_id"]
70+
if client_id != _revoke_request["client_id"]:
71+
logger.debug("{} owner of token".format(client_id))
72+
logger.warning("Client using token it was not given")
73+
return self.error_cls(error="invalid_grant", error_description="Wrong client")
74+
75+
grant = _session_info["grant"]
76+
_token = grant.get_token(request_token)
77+
78+
try:
79+
self.token_types_supported = _context.cdb[client_id]["token_revocation"][
80+
"token_types_supported"]
81+
except Exception:
82+
self.token_types_supported = self.token_revocation_kwargs.get("token_types_supported",
83+
self.token_types_supported)
84+
85+
try:
86+
self.policy = _context.cdb[client_id]["token_revocation"]["policy"]
87+
except Exception:
88+
self.policy = self.token_revocation_kwargs.get("policy", {
89+
"": {"callable": validate_token_revocation_policy}})
90+
91+
if _token.token_class not in self.token_types_supported:
92+
desc = (
93+
"The authorization server does not support the revocation of "
94+
"the presented token type. That is, the client tried to revoke an access "
95+
"token on a server not supporting this feature."
96+
)
97+
return self.error_cls(error="unsupported_token_type", error_description=desc)
98+
99+
return self._revoke(_revoke_request, _session_info)
100+
101+
def _revoke(self, request, session_info):
102+
_context = self.upstream_get("endpoint_context")
103+
_mngr = _context.session_manager
104+
_token = _mngr.find_token(session_info["branch_id"], request["token"])
105+
106+
_cls = _token.token_class
107+
if _cls not in self.policy:
108+
_cls = ""
109+
110+
temp_policy = self.policy[_cls]
111+
callable = temp_policy["callable"]
112+
kwargs = temp_policy.get("kwargs", {})
113+
114+
if isinstance(callable, str):
115+
try:
116+
fn = importer(callable)
117+
except Exception:
118+
raise ImproperlyConfigured(f"Error importing {callable} policy callable")
119+
else:
120+
fn = callable
121+
122+
try:
123+
return fn(_token, session_info=session_info, **kwargs)
124+
except Exception as e:
125+
logger.error(f"Error while executing the {fn} policy callable: {e}")
126+
return self.error_cls(error="server_error", error_description="Internal server error")
127+
128+
129+
def validate_token_revocation_policy(token, session_info, **kwargs):
130+
_token = token
131+
_token.revoke()
132+
133+
response_args = {"response_args": {}}
134+
return oauth2.TokenRevocationResponse(**response_args)

src/idpyoidc/server/oidc/token.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,5 @@ class Token(token.Token):
4242
"urn:openid:params:grant-type:ciba": CIBATokenHelper,
4343
"urn:ietf:params:oauth:grant-type:token-exchange": TokenExchangeHelper,
4444
}
45+
46+
token_exchange_helper = TokenExchangeHelper

src/idpyoidc/server/oidc/token_helper.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222

2323
class AccessTokenHelper(TokenEndpointHelper):
24+
2425
def _get_session_info(self, request, session_manager):
2526
if request["grant_type"] != "authorization_code":
2627
return self.error_cls(error="invalid_request", error_description="Unknown grant_type")
@@ -208,6 +209,7 @@ def post_parse_request(
208209

209210

210211
class RefreshTokenHelper(TokenEndpointHelper):
212+
211213
def process_request(self, req: Union[Message, dict], **kwargs):
212214
_context = self.endpoint.upstream_get("context")
213215
_mngr = _context.session_manager

src/idpyoidc/server/util.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,4 +169,3 @@ def execute(spec, **kwargs):
169169
return _func(**kwargs)
170170
else:
171171
return kwargs
172-

0 commit comments

Comments
 (0)