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/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")