Skip to content

Commit e158c73

Browse files
committed
Add unit tests for impersonation
1 parent ddfb933 commit e158c73

File tree

1 file changed

+96
-20
lines changed

1 file changed

+96
-20
lines changed

cmstestsuite/unit_tests/server/contest/authentication_test.py

Lines changed: 96 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,13 @@ def setUp(self):
5050
self.participation = self.add_participation(
5151
contest=self.contest, user=self.user)
5252

53-
def assertSuccess(self, username, password, ip_address):
53+
# Set up a temporary admin token
54+
patcher = patch.object(config, "contest_admin_token", "admin-token")
55+
self.addCleanup(patcher.stop)
56+
patcher.start()
57+
58+
59+
def assertSuccess(self, username, password, ip_address, admin_token=""):
5460
# We had an issue where due to a misuse of contains_eager we ended up
5561
# with the wrong user attached to the participation. This only happens
5662
# if the correct user isn't already in the identity map, which is what
@@ -61,18 +67,20 @@ def assertSuccess(self, username, password, ip_address):
6167

6268
authenticated_participation, cookie = validate_login(
6369
self.session, self.contest, self.timestamp,
64-
username, password, ipaddress.ip_address(ip_address))
70+
username, password, ipaddress.ip_address(ip_address),
71+
admin_token)
6572

6673
self.assertIsNotNone(authenticated_participation)
6774
self.assertIsNotNone(cookie)
6875
self.assertIs(authenticated_participation, self.participation)
6976
self.assertIs(authenticated_participation.user, self.user)
7077
self.assertIs(authenticated_participation.contest, self.contest)
7178

72-
def assertFailure(self, username, password, ip_address):
79+
def assertFailure(self, username, password, ip_address, admin_token=""):
7380
authenticated_participation, cookie = validate_login(
7481
self.session, self.contest, self.timestamp,
75-
username, password, ipaddress.ip_address(ip_address))
82+
username, password, ipaddress.ip_address(ip_address),
83+
admin_token)
7684

7785
self.assertIsNone(authenticated_participation)
7886
self.assertIsNone(cookie)
@@ -150,6 +158,30 @@ def test_deactivated_ip_lock(self):
150158

151159
self.assertSuccess("myuser", "mypass", "10.0.1.1")
152160

161+
def test_successful_impersonation(self):
162+
self.assertSuccess("myuser", "", "127.0.0.1", "admin-token")
163+
164+
def test_unsuccessful_impersonation(self):
165+
self.assertFailure("myuser", "", "127.0.0.1", "bad-admin-token")
166+
167+
def test_impersonation_overrides_unallowed_password_authentication(self):
168+
self.contest.allow_password_authentication = False
169+
170+
self.assertSuccess("myuser", "", "127.0.0.1", "admin-token")
171+
172+
def test_impersonation_overrides_unallowed_hidden_participation(self):
173+
self.contest.block_hidden_participations = True
174+
self.participation.hidden = True
175+
176+
self.assertSuccess("myuser", "", "127.0.0.1", "admin-token")
177+
178+
def test_impersonation_overrides_ip_lock(self):
179+
self.contest.ip_restriction = True
180+
self.participation.ip = [ipaddress.ip_network("10.0.0.0/24")]
181+
182+
self.assertSuccess("myuser", "mypass", "10.0.0.1", "admin-token")
183+
self.assertSuccess("myuser", "mypass", "10.0.1.1", "admin-token")
184+
153185

154186
class TestAuthenticateRequest(DatabaseMixin, unittest.TestCase):
155187

@@ -158,7 +190,6 @@ def setUp(self):
158190
self.timestamp = make_datetime()
159191
self.add_contest()
160192
self.contest = self.add_contest()
161-
self.add_user(username="otheruser")
162193
self.user = self.add_user(
163194
username="myuser", password=build_password("mypass"))
164195
self.participation = self.add_participation(
@@ -167,7 +198,26 @@ def setUp(self):
167198
self.session, self.contest, self.timestamp, self.user.username,
168199
"mypass", ipaddress.ip_address("10.0.0.1"))
169200

170-
def attempt_authentication(self, **kwargs):
201+
# For testing impersonation by admin token
202+
self.impersonated_user = self.add_user(username="otheruser")
203+
self.impersonated_participation = self.add_participation(
204+
contest=self.contest, user=self.impersonated_user)
205+
with patch.object(config, "contest_admin_token", "admin-token"):
206+
_, self.impersonated_cookie = validate_login(
207+
self.session, self.contest, self.timestamp, "otheruser",
208+
"", ipaddress.ip_address("10.0.0.2"), "admin-token")
209+
210+
def attempt_authentication(self, db_flush=True, **kwargs):
211+
# We had an issue where due to a misuse of contains_eager we ended up
212+
# with the wrong user attached to the participation. This only happens
213+
# if the correct user isn't already in the identity map, which is what
214+
# these lines trigger.
215+
if db_flush:
216+
self.session.flush()
217+
self.session.expire(self.user)
218+
self.session.expire(self.impersonated_user)
219+
self.session.expire(self.contest)
220+
171221
# The arguments need to be passed as keywords and are timestamp, cookie
172222
# and ip_address. A missing argument means the default value is used
173223
# instead. An argument passed as None means that None will be used.
@@ -179,19 +229,6 @@ def attempt_authentication(self, **kwargs):
179229
ipaddress.ip_address(kwargs.get("ip_address", "10.0.0.1")))
180230

181231
def assertSuccess(self, **kwargs):
182-
# Assert that the authentication succeeds in any way (be it through IP
183-
# autologin or thanks to the cookie) and return the cookie that should
184-
# be set (or None, if it should be cleared/left unset).
185-
# The arguments are the same as those of attempt_authentication.
186-
187-
# We had an issue where due to a misuse of contains_eager we ended up
188-
# with the wrong user attached to the participation. This only happens
189-
# if the correct user isn't already in the identity map, which is what
190-
# these lines trigger.
191-
self.session.flush()
192-
self.session.expire(self.user)
193-
self.session.expire(self.contest)
194-
195232
authenticated_participation, cookie, impersonated = \
196233
self.attempt_authentication(**kwargs)
197234

@@ -221,6 +258,21 @@ def assertSuccessAndCookieCleared(self, **kwargs):
221258
cookie = self.assertSuccess(**kwargs)
222259
self.assertIsNone(cookie)
223260

261+
def assertImpersonationSuccess(self, **kwargs):
262+
# Assert that the impersonation succeeds.
263+
# The arguments are the same as those of attempt_authentication.
264+
265+
authenticated_participation, cookie, impersonated = \
266+
self.attempt_authentication(cookie=self.impersonated_cookie, **kwargs)
267+
268+
self.assertIsNotNone(authenticated_participation)
269+
self.assertIs(authenticated_participation, self.impersonated_participation)
270+
self.assertIs(authenticated_participation.user, self.impersonated_user)
271+
self.assertIs(authenticated_participation.contest, self.contest)
272+
self.assertIs(impersonated, True)
273+
274+
return cookie
275+
224276
def assertFailure(self, **kwargs):
225277
# Assert that the authentication fails.
226278
# The arguments are the same as those of attempt_authentication.
@@ -337,7 +389,7 @@ def test_authorization_header(self):
337389

338390
def test_no_user(self):
339391
self.session.delete(self.user)
340-
self.assertFailure()
392+
self.assertFailure(db_flush=False)
341393

342394
def test_no_participation_for_user_in_contest(self):
343395
self.session.delete(self.participation)
@@ -372,6 +424,30 @@ def test_ip_lock(self):
372424
self.participation.ip = None
373425
self.assertSuccessAndCookieRefreshed()
374426

427+
def test_impersonate(self):
428+
self.contest.ip_autologin = False
429+
self.contest.allow_password_authentication = False
430+
self.assertImpersonationSuccess()
431+
432+
def test_impersonate_overridden_by_ip_autologin(self):
433+
self.contest.ip_autologin = True
434+
self.contest.allow_password_authentication = False
435+
436+
self.participation.ip = [ipaddress.ip_network("10.0.0.1/32")]
437+
self.assertSuccessAndCookieCleared(cookie=self.impersonated_cookie)
438+
439+
def test_impersonation_overrides_unallowed_hidden_participation(self):
440+
self.contest.block_hidden_participations = True
441+
self.participation.hidden = True
442+
self.assertImpersonationSuccess()
443+
444+
def test_impersonation_overrides_ip_lock(self):
445+
self.contest.ip_restriction = True
446+
self.participation.ip = [ipaddress.ip_network("10.0.0.0/24")]
447+
448+
self.assertImpersonationSuccess(ip_address="10.0.0.1")
449+
self.assertImpersonationSuccess(ip_address="10.0.1.1")
450+
375451

376452
if __name__ == "__main__":
377453
unittest.main()

0 commit comments

Comments
 (0)