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

Commit a84270b

Browse files
committed
Add callback uris handled by the registration service instead of the RP handler.
1 parent 7bce59c commit a84270b

10 files changed

Lines changed: 274 additions & 121 deletions

src/oidcrp/oauth2/utils.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from typing import Union
44

55
from oidcmsg.exception import MissingParameter
6+
from oidcmsg.exception import MissingRequiredAttribute
67
from oidcmsg.message import Message
78

89
from oidcrp.service import Service
@@ -32,7 +33,9 @@ def pick_redirect_uri(context,
3233
if 'redirect_uri' in request_args:
3334
return request_args["redirect_uri"]
3435

35-
if context.callback:
36+
if context.redirect_uris:
37+
redirect_uri = context.redirect_uris[0]
38+
elif context.callback:
3639
if not response_type:
3740
_conf_resp_types = context.behaviour.get('response_types', [])
3841
response_type = request_args.get('response_type')
@@ -52,7 +55,8 @@ def pick_redirect_uri(context,
5255
f"pick_redirect_uris: response_type={response_type}, response_mode={_response_mode}, "
5356
f"redirect_uri={redirect_uri}")
5457
else:
55-
redirect_uri = context.redirect_uris[0]
58+
logger.error("No redirect_uri")
59+
raise MissingRequiredAttribute('redirect_uri')
5660

5761
return redirect_uri
5862

src/oidcrp/oidc/registration.py

Lines changed: 141 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
import hashlib
12
import logging
3+
from typing import List
4+
from typing import Optional
25

6+
from cryptojwt.utils import as_bytes
37
from oidcmsg import oidc
48
from oidcmsg.oauth2 import ResponseMessage
59

@@ -37,19 +41,133 @@ def response_types_to_grant_types(response_types):
3741
return list(_res)
3842

3943

40-
def add_request_uri(request_args=None, service=None, **kwargs):
41-
_context = service.client_get("service_context")
42-
if _context.requests_dir:
43-
_pi = _context.provider_info
44-
if _pi:
45-
_req = _pi.get('require_request_uri_registration', False)
46-
if _req is True:
47-
request_args['request_uris'] = _context.generate_request_uris(_context.requests_dir)
44+
def create_callbacks(issuer: str,
45+
hash_seed: str,
46+
base_url: str,
47+
code: Optional[bool] = False,
48+
implicit: Optional[bool] = False,
49+
form_post: Optional[bool] = False,
50+
request_uris: Optional[bool] = False,
51+
backchannel_logout_uri: Optional[bool] = False,
52+
frontchannel_logout_uri: Optional[bool] = False):
53+
"""
54+
To mitigate some security issues the redirect_uris should be OP/AS
55+
specific. This method creates a set of redirect_uris unique to the
56+
OP/AS.
57+
58+
:param frontchannel_logout_uri: Whether a front-channel logout uri should be constructed
59+
:param backchannel_logout_uri: Whether a back-channel logout uri should be constructed
60+
:param request_uri: Whether a request_uri should be constructed
61+
:param issuer: Issuer ID
62+
:return: A set of redirect_uris
63+
"""
64+
_hash = hashlib.sha256()
65+
_hash.update(hash_seed)
66+
_hash.update(as_bytes(issuer))
67+
_hex = _hash.hexdigest()
68+
69+
res = {'__hex': _hex}
70+
71+
if code:
72+
res['code'] = f"{base_url}/authz_cb/{_hex}"
73+
74+
if implicit:
75+
res['implicit'] = f"{base_url}/authz_im_cb/{_hex}"
76+
77+
if form_post:
78+
res['form_post'] = f"{base_url}/authz_fp_cb/{_hex}"
79+
80+
if request_uris:
81+
res["request_uris"] = f"{base_url}/req_uri/{_hex}"
82+
83+
if backchannel_logout_uri or frontchannel_logout_uri:
84+
res["post_logout_redirect_uris"] = f"{base_url}/session_logout/{_hex}"
85+
86+
if backchannel_logout_uri:
87+
res["backchannel_logout_uri"] = f"{base_url}/bc_logout/{_hex}"
88+
89+
if frontchannel_logout_uri:
90+
res["frontchannel_logout_uri"] = f"{base_url}/fc_logout/{_hex}"
91+
92+
logger.debug(f"Created callback URIs: {res}")
93+
return res
94+
95+
96+
def _cmp(a, b):
97+
if b is None: # Don't care about the value as long as there is one
98+
return True
99+
elif isinstance(a, str) and a == b:
100+
return True
101+
elif isinstance(a, list) and b in a:
102+
return True
103+
104+
return a == b
48105

49-
return request_args, {}
50106

107+
def _in_config_or_client_preferences(config, attr, val):
108+
_val = config.get("client_preferences", {}).get(attr)
109+
if _cmp(_val, val):
110+
return True
111+
_val = config.get(attr)
112+
return _cmp(_val, val)
51113

52-
def add_post_logout_redirect_uris(request_args=None, service=None, **kwargs):
114+
115+
def add_callbacks(context, ignore: Optional[List[str]] = None):
116+
if ignore is None:
117+
ignore = []
118+
_iss = context.get('issuer')
119+
120+
_uris = {}
121+
122+
_pi = context.get('provider_info')
123+
_cp = context.config.get("client_preferences")
124+
125+
if "redirect_uris" not in ignore:
126+
# code and/or implicit
127+
if _in_config_or_client_preferences(context.config, "response_types", "code"):
128+
_uris['code'] = True
129+
for rt in ["id_token", "id_token token", "code id_token token", "code idtoken",
130+
"code token"]:
131+
if _in_config_or_client_preferences(context.config, "response_types", rt):
132+
_uris["implicit"] = True
133+
break
134+
135+
if "form_post" not in ignore:
136+
if _in_config_or_client_preferences(context.config, "form_post_usable", True):
137+
_uris["form_post"] = True
138+
139+
if "request_uris" not in ignore:
140+
if 'require_request_uri_registration' in _pi and _in_config_or_client_preferences(
141+
context.config, "request_uri_usable", True):
142+
_uris['request_uris'] = True
143+
144+
if "frontchannel_logout_uri" not in ignore:
145+
if 'frontchannel_logout_supported' in _pi and _in_config_or_client_preferences(
146+
context.config, "frontchannel_logout_usable", True):
147+
_uris["frontchannel_logout_uri"] = True
148+
149+
if "backchannel_logout_uri" not in ignore:
150+
if 'backchannel_logout_supported' in _pi and _in_config_or_client_preferences(
151+
context.config, "backchannel_logout_usable", True):
152+
_uris["backchannel_logout_uri"] = True
153+
154+
callbacks = create_callbacks(_iss,
155+
hash_seed=context.get('hash_seed'),
156+
base_url=context.get("base_url"),
157+
**_uris)
158+
context.hash2issuer[callbacks['__hex']] = _iss
159+
160+
if "redirect_uris" not in ignore:
161+
_redirect_uris = [v for k, v in callbacks.items() if k in ["code", "implicit", "form_post"]]
162+
callbacks["redirect_uris"] = _redirect_uris
163+
context.set('callback', callbacks)
164+
165+
166+
CALLBACK_URIS = ["post_logout_redirect_uris", "backchannel_logout_uri", "frontchannel_logout_uri",
167+
"request_uris", 'redirect_uris']
168+
169+
170+
def add_callback_uris(request_args=None, service=None, **kwargs):
53171
"""
54172
55173
:param request_args:
@@ -59,10 +177,17 @@ def add_post_logout_redirect_uris(request_args=None, service=None, **kwargs):
59177
:return:
60178
"""
61179

62-
if "post_logout_redirect_uris" not in request_args:
63-
_uris = service.client_get("service_context").register_args.get("post_logout_redirect_uris")
64-
if _uris:
65-
request_args["post_logout_redirect_uris"] = _uris
180+
_context = service.client_get("service_context")
181+
_ignore = [k for k in list(request_args.keys()) if k in CALLBACK_URIS]
182+
add_callbacks(_context, ignore=_ignore)
183+
for _key in CALLBACK_URIS:
184+
_req_val = request_args.get(_key)
185+
if not _req_val:
186+
_uri = _context.register_args.get(_key)
187+
if _uri is None:
188+
_uri = _context.callback.get(_key)
189+
if _uri:
190+
request_args[_key] = _uri
66191

67192
return request_args, {}
68193

@@ -107,8 +232,8 @@ def __init__(self, client_get, client_authn_factory=None, conf=None):
107232
client_authn_factory=client_authn_factory,
108233
conf=conf)
109234
self.pre_construct = [self.add_client_behaviour_preference,
110-
add_redirect_uris, add_request_uri,
111-
add_post_logout_redirect_uris,
235+
#add_redirect_uris,
236+
add_callback_uris,
112237
add_jwks_uri_or_jwks]
113238
self.post_construct = [self.oidc_post_construct]
114239

src/oidcrp/rp_handler.py

Lines changed: 50 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import hashlib
21
import logging
32
import sys
43
import traceback
@@ -29,6 +28,8 @@
2928
from .exception import OidcServiceError
3029
from .oauth2 import Client
3130
from .oauth2.utils import pick_redirect_uri
31+
from .oidc.registration import CALLBACK_URIS
32+
from .oidc.registration import add_callbacks
3233
from .util import add_path
3334
from .util import dynamic_provider_info_discovery
3435
from .util import load_registration_response
@@ -237,33 +238,10 @@ def do_provider_info(self, client=None, state='', behaviour_args=None):
237238
except KeyError:
238239
return _context.get('issuer')
239240

240-
def add_callbacks(self, context):
241-
_iss = context.get('issuer')
242-
# Create the necessary callback URLs
243-
# as a side effect self.hash2issuer is set
244-
_extra_uris = {
245-
"request_uri": False,
246-
"backchannel_logout_uri": False,
247-
"frontchannel_logout_uri": False
248-
}
249-
_pi = context.get('provider_info')
250-
_cp = context.config.get("client_preferences")
251-
if 'require_request_uri_registration' in _pi and "request_uri_usable" in _cp:
252-
_extra_uris['request_uri'] = True
253-
if 'frontchannel_logout_supported' in _pi and "frontchannel_logout_usable" in _cp:
254-
_extra_uris["frontchannel_logout_uri"] = True
255-
if 'backchannel_logout_supported' in _pi and "backchannel_logout_usable" in _cp:
256-
_extra_uris["backchannel_logout_uri"] = True
257-
258-
callbacks = self.create_callbacks(_iss, **_extra_uris)
259-
260-
context.set('redirect_uris', [
261-
v for k, v in callbacks.items() if not k.startswith('__')])
262-
context.set('callback', callbacks)
263-
264241
def do_client_registration(self, client=None,
265242
iss_id: Optional[str] = '',
266243
state: Optional[str] = '',
244+
request_args: Optional[dict] = None,
267245
behaviour_args: Optional[dict] = None):
268246
"""
269247
Prepare for and do client registration if configured to do so
@@ -289,14 +267,19 @@ def do_client_registration(self, client=None,
289267
_context.post_logout_redirect_uris = [self.base_url]
290268

291269
if not _context.client_id: # means I have to do dynamic client registration
292-
if not _context.get('redirect_uris'):
293-
self.add_callbacks(_context)
270+
if request_args is None:
271+
request_args = {}
294272

295273
if behaviour_args:
296274
_params = RegistrationRequest().parameters()
297-
request_args = {k: v for k, v in behaviour_args.items() if k in _params}
298-
else:
299-
request_args = {}
275+
request_args.update({k: v for k, v in behaviour_args.items() if k in _params})
276+
277+
# _ignore = [k for k in list(request_args.keys()) if k in CALLBACK_URIS]
278+
# if _context.get('redirect_uris'):
279+
# if 'redirect_uris' not in _ignore:
280+
# _ignore.append('redirect_uris')
281+
#
282+
# add_callbacks(_context, _ignore)
300283

301284
load_registration_response(client, request_args=request_args)
302285

@@ -358,43 +341,43 @@ def client_setup(self, iss_id='', user='', behaviour_args=None):
358341
self.issuer2rp[issuer] = client
359342
return client
360343

361-
def create_callbacks(self, issuer, request_uri=False, backchannel_logout_uri=False,
362-
frontchannel_logout_uri=False):
363-
"""
364-
To mitigate some security issues the redirect_uris should be OP/AS
365-
specific. This method creates a set of redirect_uris unique to the
366-
OP/AS.
367-
368-
:param frontchannel_logout_uri: Whether a front-channel logout uri should be constructed
369-
:param backchannel_logout_uri: Whether a back-channel logout uri should be constructed
370-
:param request_uri: Whether a request_uri should be constructed
371-
:param issuer: Issuer ID
372-
:return: A set of redirect_uris
373-
"""
374-
_hash = hashlib.sha256()
375-
_hash.update(self.hash_seed)
376-
_hash.update(as_bytes(issuer))
377-
_hex = _hash.hexdigest()
378-
self.hash2issuer[_hex] = issuer
379-
res = {
380-
'code': "{}/authz_cb/{}".format(self.base_url, _hex),
381-
'implicit': "{}/authz_im_cb/{}".format(self.base_url, _hex),
382-
'form_post': "{}/authz_fp_cb/{}".format(self.base_url, _hex),
383-
'__hex': _hex
384-
}
385-
if request_uri:
386-
res["request_uri"] = f"{self.base_url}/req_uri/{_hex}"
387-
388-
if backchannel_logout_uri or frontchannel_logout_uri:
389-
res["post_logout_redirect_uris"] = f"{self.base_url}/session_logout/{_hex}"
390-
391-
if backchannel_logout_uri:
392-
res["backchannel_logout_uri"] = f"{self.base_url}/bc_logout/{_hex}"
393-
if frontchannel_logout_uri:
394-
res["frontchannel_logout_uri"] = f"{self.base_url}/fc_logout/{_hex}"
395-
396-
logger.debug(f"Created callback URIs: {res}")
397-
return res
344+
# def create_callbacks(self, issuer, request_uri=False, backchannel_logout_uri=False,
345+
# frontchannel_logout_uri=False):
346+
# """
347+
# To mitigate some security issues the redirect_uris should be OP/AS
348+
# specific. This method creates a set of redirect_uris unique to the
349+
# OP/AS.
350+
#
351+
# :param frontchannel_logout_uri: Whether a front-channel logout uri should be constructed
352+
# :param backchannel_logout_uri: Whether a back-channel logout uri should be constructed
353+
# :param request_uri: Whether a request_uri should be constructed
354+
# :param issuer: Issuer ID
355+
# :return: A set of redirect_uris
356+
# """
357+
# _hash = hashlib.sha256()
358+
# _hash.update(self.hash_seed)
359+
# _hash.update(as_bytes(issuer))
360+
# _hex = _hash.hexdigest()
361+
# self.hash2issuer[_hex] = issuer
362+
# res = {
363+
# 'code': "{}/authz_cb/{}".format(self.base_url, _hex),
364+
# 'implicit': "{}/authz_im_cb/{}".format(self.base_url, _hex),
365+
# 'form_post': "{}/authz_fp_cb/{}".format(self.base_url, _hex),
366+
# '__hex': _hex
367+
# }
368+
# if request_uri:
369+
# res["request_uri"] = f"{self.base_url}/req_uri/{_hex}"
370+
#
371+
# if backchannel_logout_uri or frontchannel_logout_uri:
372+
# res["post_logout_redirect_uris"] = f"{self.base_url}/session_logout/{_hex}"
373+
#
374+
# if backchannel_logout_uri:
375+
# res["backchannel_logout_uri"] = f"{self.base_url}/bc_logout/{_hex}"
376+
# if frontchannel_logout_uri:
377+
# res["frontchannel_logout_uri"] = f"{self.base_url}/fc_logout/{_hex}"
378+
#
379+
# logger.debug(f"Created callback URIs: {res}")
380+
# return res
398381

399382
def _get_response_type(self, context, req_args: Optional[dict] = None):
400383
if req_args:

0 commit comments

Comments
 (0)