Skip to content

Commit 5a6f2b7

Browse files
committed
fix(api): use weblogin access token with API base_url redirect
1 parent 36a46b8 commit 5a6f2b7

File tree

2 files changed

+63
-39
lines changed

2 files changed

+63
-39
lines changed

src/elmo/api/client.py

+19-21
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,14 @@ def __init__(self, base_url=None, domain=None, session_id=None):
5353
self._session = Session()
5454
self._session_id = session_id
5555
self._panel = None
56+
self._web_login = base_url == ELMO_E_CONNECT
5657
self._lock = Lock()
5758

5859
# Debug
5960
_LOGGER.debug(f"Client | Library version: {__version__}")
6061
_LOGGER.debug(f"Client | Router: {self._router._base_url}")
6162
_LOGGER.debug(f"Client | Domain: {self._domain}")
63+
_LOGGER.debug(f"Client | Web login: {self._web_login}")
6264

6365
def auth(self, username, password):
6466
"""Authenticate the client and retrieves the access token. This method uses
@@ -75,23 +77,6 @@ def auth(self, username, password):
7577
the `ElmoClient` instance.
7678
"""
7779
try:
78-
if self._router._base_url == ELMO_E_CONNECT:
79-
# Web login is required for Elmo E-Connect because, at the moment, the
80-
# e-Connect Cloud API login does not register the client session in the backend.
81-
# This prevents the client from attaching to server events (e.g. long polling updates).
82-
web_login_url = f"https://webservice.elmospa.com/{self._domain}"
83-
payload = {
84-
"IsDisableAccountCreation": "True",
85-
"IsAllowThemeChange": "True",
86-
"UserName": username,
87-
"Password": password,
88-
"RememberMe": "false",
89-
}
90-
_LOGGER.debug("Client | e-Connect Web Login detected")
91-
web_response = self._session.post(web_login_url, data=payload)
92-
web_response.raise_for_status()
93-
94-
# API login
9580
payload = {"username": username, "password": password}
9681
if self._domain is not None:
9782
payload["domain"] = self._domain
@@ -107,11 +92,8 @@ def auth(self, username, password):
10792

10893
# Store the session_id and the panel details (if available)
10994
data = response.json()
95+
self._session_id = data["SessionId"]
11096
self._panel = {_camel_to_snake_case(k): v for k, v in data.get("Panel", {}).items()}
111-
if self._router._base_url == ELMO_E_CONNECT:
112-
self._session_id = extract_session_id_from_html(web_response.text)
113-
else:
114-
self._session_id = data["SessionId"]
11597

11698
# Register the redirect URL and try the authentication again
11799
if data["Redirect"]:
@@ -122,6 +104,22 @@ def auth(self, username, password):
122104
data = redirect.json()
123105
self._session_id = data["SessionId"]
124106

107+
if self._web_login:
108+
# Web login is required for Elmo E-Connect because, at the moment, the
109+
# e-Connect Cloud API login does not register the client session in the backend.
110+
# This prevents the client from attaching to server events (e.g. long polling updates).
111+
web_login_url = f"https://webservice.elmospa.com/{self._domain}"
112+
payload = {
113+
"IsDisableAccountCreation": "True",
114+
"IsAllowThemeChange": "True",
115+
"UserName": username,
116+
"Password": password,
117+
"RememberMe": "false",
118+
}
119+
web_response = self._session.post(web_login_url, data=payload)
120+
web_response.raise_for_status()
121+
self._session_id = extract_session_id_from_html(web_response.text)
122+
125123
_LOGGER.debug(f"Client | Authentication successful: {_sanitize_session_id(self._session_id)}")
126124
return self._session_id
127125

tests/test_client.py

+44-18
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,46 @@ def test_client_auth_redirect(server):
239239
assert len(server.calls) == 2
240240

241241

242+
def test_client_auth_redirect_web_login(server):
243+
"""Ensure web login session token is used when redirect is required.
244+
Regression test: https://github.com/palazzem/econnect-python/issues/158
245+
"""
246+
redirect = """
247+
{
248+
"SessionId": "00000000-0000-0000-0000-000000000000",
249+
"Domain": "domain",
250+
"Redirect": true,
251+
"RedirectTo": "https://redirect.example.com"
252+
}
253+
"""
254+
login = """
255+
{
256+
"SessionId": "99999999-9999-9999-9999-999999999999",
257+
"Username": "test",
258+
"Domain": "domain",
259+
"Language": "en",
260+
"IsActivated": true,
261+
"IsConnected": true,
262+
"IsLoggedIn": false,
263+
"IsLoginInProgress": false,
264+
"CanElevate": true,
265+
"AccountId": 100,
266+
"IsManaged": false,
267+
"Redirect": false,
268+
"IsElevation": false
269+
}
270+
"""
271+
server.add(responses.GET, "https://connect.elmospa.com/api/login", body=redirect, status=200)
272+
server.add(responses.GET, "https://redirect.example.com/api/login", body=login, status=200)
273+
server.add(responses.POST, "https://webservice.elmospa.com/domain", body=r.STATUS_PAGE, status=200)
274+
client = ElmoClient(base_url=ELMO_E_CONNECT, domain="domain")
275+
# Test
276+
assert client.auth("test", "test")
277+
assert len(server.calls) == 3
278+
assert client._router._base_url == "https://redirect.example.com"
279+
assert client._session_id == "f8h23b4e-7a9f-4d3f-9b08-2769263ee33c"
280+
281+
242282
def test_client_auth_infinite_redirect(server):
243283
"""Should prevent infinite redirects in the auth() call."""
244284
redirect = """
@@ -349,16 +389,16 @@ def test_client_poll(server):
349389

350390

351391
def test_client_auth_econnect_web_login(server):
352-
"""Web login should be used when accessing with e-Connect.
392+
"""Ensure API and Web login are executed when using e-Connect cloud API.
353393
Regression test: https://github.com/palazzem/econnect-python/issues/158
354394
"""
395+
server.add(responses.GET, "https://connect.elmospa.com/api/login", body=r.LOGIN, status=200)
355396
server.add(responses.POST, "https://webservice.elmospa.com/domain", body=r.STATUS_PAGE, status=200)
356-
server.add(responses.GET, f"{ELMO_E_CONNECT}/api/login", body=r.LOGIN, status=200)
357397
client = ElmoClient(base_url=ELMO_E_CONNECT, domain="domain")
358398
# Test
359399
client.auth("test", "test")
360-
request_body = dict(item.split("=") for item in server.calls[0].request.body.split("&"))
361400
assert len(server.calls) == 2
401+
request_body = dict(item.split("=") for item in server.calls[1].request.body.split("&"))
362402
assert client._session_id == "f8h23b4e-7a9f-4d3f-9b08-2769263ee33c"
363403
assert request_body == {
364404
"IsDisableAccountCreation": "True",
@@ -373,28 +413,14 @@ def test_client_auth_econnect_web_login_metronet(server):
373413
"""Web login should NOT be used when accessing with Metronet.
374414
Regression test: https://github.com/palazzem/econnect-python/issues/158
375415
"""
376-
server.add(responses.GET, f"{IESS_METRONET}/api/login", body=r.LOGIN, status=200)
416+
server.add(responses.GET, "https://metronet.iessonline.com/api/login", body=r.LOGIN, status=200)
377417
client = ElmoClient(base_url=IESS_METRONET, domain="domain")
378418
# Test
379419
client.auth("test", "test")
380420
assert client._session_id == "00000000-0000-0000-0000-000000000000"
381421
assert len(server.calls) == 1
382422

383423

384-
def test_client_auth_econnect_web_login_forbidden(server):
385-
"""Should raise an exception if credentials are not valid in the web login form."""
386-
server.add(
387-
responses.POST, "https://webservice.elmospa.com/domain", body="Username or Password is invalid", status=403
388-
)
389-
client = ElmoClient(base_url=ELMO_E_CONNECT, domain="domain")
390-
# Test
391-
with pytest.raises(CredentialError):
392-
client.auth("test", "test")
393-
assert client._session_id is None
394-
assert client._panel is None
395-
assert len(server.calls) == 1
396-
397-
398424
def test_client_poll_with_changes(server):
399425
"""Should return a dict with updated states."""
400426
html = """

0 commit comments

Comments
 (0)