11import base64
22import logging
3+ from urllib .parse import unquote_plus
34
45from cryptojwt .exception import BadSignature
56from cryptojwt .exception import Invalid
89from cryptojwt .jwt import utc_time_sans_frac
910from cryptojwt .utils import as_bytes
1011from cryptojwt .utils import as_unicode
12+ from oidcmsg .oidc import JsonWebToken
1113from oidcmsg .oidc import verified_claim_name
1214
1315from oidcendpoint import JWT_BEARER
1618from oidcendpoint .exception import MultipleUsage
1719from oidcendpoint .exception import NotForMe
1820from oidcendpoint .exception import UnknownClient
21+ from oidcendpoint .util import importer
1922
2023logger = logging .getLogger (__name__ )
2124
@@ -52,7 +55,17 @@ def verify(self, **kwargs):
5255 :param kwargs:
5356 :return:
5457 """
55- raise NotImplementedError
58+ raise NotImplementedError ()
59+
60+ def is_usable (self , request = None , authorization_info = None ):
61+ """
62+ Verify that this authentication method is applicable.
63+
64+ :param request: The request
65+ :param authorization_info: Other authorization information
66+ :return: True/False
67+ """
68+ raise NotImplementedError ()
5669
5770
5871def basic_authn (authn ):
@@ -62,7 +75,7 @@ def basic_authn(authn):
6275 _tok = as_bytes (authn [6 :])
6376 # Will raise ValueError type exception if not base64 encoded
6477 _tok = base64 .b64decode (_tok )
65- part = as_unicode (_tok ).split (":" )
78+ part = [ unquote_plus ( p ) for p in as_unicode (_tok ).split (":" )]
6679 if len (part ) == 2 :
6780 return dict (zip (["id" , "secret" ], part ))
6881 else :
@@ -75,14 +88,17 @@ class ClientSecretBasic(ClientAuthnMethod):
7588 Server, authenticate with the Authorization Server in accordance with
7689 Section 3.2.1 of OAuth 2.0 [RFC6749] using HTTP Basic authentication scheme.
7790 """
91+ tag = "client_secret_basic"
92+
93+ def is_usable (self , request = None , authorization_info = None ):
94+ if authorization_info is not None and authorization_info .startswith ("Basic " ):
95+ return True
96+ return False
7897
79- def verify (self , request , authorization_info , ** kwargs ):
98+ def verify (self , authorization_info , ** kwargs ):
8099 client_info = basic_authn (authorization_info )
81100
82- if (
83- self .endpoint_context .cdb [client_info ["id" ]]["client_secret" ]
84- == client_info ["secret" ]
85- ):
101+ if self .endpoint_context .cdb [client_info ["id" ]]["client_secret" ] == client_info ["secret" ]:
86102 return {"client_id" : client_info ["id" ]}
87103 else :
88104 raise AuthnFailure ()
@@ -95,12 +111,18 @@ class ClientSecretPost(ClientSecretBasic):
95111 Section 3.2.1 of OAuth 2.0 [RFC6749] by including the Client Credentials in
96112 the request body.
97113 """
114+ tag = "client_secret_post"
115+
116+ def is_usable (self , request = None , authorization_info = None ):
117+ if request is None :
118+ return False
119+ if "client_id" in request and "client_secret" in request :
120+ return True
121+ return False
98122
99123 def verify (self , request , ** kwargs ):
100- if (
101- self .endpoint_context .cdb [request ["client_id" ]]["client_secret" ]
102- == request ["client_secret" ]
103- ):
124+ if self .endpoint_context .cdb [request ["client_id" ]]["client_secret" ] == request [
125+ "client_secret" ]:
104126 return {"client_id" : request ["client_id" ]}
105127 else :
106128 raise AuthnFailure ("secrets doesn't match" )
@@ -109,38 +131,70 @@ def verify(self, request, **kwargs):
109131class BearerHeader (ClientSecretBasic ):
110132 """
111133 """
134+ tag = "bearer_header"
112135
113- def verify (self , request , authorization_info , ** kwargs ):
114- if not authorization_info .startswith ("Bearer " ):
115- raise AuthnFailure ("Wrong type of authorization token" )
136+ def is_usable (self , request = None , authorization_info = None ):
137+ if authorization_info is not None and authorization_info .startswith ("Bearer " ):
138+ return True
139+ return False
116140
141+ def verify (self , authorization_info , ** kwargs ):
117142 return {"token" : authorization_info .split (" " , 1 )[1 ]}
118143
119144
120145class BearerBody (ClientSecretPost ):
121146 """
122147 Same as Client Secret Post
123148 """
149+ tag = "bearer_body"
150+
151+ def is_usable (self , request = None , authorization_info = None ):
152+ if request is not None and "access_token" in request :
153+ return True
154+ return False
124155
125156 def verify (self , request , ** kwargs ):
126- try :
127- return {"token" : request ["access_token" ]}
128- except KeyError :
157+ _token = request .get ("access_token" )
158+ if _token is None :
129159 raise AuthnFailure ("No access token" )
130160
161+ res = {"token" : _token }
162+ _client_id = request .get ("client_id" )
163+ if _client_id :
164+ res ["client_id" ] = _client_id
165+ return res
166+
131167
132168class JWSAuthnMethod (ClientAuthnMethod ):
133- def verify (self , request , ** kwargs ):
134- _jwt = JWT (self .endpoint_context .keyjar )
169+ def is_usable (self , request = None , authorization_info = None ):
170+ if request is None :
171+ return False
172+ if "client_assertion" in request :
173+ return True
174+ return False
175+
176+ def verify (self , request , key_type , ** kwargs ):
177+ _jwt = JWT (self .endpoint_context .keyjar , msg_cls = JsonWebToken )
135178 try :
136179 ca_jwt = _jwt .unpack (request ["client_assertion" ])
137180 except (Invalid , MissingKey , BadSignature ) as err :
138181 logger .info ("%s" % sanitize (err ))
139182 raise AuthnFailure ("Could not verify client_assertion." )
140183
141- authtoken = sanitize (ca_jwt )
142- if hasattr (ca_jwt , "to_dict" ) and callable (ca_jwt , "to_dict" ):
143- authtoken = sanitize (ca_jwt .to_dict ())
184+ _sign_alg = ca_jwt .jws_header .get ("alg" )
185+ if _sign_alg and _sign_alg .startswith ("HS" ):
186+ if key_type == "private_key" :
187+ raise AttributeError ("Wrong key type" )
188+ keys = self .endpoint_context .keyjar .get ("sig" , 'oct' , ca_jwt ["iss" ],
189+ ca_jwt .jws_header .get ("kid" ))
190+ _secret = self .endpoint_context .cdb [ca_jwt ["iss" ]].get ('client_secret' )
191+ if _secret and keys [0 ].key != as_bytes (_secret ):
192+ raise AttributeError ("Oct key used for signing not client_secret" )
193+ else :
194+ if key_type == "client_secret" :
195+ raise AttributeError ("Wrong key type" )
196+
197+ authtoken = sanitize (ca_jwt .to_dict ())
144198 logger .debug ("authntoken: {}" .format (authtoken ))
145199
146200 _endpoint = kwargs .get ("endpoint" )
@@ -178,12 +232,28 @@ class ClientSecretJWT(JWSAuthnMethod):
178232 The HMAC (Hash-based Message Authentication Code) is calculated using the
179233 bytes of the UTF-8 representation of the client_secret as the shared key.
180234 """
235+ tag = "client_secret_jwt"
236+
237+ def verify (self , request = None , ** kwargs ):
238+ res = JWSAuthnMethod .verify (self , request , key_type = "client_secret" ,
239+ ** kwargs )
240+ # Verify that a HS alg was used
241+ res ['method' ] = self .tag
242+ return res
181243
182244
183245class PrivateKeyJWT (JWSAuthnMethod ):
184246 """
185247 Clients that have registered a public key sign a JWT using that key.
186248 """
249+ tag = "private_key_jwt"
250+
251+ def verify (self , request = None , ** kwargs ):
252+ res = JWSAuthnMethod .verify (self , request , key_type = "private_key" ,
253+ ** kwargs )
254+ # Verify that an RS or ES alg was used
255+ res ['method' ] = self .tag
256+ return res
187257
188258
189259CLIENT_AUTHN_METHOD = {
@@ -207,9 +277,12 @@ def valid_client_info(cinfo):
207277
208278
209279def verify_client (
210- endpoint_context , request , authorization_info = None , get_client_id_from_token = None ,
211- endpoint = None , also_known_as = None
212- ):
280+ endpoint_context ,
281+ request ,
282+ authorization_info = None ,
283+ get_client_id_from_token = None ,
284+ endpoint = None ,
285+ also_known_as = None ):
213286 """
214287 Initiated Guessing !
215288
@@ -228,34 +301,28 @@ def verify_client(
228301 strings_parade = ("{} {}" .format (k , v ) for k , v in authorization_info .items ())
229302 authorization_info = " " .join (strings_parade )
230303
231- if authorization_info is None :
232- if "client_id" in request and "client_secret" in request :
233- auth_info = ClientSecretPost (endpoint_context ).verify (request )
234- auth_info ["method" ] = "client_secret_post"
235- elif "client_assertion" in request :
236- auth_info = JWSAuthnMethod (endpoint_context ).verify (request , endpoint = endpoint )
237- # If symmetric key was used
238- # auth_method = 'client_secret_jwt'
239- # If asymmetric key was used
240- auth_info ["method" ] = "private_key_jwt"
241- elif "access_token" in request :
242- auth_info = BearerBody (endpoint_context ).verify (request )
243- auth_info ["method" ] = "bearer_body"
244- else :
245- raise UnknownOrNoAuthnMethod ()
246- else :
247- if authorization_info .startswith ("Basic " ):
248- auth_info = ClientSecretBasic (endpoint_context ).verify (
249- request , authorization_info
250- )
251- auth_info ["method" ] = "client_secret_basic"
252- elif authorization_info .startswith ("Bearer " ):
253- auth_info = BearerHeader (endpoint_context ).verify (
254- request , authorization_info
255- )
256- auth_info ["method" ] = "bearer_header"
257- else :
258- raise UnknownOrNoAuthnMethod (authorization_info )
304+ auth_info = {}
305+ _methods = []
306+ if endpoint :
307+ try :
308+ _methods = endpoint_context .endpoint [endpoint ].client_authn_method
309+ except AttributeError :
310+ pass
311+
312+ for _method in _methods :
313+ if _method .is_usable (request , authorization_info ):
314+ try :
315+ auth_info = _method .verify (request = request , authorization_info = authorization_info ,
316+ endpoint = endpoint )
317+ except Exception as err :
318+ logger .warning ("Verifying auth using {} failed: {}" .format (_method .tag , err ))
319+ else :
320+ if "method" not in auth_info :
321+ auth_info ["method" ] = _method .tag
322+ break
323+
324+ if not auth_info :
325+ return auth_info
259326
260327 if also_known_as :
261328 client_id = also_known_as [auth_info .get ("client_id" )]
@@ -280,19 +347,14 @@ def verify_client(
280347
281348 # store what authn method was used
282349 if auth_info .get ("method" ):
283- if (
284- endpoint_context .cdb [client_id ].get ("auth_method" )
285- and request .__class__ .__name__
286- in endpoint_context .cdb [client_id ]["auth_method" ]
287- ):
288- endpoint_context .cdb [client_id ]["auth_method" ][
289- request .__class__ .__name__
290- ] = auth_info ["method" ]
350+ _request_type = request .__class__ .__name__
351+ _used_authn_method = endpoint_context .cdb [client_id ].get ("auth_method" )
352+ if _used_authn_method :
353+ endpoint_context .cdb [client_id ]["auth_method" ][_request_type ] = auth_info ["method" ]
291354 else :
292355 endpoint_context .cdb [client_id ]["auth_method" ] = {
293- request . __class__ . __name__ : auth_info ["method" ]
356+ _request_type : auth_info ["method" ]
294357 }
295-
296358 elif not client_id and get_client_id_from_token :
297359 if not _token :
298360 logger .warning ("No token" )
@@ -307,3 +369,16 @@ def verify_client(
307369 raise ValueError ("Unknown token" )
308370
309371 return auth_info
372+
373+
374+ def client_auth_setup (auth_set , endpoint_context ):
375+ res = []
376+
377+ for item in auth_set :
378+ _cls = CLIENT_AUTHN_METHOD .get (item )
379+ if _cls :
380+ res .append (_cls (endpoint_context ))
381+ else :
382+ res .append (importer (item )(endpoint_context ))
383+
384+ return res
0 commit comments