esgpull 0.9.1__py3-none-any.whl → 0.9.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- esgpull/cli/__init__.py +2 -2
- esgpull/cli/index_nodes.py +94 -0
- esgpull/cli/plugins.py +1 -1
- esgpull/cli/search.py +22 -8
- esgpull/cli/update.py +1 -0
- esgpull/config.py +211 -295
- esgpull/constants.py +1 -0
- esgpull/context.py +69 -11
- esgpull/esgpull.py +0 -5
- esgpull/migrations/versions/0.9.2_update_tables.py +28 -0
- esgpull/migrations/versions/0.9.3_update_tables.py +28 -0
- esgpull/plugin.py +25 -21
- esgpull/processor.py +0 -4
- esgpull/tui.py +5 -5
- esgpull/utils.py +0 -17
- {esgpull-0.9.1.dist-info → esgpull-0.9.3.dist-info}/METADATA +3 -2
- {esgpull-0.9.1.dist-info → esgpull-0.9.3.dist-info}/RECORD +20 -19
- esgpull/auth.py +0 -181
- esgpull/cli/login.py +0 -56
- {esgpull-0.9.1.dist-info → esgpull-0.9.3.dist-info}/WHEEL +0 -0
- {esgpull-0.9.1.dist-info → esgpull-0.9.3.dist-info}/entry_points.txt +0 -0
- {esgpull-0.9.1.dist-info → esgpull-0.9.3.dist-info}/licenses/LICENSE +0 -0
esgpull/auth.py
DELETED
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from enum import Enum, unique
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from shutil import rmtree
|
|
6
|
-
from typing import Any
|
|
7
|
-
from urllib.parse import (
|
|
8
|
-
ParseResult,
|
|
9
|
-
ParseResultBytes,
|
|
10
|
-
urljoin,
|
|
11
|
-
urlparse,
|
|
12
|
-
urlunparse,
|
|
13
|
-
)
|
|
14
|
-
from xml.etree import ElementTree
|
|
15
|
-
|
|
16
|
-
import httpx
|
|
17
|
-
import tomlkit
|
|
18
|
-
from attrs import Factory, define, field
|
|
19
|
-
from myproxy.client import MyProxyClient
|
|
20
|
-
from OpenSSL import crypto
|
|
21
|
-
|
|
22
|
-
from esgpull.config import Config
|
|
23
|
-
from esgpull.constants import PROVIDERS
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class Secret:
|
|
27
|
-
def __init__(self, value: str | None = None) -> None:
|
|
28
|
-
self._value = value
|
|
29
|
-
|
|
30
|
-
def get_value(self) -> str | None:
|
|
31
|
-
return self._value
|
|
32
|
-
|
|
33
|
-
def __str__(self) -> str:
|
|
34
|
-
if self.get_value() is None:
|
|
35
|
-
return str(None)
|
|
36
|
-
else:
|
|
37
|
-
return "*" * 10
|
|
38
|
-
|
|
39
|
-
def __repr__(self) -> str:
|
|
40
|
-
return str(self)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
@define
|
|
44
|
-
class Credentials:
|
|
45
|
-
provider: str | None = None
|
|
46
|
-
user: str | None = None
|
|
47
|
-
password: Secret = field(default=None, converter=Secret)
|
|
48
|
-
|
|
49
|
-
@staticmethod
|
|
50
|
-
def from_config(config: Config) -> Credentials:
|
|
51
|
-
path = config.paths.auth / config.credentials.filename
|
|
52
|
-
return Credentials.from_path(path)
|
|
53
|
-
|
|
54
|
-
@staticmethod
|
|
55
|
-
def from_path(path: Path) -> Credentials:
|
|
56
|
-
if path.is_file():
|
|
57
|
-
with path.open() as fh:
|
|
58
|
-
doc = tomlkit.load(fh)
|
|
59
|
-
return Credentials(**doc)
|
|
60
|
-
else:
|
|
61
|
-
return Credentials()
|
|
62
|
-
|
|
63
|
-
def write(self, path: Path) -> None:
|
|
64
|
-
if path.is_file():
|
|
65
|
-
raise FileExistsError(path)
|
|
66
|
-
with path.open("w") as f:
|
|
67
|
-
cred_dict = dict(
|
|
68
|
-
provider=self.provider,
|
|
69
|
-
user=self.user,
|
|
70
|
-
password=self.password.get_value(),
|
|
71
|
-
)
|
|
72
|
-
tomlkit.dump(cred_dict, f)
|
|
73
|
-
|
|
74
|
-
def parse_openid(self) -> ParseResult | ParseResultBytes | Any:
|
|
75
|
-
if self.provider not in PROVIDERS:
|
|
76
|
-
raise ValueError(f"unknown provider: {self.provider}")
|
|
77
|
-
ns = {"x": "xri://$xrd*($v*2.0)"}
|
|
78
|
-
provider = urlunparse(
|
|
79
|
-
[
|
|
80
|
-
"https",
|
|
81
|
-
self.provider,
|
|
82
|
-
urljoin(PROVIDERS[self.provider], self.user),
|
|
83
|
-
"",
|
|
84
|
-
"",
|
|
85
|
-
"",
|
|
86
|
-
]
|
|
87
|
-
)
|
|
88
|
-
resp = httpx.get(str(provider))
|
|
89
|
-
resp.raise_for_status()
|
|
90
|
-
root = ElementTree.fromstring(resp.text)
|
|
91
|
-
services = root.findall(".//x:Service", namespaces=ns)
|
|
92
|
-
for service in services:
|
|
93
|
-
t = service.find("x:Type", namespaces=ns)
|
|
94
|
-
if t is None:
|
|
95
|
-
continue
|
|
96
|
-
elif t.text == "urn:esg:security:myproxy-service":
|
|
97
|
-
url = service.find("x:URI", namespaces=ns)
|
|
98
|
-
if url is not None:
|
|
99
|
-
return urlparse(url.text)
|
|
100
|
-
raise ValueError("did not found host/port")
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
@unique
|
|
104
|
-
class AuthStatus(Enum):
|
|
105
|
-
Valid = ("valid", "green")
|
|
106
|
-
Expired = ("expired", "orange")
|
|
107
|
-
Missing = ("missing", "red")
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
@define
|
|
111
|
-
class Auth:
|
|
112
|
-
cert_dir: Path
|
|
113
|
-
cert_file: Path
|
|
114
|
-
credentials: Credentials = Factory(Credentials)
|
|
115
|
-
__status: AuthStatus | None = field(init=False, default=None, repr=False)
|
|
116
|
-
|
|
117
|
-
Valid = AuthStatus.Valid
|
|
118
|
-
Expired = AuthStatus.Expired
|
|
119
|
-
Missing = AuthStatus.Missing
|
|
120
|
-
|
|
121
|
-
@staticmethod
|
|
122
|
-
def from_config(
|
|
123
|
-
config: Config, credentials: Credentials = Credentials()
|
|
124
|
-
) -> Auth:
|
|
125
|
-
return Auth.from_path(config.paths.auth, credentials)
|
|
126
|
-
|
|
127
|
-
@staticmethod
|
|
128
|
-
def from_path(
|
|
129
|
-
path: Path, credentials: Credentials = Credentials()
|
|
130
|
-
) -> Auth:
|
|
131
|
-
cert_dir = path / "certificates"
|
|
132
|
-
cert_file = path / "credentials.pem"
|
|
133
|
-
return Auth(cert_dir, cert_file, credentials)
|
|
134
|
-
|
|
135
|
-
@property
|
|
136
|
-
def cert(self) -> str | None:
|
|
137
|
-
if self.status == AuthStatus.Valid:
|
|
138
|
-
return str(self.cert_file)
|
|
139
|
-
else:
|
|
140
|
-
return None
|
|
141
|
-
|
|
142
|
-
@property
|
|
143
|
-
def status(self) -> AuthStatus:
|
|
144
|
-
if self.__status is None:
|
|
145
|
-
self.__status = self._get_status()
|
|
146
|
-
return self.__status
|
|
147
|
-
|
|
148
|
-
def _get_status(self) -> AuthStatus:
|
|
149
|
-
if not self.cert_file.exists():
|
|
150
|
-
return AuthStatus.Missing
|
|
151
|
-
with self.cert_file.open("rb") as f:
|
|
152
|
-
content = f.read()
|
|
153
|
-
filetype = crypto.FILETYPE_PEM
|
|
154
|
-
pem = crypto.load_certificate(filetype, content)
|
|
155
|
-
if pem.has_expired():
|
|
156
|
-
return AuthStatus.Expired
|
|
157
|
-
return AuthStatus.Valid
|
|
158
|
-
|
|
159
|
-
# TODO: review this
|
|
160
|
-
def renew(self) -> None:
|
|
161
|
-
if self.cert_dir.is_dir():
|
|
162
|
-
rmtree(self.cert_dir)
|
|
163
|
-
self.cert_file.unlink(missing_ok=True)
|
|
164
|
-
openid = self.credentials.parse_openid()
|
|
165
|
-
client = MyProxyClient(
|
|
166
|
-
hostname=openid.hostname,
|
|
167
|
-
port=openid.port,
|
|
168
|
-
caCertDir=str(self.cert_dir),
|
|
169
|
-
proxyCertLifetime=12 * 60 * 60,
|
|
170
|
-
)
|
|
171
|
-
creds = client.logon(
|
|
172
|
-
self.credentials.user,
|
|
173
|
-
self.credentials.password.get_value(),
|
|
174
|
-
bootstrap=True,
|
|
175
|
-
updateTrustRoots=True,
|
|
176
|
-
authnGetTrustRootsCall=False,
|
|
177
|
-
)
|
|
178
|
-
with self.cert_file.open("wb") as file:
|
|
179
|
-
for cred in creds:
|
|
180
|
-
file.write(cred)
|
|
181
|
-
self.__status = None
|
esgpull/cli/login.py
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import click
|
|
2
|
-
from click.exceptions import Abort
|
|
3
|
-
|
|
4
|
-
from esgpull.auth import Auth, AuthStatus, Credentials
|
|
5
|
-
from esgpull.cli.decorators import opts
|
|
6
|
-
from esgpull.cli.utils import init_esgpull
|
|
7
|
-
from esgpull.constants import PROVIDERS
|
|
8
|
-
from esgpull.tui import Verbosity
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@click.command()
|
|
12
|
-
@opts.verbosity
|
|
13
|
-
@opts.force
|
|
14
|
-
def login(verbosity: Verbosity, force: bool):
|
|
15
|
-
"""
|
|
16
|
-
OpenID authentication and certificates renewal
|
|
17
|
-
|
|
18
|
-
The first call to `login` is a prompt asking for provider/username/password.
|
|
19
|
-
|
|
20
|
-
Subsequent calls check whether the login certificates are valid, renewing them if needed.
|
|
21
|
-
Renewal can be forced using the `--force` flag.
|
|
22
|
-
"""
|
|
23
|
-
esg = init_esgpull(verbosity)
|
|
24
|
-
with esg.ui.logging("login", onraise=Abort):
|
|
25
|
-
cred_file = esg.config.paths.auth / esg.config.credentials.filename
|
|
26
|
-
if not cred_file.is_file():
|
|
27
|
-
esg.ui.print("No credentials found.")
|
|
28
|
-
choices = []
|
|
29
|
-
providers = list(PROVIDERS)
|
|
30
|
-
for i, provider in enumerate(providers):
|
|
31
|
-
choices.append(str(i))
|
|
32
|
-
esg.ui.print(f" [{i}] [i green]{provider}[/]")
|
|
33
|
-
provider_idx = esg.ui.choice(
|
|
34
|
-
"Select a provider",
|
|
35
|
-
choices=choices,
|
|
36
|
-
show_choices=False,
|
|
37
|
-
)
|
|
38
|
-
provider = providers[int(provider_idx)]
|
|
39
|
-
user = esg.ui.prompt("User")
|
|
40
|
-
password = esg.ui.prompt("Password", password=True)
|
|
41
|
-
credentials = Credentials(provider, user, password)
|
|
42
|
-
credentials.write(cred_file)
|
|
43
|
-
esg.auth = Auth.from_config(esg.config, credentials)
|
|
44
|
-
renew = force
|
|
45
|
-
status = esg.auth.status
|
|
46
|
-
status_name = status.value[0]
|
|
47
|
-
status_color = status.value[1]
|
|
48
|
-
esg.ui.print(f"Certificates are [{status_color}]{status_name}[/].")
|
|
49
|
-
if esg.auth.status == AuthStatus.Expired:
|
|
50
|
-
renew = renew or esg.ui.ask("Renew?")
|
|
51
|
-
elif esg.auth.status == AuthStatus.Missing:
|
|
52
|
-
renew = True
|
|
53
|
-
if renew:
|
|
54
|
-
with esg.ui.spinner("Renewing certificates"):
|
|
55
|
-
esg.auth.renew()
|
|
56
|
-
esg.ui.print(":+1: Renewed successfully")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|