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

Commit b618bd3

Browse files
authored
Merge pull request #49 from IdentityPython/develop
Changes necessary to make OIDC certification a success.
2 parents 06493f5 + 3ea017e commit b618bd3

29 files changed

+810
-353
lines changed

example/flask_rp/conf.json

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,7 @@
162162
"redirect_uris": [
163163
"https://{domain}:{port}/authz_cb/local"
164164
],
165-
"post_logout_redirect_uris": [
166-
"https://{domain}:{port}/session_logout/local"
167-
],
165+
"post_logout_redirect_uri": "https://{domain}:{port}/session_logout/local",
168166
"frontchannel_logout_uri": "https://{domain}:{port}/fc_logout/local",
169167
"frontchannel_logout_session_required": true,
170168
"backchannel_logout_uri": "https://{domain}:{port}/bc_logout/local",
@@ -231,9 +229,7 @@
231229
"redirect_uris": [
232230
"https://{domain}:{port}/authz_cb/django"
233231
],
234-
"post_logout_redirect_uris": [
235-
"https://{domain}:{port}/session_logout/django"
236-
],
232+
"post_logout_redirect_uris": "https://{domain}:{port}/session_logout/django",
237233
"frontchannel_logout_uri": "https://{domain}:{port}/fc_logout/django",
238234
"frontchannel_logout_session_required": true,
239235
"backchannel_logout_uri": "https://{domain}:{port}/bc_logout/django",

example/flask_rp/templates/opresult.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ <h3>Endpoints</h3>
1818
<dd>{{ url }}</dd>
1919
{% endfor %}
2020
</dl>
21+
<dl>
22+
<dt>ID Token</dt>
23+
<dd>{{ id_token }}</dd>
24+
</dl>
2125
<h3>User information</h3>
2226
<dl>
2327
{% for key, value in userinfo.items() %}

example/flask_rp/views.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,14 @@ def rp():
5353
uid = ''
5454

5555
if iss or uid:
56+
args = {
57+
'req_args': {
58+
"claims": {"id_token": {"acr": {"value": "https://refeds.org/profile/mfa"}}}
59+
}
60+
}
61+
5662
if uid:
57-
args = {'user_id': uid}
58-
else:
59-
args = {}
63+
args['user_id'] = uid
6064

6165
session['op_identifier'] = iss
6266
try:
@@ -145,14 +149,15 @@ def finalize(op_identifier, request_args):
145149
return render_template('opresult.html', endpoints=endpoints,
146150
userinfo=res['userinfo'],
147151
access_token=res['token'],
152+
id_token=res["id_token"],
148153
**kwargs)
149154
else:
150155
return make_response(res['error'], 400)
151156

152157

153158
def get_op_identifier_by_cb_uri(url: str):
154159
uri = splitquery(url)[0]
155-
for k,v in current_app.rph.issuer2rp.items():
160+
for k, v in current_app.rph.issuer2rp.items():
156161
_cntx = v.get_service_context()
157162
for endpoint in ("redirect_uris",
158163
"post_logout_redirect_uris",
@@ -180,10 +185,10 @@ def repost_fragment():
180185
return finalize(op_identifier, args)
181186

182187

183-
@oidc_rp_views.route('/ihf_cb')
184-
def ihf_cb(self, op_identifier='', **kwargs):
188+
@oidc_rp_views.route('/authz_im_cb')
189+
def authz_im_cb(op_identifier='', **kwargs):
185190
logger.debug('implicit_hybrid_flow kwargs: {}'.format(kwargs))
186-
return render_template('repost_fragment.html')
191+
return render_template('repost_fragment.html', op_identifier=op_identifier)
187192

188193

189194
@oidc_rp_views.route('/session_iframe')

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def run_tests(self):
7474
"Programming Language :: Python :: 3.9",
7575
"Topic :: Software Development :: Libraries :: Python Modules"],
7676
install_requires=[
77-
'oidcmsg==1.3.3-1',
77+
'oidcmsg==1.4.1',
7878
'pyyaml>=5.1.2',
7979
'responses'
8080
],

src/oidcrp/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import logging
22

33
__author__ = 'Roland Hedberg'
4-
__version__ = '2.0.1'
4+
__version__ = '2.1.0'
55

66
logger = logging.getLogger(__name__)
77

src/oidcrp/client_auth.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
from oidcmsg.oauth2 import SINGLE_OPTIONAL_STRING
1313
from oidcmsg.oidc import AuthnToken
1414
from oidcmsg.time_util import utc_time_sans_frac
15+
from oidcmsg.util import rndstr
1516

16-
from oidcrp.util import rndstr
1717
from oidcrp.util import sanitize
1818
from .defaults import DEF_SIGN_ALG
1919
from .defaults import JWT_BEARER

src/oidcrp/configure.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
try:
1515
from secrets import token_urlsafe as rnd_token
1616
except ImportError:
17-
from oidcendpoint import rndstr as rnd_token
17+
from cryptojwt import rndstr as rnd_token
1818

1919
DEFAULT_FILE_ATTRIBUTE_NAMES = ['server_key', 'server_cert', 'filename', 'template_dir',
2020
'private_path', 'public_path', 'db_file']

src/oidcrp/oauth2/__init__.py

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
from json import JSONDecodeError
22
import logging
3+
from typing import Optional
34

45
from oidcmsg.exception import FormatError
6+
from oidcmsg.message import Message
7+
from oidcmsg.oauth2 import is_error_message
58

69
from oidcrp.entity import Entity
10+
from oidcrp.exception import ConfigurationError
711
from oidcrp.exception import OidcServiceError
812
from oidcrp.exception import ParseError
913
from oidcrp.http import HTTPLib
1014
from oidcrp.service import REQUEST_INFO
1115
from oidcrp.service import SUCCESSFUL
16+
from oidcrp.service import Service
1217
from oidcrp.util import do_add_ons
1318
from oidcrp.util import get_deserialization_method
1419

@@ -72,7 +77,11 @@ def __init__(self, client_authn_factory=None, keyjar=None, verify_ssl=True, conf
7277
# just ignore verify_ssl until it goes away
7378
self.verify_ssl = self.httpc_params.get("verify", True)
7479

75-
def do_request(self, request_type, response_body_type="", request_args=None, **kwargs):
80+
def do_request(self,
81+
request_type: str,
82+
response_body_type: Optional[str] = "",
83+
request_args: Optional[dict] = None,
84+
behaviour_args: Optional[dict] = None, **kwargs):
7685
_srv = self._service[request_type]
7786

7887
_info = _srv.get_request_parameters(request_args=request_args, **kwargs)
@@ -93,8 +102,13 @@ def set_client_id(self, client_id):
93102
self.client_id = client_id
94103
self._service_context.set('client_id', client_id)
95104

96-
def get_response(self, service, url, method="GET", body=None, response_body_type="",
97-
headers=None, **kwargs):
105+
def get_response(self,
106+
service: Service,
107+
url: str,
108+
method: Optional[str] = "GET",
109+
body: Optional[dict] = None,
110+
response_body_type: Optional[str] = "",
111+
headers: Optional[dict] = None, **kwargs):
98112
"""
99113
100114
:param url:
@@ -129,8 +143,13 @@ def get_response(self, service, url, method="GET", body=None, response_body_type
129143
return self.parse_request_response(service, resp,
130144
response_body_type, **kwargs)
131145

132-
def service_request(self, service, url, method="GET", body=None,
133-
response_body_type="", headers=None, **kwargs):
146+
def service_request(self,
147+
service: Service,
148+
url: str,
149+
method: Optional[str] = "GET",
150+
body: Optional[dict] = None,
151+
response_body_type: Optional[str] = "",
152+
headers: Optional[dict] = None, **kwargs) -> Message:
134153
"""
135154
The method that sends the request and handles the response returned.
136155
This assumes that the response arrives in the HTTP response.
@@ -249,3 +268,27 @@ def parse_request_response(self, service, reqresp, response_body_type='',
249268
reqresp.text))
250269
raise OidcServiceError("HTTP ERROR: %s [%s] on %s" % (
251270
reqresp.text, reqresp.status_code, reqresp.url))
271+
272+
273+
def dynamic_provider_info_discovery(client: Client, behaviour_args: Optional[dict]=None):
274+
"""
275+
This is about performing dynamic Provider Info discovery
276+
277+
:param behaviour_args:
278+
:param client: A :py:class:`oidcrp.oidc.Client` instance
279+
"""
280+
try:
281+
client.get_service('provider_info')
282+
except KeyError:
283+
raise ConfigurationError(
284+
'Can not do dynamic provider info discovery')
285+
else:
286+
_context = client.client_get("service_context")
287+
try:
288+
_context.set('issuer', _context.config['srv_discovery_url'])
289+
except KeyError:
290+
pass
291+
292+
response = client.do_request('provider_info', behaviour_args=behaviour_args)
293+
if is_error_message(response):
294+
raise OidcServiceError(response['error'])

src/oidcrp/oauth2/authorization.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from oidcmsg.time_util import time_sans_frac
88

99
from oidcrp.oauth2.utils import get_state_parameter
10-
from oidcrp.oauth2.utils import pick_redirect_uris
10+
from oidcrp.oauth2.utils import pre_construct_pick_redirect_uri
1111
from oidcrp.oauth2.utils import set_state_parameter
1212
from oidcrp.service import Service
1313

@@ -32,7 +32,7 @@ class Authorization(Service):
3232
def __init__(self, client_get, client_authn_factory=None, conf=None):
3333
Service.__init__(self, client_get,
3434
client_authn_factory=client_authn_factory, conf=conf)
35-
self.pre_construct.extend([pick_redirect_uris, set_state_parameter])
35+
self.pre_construct.extend([pre_construct_pick_redirect_uri, set_state_parameter])
3636
self.post_construct.append(self.store_auth_request)
3737

3838
def update_service_context(self, resp, key='', **kwargs):

src/oidcrp/oauth2/utils.py

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
1+
import logging
2+
from typing import Optional
3+
from typing import Union
4+
15
from oidcmsg.exception import MissingParameter
6+
from oidcmsg.exception import MissingRequiredAttribute
7+
from oidcmsg.message import Message
8+
9+
from oidcrp.service import Service
10+
11+
logger = logging.getLogger(__name__)
212

313

414
def get_state_parameter(request_args, kwargs):
@@ -14,35 +24,48 @@ def get_state_parameter(request_args, kwargs):
1424
return _state
1525

1626

17-
def pick_redirect_uris(request_args=None, service=None, **kwargs):
18-
"""Pick one redirect_uri base on response_mode out of a list of such."""
19-
_context = service.client_get("service_context")
27+
def pick_redirect_uri(context,
28+
request_args: Optional[Union[Message, dict]] = None,
29+
response_type: Optional[str] = ''):
30+
if request_args is None:
31+
request_args = {}
2032

2133
if 'redirect_uri' in request_args:
22-
return request_args, {}
34+
return request_args["redirect_uri"]
2335

24-
_callback = _context.callback
25-
if _callback:
26-
try:
27-
_response_type = request_args['response_type']
28-
except KeyError:
29-
_response_type = _context.behaviour['response_types'][0]
30-
request_args['response_type'] = _response_type
36+
if context.redirect_uris:
37+
redirect_uri = context.redirect_uris[0]
38+
elif context.callback:
39+
if not response_type:
40+
_conf_resp_types = context.behaviour.get('response_types', [])
41+
response_type = request_args.get('response_type')
42+
if not response_type and _conf_resp_types:
43+
response_type = _conf_resp_types[0]
3144

32-
try:
33-
_response_mode = request_args['response_mode']
34-
except KeyError:
35-
_response_mode = ''
45+
_response_mode = request_args.get('response_mode')
3646

37-
if _response_mode == 'form_post':
38-
request_args['redirect_uri'] = _callback['form_post']
39-
elif _response_type == 'code':
40-
request_args['redirect_uri'] = _callback['code']
47+
if _response_mode == 'form_post' or response_type == ["form_post"]:
48+
redirect_uri = context.callback['form_post']
49+
elif response_type == 'code' or response_type == ["code"]:
50+
redirect_uri = context.callback['code']
4151
else:
42-
request_args['redirect_uri'] = _callback['implicit']
52+
redirect_uri = context.callback['implicit']
53+
54+
logger.debug(
55+
f"pick_redirect_uris: response_type={response_type}, response_mode={_response_mode}, "
56+
f"redirect_uri={redirect_uri}")
4357
else:
44-
request_args['redirect_uri'] = _context.redirect_uris[0]
58+
logger.error("No redirect_uri")
59+
raise MissingRequiredAttribute('redirect_uri')
4560

61+
return redirect_uri
62+
63+
64+
def pre_construct_pick_redirect_uri(request_args: Optional[Union[Message, dict]] = None,
65+
service: Optional[Service] = None, **kwargs):
66+
_context = service.client_get("service_context")
67+
request_args["redirect_uri"] = pick_redirect_uri(_context,
68+
request_args=request_args)
4669
return request_args, {}
4770

4871

src/oidcrp/oidc/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import logging
33

44
from oidcrp.client_auth import BearerHeader
5+
from oidcrp.oidc.registration import CALLBACK_URIS
56

67
try:
78
from json import JSONDecodeError
@@ -112,6 +113,15 @@ def __init__(self, client_authn_factory=None,
112113
keyjar=keyjar, verify_ssl=verify_ssl, config=config,
113114
httplib=httplib, services=_srvs, httpc_params=httpc_params)
114115

116+
_context = self.get_service_context()
117+
if _context.callback is None:
118+
_context.callback = {}
119+
120+
for _cb in CALLBACK_URIS:
121+
_uri = config.get(_cb)
122+
if _uri:
123+
_context.callback[_cb] = _uri
124+
115125
def fetch_distributed_claims(self, userinfo, callback=None):
116126
"""
117127

src/oidcrp/oidc/access_token.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import logging
22
from typing import Optional
3+
from typing import Union
34

45
from oidcmsg import oidc
6+
from oidcmsg.message import Message
57
from oidcmsg.oidc import verified_claim_name
68
from oidcmsg.time_util import time_sans_frac
79

@@ -26,7 +28,9 @@ def __init__(self,
2628
access_token.AccessToken.__init__(self, client_get,
2729
client_authn_factory=client_authn_factory, conf=conf)
2830

29-
def gather_verify_arguments(self):
31+
def gather_verify_arguments(self,
32+
response: Optional[Union[dict, Message]] = None,
33+
behaviour_args: Optional[dict] = None):
3034
"""
3135
Need to add some information before running verify()
3236

0 commit comments

Comments
 (0)