appmesh 1.6.6__py3-none-any.whl → 1.6.8__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.
- appmesh/client_http.py +49 -5
- appmesh/server_http.py +7 -7
- {appmesh-1.6.6.dist-info → appmesh-1.6.8.dist-info}/METADATA +1 -1
- {appmesh-1.6.6.dist-info → appmesh-1.6.8.dist-info}/RECORD +6 -6
- {appmesh-1.6.6.dist-info → appmesh-1.6.8.dist-info}/WHEEL +0 -0
- {appmesh-1.6.6.dist-info → appmesh-1.6.8.dist-info}/top_level.txt +0 -0
appmesh/client_http.py
CHANGED
@@ -17,7 +17,6 @@ from urllib import parse
|
|
17
17
|
import aniso8601
|
18
18
|
import jwt
|
19
19
|
import requests
|
20
|
-
from requests.auth import _basic_auth_str
|
21
20
|
from .app import App
|
22
21
|
from .app_run import AppRun
|
23
22
|
from .app_output import AppOutput
|
@@ -445,6 +444,44 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
445
444
|
|
446
445
|
self._forward_to = host
|
447
446
|
|
447
|
+
def _normalize_totp_secret(self, secret: str) -> str:
|
448
|
+
"""
|
449
|
+
Normalize a TOTP secret to a valid RFC 4648 Base32 format.
|
450
|
+
This includes:
|
451
|
+
- Removing all whitespace
|
452
|
+
- Uppercasing
|
453
|
+
- Replacing visually similar or Crockford-like digits:
|
454
|
+
'1' -> 'I', '0' -> 'O', '8' -> 'B', '9' -> 'G'
|
455
|
+
- Adding RFC 4648 padding
|
456
|
+
"""
|
457
|
+
if not secret:
|
458
|
+
raise ValueError("Empty TOTP secret")
|
459
|
+
|
460
|
+
# Remove all whitespace and uppercase
|
461
|
+
secret = "".join(secret.split()).upper()
|
462
|
+
|
463
|
+
# Crockford/Base32-friendly replacements
|
464
|
+
replacements = {
|
465
|
+
"1": "I",
|
466
|
+
"0": "O",
|
467
|
+
"8": "B",
|
468
|
+
"9": "G",
|
469
|
+
}
|
470
|
+
|
471
|
+
for old, new in replacements.items():
|
472
|
+
secret = secret.replace(old, new)
|
473
|
+
|
474
|
+
# Validate that only RFC 4648 Base32 characters remain
|
475
|
+
valid_chars = set("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567")
|
476
|
+
if not set(secret).issubset(valid_chars):
|
477
|
+
raise ValueError("Invalid characters in TOTP secret")
|
478
|
+
|
479
|
+
# Add padding to make length a multiple of 8 (RFC 4648)
|
480
|
+
padding = (8 - (len(secret) % 8)) % 8
|
481
|
+
secret += "=" * padding
|
482
|
+
|
483
|
+
return secret
|
484
|
+
|
448
485
|
########################################
|
449
486
|
# Security
|
450
487
|
########################################
|
@@ -474,7 +511,7 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
474
511
|
AppMeshClient.Method.POST,
|
475
512
|
path="/appmesh/login",
|
476
513
|
header={
|
477
|
-
self.HTTP_HEADER_KEY_AUTH:
|
514
|
+
self.HTTP_HEADER_KEY_AUTH: "Basic " + base64.b64encode(f"{user_name}:{user_pwd}".encode()).decode(),
|
478
515
|
"X-Expire-Seconds": str(self._parse_duration(timeout_seconds)),
|
479
516
|
**({"X-Audience": audience} if audience else {}),
|
480
517
|
# **({"X-Totp-Code": totp_code} if totp_code else {}),
|
@@ -610,15 +647,22 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
610
647
|
raise Exception(f"Token renewal failed: {str(e)}") from e
|
611
648
|
|
612
649
|
def get_totp_secret(self) -> str:
|
613
|
-
"""
|
650
|
+
"""
|
651
|
+
Generate TOTP secret for the current user and return a normalized secret.
|
614
652
|
|
615
653
|
Returns:
|
616
|
-
str: TOTP secret
|
654
|
+
str: Normalized TOTP secret string
|
617
655
|
"""
|
618
656
|
resp = self._request_http(method=AppMeshClient.Method.POST, path="/appmesh/totp/secret")
|
619
657
|
if resp.status_code == HTTPStatus.OK:
|
620
658
|
totp_uri = base64.b64decode(resp.json()["mfa_uri"]).decode()
|
621
|
-
|
659
|
+
secret = self._parse_totp_uri(totp_uri).get("secret")
|
660
|
+
if not secret:
|
661
|
+
raise ValueError("TOTP secret missing in response")
|
662
|
+
|
663
|
+
# Normalize to standard Base32
|
664
|
+
return self._normalize_totp_secret(secret)
|
665
|
+
|
622
666
|
raise Exception(resp.text)
|
623
667
|
|
624
668
|
def setup_totp(self, totp_code: str) -> str:
|
appmesh/server_http.py
CHANGED
@@ -58,13 +58,13 @@ class AppMeshServer(metaclass=abc.ABCMeta):
|
|
58
58
|
@staticmethod
|
59
59
|
def _get_runtime_env() -> Tuple[str, str]:
|
60
60
|
"""Read and validate required runtime environment variables."""
|
61
|
-
|
61
|
+
process_key = os.getenv("APP_MESH_PROCESS_KEY")
|
62
62
|
app_name = os.getenv("APP_MESH_APPLICATION_NAME")
|
63
|
-
if not
|
63
|
+
if not process_key:
|
64
64
|
raise Exception("Missing environment variable: APP_MESH_PROCESS_KEY. This must be set by App Mesh service.")
|
65
65
|
if not app_name:
|
66
66
|
raise Exception("Missing environment variable: APP_MESH_APPLICATION_NAME. This must be set by App Mesh service.")
|
67
|
-
return
|
67
|
+
return process_key, app_name
|
68
68
|
|
69
69
|
def task_fetch(self) -> Union[str, bytes]:
|
70
70
|
"""Fetch task data in the currently running App Mesh application process.
|
@@ -76,14 +76,14 @@ class AppMeshServer(metaclass=abc.ABCMeta):
|
|
76
76
|
Returns:
|
77
77
|
Union[str, bytes]: The payload provided by the client as returned by the service.
|
78
78
|
"""
|
79
|
-
|
79
|
+
pkey, app_name = self._get_runtime_env()
|
80
80
|
path = f"/appmesh/app/{app_name}/task"
|
81
81
|
|
82
82
|
while True:
|
83
83
|
resp = self._client._request_http(
|
84
84
|
AppMeshClient.Method.GET,
|
85
85
|
path=path,
|
86
|
-
query={"
|
86
|
+
query={"process_key": pkey},
|
87
87
|
)
|
88
88
|
|
89
89
|
if resp.status_code != HTTPStatus.OK:
|
@@ -102,13 +102,13 @@ class AppMeshServer(metaclass=abc.ABCMeta):
|
|
102
102
|
Args:
|
103
103
|
result (Union[str, bytes]): Result payload to be delivered back to the client.
|
104
104
|
"""
|
105
|
-
|
105
|
+
pkey, app_name = self._get_runtime_env()
|
106
106
|
path = f"/appmesh/app/{app_name}/task"
|
107
107
|
|
108
108
|
resp = self._client._request_http(
|
109
109
|
AppMeshClient.Method.PUT,
|
110
110
|
path=path,
|
111
|
-
query={"
|
111
|
+
query={"process_key": pkey},
|
112
112
|
body=result,
|
113
113
|
)
|
114
114
|
|
@@ -3,14 +3,14 @@ appmesh/app.py,sha256=crD4DRFZJuHtZMfSsz7C-EwvjPmGZbFXYXvA_wCdvdI,10734
|
|
3
3
|
appmesh/app_output.py,sha256=vfn322AyixblI8DbXds08h6L_ybObiaRSifsA1-Xcoo,1035
|
4
4
|
appmesh/app_run.py,sha256=aYq852a29OThIi32Xtx5s0sTXZ97T0lHD5WXH8yfPoc,2018
|
5
5
|
appmesh/appmesh_client.py,sha256=ywB2222PtJUffdfdxZcBfdhZs1KYyc7JvzMxwuK2qyI,378
|
6
|
-
appmesh/client_http.py,sha256=
|
6
|
+
appmesh/client_http.py,sha256=l8eUmJxXIDjowPvPKIppCLfwlk2kjztU-FXoaOac_tc,59003
|
7
7
|
appmesh/client_http_oauth.py,sha256=1d51o0JX_xtB8d2bEuM7_XJHcwMnhcjkbIq7GE1Zxm8,6120
|
8
8
|
appmesh/client_tcp.py,sha256=aq6UUzytZA4ibE9WQMMWdo1uW8sHETEhJjsbM6IYSno,11457
|
9
|
-
appmesh/server_http.py,sha256=
|
9
|
+
appmesh/server_http.py,sha256=rBIYO9rbR-r3x1Jcry440Sp--IM-OWKRaOhNpGdkxh8,4299
|
10
10
|
appmesh/server_tcp.py,sha256=-CU5tw97WJmDcUNsNPWqpdZ0wxRzRD6kUP3XyNZUTHc,1444
|
11
11
|
appmesh/tcp_messages.py,sha256=H9S_iCy0IuufY2v50_SUgRvcyQmJsySG65tBe_xb3Ko,1878
|
12
12
|
appmesh/tcp_transport.py,sha256=0hRSp5fpL9wKB05JIyIRIuyBC8w1IdokryhMDHqtN4M,8946
|
13
|
-
appmesh-1.6.
|
14
|
-
appmesh-1.6.
|
15
|
-
appmesh-1.6.
|
16
|
-
appmesh-1.6.
|
13
|
+
appmesh-1.6.8.dist-info/METADATA,sha256=bDxN-Vm4004kC7FdM52yMx-YcmNcFzexBDYBr3p6db4,11828
|
14
|
+
appmesh-1.6.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
15
|
+
appmesh-1.6.8.dist-info/top_level.txt,sha256=-y0MNQOGJxUzLdHZ6E_Rfv5_LNCkV-GTmOBME_b6pg8,8
|
16
|
+
appmesh-1.6.8.dist-info/RECORD,,
|
File without changes
|
File without changes
|