1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3# 4# Copyright (C) 2009-2021 Edgewall Software 5# All rights reserved. 6# 7# This software is licensed as described in the file COPYING, which 8# you should have received as part of this distribution. The terms 9# are also available at https://trac.edgewall.org/wiki/TracLicense. 10# 11# This software consists of voluntary contributions made by many 12# individuals. For the exact contribution history, see the revision 13# history and logs, available at https://trac.edgewall.org/log/. 14 15import unittest 16 17from trac.perm import PermissionSystem 18from trac.tests.functional import FunctionalTestCaseSetup, tc 19from trac.util.text import unicode_to_base64 20 21 22class AuthorizationTestCaseSetup(FunctionalTestCaseSetup): 23 def test_authorization(self, href, perms, h2_text): 24 """Check permissions required to access an administration panel. 25 26 :param href: the relative href of the administration panel 27 :param perms: list or tuple of permissions required to access 28 the administration panel 29 :param h2_text: the body of the h2 heading on the administration 30 panel""" 31 self._tester.go_to_front() 32 self._tester.logout() 33 self._tester.login('user') 34 if isinstance(perms, str): 35 perms = (perms, ) 36 37 h2 = r'<h2>[ \t\n]*%s[ \t\n]*' \ 38 r'( <span class="trac-count">\(\d+\)</span>)?[ \t\n]*</h2>' 39 try: 40 for perm in perms: 41 try: 42 tc.go(href) 43 tc.find("No administration panels available") 44 self._testenv.grant_perm('user', perm) 45 tc.go(href) 46 tc.find(h2 % h2_text) 47 finally: 48 self._testenv.revoke_perm('user', perm) 49 try: 50 tc.go(href) 51 tc.find("No administration panels available") 52 self._testenv.enable_authz_permpolicy({ 53 href.strip('/').replace('/', ':', 1): {'user': perm}, 54 }) 55 tc.go(href) 56 tc.find(h2 % h2_text) 57 finally: 58 self._testenv.disable_authz_permpolicy() 59 finally: 60 self._tester.go_to_front() 61 self._tester.logout() 62 self._tester.login('admin') 63 64 65class TestBasicSettings(FunctionalTestCaseSetup): 66 def runTest(self): 67 """Check basic settings.""" 68 self._tester.go_to_admin() 69 tc.formvalue('modbasic', 'url', 'https://my.example.com/something') 70 tc.submit() 71 tc.find('https://my.example.com/something') 72 73 try: 74 tc.formvalue('modbasic', 'default_dateinfo_format', 'absolute') 75 tc.submit() 76 tc.find(r'<option selected="selected" value="absolute">') 77 tc.formvalue('modbasic', 'default_dateinfo_format', 'relative') 78 tc.submit() 79 tc.find(r'<option selected="selected" value="relative">') 80 finally: 81 self._testenv.remove_config('trac', 'default_dateinfo_format') 82 self._tester.go_to_admin() 83 tc.find(r'<option selected="selected" value="relative">') 84 tc.find(r'<option value="absolute">') 85 86 87class TestBasicSettingsAuthorization(AuthorizationTestCaseSetup): 88 def runTest(self): 89 """Check permissions required to access Basic Settings panel.""" 90 self.test_authorization('/admin/general/basics', 'TRAC_ADMIN', 91 "Basic Settings") 92 93 94class TestDefaultHandler(FunctionalTestCaseSetup): 95 def runTest(self): 96 """Set default handler from the Basic Settings page.""" 97 98 # Confirm default value. 99 self._tester.go_to_admin("Basic Settings") 100 tc.find(r'<option selected="selected" value="WikiModule">' 101 r'WikiModule</option>') 102 tc.go(self._tester.url) 103 tc.find("Welcome to Trac") 104 105 # Set to another valid default handler. 106 self._tester.go_to_admin("Basic Settings") 107 tc.formvalue('modbasic', 'default_handler', 'TimelineModule') 108 tc.submit() 109 tc.find("Your changes have been saved.") 110 tc.find(r'<option selected="selected" value="TimelineModule">' 111 r'TimelineModule</option>') 112 tc.go(self._tester.url) 113 tc.find(r'<h1>Timeline</h1>') 114 115 # Set to valid disabled default handler. 116 try: 117 self._testenv.set_config('components', 118 'trac.timeline.web_ui.TimelineModule', 119 'disabled') 120 self._tester.go_to_admin("Basic Settings") 121 tc.find(r'<option value="TimelineModule">TimelineModule</option>') 122 tc.find(r'<span class="hint">\s*TimelineModule is not a valid ' 123 r'IRequestHandler or is not enabled.\s*</span>') 124 tc.go(self._tester.url) 125 tc.find(r'<h1>Configuration Error</h1>') 126 tc.find(r'Cannot find an implementation of the ' 127 r'<code>IRequestHandler</code> interface named ' 128 r'<code>TimelineModule</code>') 129 finally: 130 self._testenv.remove_config('components', 131 'trac.timeline.web_ui.timelinemodule') 132 133 # Set to invalid default handler. 134 try: 135 self._testenv.set_config('trac', 'default_handler', 136 'BatchModifyModule') 137 self._tester.go_to_admin("Basic Settings") 138 tc.find(r'<option value="BatchModifyModule">BatchModifyModule' 139 r'</option>') 140 tc.find(r'<span class="hint">\s*BatchModifyModule is not a valid ' 141 r'IRequestHandler or is not enabled.\s*</span>') 142 tc.formvalue('modbasic', 'default_handler', 'BatchModifyModule') 143 tc.submit() # Invalid value should not be replaced on submit 144 tc.find(r'<option value="BatchModifyModule">BatchModifyModule' 145 r'</option>') 146 tc.find(r'<span class="hint">\s*BatchModifyModule is not a valid ' 147 r'IRequestHandler or is not enabled.\s*</span>') 148 tc.go(self._tester.url) 149 tc.find(r'<h1>Configuration Error</h1>') 150 tc.find(r'<code>BatchModifyModule</code> is not a valid default ' 151 r'handler.') 152 finally: 153 self._testenv.set_config('trac', 'default_handler', 'WikiModule') 154 155 156class TestLoggingNone(FunctionalTestCaseSetup): 157 def runTest(self): 158 """Turn off logging.""" 159 # For now, we just check that it shows up. 160 self._tester.go_to_admin("Logging") 161 tc.find('trac.log') 162 tc.formvalue('modlog', 'log_type', 'none') 163 tc.submit() 164 tc.find('selected="selected" value="none">None</option') 165 166 167class TestLoggingAuthorization(AuthorizationTestCaseSetup): 168 def runTest(self): 169 """Check permissions required to access Logging panel.""" 170 self.test_authorization('/admin/general/logging', 'TRAC_ADMIN', 171 "Logging") 172 173 174class TestLoggingToFile(FunctionalTestCaseSetup): 175 def runTest(self): 176 """Turn logging back on.""" 177 # For now, we just check that it shows up. 178 self._tester.go_to_admin("Logging") 179 tc.find('trac.log') 180 tc.formvalue('modlog', 'log_type', 'file') 181 tc.formvalue('modlog', 'log_file', 'trac.log2') 182 tc.formvalue('modlog', 'log_level', 'INFO') 183 tc.submit() 184 tc.find('selected="selected" value="file">File</option') 185 tc.find('id="log_file".*value="trac.log2"') 186 tc.find('selected="selected">INFO</option>') 187 188 189class TestLoggingToFileNormal(FunctionalTestCaseSetup): 190 def runTest(self): 191 """Setting logging back to normal.""" 192 # For now, we just check that it shows up. 193 self._tester.go_to_admin("Logging") 194 tc.find('trac.log') 195 tc.formvalue('modlog', 'log_file', 'trac.log') 196 tc.formvalue('modlog', 'log_level', 'DEBUG') 197 tc.submit() 198 tc.find('selected="selected" value="file">File</option') 199 tc.find('id="log_file".*value="trac.log"') 200 tc.find('selected="selected">DEBUG</option>') 201 202 203class TestPermissionsAuthorization(AuthorizationTestCaseSetup): 204 def runTest(self): 205 """Check permissions required to access Permissions panel.""" 206 self.test_authorization('/admin/general/perm', 207 ('PERMISSION_GRANT', 'PERMISSION_REVOKE'), 208 "Manage Permissions and Groups") 209 210 211class TestCreatePermissionGroup(FunctionalTestCaseSetup): 212 def runTest(self): 213 """Create a permissions group""" 214 self._tester.go_to_admin("Permissions") 215 tc.find('Manage Permissions') 216 tc.formvalue('addperm', 'gp_subject', 'somegroup') 217 tc.formvalue('addperm', 'action', 'REPORT_CREATE') 218 tc.submit() 219 somegroup = unicode_to_base64('somegroup') 220 REPORT_CREATE = unicode_to_base64('REPORT_CREATE') 221 tc.find('%s:%s' % (somegroup, REPORT_CREATE)) 222 223 224class TestRemovePermissionGroup(FunctionalTestCaseSetup): 225 def runTest(self): 226 """Remove a permissions group""" 227 self._tester.go_to_admin("Permissions") 228 tc.find('Manage Permissions') 229 somegroup = unicode_to_base64('somegroup') 230 REPORT_CREATE = unicode_to_base64('REPORT_CREATE') 231 tc.find('%s:%s' % (somegroup, REPORT_CREATE)) 232 tc.formvalue('revokeform', 'sel', '%s:%s' % (somegroup, REPORT_CREATE)) 233 tc.submit(formname='revokeform') 234 tc.notfind('%s:%s' % (somegroup, REPORT_CREATE)) 235 tc.notfind(somegroup) 236 237 238class TestAddUserToGroup(FunctionalTestCaseSetup): 239 def runTest(self): 240 """Add a user to a permissions group""" 241 self._tester.go_to_admin("Permissions") 242 tc.find('Manage Permissions') 243 tc.formvalue('addsubj', 'sg_subject', 'authenticated') 244 tc.formvalue('addsubj', 'sg_group', 'somegroup') 245 tc.submit() 246 authenticated = unicode_to_base64('authenticated') 247 somegroup = unicode_to_base64('somegroup') 248 tc.find('%s:%s' % (authenticated, somegroup)) 249 250 revoke_checkbox = '%s:%s' % (unicode_to_base64('anonymous'), 251 unicode_to_base64('PERMISSION_GRANT')) 252 tc.formvalue('addperm', 'gp_subject', 'anonymous') 253 tc.formvalue('addperm', 'action', 'PERMISSION_GRANT') 254 tc.submit() 255 tc.find(revoke_checkbox) 256 self._testenv.get_trac_environment().config.touch() 257 self._tester.logout() 258 self._tester.go_to_admin("Permissions") 259 try: 260 tc.formvalue('addsubj', 'sg_subject', 'someuser') 261 tc.formvalue('addsubj', 'sg_group', 'authenticated') 262 tc.submit() 263 tc.find("The subject <strong>someuser</strong> was not added " 264 "to the group <strong>authenticated</strong>. The group " 265 "has <strong>TICKET_CREATE</strong> permission and you " 266 "cannot grant permissions you don't possess.") 267 finally: 268 self._tester.login('admin') 269 self._tester.go_to_admin("Permissions") 270 tc.formvalue('revokeform', 'sel', revoke_checkbox) 271 tc.submit(formname='revokeform') 272 tc.notfind(revoke_checkbox) 273 274 275class TestRemoveUserFromGroup(FunctionalTestCaseSetup): 276 def runTest(self): 277 """Remove a user from a permissions group""" 278 self._tester.go_to_admin("Permissions") 279 tc.find('Manage Permissions') 280 authenticated = unicode_to_base64('authenticated') 281 somegroup = unicode_to_base64('somegroup') 282 tc.find('%s:%s' % (authenticated, somegroup)) 283 tc.formvalue('revokeform', 'sel', '%s:%s' % (authenticated, somegroup)) 284 tc.submit(formname='revokeform') 285 tc.notfind('%s:%s' % (authenticated, somegroup)) 286 287 288class TestCopyPermissions(FunctionalTestCaseSetup): 289 def runTest(self): 290 """Tests for the Copy Permissions functionality 291 added in https://trac.edgewall.org/ticket/11099.""" 292 checkbox_value = lambda s, p: '%s:%s' % (unicode_to_base64(s), 293 unicode_to_base64(p)) 294 grant_msg = "The subject %s has been granted the permission %s\." 295 def grant_permission(subject, action): 296 tc.formvalue('addperm', 'gp_subject', subject) 297 tc.formvalue('addperm', 'action', action) 298 tc.submit() 299 tc.find(grant_msg % (subject, action)) 300 tc.find(checkbox_value(subject, action)) 301 302 env = self._testenv.get_trac_environment() 303 304 # Copy permissions from subject to target 305 self._tester.go_to_admin('Permissions') 306 perm_sys = PermissionSystem(env) 307 anon_perms = perm_sys.store.get_user_permissions('anonymous') 308 for perm in anon_perms: 309 tc.find(checkbox_value('anonymous', perm)) 310 tc.notfind(checkbox_value('user1', perm)) 311 tc.formvalue('copyperm', 'cp_subject', 'anonymous') 312 tc.formvalue('copyperm', 'cp_target', 'user1') 313 tc.submit() 314 for perm in anon_perms: 315 tc.find("The subject user1 has been granted the permission %s\." 316 % perm) 317 tc.find(checkbox_value('user1', perm)) 318 319 # Subject doesn't have any permissions 320 tc.notfind(checkbox_value('noperms', '')) 321 tc.formvalue('copyperm', 'cp_subject', 'noperms') 322 tc.formvalue('copyperm', 'cp_target', 'user1') 323 tc.submit() 324 tc.find("The subject noperms does not have any permissions\.") 325 326 # Subject belongs to group but doesn't directly have any permissions 327 grant_permission('group1', 'TICKET_VIEW') 328 tc.formvalue('addsubj', 'sg_subject', 'noperms') 329 tc.formvalue('addsubj', 'sg_group', 'group1') 330 tc.submit() 331 tc.find("The subject noperms has been added to the group group1\.") 332 333 tc.formvalue('copyperm', 'cp_subject', 'noperms') 334 tc.formvalue('copyperm', 'cp_target', 'user1') 335 tc.submit() 336 tc.find("The subject noperms does not have any permissions\.") 337 338 # Target uses reserved all upper-case form 339 tc.formvalue('copyperm', 'cp_subject', 'noperms') 340 tc.formvalue('copyperm', 'cp_target', 'USER1') 341 tc.submit() 342 tc.find("All upper-cased tokens are reserved for permission names\.") 343 self._tester.go_to_admin("Permissions") 344 345 # Subject users reserved all upper-case form 346 tc.formvalue('copyperm', 'cp_subject', 'USER1') 347 tc.formvalue('copyperm', 'cp_target', 'noperms') 348 tc.submit() 349 tc.find("All upper-cased tokens are reserved for permission names\.") 350 self._tester.go_to_admin("Permissions") 351 352 # Target already possess one of the permissions 353 anon_perms = perm_sys.store.get_user_permissions('anonymous') 354 for perm in anon_perms: 355 tc.notfind(checkbox_value('user2', perm)) 356 grant_permission('user2', anon_perms[0]) 357 358 tc.formvalue('copyperm', 'cp_subject', 'anonymous') 359 tc.formvalue('copyperm', 'cp_target', 'user2') 360 tc.submit() 361 362 tc.notfind("The subject <em>user2</em> has been granted the " 363 "permission %s\." % anon_perms[0]) 364 for perm in anon_perms[1:]: 365 tc.find("The subject user2 has been granted the permission %s\." 366 % perm) 367 tc.find(checkbox_value('user2', perm)) 368 369 # Subject has a permission that is no longer defined 370 try: 371 env.db_transaction("INSERT INTO permission VALUES (%s,%s)", 372 ('anonymous', 'NOTDEFINED_PERMISSION')) 373 except env.db_exc.IntegrityError: 374 pass 375 env.config.touch() # invalidate permission cache 376 tc.reload() 377 tc.find(checkbox_value('anonymous', 'NOTDEFINED_PERMISSION')) 378 perm_sys = PermissionSystem(env) 379 anon_perms = perm_sys.store.get_user_permissions('anonymous') 380 for perm in anon_perms: 381 tc.notfind(checkbox_value('user3', perm)) 382 383 tc.formvalue('copyperm', 'cp_subject', 'anonymous') 384 tc.formvalue('copyperm', 'cp_target', 'user3') 385 tc.submit() 386 387 for perm in anon_perms: 388 msg = grant_msg % ('user3', perm) 389 if perm == 'NOTDEFINED_PERMISSION': 390 tc.notfind(msg) 391 tc.notfind(checkbox_value('user3', perm)) 392 else: 393 tc.find(msg) 394 tc.find(checkbox_value('user3', perm)) 395 perm_sys.revoke_permission('anonymous', 'NOTDEFINED_PERMISSION') 396 397 # Actor doesn't posses permission 398 grant_permission('anonymous', 'PERMISSION_GRANT') 399 grant_permission('user3', 'TRAC_ADMIN') 400 self._tester.logout() 401 self._tester.go_to_admin("Permissions") 402 403 try: 404 tc.formvalue('copyperm', 'cp_subject', 'user3') 405 tc.formvalue('copyperm', 'cp_target', 'user4') 406 tc.submit() 407 408 perm_sys = PermissionSystem(env) 409 for perm in [perm[1] for perm in perm_sys.get_all_permissions() 410 if perm[0] == 'user3' 411 and perm[1] != 'TRAC_ADMIN']: 412 tc.find(grant_msg % ('user4', perm)) 413 tc.notfind("The permission TRAC_ADMIN was not granted to user4 " 414 "because users cannot grant permissions they don't " 415 "possess.") 416 finally: 417 self._testenv.revoke_perm('anonymous', 'PERMISSION_GRANT') 418 self._tester.login('admin') 419 420 421class TestPluginSettings(FunctionalTestCaseSetup): 422 def runTest(self): 423 """Check plugin settings.""" 424 self._tester.go_to_admin("Plugins") 425 tc.find('Manage Plugins') 426 tc.find('Install Plugin') 427 428 429class TestPluginsAuthorization(AuthorizationTestCaseSetup): 430 def runTest(self): 431 """Check permissions required to access Logging panel.""" 432 self.test_authorization('/admin/general/plugin', 'TRAC_ADMIN', 433 "Manage Plugins") 434 435 436class RegressionTestTicket10752(FunctionalTestCaseSetup): 437 def runTest(self): 438 """Test for regression of https://trac.edgewall.org/ticket/10752 439 Permissions on the web admin page should be greyed out when they 440 are no longer defined. 441 """ 442 env = self._testenv.get_trac_environment() 443 try: 444 env.db_transaction("INSERT INTO permission VALUES (%s,%s)", 445 ('user', 'NOTDEFINED_PERMISSION')) 446 except env.db_exc.IntegrityError: 447 pass 448 env.config.touch() 449 450 self._tester.go_to_admin("Permissions") 451 tc.find('<span class="missing" ' 452 'title="NOTDEFINED_PERMISSION is no longer defined">' 453 'NOTDEFINED_PERMISSION</span>') 454 tc.notfind('<input type="checkbox" [^>]+ disabled="disabled"') 455 tc.notfind('<input type="checkbox" [^>]+' 456 'title="You don\'t have permission to revoke this action" ' 457 '[^>]+>') 458 459 460class RegressionTestTicket11069(FunctionalTestCaseSetup): 461 def runTest(self): 462 """Test for regression of https://trac.edgewall.org/ticket/11069 463 The permissions list should only be populated with permissions that 464 the user can grant.""" 465 self._tester.go_to_front() 466 self._tester.logout() 467 self._tester.login('user') 468 self._testenv.grant_perm('user', 'PERMISSION_GRANT') 469 env = self._testenv.get_trac_environment() 470 user_perms = PermissionSystem(env).get_user_permissions('user') 471 all_actions = PermissionSystem(env).get_actions() 472 try: 473 self._tester.go_to_admin("Permissions") 474 for action in all_actions: 475 option = r"<option>%s</option>" % action 476 if action in user_perms and user_perms[action] is True: 477 tc.find(option) 478 else: 479 tc.notfind(option) 480 finally: 481 self._testenv.revoke_perm('user', 'PERMISSION_GRANT') 482 self._tester.go_to_front() 483 self._tester.logout() 484 self._tester.login('admin') 485 486 487class RegressionTestTicket11095(FunctionalTestCaseSetup): 488 """Test for regression of https://trac.edgewall.org/ticket/11095 489 The permission is truncated if it overflows the available space (CSS) 490 and the full permission name is shown in the title on hover. 491 """ 492 def runTest(self): 493 self._tester.go_to_admin("Permissions") 494 tc.find('<span title="MILESTONE_VIEW">MILESTONE_VIEW</span>') 495 tc.find('<span title="WIKI_VIEW">WIKI_VIEW</span>') 496 497 498class RegressionTestTicket11117(FunctionalTestCaseSetup): 499 """Test for regression of https://trac.edgewall.org/ticket/11117 500 Hint should be shown on the Basic Settings admin panel when pytz is not 501 installed. 502 """ 503 def runTest(self): 504 self._tester.go_to_admin("Basic Settings") 505 pytz_hint = "Install pytz for a complete list of timezones." 506 from trac.util.datefmt import pytz 507 if pytz is None: 508 tc.find(pytz_hint) 509 else: 510 tc.notfind(pytz_hint) 511 512 513class RegressionTestTicket11257(FunctionalTestCaseSetup): 514 """Test for regression of https://trac.edgewall.org/ticket/11257 515 Hints should be shown on the Basic Settings admin panel when Babel is not 516 installed. 517 """ 518 def runTest(self): 519 from trac.util.translation import get_available_locales, has_babel 520 521 babel_hint_lang = "Install Babel for extended language support." 522 babel_hint_date = "Install Babel for localized date formats." 523 catalog_hint = "Message catalogs have not been compiled." 524 language_select = '<select name="default_language">' 525 disabled_language_select = \ 526 '<select disabled="disabled" name="default_language" ' \ 527 'title="Translations are currently unavailable">' 528 529 self._tester.go_to_admin("Basic Settings") 530 if has_babel: 531 tc.notfind(babel_hint_lang) 532 tc.notfind(babel_hint_date) 533 if get_available_locales(): 534 tc.find(language_select) 535 tc.notfind(catalog_hint) 536 else: 537 tc.find(disabled_language_select) 538 tc.find(catalog_hint) 539 else: 540 tc.find(disabled_language_select) 541 tc.find(babel_hint_lang) 542 tc.find(babel_hint_date) 543 tc.notfind(catalog_hint) 544 545 546def functionalSuite(suite=None): 547 if not suite: 548 import trac.tests.functional 549 suite = trac.tests.functional.functionalSuite() 550 suite.addTest(TestBasicSettings()) 551 suite.addTest(TestBasicSettingsAuthorization()) 552 suite.addTest(TestDefaultHandler()) 553 suite.addTest(TestLoggingNone()) 554 suite.addTest(TestLoggingAuthorization()) 555 suite.addTest(TestLoggingToFile()) 556 suite.addTest(TestLoggingToFileNormal()) 557 suite.addTest(TestPermissionsAuthorization()) 558 suite.addTest(TestCreatePermissionGroup()) 559 suite.addTest(TestRemovePermissionGroup()) 560 suite.addTest(TestAddUserToGroup()) 561 suite.addTest(TestRemoveUserFromGroup()) 562 suite.addTest(TestCopyPermissions()) 563 suite.addTest(TestPluginSettings()) 564 suite.addTest(TestPluginsAuthorization()) 565 suite.addTest(RegressionTestTicket10752()) 566 suite.addTest(RegressionTestTicket11069()) 567 suite.addTest(RegressionTestTicket11095()) 568 suite.addTest(RegressionTestTicket11117()) 569 suite.addTest(RegressionTestTicket11257()) 570 return suite 571 572 573test_suite = functionalSuite 574 575 576if __name__ == '__main__': 577 unittest.main(defaultTest='test_suite') 578