Skip to content

Commit ec942c2

Browse files
committed
Refactor internals of openapi class
This will mask attributes of the class private and add a bit of abstraction from requests.
1 parent 76ede0c commit ec942c2

File tree

12 files changed

+554
-317
lines changed

12 files changed

+554
-317
lines changed

lint_requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ mypy==1.19.0
77
shellcheck-py==0.11.0.1
88

99
# Type annotation stubs
10+
types-aiofiles
1011
types-pygments
1112
types-PyYAML
1213
types-requests

lower_bounds_constraints.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
aiofiles==25.1.0
2+
aiohttp==3.12.0
13
click==8.0.0
24
packaging==20.0
35
PyYAML==5.3

pulp-glue/pulp_glue/common/authentication.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,143 @@
44
import requests
55

66

7+
class AuthProviderBase:
8+
"""
9+
Base class for auth providers.
10+
11+
This abstract base class will analyze the authentication proposals of the openapi specs.
12+
Different authentication schemes should be implemented by subclasses.
13+
Returned auth objects need to be compatible with `requests.auth.AuthBase`.
14+
"""
15+
16+
def __init__(self) -> None:
17+
self._oauth2_token: str | None = None
18+
self._oauth2_expires: datetime = datetime.now()
19+
20+
def can_complete_http_basic(self) -> bool:
21+
return False
22+
23+
def can_complete_mutualTLS(self) -> bool:
24+
return False
25+
26+
def can_complete_oauth2_client_credentials(self, scopes: list[str]) -> bool:
27+
return False
28+
29+
def can_complete_scheme(self, scheme: dict[str, t.Any], scopes: list[str]) -> bool:
30+
if scheme["type"] == "http":
31+
if scheme["scheme"] == "basic":
32+
return self.can_complete_http_basic()
33+
elif scheme["type"] == "mutualTLS":
34+
return self.can_complete_mutualTLS()
35+
elif scheme["type"] == "oauth2":
36+
for flow_name, flow in scheme["flows"].items():
37+
if (
38+
flow_name == "clientCredentials"
39+
and self.can_complete_oauth2_client_credentials(flow["scopes"])
40+
):
41+
return True
42+
return False
43+
44+
def can_complete(
45+
self, proposal: dict[str, list[str]], security_schemes: dict[str, dict[str, t.Any]]
46+
) -> bool:
47+
for name, scopes in proposal.items():
48+
scheme = security_schemes.get(name)
49+
if scheme is None or not self.can_complete_scheme(scheme, scopes):
50+
return False
51+
# This covers the case where `[]` allows for no auth at all.
52+
return True
53+
54+
async def http_basic_credentials(self) -> tuple[bytes, bytes]:
55+
raise NotImplementedError()
56+
57+
async def oauth2_client_credentials(self) -> tuple[bytes, bytes]:
58+
raise NotImplementedError()
59+
60+
def tls_credentials(self) -> tuple[str, str | None]:
61+
raise NotImplementedError()
62+
63+
64+
class BasicAuthProvider(AuthProviderBase):
65+
"""
66+
AuthProvider providing basic auth with fixed `username`, `password`.
67+
"""
68+
69+
def __init__(self, username: t.AnyStr, password: t.AnyStr):
70+
super().__init__()
71+
self.username: bytes = username.encode("latin1") if isinstance(username, str) else username
72+
self.password: bytes = password.encode("latin1") if isinstance(password, str) else password
73+
74+
def can_complete_http_basic(self) -> bool:
75+
return True
76+
77+
async def http_basic_credentials(self) -> tuple[bytes, bytes]:
78+
return self.username, self.password
79+
80+
81+
class GlueAuthProvider(AuthProviderBase):
82+
"""
83+
AuthProvider allowing to be used with prepared credentials.
84+
"""
85+
86+
def __init__(
87+
self,
88+
username: t.AnyStr | None = None,
89+
password: t.AnyStr | None = None,
90+
client_id: t.AnyStr | None = None,
91+
client_secret: t.AnyStr | None = None,
92+
cert: str | None = None,
93+
key: str | None = None,
94+
):
95+
super().__init__()
96+
self.username: bytes | None = None
97+
self.password: bytes | None = None
98+
self.client_id: bytes | None = None
99+
self.client_secret: bytes | None = None
100+
self.cert: str | None = cert
101+
self.key: str | None = key
102+
103+
if username is not None:
104+
assert password is not None
105+
self.username = username.encode("latin1") if isinstance(username, str) else username
106+
self.password = password.encode("latin1") if isinstance(password, str) else password
107+
if client_id is not None:
108+
assert client_secret is not None
109+
self.client_id = client_id.encode("latin1") if isinstance(client_id, str) else client_id
110+
self.client_secret = (
111+
client_secret.encode("latin1") if isinstance(client_secret, str) else client_secret
112+
)
113+
114+
if cert is None and key is not None:
115+
raise RuntimeError("Key can only be used together with a cert.")
116+
117+
def can_complete_http_basic(self) -> bool:
118+
return self.username is not None
119+
120+
def can_complete_oauth2_client_credentials(self, scopes: list[str]) -> bool:
121+
return self.client_id is not None
122+
123+
def can_complete_mutualTLS(self) -> bool:
124+
return self.cert is not None
125+
126+
async def http_basic_credentials(self) -> tuple[bytes, bytes]:
127+
assert self.username is not None
128+
assert self.password is not None
129+
return self.username, self.password
130+
131+
async def oauth2_client_credentials(self) -> tuple[bytes, bytes]:
132+
assert self.client_id is not None
133+
assert self.client_secret is not None
134+
return self.client_id, self.client_secret
135+
136+
def tls_credentials(self) -> tuple[str, str | None]:
137+
assert self.cert is not None
138+
return (self.cert, self.key)
139+
140+
141+
# ----------------------8<----8<------------------------
142+
143+
7144
class OAuth2ClientCredentialsAuth(requests.auth.AuthBase):
8145
"""
9146
This implements the OAuth2 ClientCredentials Grant authentication flow.

pulp-glue/pulp_glue/common/context.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from packaging.specifiers import SpecifierSet
1111

12+
from pulp_glue.common.authentication import BasicAuthProvider
1213
from pulp_glue.common.exceptions import (
1314
NotImplementedFake,
1415
OpenAPIError,
@@ -19,7 +20,7 @@
1920
UnsafeCallError,
2021
)
2122
from pulp_glue.common.i18n import get_translation
22-
from pulp_glue.common.openapi import BasicAuthProvider, OpenAPI
23+
from pulp_glue.common.openapi import OpenAPI
2324

2425
if sys.version_info >= (3, 11):
2526
import tomllib
@@ -399,10 +400,10 @@ def api(self) -> OpenAPI:
399400
)
400401
except OpenAPIError as e:
401402
raise PulpException(str(e))
403+
self._patch_api_spec()
402404
# Rerun scheduled version checks
403405
for plugin_requirement in self._needed_plugins:
404406
self.needs_plugin(plugin_requirement)
405-
self._patch_api_spec()
406407
return self._api
407408

408409
@property

0 commit comments

Comments
 (0)