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

Commit 61d5641

Browse files
committed
Fixed fetch_distributed_claims.
Updated to work with cryptojwt=0.4.4, oidcmsg=0.6.1 and oidcservice=0.5.13
1 parent e5fca20 commit 61d5641

10 files changed

Lines changed: 263 additions & 79 deletions

File tree

.pytest_cache/v/cache/lastfailed

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2+
"tests/test_14_oidc.py::TestClient::()::test_service_request": true,
23
"tests/test_20_rp_handler.py::TestRPHandler::()::test_get_accesstoken": true,
34
"tests/test_20_rp_handler.py::TestRPHandler::()::test_get_userinfo": true
45
}

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Contributing to JwtConnect
1+
# Contributing to JwtConnect-Python
22

33
All contributions to the Python JwtConnect packages are welcome!
44

src/oidcrp/__init__.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import traceback
55
from importlib import import_module
66

7-
from cryptojwt import as_bytes
7+
from cryptojwt.utils import as_bytes
88
from oidcmsg.oauth2 import is_error_message
99
from oidcmsg.oauth2 import ResponseMessage
1010
from oidcmsg.oidc import AccessTokenResponse
@@ -22,7 +22,7 @@
2222
from oidcrp import provider
2323

2424
__author__ = 'Roland Hedberg'
25-
__version__ = '0.4.9'
25+
__version__ = '0.5.0'
2626

2727
logger = logging.getLogger(__name__)
2828

@@ -494,7 +494,8 @@ def get_access_token(self, state, client=None):
494494
state=state
495495
)
496496
except Exception as err:
497-
logger.error("%s", err)
497+
message = traceback.format_exception(*sys.exc_info())
498+
logger.error(message)
498499
raise
499500
else:
500501
if is_error_message(tokenresp):
@@ -529,7 +530,8 @@ def refresh_access_token(self, state, client=None, scope=''):
529530
state=state, request_args=req_args
530531
)
531532
except Exception as err:
532-
logger.error("%s", err)
533+
message = traceback.format_exception(*sys.exc_info())
534+
logger.error(message)
533535
raise
534536
else:
535537
if is_error_message(tokenresp):
@@ -596,6 +598,8 @@ def finalize_auth(self, client, issuer, response):
596598
sformat='dict')
597599
except Exception as err:
598600
logger.error('Parsing authorization_response: {}'.format(err))
601+
message = traceback.format_exception(*sys.exc_info())
602+
logger.error(message)
599603
raise
600604
else:
601605
logger.debug(

src/oidcrp/cookie.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@
99

1010
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
1111

12-
from cryptojwt import as_bytes
13-
from cryptojwt import as_unicode
14-
from cryptojwt import safe_str_cmp
15-
from cryptojwt.jwe import JWEException
16-
from cryptojwt.jwe import split_ctx_and_tag
12+
from cryptojwt.utils import as_bytes
13+
from cryptojwt.utils import as_unicode
14+
from cryptojwt.jwe.exception import JWEException
15+
from cryptojwt.jwe.utils import split_ctx_and_tag
1716

1817
from oidcservice import rndstr
1918
from oidcservice.exception import ImproperlyConfigured
@@ -35,6 +34,16 @@ class InvalidCookieSign(Exception):
3534
pass
3635

3736

37+
# 'Stolen' from Werkzeug
38+
def safe_str_cmp(a, b):
39+
"""Compare two strings in constant time."""
40+
if len(a) != len(b):
41+
return False
42+
r = 0
43+
for c, d in zip(a, b):
44+
r |= ord(c) ^ ord(d)
45+
return r == 0
46+
3847
def _expiration(timeout, time_format=None):
3948
"""
4049
Return an expiration time

src/oidcrp/http.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import requests
88

9-
from oidcmsg.key_jar import KeyJar
9+
from cryptojwt.key_jar import KeyJar
1010

1111
from oidcservice import sanitize
1212
from oidcservice.exception import NonFatalException

src/oidcrp/oauth2/__init__.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import logging
22

33
import cherrypy
4-
from oidcmsg.key_jar import KeyJar
4+
5+
from cryptojwt.key_jar import KeyJar
6+
57
from oidcservice.client_auth import factory as ca_factory
68
from oidcservice.exception import OidcServiceError
79
from oidcservice.exception import ParseError
@@ -10,6 +12,7 @@
1012
from oidcservice.service import SUCCESSFUL
1113
from oidcservice.service import build_services
1214
from oidcservice.service_context import ServiceContext
15+
from oidcservice.state_interface import StateInterface
1316

1417
from oidcrp.http import HTTPLib
1518
from oidcrp.util import get_deserialization_method
@@ -59,7 +62,7 @@ def __init__(self, state_db, ca_certs=None, client_authn_factory=None,
5962
:return: Client instance
6063
"""
6164

62-
self.state_db = state_db
65+
self.session_interface = StateInterface(state_db)
6366
self.http = httplib or HTTPLib(ca_certs=ca_certs,
6467
verify_ssl=verify_ssl,
6568
client_cert=client_cert,

src/oidcrp/oidc/__init__.py

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import json
12
import logging
23

4+
from oidcservice.client_auth import BearerHeader
5+
36
try:
47
from json import JSONDecodeError
58
except ImportError: # Only works for >= 3.5
@@ -62,6 +65,10 @@
6265
}
6366

6467

68+
class FetchException(Exception):
69+
pass
70+
71+
6572
class RP(oauth2.Client):
6673
def __init__(self, state_db, ca_certs=None, client_authn_factory=None,
6774
keyjar=None, verify_ssl=True, config=None, client_cert=None,
@@ -76,12 +83,11 @@ def __init__(self, state_db, ca_certs=None, client_authn_factory=None,
7683
httplib=httplib, services=_srvs,
7784
service_factory=service_factory)
7885

79-
def fetch_distributed_claims(self, userinfo, service, callback=None):
86+
def fetch_distributed_claims(self, userinfo, callback=None):
8087
"""
8188
82-
:param userinfo: A :py:class:`oidcmsg.message.Message` sub class instance
83-
:param service: Possibly an instance of the
84-
:py:class:`oidcservice.oidc.service.UserInfo` class
89+
:param userinfo: A :py:class:`oidcmsg.message.Message` sub class
90+
instance
8591
:param callback: A function that can be used to fetch things
8692
:return: Updated userinfo instance
8793
"""
@@ -93,27 +99,41 @@ def fetch_distributed_claims(self, userinfo, service, callback=None):
9399
for csrc, spec in _csrc.items():
94100
if "endpoint" in spec:
95101
if "access_token" in spec:
96-
_uinfo = self.service_request(
97-
service, spec["endpoint"], method='GET',
98-
token=spec["access_token"])
102+
cauth = BearerHeader()
103+
http_args = cauth.construct(
104+
service=self.service['userinfo'],
105+
access_token= spec['access_token'])
106+
_resp = self.http.send(spec["endpoint"], 'GET',
107+
**http_args)
99108
else:
100109
if callback:
101-
_uinfo = self.service_request(
102-
service, spec["endpoint"], method='GET',
103-
token=callback(spec['endpoint']))
110+
token = callback(spec['endpoint'])
111+
cauth = BearerHeader()
112+
http_args = cauth.construct(
113+
service=self.service['userinfo'],
114+
access_token=token)
115+
_resp = self.http.send(
116+
spec["endpoint"], 'GET', **http_args)
104117
else:
105-
_uinfo = self.service_request(
106-
service, spec["endpoint"], method='GET')
118+
_resp = self.http.send(spec["endpoint"], 'GET')
119+
120+
if _resp.status_code == 200:
121+
_uinfo = json.loads(_resp.text)
122+
else: # There shouldn't be any redirect
123+
raise FetchException(
124+
'HTTP error {}: {}'.format(_resp.status_code,
125+
_resp.reason))
107126

108127
claims = [value for value, src in
109128
userinfo["_claim_names"].items() if src == csrc]
110129

111-
if set(claims) != set(list(_uinfo.keys())):
130+
if set(claims) != set(_uinfo.keys()):
112131
logger.warning(
113132
"Claims from claim source doesn't match what's in "
114133
"the userinfo")
115134

116-
for key, vals in _uinfo.items():
117-
userinfo[key] = vals
135+
# only add those I expected
136+
for key in claims:
137+
userinfo[key] = _uinfo[key]
118138

119139
return userinfo

tests/test_11_oauth2.py

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@
33
import sys
44
import time
55

6-
from cryptojwt.jwk import rsa_load
6+
from cryptojwt.jwk.rsa import rsa_load
7+
from cryptojwt.key_bundle import KeyBundle
78

8-
from oidcmsg.key_bundle import KeyBundle
9-
from oidcmsg.oauth2 import AccessTokenRequest, AuthorizationResponse
9+
from oidcmsg.oauth2 import AccessTokenRequest
1010
from oidcmsg.oauth2 import AccessTokenResponse
1111
from oidcmsg.oauth2 import AuthorizationRequest
12+
from oidcmsg.oauth2 import AuthorizationResponse
1213
from oidcmsg.oauth2 import RefreshAccessTokenRequest
1314
from oidcmsg.oidc import IdToken
1415
from oidcmsg.time_util import utc_time_sans_frac
1516

16-
from oidcservice.client_auth import CLIENT_AUTHN_METHOD
1717
from oidcservice.state_interface import State
1818

1919
from oidcrp.oauth2 import Client
@@ -60,7 +60,7 @@ def test_construct_authorization_request(self):
6060
'redirect_uri': 'https://example.com/auth_cb',
6161
'response_type': ['code']}
6262

63-
self.client.state_db.set('ABCDE', State(iss='issuer').to_json())
63+
self.client.session_interface.create_state('issuer',key='ABCDE')
6464
msg = self.client.service['authorization'].construct(
6565
request_args=req_args)
6666
assert isinstance(msg, AuthorizationRequest)
@@ -71,17 +71,24 @@ def test_construct_accesstoken_request(self):
7171
# Bind access code to state
7272
req_args = {}
7373

74+
self.client.session_interface.create_state('issuer', 'ABCDE')
75+
7476
auth_request = AuthorizationRequest(
7577
redirect_uri='https://example.com/cli/authz_cb',
7678
state='state'
7779
)
80+
81+
self.client.session_interface.store_item(auth_request, 'auth_request',
82+
'ABCDE')
83+
7884
auth_response = AuthorizationResponse(code='access_code')
79-
_state = State(auth_response=auth_response.to_json(),
80-
auth_request=auth_request.to_json())
81-
self.client.state_db.set('ABCDE', _state.to_json())
85+
86+
self.client.session_interface.store_item(auth_response,
87+
'auth_response', 'ABCDE')
8288

8389
msg = self.client.service['accesstoken'].construct(
8490
request_args=req_args, state='ABCDE')
91+
8592
assert isinstance(msg, AccessTokenRequest)
8693
assert msg.to_dict() == {'client_id': 'client_1',
8794
'code': 'access_code',
@@ -92,18 +99,28 @@ def test_construct_accesstoken_request(self):
9299
'state': 'state'}
93100

94101
def test_construct_refresh_token_request(self):
102+
103+
self.client.session_interface.create_state('issuer', 'ABCDE')
104+
95105
auth_request = AuthorizationRequest(
96106
redirect_uri='https://example.com/cli/authz_cb',
97107
state='state'
98108
)
109+
110+
self.client.session_interface.store_item(auth_request, 'auth_request',
111+
'ABCDE')
112+
99113
auth_response = AuthorizationResponse(code='access_code')
114+
115+
self.client.session_interface.store_item(auth_response,
116+
'auth_response', 'ABCDE')
117+
100118
token_response = AccessTokenResponse(refresh_token="refresh_with_me",
101119
access_token="access")
102-
_state = State(auth_response=auth_response.to_json(),
103-
auth_request=auth_request.to_json(),
104-
token_response=token_response.to_json())
105120

106-
self.client.state_db.set('ABCDE', _state.to_json())
121+
self.client.session_interface.store_item(token_response,
122+
'token_response', 'ABCDE')
123+
107124
req_args = {}
108125
msg = self.client.service['refresh_token'].construct(
109126
request_args=req_args, state='ABCDE')

0 commit comments

Comments
 (0)