@@ -67,6 +67,46 @@ class AuthProviderBase:
6767 Returned auth objects need to be compatible with `requests.auth.AuthBase`.
6868 """
6969
70+ def can_complete_http_basic (self ) -> bool :
71+ return False
72+
73+ def can_complete_mutualTLS (self ) -> bool :
74+ return False
75+
76+ def can_complete_oauth2_client_credentials (self , scopes : list [str ]) -> bool :
77+ return False
78+
79+ def can_complete_scheme (self , scheme : dict [str , t .Any ], scopes : list [str ]) -> bool :
80+ if scheme ["type" ] == "http" :
81+ if scheme ["scheme" ] == "basic" :
82+ return self .can_complete_http_basic ()
83+ elif scheme ["type" ] == "mutualTLS" :
84+ return self .can_complete_mutualTLS ()
85+ elif scheme ["type" ] == "oauth2" :
86+ for flow_name , flow in scheme ["flows" ].items ():
87+ if (
88+ flow_name == "clientCredentials"
89+ and self .can_complete_oauth2_client_credentials (flow ["scopes" ])
90+ ):
91+ return True
92+ return False
93+
94+ def can_complete (
95+ self , proposal : dict [str , list [str ]], security_schemes : dict [str , dict [str , t .Any ]]
96+ ) -> bool :
97+ for name , scopes in proposal .items ():
98+ scheme = security_schemes .get (name )
99+ if scheme is None or not self .can_complete_scheme (scheme , scopes ):
100+ return False
101+ # This covers the case where `[]` allows for no auth at all.
102+ return True
103+
104+ async def http_basic_credentials (self ) -> tuple [bytes , bytes ]:
105+ raise NotImplementedError ()
106+
107+ async def oauth2_client_credentials (self ) -> tuple [bytes , bytes ]:
108+ raise NotImplementedError ()
109+
70110 def basic_auth (self , scopes : list [str ]) -> requests .auth .AuthBase | None :
71111 """Implement this to provide means of http basic auth."""
72112 return None
@@ -145,11 +185,17 @@ class BasicAuthProvider(AuthProviderBase):
145185 Implementation for AuthProviderBase providing basic auth with fixed `username`, `password`.
146186 """
147187
148- def __init__ (self , username : str , password : str ):
149- self .username = username
150- self .password = password
188+ def __init__ (self , username : t . AnyStr , password : t . AnyStr ):
189+ self .username : bytes = username . encode ( "latin1" ) if isinstance ( username , str ) else username
190+ self .password : bytes = password . encode ( "latin1" ) if isinstance ( password , str ) else password
151191 self .auth = requests .auth .HTTPBasicAuth (username , password )
152192
193+ def can_complete_http_basic (self ) -> bool :
194+ return True
195+
196+ async def http_basic_credentials (self ) -> tuple [bytes , bytes ]:
197+ return self .username , self .password
198+
153199 def basic_auth (self , scopes : list [str ]) -> requests .auth .AuthBase | None :
154200 return self .auth
155201
@@ -171,22 +217,41 @@ async def __call__(
171217 ) -> aiohttp .ClientResponse :
172218 if self ._security :
173219 assert self ._openapi ._auth_provider is not None
174- auth = self ._openapi ._auth_provider (
175- self ._security , self ._openapi .api_spec ["components" ]["securitySchemes" ]
176- )
177- if isinstance (auth , requests .auth .HTTPBasicAuth ):
178- username = (
179- auth .username .encode ("latin1" )
180- if isinstance (auth .username , str )
181- else auth .username
182- )
183- password = (
184- auth .password .encode ("latin1" )
185- if isinstance (auth .password , str )
186- else auth .password
187- )
188- secret = b64encode (username + b":" + password )
189- request .headers ["Authorization" ] = "Basic " + secret .decode ()
220+ security_schemes : dict [str , dict [str , t .Any ]] = self ._openapi .api_spec ["components" ][
221+ "securitySchemes"
222+ ]
223+ for proposal in self ._security :
224+ if self ._openapi ._auth_provider .can_complete (proposal , security_schemes ):
225+ break
226+ else :
227+ raise OpenAPIError (_ ("No suitable auth scheme found." ))
228+
229+ assert proposal is not None
230+ for scheme_name , scopes in proposal .items ():
231+ scheme = security_schemes [scheme_name ]
232+ if scheme ["type" ] == "http" :
233+ if scheme ["scheme" ] == "basic" :
234+ username , password = (
235+ await self ._openapi ._auth_provider .http_basic_credentials ()
236+ )
237+ secret = b64encode (username + b":" + password )
238+ request .headers .add ("Authorization" , "Basic " + secret .decode ())
239+ else :
240+ raise NotImplementedError ("Auth scheme: http " + scheme ["scheme" ])
241+ elif scheme ["type" ] == "mutualTLS" :
242+ # At this point, we assume the cert has already been loaded into the sslcontext.
243+ pass
244+ elif scheme ["type" ] == "oauth2" :
245+ flow = scheme ["flows" ].get ("clientCredentials" )
246+ if flow is None :
247+ raise NotImplementedError (
248+ "OAuth2: Only client credential flow is available."
249+ )
250+ token = "DEADBEEF"
251+ request .headers .add ("Authorization" , f"Bearer { token } " )
252+ else :
253+ raise NotImplementedError ("Auth type: " + scheme ["type" ])
254+
190255 response = await handler (request )
191256
192257 if "Correlation-Id" in response .headers :
0 commit comments