@@ -50,7 +50,13 @@ def setUp(self):
50
50
self .participation = self .add_participation (
51
51
contest = self .contest , user = self .user )
52
52
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 = "" ):
54
60
# We had an issue where due to a misuse of contains_eager we ended up
55
61
# with the wrong user attached to the participation. This only happens
56
62
# 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):
61
67
62
68
authenticated_participation , cookie = validate_login (
63
69
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 )
65
72
66
73
self .assertIsNotNone (authenticated_participation )
67
74
self .assertIsNotNone (cookie )
68
75
self .assertIs (authenticated_participation , self .participation )
69
76
self .assertIs (authenticated_participation .user , self .user )
70
77
self .assertIs (authenticated_participation .contest , self .contest )
71
78
72
- def assertFailure (self , username , password , ip_address ):
79
+ def assertFailure (self , username , password , ip_address , admin_token = "" ):
73
80
authenticated_participation , cookie = validate_login (
74
81
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 )
76
84
77
85
self .assertIsNone (authenticated_participation )
78
86
self .assertIsNone (cookie )
@@ -150,6 +158,30 @@ def test_deactivated_ip_lock(self):
150
158
151
159
self .assertSuccess ("myuser" , "mypass" , "10.0.1.1" )
152
160
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
+
153
185
154
186
class TestAuthenticateRequest (DatabaseMixin , unittest .TestCase ):
155
187
@@ -158,7 +190,6 @@ def setUp(self):
158
190
self .timestamp = make_datetime ()
159
191
self .add_contest ()
160
192
self .contest = self .add_contest ()
161
- self .add_user (username = "otheruser" )
162
193
self .user = self .add_user (
163
194
username = "myuser" , password = build_password ("mypass" ))
164
195
self .participation = self .add_participation (
@@ -167,7 +198,26 @@ def setUp(self):
167
198
self .session , self .contest , self .timestamp , self .user .username ,
168
199
"mypass" , ipaddress .ip_address ("10.0.0.1" ))
169
200
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
+
171
221
# The arguments need to be passed as keywords and are timestamp, cookie
172
222
# and ip_address. A missing argument means the default value is used
173
223
# instead. An argument passed as None means that None will be used.
@@ -179,19 +229,6 @@ def attempt_authentication(self, **kwargs):
179
229
ipaddress .ip_address (kwargs .get ("ip_address" , "10.0.0.1" )))
180
230
181
231
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
-
195
232
authenticated_participation , cookie , impersonated = \
196
233
self .attempt_authentication (** kwargs )
197
234
@@ -221,6 +258,21 @@ def assertSuccessAndCookieCleared(self, **kwargs):
221
258
cookie = self .assertSuccess (** kwargs )
222
259
self .assertIsNone (cookie )
223
260
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
+
224
276
def assertFailure (self , ** kwargs ):
225
277
# Assert that the authentication fails.
226
278
# The arguments are the same as those of attempt_authentication.
@@ -337,7 +389,7 @@ def test_authorization_header(self):
337
389
338
390
def test_no_user (self ):
339
391
self .session .delete (self .user )
340
- self .assertFailure ()
392
+ self .assertFailure (db_flush = False )
341
393
342
394
def test_no_participation_for_user_in_contest (self ):
343
395
self .session .delete (self .participation )
@@ -372,6 +424,30 @@ def test_ip_lock(self):
372
424
self .participation .ip = None
373
425
self .assertSuccessAndCookieRefreshed ()
374
426
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
+
375
451
376
452
if __name__ == "__main__" :
377
453
unittest .main ()
0 commit comments