Skip to content

Commit 712625e

Browse files
committed
Mostly CIBA related things.
Fixed the case where new client authentication methods where added by the configuration.
1 parent fa5958e commit 712625e

File tree

9 files changed

+487
-27
lines changed

9 files changed

+487
-27
lines changed

src/idpyoidc/actor/__init__.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
from typing import Optional
2+
from uuid import uuid4
3+
4+
from cryptojwt.key_jar import KeyJar
5+
6+
from idpyoidc.impexp import ImpExp
7+
8+
9+
class CIBAClient(ImpExp):
10+
parameter = {
11+
"context": {}
12+
}
13+
14+
def __init__(
15+
self,
16+
keyjar: Optional[KeyJar] = None,
17+
):
18+
ImpExp.__init__(self)
19+
self.keyjar = keyjar
20+
self.server = None
21+
self.client = None
22+
self.context = {}
23+
24+
def create_authentication_request(self, scope, binding_message, login_hint):
25+
_service = self.client.client_get("service", "backchannel_authentication")
26+
27+
client_notification_token = uuid4().hex
28+
29+
request_args = {
30+
"scope": scope,
31+
"client_notification_token": client_notification_token,
32+
"binding_message": binding_message,
33+
"login_hint": login_hint
34+
}
35+
request = _service.get_request_parameters(request_args=request_args,
36+
authn_method="private_key_jwt")
37+
38+
self.context[client_notification_token] = {
39+
"authentication_request": request,
40+
"client_id": _service.client_get("service_context").issuer
41+
}
42+
return request
43+
44+
def get_client_id_from_token(self, token):
45+
_context = self.context[token]
46+
return _context["client_id"]
47+
48+
def do_client_notification(self, msg, http_info):
49+
_notification_endpoint = self.server.server_get("endpoint", "client_notification")
50+
_nreq = _notification_endpoint.parse_request(
51+
msg, http_info, get_client_id_from_token=self.get_client_id_from_token)
52+
_ninfo = _notification_endpoint.process_request(_nreq)
53+
54+
55+
class CIBAServer(ImpExp):
56+
parameter = {
57+
"context": {}
58+
}
59+
60+
def __init__(
61+
self,
62+
keyjar: Optional[KeyJar] = None,
63+
):
64+
ImpExp.__init__(self)
65+
self.keyjar = keyjar
66+
self.server = None
67+
self.client = None
68+
self.context = {}

src/idpyoidc/client/client_auth.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from idpyoidc.message.oidc import AuthnToken
1616
from idpyoidc.time_util import utc_time_sans_frac
1717
from idpyoidc.util import rndstr
18+
# from idpyoidc.oidc.backchannel_authentication import ClientNotificationAuthn
1819

1920
from ..message import VREQUIRED
2021
from .util import sanitize
@@ -589,6 +590,7 @@ def get_signing_key_from_keyjar(self, algorithm, service_context=None):
589590
"bearer_body": BearerBody,
590591
"client_secret_jwt": ClientSecretJWT,
591592
"private_key_jwt": PrivateKeyJWT,
593+
# "client_notification_authn": ClientNotificationAuthn
592594
}
593595

594596
TYPE_METHOD = [(JWT_BEARER, JWSAuthnMethod)]

src/idpyoidc/client/service.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,17 @@
1212
from idpyoidc.impexp import ImpExp
1313
from idpyoidc.item import DLDict
1414
from idpyoidc.message import Message
15-
from idpyoidc.message.oauth2 import ResponseMessage
1615
from idpyoidc.message.oauth2 import is_error_message
16+
from idpyoidc.message.oauth2 import ResponseMessage
1717
from idpyoidc.util import importer
18-
19-
from ..constant import JOSE_ENCODED
20-
from ..constant import JSON_ENCODED
21-
from ..constant import URL_ENCODED
2218
from .client_auth import factory as ca_factory
2319
from .configure import Configuration
2420
from .exception import ResponseError
2521
from .util import get_http_body
2622
from .util import get_http_url
23+
from ..constant import JOSE_ENCODED
24+
from ..constant import JSON_ENCODED
25+
from ..constant import URL_ENCODED
2726

2827
__author__ = 'Roland Hedberg'
2928

@@ -254,7 +253,7 @@ def init_authentication_method(self, request, authn_method,
254253
LOGGER.debug('Client authn method: %s', authn_method)
255254
try:
256255
_func = self.client_authn_factory(authn_method)
257-
except ValueError: # not one of the common
256+
except (KeyError, ValueError): # not one of the common
258257
_func = importer(authn_method)()
259258

260259
return _func.construct(request, self, http_args=http_args, **kwargs)

src/idpyoidc/server/__init__.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ def __init__(
5050
cookie_handler=cookie_handler,
5151
httpc=httpc,
5252
)
53-
self.endpoint_context.authz = self.do_authz()
53+
self.endpoint_context.authz = self.setup_authz()
5454

55-
self.do_authentication(self.endpoint_context)
55+
self.setup_authentication(self.endpoint_context)
5656

5757
self.endpoint = do_endpoints(conf, self.server_get)
5858
_cap = get_provider_capabilities(conf, self.endpoint)
@@ -68,10 +68,10 @@ def __init__(
6868
)
6969
self.endpoint_context.do_userinfo()
7070
# Must be done after userinfo
71-
self.do_login_hint_lookup()
71+
self.setup_login_hint_lookup()
7272
self.endpoint_context.set_remember_token()
7373

74-
self.do_client_authn_methods()
74+
self.setup_client_authn_methods()
7575
for endpoint_name, _ in self.endpoint.items():
7676
self.endpoint[endpoint_name].server_get = self.server_get
7777

@@ -107,14 +107,14 @@ def get_endpoint(self, endpoint_name, *arg):
107107
def get_endpoint_context(self, *arg):
108108
return self.endpoint_context
109109

110-
def do_authz(self):
110+
def setup_authz(self):
111111
authz_spec = self.conf.get("authz")
112112
if authz_spec:
113113
return init_service(authz_spec, self.server_get)
114114
else:
115115
return authz.Implicit(self.server_get)
116116

117-
def do_authentication(self, target):
117+
def setup_authentication(self, target):
118118
_conf = self.conf.get("authentication")
119119
if _conf:
120120
target.authn_broker = populate_authn_broker(
@@ -130,7 +130,7 @@ def do_authentication(self, target):
130130
except AttributeError:
131131
pass
132132

133-
def do_login_hint_lookup(self):
133+
def setup_login_hint_lookup(self):
134134
_conf = self.conf.get("login_hint_lookup")
135135
if _conf:
136136
_userinfo = None
@@ -146,7 +146,7 @@ def do_login_hint_lookup(self):
146146
self.endpoint_context.login_hint_lookup = init_service(_conf)
147147
self.endpoint_context.login_hint_lookup.userinfo = _userinfo
148148

149-
def do_client_authn_methods(self):
149+
def setup_client_authn_methods(self):
150150
self.endpoint_context.client_authn_method = client_auth_setup(self.server_get,
151151
self.conf.get(
152-
"client_authn_method"))
152+
"client_authn_methods"))

src/idpyoidc/server/client_authn.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,8 @@ def verify_client(
470470

471471
if http_info and "headers" in http_info:
472472
authorization_token = http_info["headers"].get("authorization")
473+
if not authorization_token:
474+
authorization_token = http_info["headers"].get("Authorization")
473475
else:
474476
authorization_token = None
475477

@@ -479,6 +481,7 @@ def verify_client(
479481
if not allowed_methods:
480482
allowed_methods = list(methods.keys())
481483

484+
_method = None
482485
for _method in (methods[meth] for meth in allowed_methods):
483486
if not _method.is_usable(request=request, authorization_token=authorization_token):
484487
continue
@@ -518,7 +521,7 @@ def verify_client(
518521
# Validate that the used method is allowed for this client/endpoint
519522
client_allowed_methods = _cinfo.get(f"{endpoint.endpoint_name}_client_authn_method",
520523
_cinfo.get("client_authn_method"))
521-
if client_allowed_methods is not None and _method.tag not in client_allowed_methods:
524+
if client_allowed_methods is not None and _method and _method.tag not in client_allowed_methods:
522525
logger.info(
523526
f"Allowed methods for client: {client_id} at endpoint: {endpoint.name} are: "
524527
f"`{', '.join(client_allowed_methods)}`"
@@ -542,8 +545,9 @@ def verify_client(
542545

543546
def client_auth_setup(server_get, auth_set=None):
544547
if auth_set is None:
545-
auth_set = {}
546-
auth_set = {**CLIENT_AUTHN_METHOD, **auth_set}
548+
auth_set = CLIENT_AUTHN_METHOD
549+
else:
550+
auth_set.update(CLIENT_AUTHN_METHOD)
547551
res = {}
548552

549553
for name, cls in auth_set.items():

src/idpyoidc/server/configure.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ class EntityConfiguration(Base):
145145
"capabilities": None,
146146
"claims_interface": None,
147147
"client_db": None,
148+
"client_authn_methods": {},
148149
"cookie_handler": None,
149150
"endpoint": {},
150151
"httpc_params": {},

src/idpyoidc/server/endpoint.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,11 +235,14 @@ def client_authentication(self, request: Message, http_info: Optional[dict] = No
235235
if "endpoint" not in kwargs:
236236
kwargs["endpoint"] = self
237237

238+
get_client_id_from_token = kwargs.get("get_client_id_from_token")
239+
if not get_client_id_from_token:
240+
kwargs["get_client_id_from_token"] = getattr(self, "get_client_id_from_token", None)
241+
238242
authn_info = verify_client(
239243
endpoint_context=self.server_get("endpoint_context"),
240244
request=request,
241245
http_info=http_info,
242-
get_client_id_from_token=getattr(self, "get_client_id_from_token", None),
243246
**kwargs
244247
)
245248

src/idpyoidc/server/oidc/backchannel_authentication.py

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
from idpyoidc.message.oidc.backchannel_authentication import AuthenticationRequest
1515
from idpyoidc.message.oidc.backchannel_authentication import AuthenticationResponse
1616
from idpyoidc.server import Endpoint
17+
from idpyoidc.server import EndpointContext
18+
from idpyoidc.server.client_authn import ClientSecretBasic
19+
from idpyoidc.server.exception import NoSuchAuthentication
1720
from idpyoidc.server.oidc.token_helper import AccessTokenHelper
1821
from idpyoidc.server.session.token import MintingNotAllowed
1922
from idpyoidc.server.util import execute
@@ -272,16 +275,49 @@ class ClientNotification(Endpoint):
272275
request_cls = AuthenticationRequest
273276
response_cls = AuthenticationResponse
274277
error_cls = ResponseMessage
275-
request_format = "urlencoded"
276-
response_format = "urlencoded"
277-
response_placement = "url"
278-
endpoint_name = "backchannel_authentication_endpoint"
279-
name = "backchannel_authentication"
278+
request_format = "json"
279+
endpoint_name = "client_notification_endpoint"
280+
name = "client_notification"
280281
provider_info_attributes = {
281-
"backchannel_token_delivery_modes_supported": ['poll', 'ping', 'push'],
282-
"backchannel_authentication_request_signing_alg_values_supported": None,
283-
"backchannel_user_code_parameter_supported": True,
282+
"backchannel_client_notification_endpoint": None,
284283
}
285284

286285
def __init__(self, server_get: Callable, **kwargs):
287286
Endpoint.__init__(self, server_get, **kwargs)
287+
288+
def process_request(
289+
self,
290+
request: Optional[Union[Message, dict]] = None,
291+
http_info: Optional[dict] = None,
292+
**kwargs
293+
) -> Union[Message, dict]:
294+
return {}
295+
296+
297+
class ClientNotificationAuthn(ClientSecretBasic):
298+
"""The authentication method used at the notification endpoint."""
299+
300+
tag = "client_notification_authn"
301+
302+
def is_usable(self, request=None, authorization_token=None):
303+
if authorization_token is not None and authorization_token.startswith("Bearer "):
304+
return True
305+
return False
306+
307+
def _verify(
308+
self,
309+
endpoint_context: EndpointContext,
310+
request: Optional[Union[dict, Message]] = None,
311+
authorization_token: Optional[str] = None,
312+
endpoint=None, # Optional[Endpoint]
313+
get_client_id_from_token: Optional[Callable] = None,
314+
**kwargs
315+
):
316+
ttype, token = authorization_token.split(" ", 1)
317+
if ttype != "Bearer":
318+
raise NoSuchAuthentication(f"No support for {ttype}")
319+
if get_client_id_from_token:
320+
client_id = get_client_id_from_token(token)
321+
else:
322+
client_id = ""
323+
return {"token": token, "client_id": client_id}

0 commit comments

Comments
 (0)