Skip to content

Commit e989dbe

Browse files
committed
Initial failing test for re-login Refs #12
1 parent 55c3114 commit e989dbe

2 files changed

Lines changed: 203 additions & 0 deletions

File tree

tests/mock_responses.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,26 @@ def as_instance_reference(cls):
9999
method="POST",
100100
)
101101

102+
MINT_401 = MockResponse(
103+
url=MockUrls.MINT_URL,
104+
status_code=401,
105+
reason="Unauthorized",
106+
method="GET",
107+
text="""<html>
108+
<head>
109+
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
110+
<title>Error 401 you need to log in to access this page.</title>
111+
</head>
112+
<body><h2>HTTP ERROR 401</h2>
113+
<p>Problem accessing /rest/api/vault/mint/studies. Reason:
114+
<pre> you need to log in to access this page.</pre></p><hr>
115+
<a href="http://eclipse.org/jetty">Powered by Jetty://
116+
9.4.12.v20180830</a><hr/>
117+
118+
</body>
119+
</html>""",
120+
)
121+
102122
LOGIN_IMPAX_INITIAL = MockResponse(
103123
url=MockUrls.LOGIN, text="whatever", status_code=200, method="GET"
104124
)

tests/test_servers.py

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
import uuid
2+
from typing import List, Optional
3+
14
import pytest
25
import requests
6+
from pydantic.main import BaseModel
37

48
from dicomtrolley.exceptions import DICOMTrolleyError
59
from dicomtrolley.servers import IMPAXDataCenter, VitreaConnection
@@ -8,6 +12,8 @@
812
LOGIN_DENIED_IMPAX,
913
LOGIN_IMPAX_INITIAL,
1014
LOGIN_SUCCESS_IMPAX,
15+
MINT_401,
16+
MINT_SEARCH_STUDY_LEVEL,
1117
MockUrls,
1218
)
1319

@@ -56,3 +62,180 @@ def test_impax_login_fails_connection_error(requests_mock):
5662
)
5763
with pytest.raises(DICOMTrolleyError):
5864
IMPAXDataCenter(wado_url=MockUrls.LOGIN).log_in("user", "pass")
65+
66+
67+
@pytest.fixture
68+
def mock_vitrea_server(requests_mock):
69+
"""Simulates vitrea Connection login and responses
70+
71+
Valid login with VITREA_CREDENTIALS
72+
"""
73+
server = VitreaServer(allowed_credentials=VITREA_CREDENTIALS)
74+
server.register_all_responses(requests_mock)
75+
return server
76+
77+
78+
def test_vitrea_login(mock_vitrea_server):
79+
"""Test basic login into a vitrea server"""
80+
server = mock_vitrea_server
81+
82+
# try to get some mint response. Won't work without login
83+
assert requests.Session().get(server.mint_url).status_code == 401
84+
85+
# now log in and try again
86+
connection = VitreaConnection(login_url=server.login_url)
87+
session = connection.log_in(
88+
user=VITREA_CREDENTIALS.user_id,
89+
password=VITREA_CREDENTIALS.password,
90+
realm=VITREA_CREDENTIALS.realm,
91+
)
92+
assert session.get(server.mint_url).status_code == 200
93+
94+
95+
def test_vitrea_login_session_timeout(mock_vitrea_server):
96+
"""If session times out, login should be re-attempted"""
97+
server = mock_vitrea_server
98+
99+
assert requests.Session().get(server.mint_url).status_code == 401
100+
connection = VitreaConnection(login_url=server.login_url)
101+
session = connection.log_in(
102+
user=VITREA_CREDENTIALS.user_id,
103+
password=VITREA_CREDENTIALS.password,
104+
realm=VITREA_CREDENTIALS.realm,
105+
)
106+
assert session.get(server.mint_url).status_code == 200
107+
# session times out. No 200 responses anymore from server
108+
server.allow_all = False
109+
110+
# this should trigger an internal re-login
111+
assert session.get(server.mint_url).status_code == 200
112+
113+
114+
class VitreaCredentials(BaseModel):
115+
"""Credentials needed to log in to a Vitrea server"""
116+
117+
user_id: str
118+
password: str
119+
realm: str
120+
121+
122+
class VitreaServer:
123+
"""A server that you can log in to the vitrea way, for testing login and session
124+
persistence
125+
126+
requires requests_mock to mock url calls
127+
128+
Notes
129+
-----
130+
Tried to get actual session-cookie based request auth working but could not.
131+
For some reason a Session with a valid cookie does not pass this cookie on
132+
to requests when calling session.get(). I can't figure out why.
133+
In addition, requests_mock does not pass on cookie to session. Known issue
134+
tracked here: https://github.com/jamielennox/requests-mock/pull/143
135+
136+
Opted instead to go for a server-wide 'allow all' switch after succesful login.
137+
This will fulfil testing requirements for now.
138+
"""
139+
140+
def __init__(
141+
self,
142+
allowed_credentials: Optional[VitreaCredentials] = None,
143+
url="https://mockserver",
144+
):
145+
self.url = url
146+
self.login_url = f"{url}/login"
147+
self.mint_url = f"{url}/mint"
148+
149+
self.allowed_credentials = allowed_credentials
150+
self._authorized_tokens: List[str] = []
151+
self._authorized_sessions: List[str] = []
152+
self.allow_all = False
153+
154+
def set_allowed_credentials(self, credentials: VitreaCredentials):
155+
self.allowed_credentials = credentials
156+
157+
def register_login_response(self, requests_mock):
158+
"""Register this server's login url with requests mock"""
159+
requests_mock.register_uri(
160+
"POST", url=self.login_url, text=self.create_login_callback()
161+
)
162+
163+
def register_mint_response(self, requests_mock):
164+
requests_mock.register_uri(
165+
"GET", url=self.mint_url, text=self.create_mint_callback()
166+
)
167+
168+
def register_all_responses(self, requests_mock):
169+
"""Make sure requests calls to this servers's urls are routed through here"""
170+
self.register_login_response(requests_mock)
171+
self.register_mint_response(requests_mock)
172+
173+
def add_token(self):
174+
token = str(uuid.uuid4())
175+
self._authorized_tokens.append(token)
176+
return token
177+
178+
def add_session_token(self):
179+
session_token = str(uuid.uuid4())
180+
self._authorized_sessions.append(session_token)
181+
return session_token
182+
183+
def create_login_callback(self):
184+
def callback(request, context):
185+
if self.can_login(request):
186+
self.allow_all = True
187+
context.status_code = 200
188+
context.json = {
189+
"access_token": self.add_token(),
190+
"token_type": "Bearer",
191+
}
192+
context.cookies = {"JSESSIONID": self.add_session_token()}
193+
return "success"
194+
else:
195+
context.status_code = 401
196+
context.reason = ("Unauthorized",)
197+
return "Login failed: bad username/password"
198+
199+
return callback
200+
201+
def can_login(self, request) -> bool:
202+
"""Does this request provide the right data to log in?"""
203+
provided = VitreaCredentials(
204+
user_id=request.headers.get("X-Userid"),
205+
password=request.headers.get("X-Password"),
206+
realm=request.headers.get("X-Realm"),
207+
)
208+
return provided == self.allowed_credentials
209+
210+
def create_mint_callback(self):
211+
def callback(request, context):
212+
if self.is_authenticated(request):
213+
context.status_code = MINT_SEARCH_STUDY_LEVEL.status_code
214+
return MINT_SEARCH_STUDY_LEVEL.text
215+
else:
216+
context.status_code = MINT_401.status_code
217+
context.reason = MINT_401.reason
218+
return MINT_401.text
219+
220+
return callback
221+
222+
def is_authenticated(self, request):
223+
"""Is this request allowed to get stuff?
224+
225+
Weirdly enough for the Vitrea Connection 8.2.0.1 there seems to be no
226+
checking of the access token at all. It's all based on the session cookie
227+
"""
228+
if self.allow_all:
229+
return True
230+
if hasattr(request, "cookies"): # this net gets used, see class notes
231+
return (
232+
request.cookies.get("JSESSIONID") in self._authorized_sessions
233+
)
234+
else:
235+
return False
236+
237+
238+
# for testing login
239+
VITREA_CREDENTIALS = VitreaCredentials(
240+
user_id="user", password="pass", realm="some_realm"
241+
)

0 commit comments

Comments
 (0)