OLD | NEW |
(Empty) | |
| 1 # Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is govered by a BSD-style |
| 3 # license that can be found in the LICENSE file or at |
| 4 # https://developers.google.com/open-source/licenses/bsd |
| 5 |
| 6 """Unit tests for the monorailrequest module.""" |
| 7 |
| 8 import re |
| 9 import unittest |
| 10 |
| 11 import mox |
| 12 |
| 13 from google.appengine.api import users |
| 14 |
| 15 import webapp2 |
| 16 |
| 17 from framework import monorailrequest |
| 18 from framework import permissions |
| 19 from framework import profiler |
| 20 from proto import project_pb2 |
| 21 from proto import tracker_pb2 |
| 22 from services import service_manager |
| 23 from testing import fake |
| 24 from testing import testing_helpers |
| 25 from tracker import tracker_constants |
| 26 |
| 27 |
| 28 class HostportReTest(unittest.TestCase): |
| 29 |
| 30 def testGood(self): |
| 31 test_data = [ |
| 32 'localhost:8080', |
| 33 'app.appspot.com', |
| 34 'bugs-staging.chromium.org', |
| 35 'vers10n-h3x-dot-app-id.appspot.com', |
| 36 ] |
| 37 for hostport in test_data: |
| 38 self.assertTrue(monorailrequest._HOSTPORT_RE.match(hostport), |
| 39 msg='Incorrectly rejected %r' % hostport) |
| 40 |
| 41 def testBad(self): |
| 42 test_data = [ |
| 43 '', |
| 44 ' ', |
| 45 '\t', |
| 46 '\n', |
| 47 '\'', |
| 48 '"', |
| 49 'version"cruft-dot-app-id.appspot.com', |
| 50 '\nother header', |
| 51 'version&cruft-dot-app-id.appspot.com', |
| 52 ] |
| 53 for hostport in test_data: |
| 54 self.assertFalse(monorailrequest._HOSTPORT_RE.match(hostport), |
| 55 msg='Incorrectly accepted %r' % hostport) |
| 56 |
| 57 class AuthDataTest(unittest.TestCase): |
| 58 |
| 59 def setUp(self): |
| 60 self.mox = mox.Mox() |
| 61 |
| 62 def tearDown(self): |
| 63 self.mox.UnsetStubs() |
| 64 |
| 65 def testGetUserID(self): |
| 66 pass # TODO(jrobbins): re-impement |
| 67 |
| 68 def testExamineRequestUserID(self): |
| 69 pass # TODO(jrobbins): re-implement |
| 70 |
| 71 |
| 72 class MonorailRequestUnitTest(unittest.TestCase): |
| 73 |
| 74 def setUp(self): |
| 75 self.services = service_manager.Services( |
| 76 project=fake.ProjectService(), |
| 77 user=fake.UserService(), |
| 78 usergroup=fake.UserGroupService()) |
| 79 self.project = self.services.project.TestAddProject('proj') |
| 80 self.services.user.TestAddUser('jrobbins@example.com', 111) |
| 81 |
| 82 self.profiler = profiler.Profiler() |
| 83 self.mox = mox.Mox() |
| 84 self.mox.StubOutWithMock(users, 'get_current_user') |
| 85 users.get_current_user().AndReturn(None) |
| 86 self.mox.ReplayAll() |
| 87 |
| 88 def tearDown(self): |
| 89 self.mox.UnsetStubs() |
| 90 |
| 91 def testGetIntParamConvertsQueryParamToInt(self): |
| 92 notice_id = 12345 |
| 93 mr = testing_helpers.MakeMonorailRequest( |
| 94 path='/foo?notice=%s' % notice_id) |
| 95 |
| 96 value = mr.GetIntParam('notice') |
| 97 self.assert_(isinstance(value, int)) |
| 98 self.assertEqual(notice_id, value) |
| 99 |
| 100 def testGetIntParamConvertsQueryParamToLong(self): |
| 101 notice_id = 12345678901234567890 |
| 102 mr = testing_helpers.MakeMonorailRequest( |
| 103 path='/foo?notice=%s' % notice_id) |
| 104 |
| 105 value = mr.GetIntParam('notice') |
| 106 self.assertTrue(isinstance(value, long)) |
| 107 self.assertEqual(notice_id, value) |
| 108 |
| 109 def testGetIntListParamNoParam(self): |
| 110 mr = monorailrequest.MonorailRequest() |
| 111 mr.ParseRequest( |
| 112 webapp2.Request.blank('servlet'), self.services, self.profiler) |
| 113 self.assertEquals(mr.GetIntListParam('ids'), None) |
| 114 self.assertEquals(mr.GetIntListParam('ids', default_value=['test']), |
| 115 ['test']) |
| 116 |
| 117 def testGetIntListParamOneValue(self): |
| 118 mr = monorailrequest.MonorailRequest() |
| 119 mr.ParseRequest( |
| 120 webapp2.Request.blank('servlet?ids=11'), self.services, self.profiler) |
| 121 self.assertEquals(mr.GetIntListParam('ids'), [11]) |
| 122 self.assertEquals(mr.GetIntListParam('ids', default_value=['test']), |
| 123 [11]) |
| 124 |
| 125 def testGetIntListParamMultiValue(self): |
| 126 mr = monorailrequest.MonorailRequest() |
| 127 mr.ParseRequest( |
| 128 webapp2.Request.blank('servlet?ids=21,22,23'), self.services, |
| 129 self.profiler) |
| 130 self.assertEquals(mr.GetIntListParam('ids'), [21, 22, 23]) |
| 131 self.assertEquals(mr.GetIntListParam('ids', default_value=['test']), |
| 132 [21, 22, 23]) |
| 133 |
| 134 def testGetIntListParamBogusValue(self): |
| 135 mr = monorailrequest.MonorailRequest() |
| 136 mr.ParseRequest( |
| 137 webapp2.Request.blank('servlet?ids=not_an_int'), self.services, |
| 138 self.profiler) |
| 139 self.assertEquals(mr.GetIntListParam('ids'), None) |
| 140 self.assertEquals(mr.GetIntListParam('ids', default_value=['test']), |
| 141 ['test']) |
| 142 |
| 143 def testGetIntListParamMalformed(self): |
| 144 mr = monorailrequest.MonorailRequest() |
| 145 mr.ParseRequest( |
| 146 webapp2.Request.blank('servlet?ids=31,32,,'), self.services, |
| 147 self.profiler) |
| 148 self.assertEquals(mr.GetIntListParam('ids'), None) |
| 149 self.assertEquals(mr.GetIntListParam('ids', default_value=['test']), |
| 150 ['test']) |
| 151 |
| 152 def testDefaultValuesNoUrl(self): |
| 153 """If request has no param, default param values should be used.""" |
| 154 mr = monorailrequest.MonorailRequest() |
| 155 mr.ParseRequest( |
| 156 webapp2.Request.blank('servlet'), self.services, self.profiler) |
| 157 self.assertEquals(mr.GetParam('r', 3), 3) |
| 158 self.assertEquals(mr.GetIntParam('r', 3), 3) |
| 159 self.assertEquals(mr.GetPositiveIntParam('r', 3), 3) |
| 160 self.assertEquals(mr.GetIntListParam('r', [3, 4]), [3, 4]) |
| 161 |
| 162 def _MRWithMockRequest( |
| 163 self, path, headers=None, *mr_args, **mr_kwargs): |
| 164 request = webapp2.Request.blank(path, headers=headers) |
| 165 mr = monorailrequest.MonorailRequest(*mr_args, **mr_kwargs) |
| 166 mr.ParseRequest(request, self.services, self.profiler) |
| 167 return mr |
| 168 |
| 169 def testParseQueryParameters(self): |
| 170 mr = self._MRWithMockRequest( |
| 171 '/p/proj/issues/list?q=foo+OR+bar&num=50') |
| 172 self.assertEquals('foo OR bar', mr.query) |
| 173 self.assertEquals(50, mr.num) |
| 174 |
| 175 def testParseRequest_Scheme(self): |
| 176 mr = self._MRWithMockRequest('/p/proj/') |
| 177 self.assertEquals('http', mr.request.scheme) |
| 178 |
| 179 def testParseRequest_HostportAndCurrentPageURL(self): |
| 180 mr = self._MRWithMockRequest('/p/proj/', headers={ |
| 181 'Host': 'example.com', |
| 182 'Cookie': 'asdf', |
| 183 }) |
| 184 self.assertEquals('http', mr.request.scheme) |
| 185 self.assertEquals('example.com', mr.request.host) |
| 186 self.assertEquals('http://example.com/p/proj/', mr.current_page_url) |
| 187 |
| 188 def testViewedUser_WithEmail(self): |
| 189 mr = self._MRWithMockRequest('/u/jrobbins@example.com/') |
| 190 self.assertEquals('jrobbins@example.com', mr.viewed_username) |
| 191 self.assertEquals(111, mr.viewed_user_auth.user_id) |
| 192 self.assertEquals( |
| 193 self.services.user.GetUser('fake cnxn', 111), |
| 194 mr.viewed_user_auth.user_pb) |
| 195 |
| 196 def testViewedUser_WithUserID(self): |
| 197 mr = self._MRWithMockRequest('/u/111/') |
| 198 self.assertEquals('jrobbins@example.com', mr.viewed_username) |
| 199 self.assertEquals(111, mr.viewed_user_auth.user_id) |
| 200 self.assertEquals( |
| 201 self.services.user.GetUser('fake cnxn', 111), |
| 202 mr.viewed_user_auth.user_pb) |
| 203 |
| 204 def testViewedUser_NoSuchEmail(self): |
| 205 try: |
| 206 self._MRWithMockRequest('/u/unknownuser@example.com/') |
| 207 self.fail() |
| 208 except webapp2.HTTPException as e: |
| 209 self.assertEquals(404, e.code) |
| 210 |
| 211 def testViewedUser_NoSuchUserID(self): |
| 212 with self.assertRaises(webapp2.HTTPException) as e: |
| 213 self._MRWithMockRequest('/u/234521111/') |
| 214 self.assertEquals(404, e.code) |
| 215 |
| 216 def testGetParam(self): |
| 217 mr = testing_helpers.MakeMonorailRequest( |
| 218 path='/foo?syn=error!&a=a&empty=', |
| 219 params=dict(over1='over_value1', over2='over_value2')) |
| 220 |
| 221 # test tampering |
| 222 self.assertRaises(monorailrequest.InputException, mr.GetParam, 'a', |
| 223 antitamper_re=re.compile(r'^$')) |
| 224 self.assertRaises(monorailrequest.InputException, mr.GetParam, |
| 225 'undefined', default_value='default', |
| 226 antitamper_re=re.compile(r'^$')) |
| 227 |
| 228 # test empty value |
| 229 self.assertEquals('', mr.GetParam( |
| 230 'empty', default_value='default', antitamper_re=re.compile(r'^$'))) |
| 231 |
| 232 # test default |
| 233 self.assertEquals('default', mr.GetParam( |
| 234 'undefined', default_value='default')) |
| 235 |
| 236 def testComputeColSpec(self): |
| 237 # No config passed, and nothing in URL |
| 238 mr = testing_helpers.MakeMonorailRequest( |
| 239 path='/p/proj/issues/detail?id=123') |
| 240 mr.ComputeColSpec(None) |
| 241 self.assertEquals(tracker_constants.DEFAULT_COL_SPEC, mr.col_spec) |
| 242 |
| 243 # No config passed, but set in URL |
| 244 mr = testing_helpers.MakeMonorailRequest( |
| 245 path='/p/proj/issues/detail?id=123&colspec=a b C') |
| 246 mr.ComputeColSpec(None) |
| 247 self.assertEquals('a b C', mr.col_spec) |
| 248 |
| 249 config = tracker_pb2.ProjectIssueConfig() |
| 250 |
| 251 # No default in the config, and nothing in URL |
| 252 mr = testing_helpers.MakeMonorailRequest( |
| 253 path='/p/proj/issues/detail?id=123') |
| 254 mr.ComputeColSpec(config) |
| 255 self.assertEquals(tracker_constants.DEFAULT_COL_SPEC, mr.col_spec) |
| 256 |
| 257 # No default in the config, but set in URL |
| 258 mr = testing_helpers.MakeMonorailRequest( |
| 259 path='/p/proj/issues/detail?id=123&colspec=a b C') |
| 260 mr.ComputeColSpec(config) |
| 261 self.assertEquals('a b C', mr.col_spec) |
| 262 |
| 263 config.default_col_spec = 'd e f' |
| 264 |
| 265 # Default in the config, and nothing in URL |
| 266 mr = testing_helpers.MakeMonorailRequest( |
| 267 path='/p/proj/issues/detail?id=123') |
| 268 mr.ComputeColSpec(config) |
| 269 self.assertEquals('d e f', mr.col_spec) |
| 270 |
| 271 # Default in the config, but overrided via URL |
| 272 mr = testing_helpers.MakeMonorailRequest( |
| 273 path='/p/proj/issues/detail?id=123&colspec=a b C') |
| 274 mr.ComputeColSpec(config) |
| 275 self.assertEquals('a b C', mr.col_spec) |
| 276 |
| 277 def testComputeColSpec_XSS(self): |
| 278 config_1 = tracker_pb2.ProjectIssueConfig() |
| 279 config_2 = tracker_pb2.ProjectIssueConfig() |
| 280 config_2.default_col_spec = "id '+alert(1)+'" |
| 281 mr_1 = testing_helpers.MakeMonorailRequest( |
| 282 path='/p/proj/issues/detail?id=123') |
| 283 mr_2 = testing_helpers.MakeMonorailRequest( |
| 284 path="/p/proj/issues/detail?id=123&colspec=id '+alert(1)+'") |
| 285 |
| 286 # Normal colspec in config but malicious request |
| 287 self.assertRaises( |
| 288 monorailrequest.InputException, |
| 289 mr_2.ComputeColSpec, config_1) |
| 290 |
| 291 # Malicious colspec in config but normal request |
| 292 self.assertRaises( |
| 293 monorailrequest.InputException, |
| 294 mr_1.ComputeColSpec, config_2) |
| 295 |
| 296 # Malicious colspec in config and malicious request |
| 297 self.assertRaises( |
| 298 monorailrequest.InputException, |
| 299 mr_2.ComputeColSpec, config_2) |
| 300 |
| 301 |
| 302 class TestMonorailRequestFunctions(unittest.TestCase): |
| 303 |
| 304 def testExtractPathIdenifiers_ProjectOnly(self): |
| 305 username, project_name = monorailrequest._ParsePathIdentifiers( |
| 306 '/p/proj/issues/list?q=foo+OR+bar&ts=1234') |
| 307 self.assertIsNone(username) |
| 308 self.assertEquals('proj', project_name) |
| 309 |
| 310 def testExtractPathIdenifiers_ViewedUserOnly(self): |
| 311 username, project_name = monorailrequest._ParsePathIdentifiers( |
| 312 '/u/jrobbins@example.com/') |
| 313 self.assertEquals('jrobbins@example.com', username) |
| 314 self.assertIsNone(project_name) |
| 315 |
| 316 def testExtractPathIdenifiers_ViewedUserURLSpace(self): |
| 317 username, project_name = monorailrequest._ParsePathIdentifiers( |
| 318 '/u/jrobbins@example.com/updates') |
| 319 self.assertEquals('jrobbins@example.com', username) |
| 320 self.assertIsNone(project_name) |
| 321 |
| 322 def testExtractPathIdenifiers_ViewedGroupURLSpace(self): |
| 323 username, project_name = monorailrequest._ParsePathIdentifiers( |
| 324 '/g/user-group@example.com/updates') |
| 325 self.assertEquals('user-group@example.com', username) |
| 326 self.assertIsNone(project_name) |
| 327 |
| 328 def testParseColSpec(self): |
| 329 parse = monorailrequest.ParseColSpec |
| 330 self.assertEqual(['PageName', 'Summary', 'Changed', 'ChangedBy'], |
| 331 parse(u'PageName Summary Changed ChangedBy')) |
| 332 self.assertEqual(['Foo-Bar', 'Foo-Bar-Baz', 'Release-1.2', 'Hey', 'There'], |
| 333 parse('Foo-Bar Foo-Bar-Baz Release-1.2 Hey!There')) |
| 334 self.assertEqual( |
| 335 ['\xe7\xaa\xbf\xe8\x8b\xa5\xe7\xb9\xb9'.decode('utf-8'), |
| 336 '\xe5\x9f\xba\xe5\x9c\xb0\xe3\x81\xaf'.decode('utf-8')], |
| 337 parse('\xe7\xaa\xbf\xe8\x8b\xa5\xe7\xb9\xb9 ' |
| 338 '\xe5\x9f\xba\xe5\x9c\xb0\xe3\x81\xaf'.decode('utf-8'))) |
| 339 |
| 340 |
| 341 class TestPermissionLookup(unittest.TestCase): |
| 342 OWNER_ID = 1 |
| 343 OTHER_USER_ID = 2 |
| 344 |
| 345 def setUp(self): |
| 346 self.services = service_manager.Services( |
| 347 project=fake.ProjectService(), |
| 348 user=fake.UserService(), |
| 349 usergroup=fake.UserGroupService()) |
| 350 self.services.user.TestAddUser('owner@gmail.com', self.OWNER_ID) |
| 351 self.services.user.TestAddUser('user@gmail.com', self.OTHER_USER_ID) |
| 352 self.live_project = self.services.project.TestAddProject( |
| 353 'live', owner_ids=[self.OWNER_ID]) |
| 354 self.archived_project = self.services.project.TestAddProject( |
| 355 'archived', owner_ids=[self.OWNER_ID], |
| 356 state=project_pb2.ProjectState.ARCHIVED) |
| 357 self.members_only_project = self.services.project.TestAddProject( |
| 358 'members-only', owner_ids=[self.OWNER_ID], |
| 359 access=project_pb2.ProjectAccess.MEMBERS_ONLY) |
| 360 |
| 361 self.mox = mox.Mox() |
| 362 |
| 363 def tearDown(self): |
| 364 self.mox.UnsetStubs() |
| 365 |
| 366 def CheckPermissions(self, perms, expect_view, expect_commit, expect_edit): |
| 367 may_view = perms.HasPerm(permissions.VIEW, None, None) |
| 368 self.assertEqual(expect_view, may_view) |
| 369 may_commit = perms.HasPerm(permissions.COMMIT, None, None) |
| 370 self.assertEqual(expect_commit, may_commit) |
| 371 may_edit = perms.HasPerm(permissions.EDIT_PROJECT, None, None) |
| 372 self.assertEqual(expect_edit, may_edit) |
| 373 |
| 374 def MakeRequestAsUser(self, project_name, email): |
| 375 self.mox.StubOutWithMock(users, 'get_current_user') |
| 376 users.get_current_user().AndReturn(testing_helpers.Blank( |
| 377 email=lambda: email)) |
| 378 self.mox.ReplayAll() |
| 379 |
| 380 request = webapp2.Request.blank('/p/' + project_name) |
| 381 mr = monorailrequest.MonorailRequest() |
| 382 prof = profiler.Profiler() |
| 383 with prof.Phase('parse user info'): |
| 384 mr.ParseRequest(request, self.services, prof) |
| 385 return mr |
| 386 |
| 387 def testOwnerPermissions_Live(self): |
| 388 mr = self.MakeRequestAsUser('live', 'owner@gmail.com') |
| 389 self.CheckPermissions(mr.perms, True, True, True) |
| 390 |
| 391 def testOwnerPermissions_Archived(self): |
| 392 mr = self.MakeRequestAsUser('archived', 'owner@gmail.com') |
| 393 self.CheckPermissions(mr.perms, True, False, True) |
| 394 |
| 395 def testOwnerPermissions_MembersOnly(self): |
| 396 mr = self.MakeRequestAsUser('members-only', 'owner@gmail.com') |
| 397 self.CheckPermissions(mr.perms, True, True, True) |
| 398 |
| 399 def testExternalUserPermissions_Live(self): |
| 400 mr = self.MakeRequestAsUser('live', 'user@gmail.com') |
| 401 self.CheckPermissions(mr.perms, True, False, False) |
| 402 |
| 403 def testExternalUserPermissions_Archived(self): |
| 404 mr = self.MakeRequestAsUser('archived', 'user@gmail.com') |
| 405 self.CheckPermissions(mr.perms, False, False, False) |
| 406 |
| 407 def testExternalUserPermissions_MembersOnly(self): |
| 408 mr = self.MakeRequestAsUser('members-only', 'user@gmail.com') |
| 409 self.CheckPermissions(mr.perms, False, False, False) |
| 410 |
| 411 |
| 412 if __name__ == '__main__': |
| 413 unittest.main() |
OLD | NEW |