vhi-python 0.1.1__tar.gz → 0.1.2__tar.gz
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.
- {vhi_python-0.1.1 → vhi_python-0.1.2}/PKG-INFO +2 -2
- {vhi_python-0.1.1 → vhi_python-0.1.2}/pyproject.toml +2 -2
- {vhi_python-0.1.1 → vhi_python-0.1.2}/src/vhi/client.py +24 -18
- {vhi_python-0.1.1 → vhi_python-0.1.2}/src/vhi_python.egg-info/PKG-INFO +2 -2
- {vhi_python-0.1.1 → vhi_python-0.1.2}/src/vhi_python.egg-info/SOURCES.txt +2 -1
- {vhi_python-0.1.1 → vhi_python-0.1.2}/tests/test_client.py +14 -10
- vhi_python-0.1.2/tests/test_integration.py +31 -0
- {vhi_python-0.1.1 → vhi_python-0.1.2}/README.md +0 -0
- {vhi_python-0.1.1 → vhi_python-0.1.2}/setup.cfg +0 -0
- {vhi_python-0.1.1 → vhi_python-0.1.2}/src/vhi/__init__.py +0 -0
- {vhi_python-0.1.1 → vhi_python-0.1.2}/src/vhi/exceptions.py +0 -0
- {vhi_python-0.1.1 → vhi_python-0.1.2}/src/vhi/models.py +0 -0
- {vhi_python-0.1.1 → vhi_python-0.1.2}/src/vhi_python.egg-info/dependency_links.txt +0 -0
- {vhi_python-0.1.1 → vhi_python-0.1.2}/src/vhi_python.egg-info/requires.txt +0 -0
- {vhi_python-0.1.1 → vhi_python-0.1.2}/src/vhi_python.egg-info/top_level.txt +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vhi-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: A Python client library for the Vhi API, handling authentication, MFA callbacks, claims retrieval, and document downloads.
|
|
5
5
|
Author-email: Agent <agent@example.com>
|
|
6
|
-
License
|
|
6
|
+
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/agent/vhi-python
|
|
8
8
|
Project-URL: Repository, https://github.com/agent/vhi-python.git
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "vhi-python"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "A Python client library for the Vhi API, handling authentication, MFA callbacks, claims retrieval, and document downloads."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
11
11
|
{ name = "Agent", email = "agent@example.com" }
|
|
12
12
|
]
|
|
13
|
-
license = "MIT"
|
|
13
|
+
license = { text = "MIT" }
|
|
14
14
|
classifiers = [
|
|
15
15
|
"Programming Language :: Python :: 3",
|
|
16
16
|
"Operating System :: OS Independent",
|
|
@@ -64,41 +64,48 @@ class VhiClient:
|
|
|
64
64
|
|
|
65
65
|
payload = {
|
|
66
66
|
"username": self.username,
|
|
67
|
-
"
|
|
67
|
+
"usercred": self.password
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
# 1. Primary Authentication
|
|
71
|
-
|
|
72
|
-
login_url = f"{self.apis_base_url}/auth/v1/login"
|
|
71
|
+
login_url = f"{self.apis_base_url}/api/myvhilogin/login"
|
|
73
72
|
|
|
74
73
|
response = self.session.post(login_url, json=payload, headers=headers)
|
|
75
74
|
|
|
76
75
|
if response.status_code == 200:
|
|
77
|
-
self.is_authenticated = True
|
|
78
|
-
return
|
|
79
|
-
|
|
80
|
-
elif response.status_code == 202:
|
|
81
|
-
# MFA Challenge Required
|
|
82
76
|
try:
|
|
83
77
|
data = response.json()
|
|
84
78
|
except ValueError:
|
|
85
|
-
raise VhiApiError("
|
|
79
|
+
raise VhiApiError("Login response was not valid JSON", status_code=response.status_code)
|
|
86
80
|
|
|
87
|
-
|
|
88
|
-
|
|
81
|
+
# The HAR trace uses a deeply nested response structure or a flattened one depending on Gateway
|
|
82
|
+
status_val = data.get("status") or data.get("data", {}).get("status")
|
|
83
|
+
|
|
84
|
+
if status_val == "MFA_REQUIRED":
|
|
85
|
+
state_token = data.get("stateToken") or data.get("data", {}).get("stateToken")
|
|
86
|
+
|
|
87
|
+
# Attempt to extract the verify url from the first factor
|
|
88
|
+
try:
|
|
89
|
+
factors = data.get("factors") or data.get("data", {}).get("_embedded", {}).get("factors", [])
|
|
90
|
+
verify_url = factors[0]["_links"]["verify"]["href"]
|
|
91
|
+
except (IndexError, KeyError):
|
|
92
|
+
# Fallback URL if extraction fails
|
|
93
|
+
verify_url = f"https://admin-digital.vhi.ie/api/v1/authn/factors/verify"
|
|
94
|
+
|
|
89
95
|
if not state_token:
|
|
90
96
|
raise VhiApiError("MFA required but no state_token provided in response")
|
|
91
97
|
|
|
92
|
-
self._handle_mfa_challenge(state_token, headers)
|
|
98
|
+
self._handle_mfa_challenge(state_token, verify_url, headers)
|
|
93
99
|
else:
|
|
94
|
-
|
|
100
|
+
self.is_authenticated = True
|
|
101
|
+
return
|
|
95
102
|
|
|
96
103
|
elif response.status_code == 401:
|
|
97
104
|
raise VhiAuthenticationError("Invalid credentials provided.")
|
|
98
105
|
else:
|
|
99
106
|
raise VhiApiError(f"Login failed with status {response.status_code}", status_code=response.status_code, response=response)
|
|
100
107
|
|
|
101
|
-
def _handle_mfa_challenge(self, state_token: str, base_headers: dict):
|
|
108
|
+
def _handle_mfa_challenge(self, state_token: str, verify_url: str, base_headers: dict):
|
|
102
109
|
"""
|
|
103
110
|
Internal method to submit the MFA code.
|
|
104
111
|
"""
|
|
@@ -111,13 +118,12 @@ class VhiClient:
|
|
|
111
118
|
if not otp_code:
|
|
112
119
|
raise VhiAuthenticationError("MFA callback did not return a valid OTP code.")
|
|
113
120
|
|
|
114
|
-
mfa_url = f"{self.apis_base_url}/auth/v1/mfa/verify"
|
|
115
121
|
payload = {
|
|
116
|
-
"
|
|
117
|
-
"
|
|
122
|
+
"passCode": otp_code,
|
|
123
|
+
"stateToken": state_token
|
|
118
124
|
}
|
|
119
125
|
|
|
120
|
-
response = self.session.post(
|
|
126
|
+
response = self.session.post(verify_url, json=payload, headers=base_headers)
|
|
121
127
|
|
|
122
128
|
if response.status_code == 200:
|
|
123
129
|
self.is_authenticated = True
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vhi-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: A Python client library for the Vhi API, handling authentication, MFA callbacks, claims retrieval, and document downloads.
|
|
5
5
|
Author-email: Agent <agent@example.com>
|
|
6
|
-
License
|
|
6
|
+
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/agent/vhi-python
|
|
8
8
|
Project-URL: Repository, https://github.com/agent/vhi-python.git
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -11,8 +11,8 @@ def test_login_success_no_mfa():
|
|
|
11
11
|
|
|
12
12
|
responses.add(
|
|
13
13
|
responses.POST,
|
|
14
|
-
"https://apis.vhi.ie/
|
|
15
|
-
json={"
|
|
14
|
+
"https://apis.vhi.ie/api/myvhilogin/login",
|
|
15
|
+
json={"status": "SUCCESS", "sessionToken": "test-token"},
|
|
16
16
|
status=200
|
|
17
17
|
)
|
|
18
18
|
|
|
@@ -26,19 +26,23 @@ def test_login_requires_mfa_success():
|
|
|
26
26
|
|
|
27
27
|
client = VhiClient("test@example.com", "password", mfa_callback=mfa_callback)
|
|
28
28
|
|
|
29
|
-
# Mock primary login returning
|
|
29
|
+
# Mock primary login returning 200 with MFA_REQUIRED
|
|
30
30
|
responses.add(
|
|
31
31
|
responses.POST,
|
|
32
|
-
"https://apis.vhi.ie/
|
|
33
|
-
json={
|
|
34
|
-
|
|
32
|
+
"https://apis.vhi.ie/api/myvhilogin/login",
|
|
33
|
+
json={
|
|
34
|
+
"status": "MFA_REQUIRED",
|
|
35
|
+
"stateToken": "state-123",
|
|
36
|
+
"factors": [{"_links": {"verify": {"href": "https://admin-digital.vhi.ie/api/v1/authn/factors/verify"}}}]
|
|
37
|
+
},
|
|
38
|
+
status=200
|
|
35
39
|
)
|
|
36
40
|
|
|
37
|
-
# Mock MFA verify returning 200
|
|
41
|
+
# Mock MFA verify returning 200 SUCCESS
|
|
38
42
|
responses.add(
|
|
39
43
|
responses.POST,
|
|
40
|
-
"https://
|
|
41
|
-
json={"
|
|
44
|
+
"https://admin-digital.vhi.ie/api/v1/authn/factors/verify",
|
|
45
|
+
json={"status": "SUCCESS", "sessionToken": "final-token"},
|
|
42
46
|
status=200
|
|
43
47
|
)
|
|
44
48
|
|
|
@@ -51,7 +55,7 @@ def test_login_invalid_credentials():
|
|
|
51
55
|
|
|
52
56
|
responses.add(
|
|
53
57
|
responses.POST,
|
|
54
|
-
"https://apis.vhi.ie/
|
|
58
|
+
"https://apis.vhi.ie/api/myvhilogin/login",
|
|
55
59
|
status=401
|
|
56
60
|
)
|
|
57
61
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pytest
|
|
3
|
+
from vhi.client import VhiClient
|
|
4
|
+
from vhi.exceptions import VhiMfaRequiredError, VhiAuthenticationError
|
|
5
|
+
|
|
6
|
+
@pytest.mark.skipif(
|
|
7
|
+
not os.getenv("VHI_USERNAME") or not os.getenv("VHI_PASSWORD"),
|
|
8
|
+
reason="Integration tests require VHI_USERNAME and VHI_PASSWORD environment variables"
|
|
9
|
+
)
|
|
10
|
+
def test_live_login():
|
|
11
|
+
"""
|
|
12
|
+
Test live login against Vhi API.
|
|
13
|
+
We expect to hit either a successful login or an MFA challenge.
|
|
14
|
+
"""
|
|
15
|
+
username = os.environ["VHI_USERNAME"]
|
|
16
|
+
password = os.environ["VHI_PASSWORD"]
|
|
17
|
+
|
|
18
|
+
# We initialize without an mfa_callback to ensure it raises VhiMfaRequiredError
|
|
19
|
+
# if MFA is prompted, which validates the primary credentials were correct.
|
|
20
|
+
client = VhiClient(username, password)
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
client.login()
|
|
24
|
+
# If it succeeds without MFA, we are authenticated.
|
|
25
|
+
assert client.is_authenticated is True
|
|
26
|
+
except VhiMfaRequiredError:
|
|
27
|
+
# If it throws MFA required, the API accepted the usercred and triggered Okta MFA.
|
|
28
|
+
# This confirms our primary login endpoint and payload reverse-engineering is correct.
|
|
29
|
+
pass
|
|
30
|
+
except VhiAuthenticationError as e:
|
|
31
|
+
pytest.fail(f"Integration login failed - incorrect credentials: {e}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|