Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
rohe committed May 9, 2024
2 parents cf40d7f + ae942d4 commit 677c150
Show file tree
Hide file tree
Showing 31 changed files with 380 additions and 132 deletions.
2 changes: 1 addition & 1 deletion src/idpyoidc/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
__author__ = "Roland Hedberg"
__version__ = "4.0.0"
__version__ = "4.1.0"

VERIFIED_CLAIM_PREFIX = "__verified"

Expand Down
13 changes: 9 additions & 4 deletions src/idpyoidc/client/claims/transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,16 @@ def supported_to_preferred(
_pref_val = preference.get(key) # defined in configuration
_info_val = info.get(key)
if _info_val:
# Only use provider setting if less or equal to what I support
if key.endswith("supported"): # list
preference[key] = [x for x in _pref_val if x in _info_val]
if isinstance(_info_val, bool):
if _info_val is False and _pref_val is True:
# Turn off support if server doesn't support
preference[key] = _info_val
else:
pass
# Only use provider setting if less or equal to what I support
if key.endswith("supported"): # list
preference[key] = [x for x in _pref_val if x in _info_val]
else:
pass
elif val is None: # No default, means the RP does not have a preference
# if key not in ['jwks_uri', 'jwks']:
pass
Expand Down
4 changes: 1 addition & 3 deletions src/idpyoidc/client/client_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -520,9 +520,7 @@ def _get_audience_and_algorithm(self, context, keyjar, **kwargs):

def _construct_client_assertion(self, service, **kwargs):
_context = service.upstream_get("context")
_entity = service.upstream_get("entity")
if _entity is None:
_entity = service.upstream_get("unit")
_entity = service.upstream_get("unit")

_keyjar = service.upstream_get("attribute", "keyjar")
audience, algorithm = self._get_audience_and_algorithm(_context, _keyjar, **kwargs)
Expand Down
5 changes: 2 additions & 3 deletions src/idpyoidc/client/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,6 @@ def __init__(
)

self.setup_client_authn_methods(config)

self.upstream_get = upstream_get

def get_services(self, *arg):
Expand All @@ -170,8 +169,8 @@ def get_service_by_endpoint_name(self, endpoint_name, *arg):

return None

def get_entity(self):
return self
# def get_entity(self):
# return self

def get_client_id(self):
_val = self.context.claims.get_usage("client_id")
Expand Down
72 changes: 62 additions & 10 deletions src/idpyoidc/client/oauth2/add_on/dpop.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@
from typing import Optional

from cryptojwt.jwk.jwk import key_from_jwk_dict
from cryptojwt.jws.jws import JWS
from cryptojwt.jws.jws import factory
from cryptojwt.jws.jws import JWS
from cryptojwt.key_bundle import key_by_alg

from idpyoidc.client.client_auth import BearerHeader
from idpyoidc.client.client_auth import find_token_info
from idpyoidc.client.service_context import ServiceContext
from idpyoidc.message import Message
from idpyoidc.message import SINGLE_OPTIONAL_STRING
from idpyoidc.message import SINGLE_REQUIRED_INT
from idpyoidc.message import SINGLE_REQUIRED_JSON
from idpyoidc.message import SINGLE_REQUIRED_STRING
from idpyoidc.message import Message
from idpyoidc.metadata import get_signing_algs
from idpyoidc.time_util import utc_time_sans_frac

Expand Down Expand Up @@ -91,13 +93,13 @@ def verify_header(self, dpop_header) -> Optional["DPoPProof"]:


def dpop_header(
service_context: ServiceContext,
service_endpoint: str,
http_method: str,
headers: Optional[dict] = None,
token: Optional[str] = "",
nonce: Optional[str] = "",
**kwargs
service_context: ServiceContext,
service_endpoint: str,
http_method: str,
headers: Optional[dict] = None,
token: Optional[str] = "",
nonce: Optional[str] = "",
**kwargs
) -> dict:
"""
Expand Down Expand Up @@ -159,7 +161,7 @@ def dpop_header(
return headers


def add_support(services, dpop_signing_alg_values_supported):
def add_support(services, dpop_signing_alg_values_supported, with_dpop_header=None):
"""
Add the necessary pieces to make pushed authorization happen.
Expand All @@ -185,3 +187,53 @@ def add_support(services, dpop_signing_alg_values_supported):
_userinfo_service = services.get("userinfo")
if _userinfo_service:
_userinfo_service.construct_extra_headers.append(dpop_header)
# To be backward compatible
if with_dpop_header is None:
with_dpop_header = ["userinfo"]

# Add dpop HTTP header to these
for _srv in with_dpop_header:
if _srv == "accesstoken":
continue
_service = services.get(_srv)
if _service:
_service.construct_extra_headers.append(dpop_header)


class DPoPClientAuth(BearerHeader):
tag = "dpop_client_auth"

def construct(self, request=None, service=None, http_args=None, **kwargs):
"""
Constructing the Authorization header. The value of
the Authorization header is "Bearer <access_token>".
:param request: Request class instance
:param service: The service this authentication method applies to.
:param http_args: HTTP header arguments
:param kwargs: extra keyword arguments
:return:
"""

_token_type = "access_token"

_token_info = find_token_info(request, _token_type, service, **kwargs)

if not _token_info:
raise KeyError("No bearer token available")

# The authorization value starts with the token_type
# if _token_info["token_type"].to_lower() != "bearer":
_bearer = f"DPoP {_token_info[_token_type]}"

# Add 'Authorization' to the headers
if http_args is None:
http_args = {"headers": {}}
http_args["headers"]["Authorization"] = _bearer
else:
try:
http_args["headers"]["Authorization"] = _bearer
except KeyError:
http_args["headers"] = {"Authorization": _bearer}

return http_args
6 changes: 1 addition & 5 deletions src/idpyoidc/client/oauth2/add_on/par.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,9 @@ def push_authorization(request_args, service, **kwargs):
_context.client_authn_methods[_name] = execute(spec)
authn_method = _name

_args = {}
_args = kwargs.copy()
if _context.issuer:
_args["iss"] = _context.issuer
if _name == "client_attestation":
_wia = kwargs.get("client_attestation")
if _wia:
_args["client_attestation"] = _wia

_headers = service.get_headers(
request_args, http_method=_http_method, authn_method=authn_method, **_args
Expand Down
26 changes: 15 additions & 11 deletions src/idpyoidc/client/oauth2/server_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def get_endpoint(self):
:return: Service endpoint
"""
try:
_iss = self.upstream_get("context").issuer
_iss = self.upstream_get("attribute","issuer")
except AttributeError:
_iss = self.endpoint

Expand Down Expand Up @@ -72,13 +72,16 @@ def _verify_issuer(self, resp, issuer):

# In some cases we can live with the two URLs not being
# the same. But this is an excepted that has to be explicit
try:
self.upstream_get("context").allow["issuer_mismatch"]
except KeyError:
if _issuer != _pcr_issuer:
raise OidcServiceError(
"provider info issuer mismatch '%s' != '%s'" % (_issuer, _pcr_issuer)
)
_allow = self.upstream_get("attribute", "allow")
if _allow:
_allowed = _allow.get("issuer_mismatch", None)
if _allowed:
return _issuer

if _issuer != _pcr_issuer:
raise OidcServiceError(
"provider info issuer mismatch '%s' != '%s'" % (_issuer, _pcr_issuer)
)
return _issuer

def _set_endpoints(self, resp):
Expand Down Expand Up @@ -131,9 +134,10 @@ def _update_service_context(self, resp):
# is loaded not necessarily that any keys are fetched.
if "jwks_uri" in resp:
LOGGER.debug(f"'jwks_uri' in provider info: {resp['jwks_uri']}")
_hp = self.upstream_get('entity').httpc_params
if "verify" in _hp and "verify" not in _keyjar.httpc_params:
_keyjar.httpc_params["verify"] = _hp["verify"]
_hp = self.upstream_get("attribute","httpc_params")
if _hp:
if "verify" in _hp and "verify" not in _keyjar.httpc_params:
_keyjar.httpc_params["verify"] = _hp["verify"]
_keyjar.load_keys(_pcr_issuer, jwks_uri=resp["jwks_uri"])
_loaded = True
elif "jwks" in resp:
Expand Down
1 change: 0 additions & 1 deletion src/idpyoidc/client/oidc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ def __init__(
jwks_uri: Optional[str] = "",
**kwargs
):
self.upstream_get = upstream_get
if services:
_srvs = services
else:
Expand Down
2 changes: 1 addition & 1 deletion src/idpyoidc/client/oidc/access_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def gather_verify_arguments(
:return: dictionary with arguments to the verify call
"""
_context = self.upstream_get("context")
_entity = self.upstream_get("entity")
_entity = self.upstream_get("unit")

kwargs = {
"client_id": _entity.get_client_id(),
Expand Down
2 changes: 1 addition & 1 deletion src/idpyoidc/client/oidc/authorization.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def update_service_context(self, resp, key="", **kwargs):
_context.cstate.update(key, resp)

def get_request_from_response(self, response):
_context = self.upstream_get("service_context")
_context = self.upstream_get("context")
return _context.cstate.get_set(response["state"], message=oauth2.AuthorizationRequest)

def post_parse_response(self, response, **kwargs):
Expand Down
2 changes: 1 addition & 1 deletion src/idpyoidc/client/oidc/provider_info_discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def add_redirect_uris(request_args, service=None, **kwargs):
:param kwargs: Possible extra keyword arguments
:return: A possibly augmented set of request arguments.
"""
_work_environment = service.upstream_get("context").claims
_work_environment = service.upstream_get("attribute", "claims")
if "redirect_uris" not in request_args:
# Callbacks is a dictionary with callback type 'code', 'implicit',
# 'form_post' as keys.
Expand Down
4 changes: 4 additions & 0 deletions src/idpyoidc/client/service_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ def __init__(
self.httpc_params = {}
self.client_secret_expires_at = 0
self.registration_response = {}
self.client_authn_methods = {}

# _def_value = copy.deepcopy(DEFAULT_VALUE)

Expand Down Expand Up @@ -189,6 +190,9 @@ def __init__(

self.construct_uris(response_types=_response_types)

self.map_supported_to_preferred()
self.map_preferred_to_registered()

def __setitem__(self, key, value):
setattr(self, key, value)

Expand Down
6 changes: 3 additions & 3 deletions src/idpyoidc/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,9 @@ def get_unit(self, *args):
def topmost_unit(unit):
if hasattr(unit, "upstream_get"):
if unit.upstream_get:
next_unit = unit.upstream_get("unit")
if next_unit:
unit = topmost_unit(next_unit)
superior = unit.upstream_get("unit")
if superior:
unit = topmost_unit(superior)

return unit

Expand Down
1 change: 0 additions & 1 deletion src/idpyoidc/server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ def __init__(
issuer_id=self.issuer,
)

self.upstream_get = upstream_get
if isinstance(conf, OPConfiguration) or isinstance(conf, ASConfiguration):
self.conf = conf
else:
Expand Down
2 changes: 1 addition & 1 deletion src/idpyoidc/server/authz/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def __call__(
grant.scope = scopes

# After this is where user consent should be handled
grant.claims = self.upstream_get("context").claims_interface.get_claims_all_usage(
grant.claims = _context.claims_interface.get_claims_all_usage(
session_id=session_id, scopes=scopes
)

Expand Down
12 changes: 5 additions & 7 deletions src/idpyoidc/server/endpoint_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ def __init__(
self.dev_auth_db = None
_interface = conf.get("claims_interface")
if _interface:
self.claims_interface = init_service(_interface, self.upstream_get)
self.claims_interface = init_service(_interface, self.unit_get)

if isinstance(conf, OPConfiguration):
conf = conf.conf
Expand Down Expand Up @@ -276,9 +276,7 @@ def setup_authz(self):
return authz.Implicit(self.unit_get)

def setup_client_authn_methods(self):
self.client_authn_methods = client_auth_setup(
self.upstream_get, self.conf.get("client_authn_methods")
)
self.client_authn_methods = client_auth_setup(self.unit_get, self.conf.get("client_authn_methods"))

def setup_login_hint_lookup(self):
_conf = self.conf.get("login_hint_lookup")
Expand Down Expand Up @@ -307,10 +305,10 @@ def set_scopes_handler(self):
if _spec:
_kwargs = _spec.get("kwargs", {})
_cls = importer(_spec["class"])
self.scopes_handler = _cls(self.upstream_get, **_kwargs)
self.scopes_handler = _cls(self.unit_get, **_kwargs)
else:
self.scopes_handler = Scopes(
self.upstream_get,
self.unit_get,
allowed_scopes=self.conf.get("allowed_scopes"),
scopes_to_claims=self.conf.get("scopes_to_claims"),
)
Expand Down Expand Up @@ -440,7 +438,7 @@ def setup_authentication(self):
_conf = self.conf.get("authentication")
if _conf:
self.authn_broker = populate_authn_broker(
_conf, self.upstream_get, self.template_handler
_conf, self.unit_get, self.template_handler
)
else:
self.authn_broker = {}
Expand Down
Loading

0 comments on commit 677c150

Please sign in to comment.