Skip to content

Commit 660da18

Browse files
authored
CMLDEV-910 Raise APIError when jwt is incorrect and usernam… (#194)
* Raise APIError when jwt is incorrect and username/password not provided
1 parent 12aaa3f commit 660da18

2 files changed

Lines changed: 100 additions & 0 deletions

File tree

tests/test_client_library.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import pytest
3030
import respx
3131

32+
from virl2_client.exceptions import APIError
3233
from virl2_client.models import Lab
3334
from virl2_client.virl2_client import (
3435
ClientLibrary,
@@ -224,6 +225,97 @@ def initial_different_response(
224225
assert respx.calls.call_count == 6
225226

226227

228+
@respx.mock
229+
def test_jwt_only_valid_token_does_not_call_password_auth(
230+
client_library_server_current: MagicMock,
231+
):
232+
_ = client_library_server_current
233+
234+
auth_route = respx.get(f"{FAKE_URL}api/v0/authentication").respond(
235+
200,
236+
json={
237+
"username": "jwt_user",
238+
"admin": False,
239+
"id": "6c7dd461-1cbe-428f-bdd5-545a0d766ed7",
240+
"token": "VALID_TOKEN",
241+
"error": None,
242+
},
243+
)
244+
password_auth_route = respx.post(f"{FAKE_URL}api/v0/authenticate").respond(
245+
json="SHOULD_NOT_BE_USED"
246+
)
247+
respx.get(f"{FAKE_URL}api/v0/labs").respond(json=[])
248+
249+
cl = ClientLibrary(url=FAKE_URL, jwtoken="VALID_TOKEN")
250+
cl.all_labs()
251+
252+
assert auth_route.called
253+
assert not password_auth_route.called
254+
255+
256+
@respx.mock
257+
def test_jwt_expired_with_credentials_reauths_using_password_auth(
258+
client_library_server_current: MagicMock,
259+
):
260+
_ = client_library_server_current
261+
262+
auth_route = respx.get(f"{FAKE_URL}api/v0/authentication")
263+
auth_route.side_effect = [
264+
httpx.Response(401),
265+
httpx.Response(
266+
200,
267+
json={
268+
"username": "test",
269+
"admin": True,
270+
"id": "6c7dd461-1cbe-428f-bdd5-545a0d766ed7",
271+
"token": "REFRESHED_TOKEN",
272+
"error": None,
273+
},
274+
),
275+
]
276+
password_auth_route = respx.post(f"{FAKE_URL}api/v0/authenticate").respond(
277+
json="REFRESHED_TOKEN"
278+
)
279+
280+
ClientLibrary(
281+
url=FAKE_URL,
282+
username="test",
283+
password="pa$$",
284+
jwtoken="EXPIRED_TOKEN",
285+
)
286+
287+
assert auth_route.called
288+
assert auth_route.call_count == 2
289+
assert password_auth_route.called
290+
assert json.loads(password_auth_route.calls[0].request.content) == {
291+
"username": "test",
292+
"password": "pa$$",
293+
}
294+
295+
296+
@respx.mock
297+
def test_jwt_reauth_without_credentials_fails_cleanly(
298+
client_library_server_current: MagicMock,
299+
reset_env: None,
300+
):
301+
_ = client_library_server_current, reset_env
302+
303+
auth_route = respx.get(f"{FAKE_URL}api/v0/authentication").respond(401)
304+
password_auth_route = respx.post(f"{FAKE_URL}api/v0/authenticate").respond(
305+
json="SHOULD_NOT_BE_USED"
306+
)
307+
308+
with pytest.raises(
309+
APIError,
310+
match="JWT token expired and automatic re-authentication is not possible",
311+
):
312+
ClientLibrary(url=FAKE_URL, jwtoken="EXPIRED_TOKEN")
313+
314+
assert auth_route.called
315+
assert auth_route.call_count == 1
316+
assert not password_auth_route.called
317+
318+
227319
@respx.mock
228320
def test_old_auth_url_used_with_cml_2_9(
229321
client_library_server_2_9_0: MagicMock, monkeypatch: pytest.MonkeyPatch

virl2_client/models/authentication.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,14 @@ def auth_flow(
123123
if response.status_code == 401:
124124
_LOGGER.warning("re-auth called on 401 unauthorized")
125125
self.token = None
126+
if not (self.client_library.username and self.client_library.password):
127+
raise APIError(
128+
"JWT token expired and automatic re-authentication is not "
129+
"possible because username/password are not configured. "
130+
"Set client.jwtoken, or initialize with username/password.",
131+
request=response.request,
132+
response=response,
133+
)
126134
request.headers["Authorization"] = f"Bearer {self.token}"
127135
response = yield request
128136

0 commit comments

Comments
 (0)