def import_perms(self, template_path): """Creates permissions from data stored in groups.xml. Parses this XML file to get the data we need to insert into the permissions table. If we have this data we clear the existing permission data, and then insert the template data using the PermissionSystem API. If we don't create a perm_data list, we exit the function and continue to use default data. """ # parse the tree to get username, action data # we know the file exists as we check that in import_groups() tree = ET.ElementTree(file=template_path) perm_data = [(subelement.attrib['name'], subelement.attrib['action']) for perm in tree.getroot() for subelement in perm if subelement.attrib['name'].strip()] @self.env.with_transaction() def clear_perms(db): """Clears the whole permissions table of default data.""" cursor = db.cursor() self.log.info("Clearing permissions table") # cant pass the table name as an arg so its hard coded cursor.execute("DELETE FROM permission") self.log.info("Inserting template data into permissions table") perm_system = PermissionSystem(self.env).store for username, action in perm_data: perm_system.grant_permission(username, action)
class _BaseTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(enable=[ 'trac.*', 'acct_mgr.api.*', 'acct_mgr.admin.*', 'acct_mgr.db.*', 'acct_mgr.register.*', 'acct_mgr.pwhash.HtDigestHashMethod', 'acct_mgr.tests.admin.BadCheck', 'acct_mgr.tests.admin.DummyCheck' ]) self.env.path = tempfile.mkdtemp() self.perm = PermissionSystem(self.env) # Create a user reference in the permission system. self.perm.grant_permission('admin', 'ACCTMGR_ADMIN') # Prepare a generic request object for admin actions. self.req = Mock(authname='admin', method='GET', args=dict(), abs_href=self.env.abs_href, chrome=dict(notices=[], warnings=[]), href=self.env.abs_href, locale='', redirect=lambda x: None, session=dict(), tz='' ) self.req.perm = PermissionCache(self.env, 'admin') self.acctmgr = AccountManager(self.env) def tearDown(self): shutil.rmtree(self.env.path)
def create_user_and_grant_permissions(self, req, team_member): if self.use_account_manager_integration(team_member.name): password = team_member.name AccountManager(self.env).set_password(team_member.name, password) permission_system = PermissionSystem(self.env) if not permission_system.check_permission(Role.TEAM_MEMBER, team_member.name): permission_system.grant_permission(team_member.name, Role.TEAM_MEMBER)
def add_permissions(self, permissions): perm = PermissionSystem(self.env) for agent, p in permissions.items(): for permission in p: try: perm.grant_permission(agent, permission) except: continue
class ResetActionTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(default_data=True) self.perm_sys = PermissionSystem(self.env) self.ctlr = TicketSystem(self.env).action_controllers[0] self.req1 = Mock(authname='user1', args={}, perm=PermissionCache(self.env, 'user1')) self.req2 = Mock(authname='user2', args={}, perm=PermissionCache(self.env, 'user2')) self.ticket = Ticket(self.env) self.ticket['status'] = 'invalid' self.ticket.insert() def tearDown(self): self.env.reset_db() def _reload_workflow(self): self.ctlr.actions = self.ctlr.get_all_actions() def test_default_reset_action(self): """Default reset action.""" self.perm_sys.grant_permission('user2', 'TICKET_ADMIN') self._reload_workflow() actions1 = self.ctlr.get_ticket_actions(self.req1, self.ticket) actions2 = self.ctlr.get_ticket_actions(self.req2, self.ticket) chgs2 = self.ctlr.get_ticket_changes(self.req2, self.ticket, '_reset') self.assertEqual(1, len(actions1)) self.assertNotIn((0, '_reset'), actions1) self.assertEqual(2, len(actions2)) self.assertIn((0, '_reset'), actions2) self.assertEqual('new', chgs2['status']) def test_custom_reset_action(self): """Custom reset action in [ticket-workflow] section.""" config = self.env.config['ticket-workflow'] config.set('_reset', '-> review') config.set('_reset.operations', 'reset_workflow') config.set('_reset.permissions', 'TICKET_BATCH_MODIFY') config.set('_reset.default', 2) self.perm_sys.grant_permission('user2', 'TICKET_BATCH_MODIFY') self._reload_workflow() actions1 = self.ctlr.get_ticket_actions(self.req1, self.ticket) actions2 = self.ctlr.get_ticket_actions(self.req2, self.ticket) chgs2 = self.ctlr.get_ticket_changes(self.req2, self.ticket, '_reset') self.assertEqual(1, len(actions1)) self.assertNotIn((2, '_reset'), actions1) self.assertEqual(2, len(actions2)) self.assertIn((2, '_reset'), actions2) self.assertEqual('review', chgs2['status'])
class ProductTicketSystemTestCase(TicketSystemTestCase, MultiproductTestCase): def setUp(self): self.global_env = self._setup_test_env(create_folder=False) self._upgrade_mp(self.global_env) self._setup_test_log(self.global_env) self._load_product_from_data(self.global_env, self.default_product) self.env = ProductEnvironment(self.global_env, self.default_product) self.perm = PermissionSystem(self.env) self.ticket_system = TicketSystem(self.env) self.req = Mock() def tearDown(self): self.global_env.reset_db() def test_custom_field_isolation(self): self.env.config.set('ticket-custom', 'test', 'select') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', '1') self.env.config.set('ticket-custom', 'test.options', 'option1|option2') self.global_env.config.set('ticket-custom', 'test', 'text') self.global_env.config.set('ticket-custom', 'test.label', 'Test') self.global_env.config.set('ticket-custom', 'test.value', 'Foo bar') self.global_env.config.set('ticket-custom', 'test.format', 'wiki') product_fields = TicketSystem(self.env).get_custom_fields() global_fields = TicketSystem(self.global_env).get_custom_fields() self.assertEqual({'name': 'test', 'type': 'select', 'label': 'Test', 'value': '1', 'options': ['option1', 'option2'], 'order': 0}, product_fields[0]) self.assertEqual({'name': 'test', 'type': 'text', 'label': 'Test', 'value': 'Foo bar', 'order': 0, 'format': 'wiki'}, global_fields[0]) def test_available_actions_isolation(self): # Grant TICKET_CREATE in product environment ... self.perm.grant_permission('anonymous', 'TICKET_CREATE') self.req.perm = PermissionCache(self.env) self.assertEqual(['leave', 'reopen'], self._get_actions({'status': 'closed'})) # ... but no perms in global environment self.req.perm = PermissionCache(self.global_env) product_env = self.env try: self.env = self.global_env self.assertEqual(['leave'], self._get_actions({'status': 'closed'})) finally: self.env = product_env
def test_tag_query_save(self): """Save timeline tag query string in session.""" self.assertEqual('tag_query', self.tef.key) from trac.timeline.web_ui import TimelineModule TimelineModule(self.env) perms = PermissionSystem(self.env) perms.grant_permission('anonymous', 'TAGS_VIEW') perms.grant_permission('anonymous', 'TIMELINE_VIEW') req = self._create_request(args=dict(tag_query='query_str'), path_info='/timeline', method='GET') dispatcher = RequestDispatcher(self.env) self.assertRaises(RequestDone, dispatcher.dispatch, req) self.assertEqual('query_str', req.session['timeline.tag_query'])
class _BaseTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub( enable=['trac.*', 'acct_mgr.api.*']) self.env.path = tempfile.mkdtemp() self.perm = PermissionSystem(self.env) # Create a user reference in the permission system. self.perm.grant_permission('admin', 'ACCTMGR_USER_ADMIN') # Prepare a generic registration request. args = dict(username='', name='', email='') self.req = Mock(authname='anonymous', args=args) self.req.perm = PermissionCache(self.env) def tearDown(self): shutil.rmtree(self.env.path)
def setUp(self): BaseProductAdminPanelTestCase.setUp(self) self.global_env.config.set('multiproduct', 'admin_blacklist', 'testcat1:panel1,testcat3:panel2') self.env.config.set('multiproduct', 'admin_blacklist', 'testcat1:panel3,testcat3:panel1,testcat2:*') global_permsys = PermissionSystem(self.global_env) permsys = PermissionSystem(self.env) global_permsys.grant_permission('adminuser', 'TRAC_ADMIN') global_permsys.grant_permission('prodadmin', 'PRODUCT_ADMIN') global_permsys.grant_permission('testuser', 'TEST_ADMIN') permsys.grant_permission('prodadmin', 'PRODUCT_ADMIN') permsys.grant_permission('testuser', 'TEST_ADMIN') self.req = self._get_request_obj()
class VoteSystemTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(default_data=True, enable=['trac.*', 'tracvote.*']) self.env.path = tempfile.mkdtemp() self.perm = PermissionSystem(self.env) self.req = Mock() self.db = self.env.get_db_cnx() self.votes = VoteSystem(self.env) # Current tracvotes schema is setup with enabled component anyway. # Revert these changes for getting default permissions inserted. self._revert_schema_init() self.votes.upgrade_environment(self.db) def tearDown(self): self.db.close() # Really close db connections. self.env.shutdown() shutil.rmtree(self.env.path) # Helpers def _revert_schema_init(self): cursor = self.db.cursor() cursor.execute("DROP TABLE IF EXISTS votes") cursor.execute("DELETE FROM system WHERE name='vote_version'") cursor.execute("DELETE FROM permission WHERE action %s" % self.db.like(), ('VOTE_%',)) # Tests def test_available_actions_no_perms(self): self.assertTrue(_ACTIONS['view'] in PermissionCache(self.env)) self.assertFalse(_ACTIONS['modify'] in PermissionCache(self.env)) def test_available_actions_full_perms(self): perm_map = dict(voter='VOTE_MODIFY', admin='TRAC_ADMIN') for user in perm_map: self.perm.grant_permission(user, perm_map[user]) for action in _ACTIONS.values(): self.assertTrue(action in PermissionCache(self.env, username=user)) def test_resource_provider(self): self.assertTrue(self.votes in Chrome(self.env).template_providers)
def test_save_page_with_valid_patterns(self): """Page with valid patterns can be saved.""" perm = PermissionSystem(self.env) perm.grant_permission('user', 'authenticated') text = """{{{ (?i)eventbrite\.com (?i)sneaker(?:supplier|nice)\.com }}}""" req = MockRequest(self.env, authname='user', args={ 'action': 'edit', 'text': text, 'version': 0, }, method='POST', path_info='/wiki/BadContent') self._dispatch_request(req) self.assertIn('Your changes have been saved in version 1', unicode(req.chrome['notices']))
def test_save_page_with_invalid_pattern(self): """Page cannot be saved with an invalid pattern.""" perm = PermissionSystem(self.env) perm.grant_permission('user', 'authenticated') text = """{{{ (?i)eventbrite\.com (?i)sneaker(?:supplier|nice\.com }}}""" req = MockRequest(self.env, authname='user', args={ 'action': 'edit', 'text': text, 'version': 0, }, method='POST', path_info='/wiki/BadContent') self._dispatch_request(req) self.assertIn('Invalid Wiki page: Error in pattern ' '<tt>(?i)sneaker(?:supplier|nice\\.com</tt>: ' '<i>unbalanced parenthesis</i>.', req.chrome['warnings'])
class OpenPgpFactoryTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(enable=['trac.*', 'crypto.*']) self.env.path = tempfile.mkdtemp() self.perm = PermissionSystem(self.env) self.req = Mock() self.base = CryptoBase(self.env) self.factory = OpenPgpFactory(self.env) def tearDown(self): shutil.rmtree(self.env.path) def test_openpgp_create_key(self): factory = self.factory self.assertEqual(factory.keys(), []) self.assertEqual(factory.keys(private=True), []) key = factory.create_key(name_real='John', name_email='john@foo') fp = key.fingerprint # Check for both, private and public key. self.assertEqual(factory.keys(id_only=True), [fp]) self.assertEqual(factory.keys(True, True), [fp]) def test_openpgp_delete_key_no_perms(self): factory = self.factory key = factory.create_key() self.req.perm = PermissionCache(self.env) self.assertRaises(PermissionError, factory.delete_key, key.fingerprint, self.req.perm) def test_openpgp_delete_key_full_perms(self): factory = self.factory key = factory.create_key() self.perm.grant_permission('anonymous', 'CRYPTO_DELETE') self.req.perm = PermissionCache(self.env) # Shouldn't raise an error with appropriate permission. factory.delete_key(key.fingerprint, self.req.perm) self.assertEqual(factory.keys(), []) self.assertEqual(factory.keys(private=True), [])
def setUp(self): self.env = EnvironmentStub( enable=['trac.*', 'tractags.*']) self.env.path = tempfile.mkdtemp() self.tag_s = TagSystem(self.env) self.tag_rh = TagRequestHandler(self.env) self.db = self.env.get_db_cnx() setup = TagSetup(self.env) # Current tractags schema is setup with enabled component anyway. # Revert these changes for getting a clean setup. self._revert_tractags_schema_init() setup.upgrade_environment(self.db) perms = PermissionSystem(self.env) # Revoke default permissions, because more diversity is required here. perms.revoke_permission('anonymous', 'TAGS_VIEW') perms.revoke_permission('authenticated', 'TAGS_MODIFY') perms.grant_permission('reader', 'TAGS_VIEW') perms.grant_permission('writer', 'TAGS_MODIFY') perms.grant_permission('admin', 'TAGS_ADMIN') self.anonymous = PermissionCache(self.env) self.reader = PermissionCache(self.env, 'reader') self.writer = PermissionCache(self.env, 'writer') self.admin = PermissionCache(self.env, 'admin') self.href = Href('/trac') self.abs_href = Href('http://example.org/trac')
class CryptoAdminPanelTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(enable=['trac.*', 'crypto.*']) self.env.path = tempfile.mkdtemp() self.perm = PermissionSystem(self.env) self.req = Mock() self.crypto_ap = CryptoAdminPanel(self.env) def tearDown(self): shutil.rmtree(self.env.path) def test_available_actions(self): self.failIf('CRYPTO_ADMIN' not in self.perm.get_actions()) self.failIf('CRYPTO_DELETE' not in self.perm.get_actions()) def test_available_actions_no_perms(self): self.perm.grant_permission('admin', 'authenticated') self.assertFalse(self.perm.check_permission('CRYPTO_ADMIN', 'admin')) self.assertFalse(self.perm.check_permission('CRYPTO_DELETE', 'admin')) def test_available_actions_delete_only(self): self.perm.grant_permission('admin', 'CRYPTO_DELETE') self.assertFalse(self.perm.check_permission('CRYPTO_ADMIN', 'admin')) self.assertTrue(self.perm.check_permission('CRYPTO_DELETE', 'admin')) def test_available_actions_full_perms(self): self.perm.grant_permission('admin', 'TRAC_ADMIN') self.assertTrue(self.perm.check_permission('CRYPTO_ADMIN', 'admin')) self.assertTrue(self.perm.check_permission('CRYPTO_DELETE', 'admin'))
def process_admin_request(self, req, cat, page, path_info): perm = PermissionSystem(self.env) perms = perm.get_all_permissions() subject = req.args.get('subject') action = req.args.get('action') group = req.args.get('group') if req.method == 'POST': # Grant permission to subject if req.args.get('add') and subject and action: if action not in perm.get_actions(): raise TracError('Unknown action') perm.grant_permission(subject, action) req.redirect(self.env.href.admin(cat, page)) # Add subject to group elif req.args.get('add') and subject and group: perm.grant_permission(subject, group) req.redirect(self.env.href.admin(cat, page)) # Remove permissions action elif req.args.get('remove') and req.args.get('sel'): sel = req.args.get('sel') sel = isinstance(sel, list) and sel or [sel] for key in sel: subject, action = key.split(':', 1) if (subject, action) in perms: perm.revoke_permission(subject, action) req.redirect(self.env.href.admin(cat, page)) perms.sort(lambda a, b: cmp(a[0], b[0])) req.hdf['admin.actions'] = perm.get_actions() req.hdf['admin.perms'] = [{'subject': p[0], 'action': p[1], 'key': '%s:%s' % p } for p in perms] return 'admin_perm.cs', None
def setUp(self): self.env = EnvironmentStub(default_data=True, enable=[ default_workflow.ConfigurableTicketWorkflow, DefaultPermissionPolicy, DefaultPermissionStore, BatchModifyModule, api.TicketSystem, web_ui.TicketModule ]) self.env.config.set('trac', 'permission_policies', 'DefaultPermissionPolicy') ps = PermissionSystem(self.env) ps.grant_permission('has_ta_&_bm', 'TICKET_ADMIN') ps.grant_permission('has_bm', 'TICKET_BATCH_MODIFY') ps.grant_permission('has_ta_&_bm', 'TICKET_BATCH_MODIFY')
class PermissionTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub( enable=['trac.*', 'acct_mgr.api.*']) self.env.path = tempfile.mkdtemp() self.perm = PermissionSystem(self.env) self.req = Mock() self.actions = ['ACCTMGR_ADMIN', 'ACCTMGR_CONFIG_ADMIN', 'ACCTMGR_USER_ADMIN', 'EMAIL_VIEW', 'USER_VIEW'] def tearDown(self): shutil.rmtree(self.env.path) def test_available_actions(self): for action in self.actions: self.failIf(action not in self.perm.get_actions()) def test_available_actions_no_perms(self): for action in self.actions: self.assertFalse(self.perm.check_permission(action, 'anonymous')) def test_available_actions_config_admin(self): user = '******' self.perm.grant_permission(user, 'ACCTMGR_CONFIG_ADMIN') actions = [self.actions[0]] + self.actions[2:] for action in actions: self.assertFalse(self.perm.check_permission(action, user)) def test_available_actions_user_admin(self): user = '******' self.perm.grant_permission(user, 'ACCTMGR_USER_ADMIN') for action in self.actions[2:]: self.assertTrue(self.perm.check_permission(action, user)) for action in self.actions[:2] + ['TRAC_ADMIN']: self.assertFalse(self.perm.check_permission(action, user)) def test_available_actions_full_perms(self): perm_map = dict(acctmgr_admin='ACCTMGR_ADMIN', trac_admin='TRAC_ADMIN') for user in perm_map: self.perm.grant_permission(user, perm_map[user]) for action in self.actions: self.assertTrue(self.perm.check_permission(action, user)) if user != 'trac_admin': self.assertFalse(self.perm.check_permission('TRAC_ADMIN', user))
class NavigationContributorTestCase(unittest.TestCase): def setUp(self): self.report_module = ReportModule(self.env) self.query_module = QueryModule(self.env) self.chrome_module = Chrome(self.env) self.perm_sys = PermissionSystem(self.env) if self.env.is_component_enabled(ReportModule): self.perm_sys.grant_permission('has_report_view', 'REPORT_VIEW') self.perm_sys.grant_permission('has_both', 'REPORT_VIEW') self.perm_sys.grant_permission('has_ticket_view', 'TICKET_VIEW') self.perm_sys.grant_permission('has_both', 'TICKET_VIEW') self.tickets_link = lambda href: '<a href="%s">View Tickets</a>' \ % href def tearDown(self): self.env.reset_db() def get_navigation_items(self, req, module): """Return navigation items for `module` in a list.""" for contributor in self.chrome_module.navigation_contributors: if contributor is module: return list(contributor.get_navigation_items(req)) return [] def assertNavItem(self, href, navigation_items): """Asserts that `navigation_items` contains only one entry and directs to `href`. """ self.assertEqual(1, len(navigation_items)) item = navigation_items[0] self.assertEqual(('mainnav', 'tickets'), item[0:2]) self.assertEqual(self.tickets_link(href), str(item[2]))
class ResetActionTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(default_data=True) self.perm_sys = PermissionSystem(self.env) self.ctlr = TicketSystem(self.env).action_controllers[0] self.req1 = MockRequest(self.env, authname='user1') self.req2 = MockRequest(self.env, authname='user2') self.ticket = insert_ticket(self.env, status='invalid') def tearDown(self): self.env.reset_db() def _reload_workflow(self): self.ctlr.actions = self.ctlr.get_all_actions() def test_default_reset_action(self): """Default reset action.""" self.perm_sys.grant_permission('user2', 'TICKET_ADMIN') self._reload_workflow() actions1 = self.ctlr.get_ticket_actions(self.req1, self.ticket) actions2 = self.ctlr.get_ticket_actions(self.req2, self.ticket) chgs2 = self.ctlr.get_ticket_changes(self.req2, self.ticket, '_reset') self.assertEqual(1, len(actions1)) self.assertNotIn((0, '_reset'), actions1) self.assertEqual(2, len(actions2)) self.assertIn((0, '_reset'), actions2) self.assertEqual('new', chgs2['status']) def test_default_reset_action_without_new_state(self): """Default reset action not available when no new state.""" self.perm_sys.grant_permission('user2', 'TICKET_ADMIN') config = self.env.config # Replace 'new' state with 'untriaged' config.set('ticket-workflow', 'create', '<none> -> untriaged') config.set('ticket-workflow', 'accept', 'untriaged,assigned,accepted,reopened -> accepted') config.set('ticket-workflow', 'resolve', 'untriaged,assigned,accepted,reopened -> closed') config.set('ticket-workflow', 'reassign', 'untriaged,assigned,accepted,reopened -> assigned') self._reload_workflow() actions = self.ctlr.get_ticket_actions(self.req2, self.ticket) self.assertEqual(1, len(actions)) self.assertNotIn((0, '_reset'), actions) def test_custom_reset_action(self): """Custom reset action in [ticket-workflow] section.""" config = self.env.config['ticket-workflow'] config.set('_reset', '-> review') config.set('_reset.operations', 'reset_workflow') config.set('_reset.permissions', 'TICKET_BATCH_MODIFY') config.set('_reset.default', 2) self.perm_sys.grant_permission('user2', 'TICKET_BATCH_MODIFY') self._reload_workflow() actions1 = self.ctlr.get_ticket_actions(self.req1, self.ticket) actions2 = self.ctlr.get_ticket_actions(self.req2, self.ticket) chgs2 = self.ctlr.get_ticket_changes(self.req2, self.ticket, '_reset') self.assertEqual(1, len(actions1)) self.assertNotIn((2, '_reset'), actions1) self.assertEqual(2, len(actions2)) self.assertIn((2, '_reset'), actions2) self.assertEqual('review', chgs2['status'])
def install(self): """Installer""" # Utility function to interpreted boolean option value getBool = lambda s: s.strip().lower() in ['true', 'yes'] # Utility function to parse a multi-line/multi-value parameter def cleanMultiParams(v): params = [ s.split('|') for s in [l.strip() for l in v.split('\n')] if len(s) > 0 ] cleaned_params = [] for line in params: cleaned_params.append([row.strip() for row in line]) return cleaned_params # Utility function to transform any string to an ID getId = lambda s: ''.join([c for c in s if c.isalnum()]).lower() options = self.options # Add command line scripts trac-admin and tracd into bin entry_points = [('trac-admin', 'trac.admin.console', 'run'), ('tracd', 'trac.web.standalone', 'main')] zc.buildout.easy_install.scripts(entry_points, pkg_resources.working_set, options['executable'], options['bin-directory']) #################### # Init Trac instance #################### # Generate the trac instance, if required location = options['location'] project_name = options.get('project-name', 'My project') project_url = options.get('project-url', 'http://example.com') db = 'sqlite:%s' % os.path.join('db', 'trac.db') if not os.path.exists(location): os.mkdir(location) trac = TracAdmin(location) if not trac.env_check(): trac.do_initenv('"%s" %s' % (project_name, db)) env = trac.env # Remove Trac default example data clean_up = getBool(options.get('remove-examples', 'True')) if clean_up: # Remove default milestones for mil in Milestone.select(env): if mil.name in [ 'milestone1', 'milestone2', 'milestone3', 'milestone4' ]: mil.delete() # Remove default components for comp in Component.select(env): if comp.name in ['component1', 'component2']: comp.delete() # Add custom milestones for mil_data in cleanMultiParams(options.get('milestones', '')): mil_name = mil_data[0] try: mil = Milestone(env, name=mil_name) except ResourceNotFound: mil = Milestone(env) mil.name = mil_name mil.insert() # Add custom components for comp_data in cleanMultiParams(options.get('components', '')): comp_name = comp_data[0] try: comp = Component(env, name=comp_name) except ResourceNotFound: comp = Component(env) comp.name = comp_name if len(comp_data) == 2 and comp_data[1] not in [None, '']: comp.owner = comp_data[1] comp.insert() ####################### # Generate the trac.ini ####################### # Read the trac.ini config file trac_ini = os.path.join(location, 'conf', 'trac.ini') parser = ConfigParser.ConfigParser() parser.read([trac_ini]) # Clean-up trac.ini: add missing stuff if 'components' not in parser.sections(): parser.add_section('components') # Force upgrade of informations used during initialization parser.set('project', 'name', project_name) # Set all repositories repos = cleanMultiParams(options.get('repos', None)) repo_names = [getId(r[0]) for r in repos] repo_types = {}.fromkeys([r[1].lower() for r in repos]).keys() if 'repositories' not in parser.sections(): parser.add_section('repositories') for repo in repos: repo_name = getId(repo[0]) repo_type = repo[1] repo_dir = repo[2] repo_url = repo[3] parser.set('repositories', '%s.type' % repo_name, repo_type) parser.set('repositories', '%s.dir' % repo_name, repo_dir) if repo_url not in ['', None]: parser.set('repositories', '%s.url' % repo_name, repo_url) # Set default repository default_repo = getId(options.get('default-repo', None)) if default_repo and default_repo in repo_names: parser.set('repositories', '.alias', default_repo) parser.set('repositories', '.hidden', 'true') # Set repository sync method sync_method = options.get('repos-sync', 'request').strip().lower() svn_repos = [getId(r[0]) for r in repos if r[1] == 'svn'] if sync_method == 'request': parser.set('trac', 'repository_sync_per_request', ', '.join(svn_repos)) # TODO # elif sync_method == 'hook': # do stuff... # Set project description project_descr = options.get('project-description', None) if project_descr: parser.set('project', 'descr', project_descr) parser.set('header_logo', 'alt', project_descr) # Setup logo header_logo = options.get('header-logo', '') header_logo = os.path.realpath(header_logo) if os.path.exists(header_logo): shutil.copyfile(header_logo, os.path.join(location, 'htdocs', 'logo')) parser.set('header_logo', 'src', 'site/logo') parser.set('header_logo', 'link', project_url) # Set footer message parser.set( 'project', 'footer', options.get( 'footer-message', 'This Trac instance was generated by <a href="http://pypi.python.org/pypi/pbp.recipe.trac">pbp.recipe.trac</a>.' )) # SMTP parameters for name in ('always-bcc', 'always-cc', 'default-domain', 'enabled', 'from', 'from-name', 'password', 'port', 'replyto', 'server', 'subject-prefix', 'user'): param_name = "smtp-%s" % name default_value = None if param_name == "smtp-from-name": default_value = project_name value = options.get(param_name, default_value) if value is not None: parser.set('notification', param_name.replace('-', '_'), value) ############### # Plugins setup ############### # If one repository use Mercurial, hook its plugin if 'hg' in repo_types: parser.set('components', 'tracext.hg.*', 'enabled') # Configure the NavAdd plugin menu_items = cleanMultiParams(options.get('additional-menu-items', '')) item_list = [] for item in menu_items: item_title = item[0] item_url = item[1] item_id = getId(item_title) item_list.append((item_id, item_title, item_url)) if item_list > 0: parser.set('components', 'navadd.*', 'enabled') if 'navadd' not in parser.sections(): parser.add_section('navadd') parser.set('navadd', 'add_items', ','.join([i[0] for i in item_list])) for (uid, title, url) in item_list: parser.set('navadd', '%s.target' % uid, 'mainnav') parser.set('navadd', '%s.title' % uid, title) parser.set('navadd', '%s.url' % uid, url) # Enable and setup time tracking time_tracking = options.get('time-tracking-plugin', 'disabled').strip().lower() == 'enabled' if time_tracking: parser.set('components', 'timingandestimationplugin.*', 'enabled') # Enable and setup the stat plugin stats = options.get('stats-plugin', 'disabled').strip().lower() == 'enabled' if stats: parser.set('components', 'tracstats.*', 'enabled') ####################### # Final upgrades & sync ####################### # Apply custom parameters defined by the user custom_params = cleanMultiParams(options.get('trac-ini-additional', '')) for param in custom_params: if len(param) == 3: section = param[0] if section not in parser.sections(): parser.add_section(section) parser.set(section, param[1], param[2]) # Write the final trac.ini parser.write(open(trac_ini, 'w')) # Reload the environment env.shutdown() trac = TracAdmin(location) env = trac.env # Set custom permissions perm_sys = PermissionSystem(env) for cperm in cleanMultiParams(options.get('permissions', '')): if len(cperm) == 2: user = cperm[0] current_user_perms = perm_sys.get_user_permissions(user) perm_list = [p.upper() for p in cperm[1].split(' ') if len(p)] for perm in perm_list: if perm not in current_user_perms: perm_sys.grant_permission(user, perm) # Upgrade Trac instance to keep it fresh needs_upgrade = env.needs_upgrade() force_upgrade = getBool(options.get('force-instance-upgrade', 'False')) if needs_upgrade or force_upgrade: env.upgrade(backup=True) # Force repository resync repo_resync = getBool(options.get('force-repos-resync', 'False')) if repo_resync: rm = RepositoryManager(env) repositories = rm.get_real_repositories() for repos in sorted(repositories, key=lambda r: r.reponame): repos.sync(clean=True) # Upgrade default wiki pages embedded in Trac instance wiki_upgrade = getBool(options.get('wiki-doc-upgrade', 'False')) if wiki_upgrade: # Got the command below from trac/admin/console.py pages_dir = pkg_resources.resource_filename( 'trac.wiki', 'default-pages') WikiAdmin(env).load_pages(pages_dir, ignore=['WikiStart', 'checkwiki.py'], create_only=['InterMapTxt']) # Return files that were created by the recipe. The buildout # will remove all returned files upon reinstall. return tuple()
class RestrictOwnerTestCase(unittest.TestCase): def setUp(self): tmpdir = os.path.realpath(tempfile.gettempdir()) self.env = EnvironmentStub(enable=['trac.*', AuthzPolicy], path=tmpdir) self.env.config.set('trac', 'permission_policies', 'AuthzPolicy, DefaultPermissionPolicy') self.env.config.set('ticket', 'restrict_owner', True) self.perm_sys = PermissionSystem(self.env) self.env.insert_users([('user1', 'User C', '*****@*****.**'), ('user2', 'User A', '*****@*****.**'), ('user3', 'User D', '*****@*****.**'), ('user4', 'User B', '*****@*****.**')]) self.perm_sys.grant_permission('user1', 'TICKET_MODIFY') self.perm_sys.grant_permission('user2', 'TICKET_VIEW') self.perm_sys.grant_permission('user3', 'TICKET_MODIFY') self.perm_sys.grant_permission('user4', 'TICKET_MODIFY') self.authz_file = os.path.join(tmpdir, 'trac-authz-policy') create_file(self.authz_file) self.env.config.set('authz_policy', 'authz_file', self.authz_file) self.ctlr = TicketSystem(self.env).action_controllers[0] self.req1 = MockRequest(self.env, authname='user1') self.ticket = Ticket(self.env) self.ticket['status'] = 'new' self.ticket.insert() def tearDown(self): self.env.reset_db() os.remove(self.authz_file) def _reload_workflow(self): self.ctlr.actions = self.ctlr.get_all_actions() def test_set_owner(self): """Restricted owners list contains users with TICKET_MODIFY. """ self.env.config.set('trac', 'show_full_names', False) ctrl = self.ctlr.render_ticket_action_control(self.req1, self.ticket, 'reassign') self.assertEqual('reassign', ctrl[0]) self.assertIn('value="user1">user1</option>', str(ctrl[1])) self.assertNotIn('value="user2">user2</option>', str(ctrl[1])) self.assertIn('value="user3">user3</option>', str(ctrl[1])) self.assertIn('value="user4">user4</option>', str(ctrl[1])) def test_set_owner_fine_grained_permissions(self): """Fine-grained permission checks when populating the restricted owners list (#10833). """ self.env.config.set('trac', 'show_full_names', False) create_file(self.authz_file, """\ [ticket:1] user4 = !TICKET_MODIFY """) ctrl = self.ctlr.render_ticket_action_control(self.req1, self.ticket, 'reassign') self.assertEqual('reassign', ctrl[0]) self.assertIn('value="user1">user1</option>', str(ctrl[1])) self.assertNotIn('value="user2">user2</option>', str(ctrl[1])) self.assertIn('value="user3">user3</option>', str(ctrl[1])) self.assertNotIn('value="user4">user4</option>', str(ctrl[1])) def test_set_owner_show_fullnames(self): """Full names are sorted when [trac] show_full_names = True.""" ctrl = self.ctlr.render_ticket_action_control(self.req1, self.ticket, 'reassign') self.assertEqual('reassign', ctrl[0]) self.assertEqual( """\ to <select id="action_reassign_reassign_owner"\ name="action_reassign_reassign_owner">\ <option value="user4">User B</option>\ <option selected="selected" value="user1">User C</option>\ <option value="user3">User D</option></select>\ """, str(ctrl[1]))
class SetOwnerAttributeTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(default_data=True) self.perm_sys = PermissionSystem(self.env) self.ctlr = TicketSystem(self.env).action_controllers[0] self.ticket = Ticket(self.env) self.ticket['status'] = 'new' self.ticket.insert() with self.env.db_transaction as db: for user in ('user1', 'user2', 'user3', 'user4'): db("INSERT INTO session VALUES (%s, %s, %s)", (user, 1, 0)) permissions = [('user1', 'TICKET_EDIT_CC'), ('user2', 'TICKET_EDIT_CC'), ('user2', 'TICKET_BATCH_MODIFY'), ('user3', 'TICKET_ADMIN'), ('user4', 'TICKET_VIEW'), ('user1', 'group1'), ('user2', 'group1'), ('user2', 'group2'), ('user3', 'group2'), ('user4', 'group3')] for perm in permissions: self.perm_sys.grant_permission(*perm) self.req = MockRequest(self.env, authname='user1') self.expected = """\ to <select id="action_reassign_reassign_owner" \ name="action_reassign_reassign_owner"><option selected="selected" \ value="user1">user1</option><option value="user2">user2</option>\ <option value="user3">user3</option></select>""" def _reload_workflow(self): self.ctlr.actions = self.ctlr.get_all_actions() def tearDown(self): self.env.reset_db() def test_users(self): self.env.config.set('ticket-workflow', 'reassign.set_owner', 'user1, user2, user3') self._reload_workflow() args = self.req, self.ticket, 'reassign' label, control, hints = self.ctlr.render_ticket_action_control(*args) self.assertEqual(self.expected, str(control)) def test_groups(self): self.env.config.set('ticket-workflow', 'reassign.set_owner', 'group1, group2') self._reload_workflow() args = self.req, self.ticket, 'reassign' label, control, hints = self.ctlr.render_ticket_action_control(*args) self.assertEqual(self.expected, str(control)) def test_permission(self): self.env.config.set('ticket-workflow', 'reassign.set_owner', 'TICKET_EDIT_CC, TICKET_BATCH_MODIFY') self._reload_workflow() args = self.req, self.ticket, 'reassign' label, control, hints = self.ctlr.render_ticket_action_control(*args) self.assertEqual(self.expected, str(control))
def setUp(self): self.env = EnvironmentStub() ps = PermissionSystem(self.env) ps.grant_permission('admin', 'TICKET_ADMIN') self.plugin = CustomFieldAdminPage(self.env)
class SetOwnerAttributeTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(default_data=True) self.perm_sys = PermissionSystem(self.env) self.ctlr = TicketSystem(self.env).action_controllers[0] self.ticket = Ticket(self.env) self.ticket['status'] = 'new' self.ticket.insert() with self.env.db_transaction as db: for user in ('user1', 'user2', 'user3', 'user4'): db("INSERT INTO session VALUES (%s, %s, %s)", (user, 1, 0)) permissions = [ ('user1', 'TICKET_EDIT_CC'), ('user2', 'TICKET_EDIT_CC'), ('user2', 'TICKET_BATCH_MODIFY'), ('user3', 'TICKET_ADMIN'), ('user4', 'TICKET_VIEW'), ('user1', 'group1'), ('user2', 'group1'), ('user2', 'group2'), ('user3', 'group2'), ('user4', 'group3') ] for perm in permissions: self.perm_sys.grant_permission(*perm) self.req = Mock(authname='user1', args={}, perm=PermissionCache(self.env, 'user0')) self.expected = """\ to <select name="action_reassign_reassign_owner" \ id="action_reassign_reassign_owner"><option selected="True" \ value="user1">user1</option><option value="user2">user2</option>\ <option value="user3">user3</option></select>""" def _reload_workflow(self): self.ctlr.actions = self.ctlr.get_all_actions() def tearDown(self): self.env.reset_db() def test_users(self): self.env.config.set('ticket-workflow', 'reassign.set_owner', 'user1, user2, user3') self._reload_workflow() args = self.req, self.ticket, 'reassign' label, control, hints = self.ctlr.render_ticket_action_control(*args) self.assertEqual(self.expected, str(control)) def test_groups(self): self.env.config.set('ticket-workflow', 'reassign.set_owner', 'group1, group2') self._reload_workflow() args = self.req, self.ticket, 'reassign' label, control, hints = self.ctlr.render_ticket_action_control(*args) self.assertEqual(self.expected, str(control)) def test_permission(self): self.env.config.set('ticket-workflow', 'reassign.set_owner', 'TICKET_EDIT_CC, TICKET_BATCH_MODIFY') self._reload_workflow() args = self.req, self.ticket, 'reassign' label, control, hints = self.ctlr.render_ticket_action_control(*args) self.assertEqual(self.expected, str(control))
def render_admin_panel(self, req, cat, page, path_info): perm = PermissionSystem(self.env) all_permissions = perm.get_all_permissions() all_actions = perm.get_actions() if req.method == 'POST': subject = req.args.get('subject', '').strip() target = req.args.get('target', '').strip() action = req.args.get('action') group = req.args.get('group', '').strip() if subject and subject.isupper() or \ group and group.isupper() or \ target and target.isupper(): raise TracError(_("All upper-cased tokens are reserved for " "permission names.")) # Grant permission to subject if req.args.get('add') and subject and action: req.perm('admin', 'general/perm').require('PERMISSION_GRANT') if action not in all_actions: raise TracError(_("Unknown action")) req.perm.require(action) if (subject, action) not in all_permissions: perm.grant_permission(subject, action) add_notice(req, _("The subject %(subject)s has been " "granted the permission %(action)s.", subject=subject, action=action)) req.redirect(req.href.admin(cat, page)) else: add_warning(req, _("The permission %(action)s was already " "granted to %(subject)s.", action=action, subject=subject)) # Add subject to group elif req.args.get('add') and subject and group: req.perm('admin', 'general/perm').require('PERMISSION_GRANT') for action in perm.get_user_permissions(group): if not action in all_actions: # plugin disabled? self.env.log.warn("Adding %s to group %s: " "Permission %s unavailable, skipping perm check.", subject, group, action) else: req.perm.require(action, message=_("The subject %(subject)s was not added " "to the group %(group)s because the " "group has %(perm)s permission and " "users cannot grant permissions they " "don't possess.", subject=subject, group=group, perm=action)) if (subject, group) not in all_permissions: perm.grant_permission(subject, group) add_notice(req, _("The subject %(subject)s has been added " "to the group %(group)s.", subject=subject, group=group)) req.redirect(req.href.admin(cat, page)) else: add_warning(req, _("The subject %(subject)s was already " "added to the group %(group)s.", subject=subject, group=group)) # Copy permissions to subject elif req.args.get('copy') and subject and target: req.perm.require('PERMISSION_GRANT') subject_permissions = [i[1] for i in all_permissions if i[0] == subject and i[1].isupper()] if not subject_permissions: add_warning(req,_("The subject %(subject)s does not " "have any permissions.", subject=subject)) for action in subject_permissions: if (target, action) in all_permissions: continue if not action in all_actions: # plugin disabled? self.env.log.warn("Skipped granting %s to %s: " "permission unavailable.", action, target) else: if action not in req.perm: add_warning(req, _("The permission %(action)s was " "not granted to %(subject)s " "because users cannot grant " "permissions they don't possess.", action=action, subject=subject)) continue perm.grant_permission(target, action) add_notice(req, _("The subject %(subject)s has " "been granted the permission " "%(action)s.", subject=target, action=action)) req.redirect(req.href.admin(cat, page)) # Remove permissions action elif req.args.get('remove') and req.args.get('sel'): req.perm('admin', 'general/perm').require('PERMISSION_REVOKE') sel = req.args.get('sel') sel = sel if isinstance(sel, list) else [sel] for key in sel: subject, action = key.split(':', 1) subject = unicode_from_base64(subject) action = unicode_from_base64(action) if (subject, action) in perm.get_all_permissions(): perm.revoke_permission(subject, action) add_notice(req, _("The selected permissions have been " "revoked.")) req.redirect(req.href.admin(cat, page)) return 'admin_perms.html', { 'actions': all_actions, 'perms': perm.get_users_dict(), 'groups': perm.get_groups_dict(), 'unicode_to_base64': unicode_to_base64 }
class TicketSystemTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub() self.perm = PermissionSystem(self.env) self.ticket_system = TicketSystem(self.env) self.req = Mock() def tearDown(self): self.env.reset_db() def _get_actions(self, ticket_dict): ts = TicketSystem(self.env) ticket = Ticket(self.env) ticket.populate(ticket_dict) id = ticket.insert() return ts.get_available_actions(self.req, Ticket(self.env, id)) def test_custom_field_text(self): self.env.config.set('ticket-custom', 'test', 'text') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', 'Foo bar') self.env.config.set('ticket-custom', 'test.format', 'wiki') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual({'name': 'test', 'type': 'text', 'label': 'Test', 'value': 'Foo bar', 'order': 0, 'format': 'wiki'}, fields[0]) def test_custom_field_select(self): self.env.config.set('ticket-custom', 'test', 'select') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', '1') self.env.config.set('ticket-custom', 'test.options', 'option1|option2') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual({'name': 'test', 'type': 'select', 'label': 'Test', 'value': '1', 'options': ['option1', 'option2'], 'order': 0}, fields[0]) def test_custom_field_optional_select(self): self.env.config.set('ticket-custom', 'test', 'select') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', '1') self.env.config.set('ticket-custom', 'test.options', '|option1|option2') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual({'name': 'test', 'type': 'select', 'label': 'Test', 'value': '1', 'options': ['option1', 'option2'], 'order': 0, 'optional': True}, fields[0]) def test_custom_field_textarea(self): self.env.config.set('ticket-custom', 'test', 'textarea') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', 'Foo bar') self.env.config.set('ticket-custom', 'test.cols', '60') self.env.config.set('ticket-custom', 'test.rows', '4') self.env.config.set('ticket-custom', 'test.format', 'wiki') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual({'name': 'test', 'type': 'textarea', 'label': 'Test', 'value': 'Foo bar', 'width': 60, 'height': 4, 'order': 0, 'format': 'wiki'}, fields[0]) def test_custom_field_order(self): self.env.config.set('ticket-custom', 'test1', 'text') self.env.config.set('ticket-custom', 'test1.order', '2') self.env.config.set('ticket-custom', 'test2', 'text') self.env.config.set('ticket-custom', 'test2.order', '1') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual('test2', fields[0]['name']) self.assertEqual('test1', fields[1]['name']) def test_available_actions_full_perms(self): self.perm.grant_permission('anonymous', 'TICKET_CREATE') self.perm.grant_permission('anonymous', 'TICKET_MODIFY') self.req.perm = PermissionCache(self.env) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'new'})) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave', 'reopen'], self._get_actions({'status': 'closed'})) def test_available_actions_no_perms(self): self.req.perm = PermissionCache(self.env) self.assertEqual(['leave'], self._get_actions({'status': 'new'})) self.assertEqual(['leave'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave'], self._get_actions({'status': 'closed'})) def test_available_actions_create_only(self): self.perm.grant_permission('anonymous', 'TICKET_CREATE') self.req.perm = PermissionCache(self.env) self.assertEqual(['leave'], self._get_actions({'status': 'new'})) self.assertEqual(['leave'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave', 'reopen'], self._get_actions({'status': 'closed'})) def test_available_actions_chgprop_only(self): # CHGPROP is not enough for changing a ticket's state (#3289) self.perm.grant_permission('anonymous', 'TICKET_CHGPROP') self.req.perm = PermissionCache(self.env) self.assertEqual(['leave'], self._get_actions({'status': 'new'})) self.assertEqual(['leave'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave'], self._get_actions({'status': 'closed'}))
class TicketSystemTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(default_data=True) self.perm = PermissionSystem(self.env) self.ticket_system = TicketSystem(self.env) self.req = MockRequest(self.env) def tearDown(self): self.env.reset_db() def _get_actions(self, ticket_dict): ts = TicketSystem(self.env) ticket = insert_ticket(self.env, **ticket_dict) return ts.get_available_actions(self.req, Ticket(self.env, ticket.id)) def _get_ticket_field(self, field_name): fields = TicketSystem(self.env).get_ticket_fields() return next((i for i in fields if i['name'] == field_name)) def test_custom_field_text(self): self.env.config.set('ticket-custom', 'test', 'text') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', 'Foo bar') self.env.config.set('ticket-custom', 'test.format', 'wiki') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual( { 'name': 'test', 'type': 'text', 'label': 'Test', 'value': 'Foo bar', 'max_size': 0, 'order': 0, 'format': 'wiki', 'custom': True }, fields[0]) def test_custom_field_select(self): self.env.config.set('ticket-custom', 'test', 'select') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', '1') self.env.config.set('ticket-custom', 'test.options', 'option1|option2') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual( { 'name': 'test', 'type': 'select', 'label': 'Test', 'value': '1', 'options': ['option1', 'option2'], 'order': 0, 'custom': True }, fields[0]) def test_custom_field_optional_select(self): self.env.config.set('ticket-custom', 'test', 'select') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', '1') self.env.config.set('ticket-custom', 'test.options', '|option1|option2') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual( { 'name': 'test', 'type': 'select', 'label': 'Test', 'value': '1', 'options': ['option1', 'option2'], 'order': 0, 'optional': True, 'custom': True }, fields[0]) def test_custom_field_textarea(self): self.env.config.set('ticket-custom', 'test', 'textarea') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', 'Foo bar') self.env.config.set('ticket-custom', 'test.rows', '4') self.env.config.set('ticket-custom', 'test.format', 'wiki') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual( { 'name': 'test', 'type': 'textarea', 'label': 'Test', 'value': 'Foo bar', 'height': 4, 'order': 0, 'max_size': 0, 'format': 'wiki', 'custom': True }, fields[0]) def test_custom_field_checkbox(self): def add_checkbox(name, value): self.env.config.set('ticket-custom', name, 'checkbox') self.env.config.set('ticket-custom', '%s.value' % name, value) add_checkbox('checkbox0', 'true') add_checkbox('checkbox1', 1) add_checkbox('checkbox2', 'enabled') add_checkbox('checkbox3', 0) add_checkbox('checkbox4', 'tru') add_checkbox('checkbox5', 'off') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual( { 'name': 'checkbox0', 'type': 'checkbox', 'label': 'Checkbox0', 'value': '1', 'order': 0, 'custom': True }, fields[0]) self.assertEqual('1', fields[1]['value']) self.assertEqual('1', fields[2]['value']) self.assertEqual('0', fields[3]['value']) self.assertEqual('0', fields[4]['value']) self.assertEqual('0', fields[5]['value']) def test_custom_field_time(self): self.env.config.set('ticket-custom', 'test', 'time') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', '') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual( { 'name': 'test', 'type': 'time', 'label': 'Test', 'value': '', 'order': 0, 'format': 'datetime', 'custom': True }, fields[0]) def test_custom_field_order(self): self.env.config.set('ticket-custom', 'test1', 'text') self.env.config.set('ticket-custom', 'test1.order', '2') self.env.config.set('ticket-custom', 'test2', 'text') self.env.config.set('ticket-custom', 'test2.order', '1') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual('test2', fields[0]['name']) self.assertEqual('test1', fields[1]['name']) def test_custom_field_label(self): self.env.config.set('ticket-custom', '_test_one', 'text') self.env.config.set('ticket-custom', 'test_two', 'text') self.env.config.set('ticket-custom', 'test_two.label', 'test_2') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual('Test one', fields[0]['label']) self.assertEqual('test_2', fields[1]['label']) def test_available_actions_full_perms(self): self.perm.grant_permission('anonymous', 'TICKET_CREATE') self.perm.grant_permission('anonymous', 'TICKET_MODIFY') self.req.perm = PermissionCache(self.env) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'new'})) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave', 'reopen'], self._get_actions({'status': 'closed'})) def test_available_actions_no_perms(self): self.req.perm = PermissionCache(self.env) self.assertEqual(['leave'], self._get_actions({'status': 'new'})) self.assertEqual(['leave'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave'], self._get_actions({'status': 'closed'})) def test_available_actions_create_only(self): self.perm.grant_permission('anonymous', 'TICKET_CREATE') self.req.perm = PermissionCache(self.env) self.assertEqual(['leave'], self._get_actions({'status': 'new'})) self.assertEqual(['leave'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave', 'reopen'], self._get_actions({'status': 'closed'})) def test_available_actions_chgprop_only(self): # CHGPROP is not enough for changing a ticket's state (#3289) self.perm.grant_permission('anonymous', 'TICKET_CHGPROP') self.req.perm = PermissionCache(self.env) self.assertEqual(['leave'], self._get_actions({'status': 'new'})) self.assertEqual(['leave'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave'], self._get_actions({'status': 'closed'})) def test_get_allowed_owners_restrict_owner_false(self): self.env.config.set('ticket', 'restrict_owner', False) self.assertIsNone(self.ticket_system.get_allowed_owners()) def test_get_allowed_owners_restrict_owner_true(self): self.env.config.set('ticket', 'restrict_owner', True) self.env.insert_users([('user3', None, None), ('user1', None, None)]) self.perm.grant_permission('user4', 'TICKET_MODIFY') self.perm.grant_permission('user3', 'TICKET_MODIFY') self.perm.grant_permission('user2', 'TICKET_VIEW') self.perm.grant_permission('user1', 'TICKET_MODIFY') self.assertEqual(['user1', 'user3'], self.ticket_system.get_allowed_owners()) def test_get_ticket_fields_version_rename(self): """Cached ticket fields are updated when version is renamed.""" fields = self.ticket_system.get_ticket_fields() version_field = self._get_ticket_field('version') v2 = Version(self.env, '2.0') v2.name = '0.0' v2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_version_field = self._get_ticket_field('version') self.assertNotEqual(fields, updated_fields) self.assertEqual(['2.0', '1.0'], version_field['options']) self.assertEqual(['1.0', '0.0'], updated_version_field['options']) def test_get_ticket_fields_version_update_time(self): """Cached ticket fields are updated when version release time is changed. """ fields = self.ticket_system.get_ticket_fields() version_field = self._get_ticket_field('version') v1 = Version(self.env, '1.0') v1.time = datetime_now(utc) v2 = Version(self.env, '2.0') v2.time = v1.time - timedelta(seconds=1) v1.update() v2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_version_field = self._get_ticket_field('version') self.assertNotEqual(fields, updated_fields) self.assertEqual(['2.0', '1.0'], version_field['options']) self.assertEqual(['1.0', '2.0'], updated_version_field['options']) def test_get_ticket_fields_milestone_rename(self): """Cached ticket fields are updated when milestone is renamed.""" fields = self.ticket_system.get_ticket_fields() milestone_field = self._get_ticket_field('milestone') m2 = Milestone(self.env, 'milestone2') m2.name = 'milestone5' m2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_milestone_field = self._get_ticket_field('milestone') self.assertNotEqual(fields, updated_fields) self.assertEqual( ['milestone1', 'milestone2', 'milestone3', 'milestone4'], milestone_field['options']) self.assertEqual( ['milestone1', 'milestone3', 'milestone4', 'milestone5'], updated_milestone_field['options']) def test_get_ticket_fields_milestone_update_completed(self): """Cached ticket fields are updated when milestone is completed date is changed. """ fields = self.ticket_system.get_ticket_fields() milestone_field = self._get_ticket_field('milestone') m2 = Milestone(self.env, 'milestone2') m2.completed = datetime_now(utc) m2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_milestone_field = self._get_ticket_field('milestone') self.assertNotEqual(fields, updated_fields) self.assertEqual( ['milestone1', 'milestone2', 'milestone3', 'milestone4'], milestone_field['options']) self.assertEqual( ['milestone2', 'milestone1', 'milestone3', 'milestone4'], updated_milestone_field['options']) def test_get_ticket_fields_milestone_update_due(self): """Cached ticket fields are updated when milestone due date is changed. """ fields = self.ticket_system.get_ticket_fields() milestone_field = self._get_ticket_field('milestone') m2 = Milestone(self.env, 'milestone2') m2.due = datetime_now(utc) m2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_milestone_field = self._get_ticket_field('milestone') self.assertNotEqual(fields, updated_fields) self.assertEqual( ['milestone1', 'milestone2', 'milestone3', 'milestone4'], milestone_field['options']) self.assertEqual( ['milestone2', 'milestone1', 'milestone3', 'milestone4'], updated_milestone_field['options']) def test_resource_exists_valid_resource_id(self): insert_ticket(self.env) r1 = Resource('ticket', 1) r2 = Resource('ticket', 2) self.assertTrue(self.ticket_system.resource_exists(r1)) self.assertFalse(self.ticket_system.resource_exists(r2)) def test_resource_exists_invalid_resource_id(self): """Exception is trapped from resource with invalid id.""" r1 = Resource('ticket', None) r2 = Resource('ticket', 'abc') r3 = Resource('ticket', '2.') r4 = Resource('ticket', r2) self.assertFalse(self.ticket_system.resource_exists(r1)) self.assertFalse(self.ticket_system.resource_exists(r2)) self.assertFalse(self.ticket_system.resource_exists(r3)) self.assertFalse(self.ticket_system.resource_exists(r4))
class TicketSystemTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub() self.perm = PermissionSystem(self.env) self.ticket_system = TicketSystem(self.env) self.req = Mock() def tearDown(self): self.env.reset_db() def _get_actions(self, ticket_dict): ts = TicketSystem(self.env) ticket = Ticket(self.env) ticket.populate(ticket_dict) id = ticket.insert() return ts.get_available_actions(self.req, Ticket(self.env, id)) def test_custom_field_text(self): self.env.config.set('ticket-custom', 'test', 'text') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', 'Foo bar') self.env.config.set('ticket-custom', 'test.format', 'wiki') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual({'name': 'test', 'type': 'text', 'label': 'Test', 'value': 'Foo bar', 'order': 0, 'format': 'wiki', 'custom': True}, fields[0]) def test_custom_field_select(self): self.env.config.set('ticket-custom', 'test', 'select') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', '1') self.env.config.set('ticket-custom', 'test.options', 'option1|option2') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual({'name': 'test', 'type': 'select', 'label': 'Test', 'value': '1', 'options': ['option1', 'option2'], 'order': 0, 'custom': True}, fields[0]) def test_custom_field_optional_select(self): self.env.config.set('ticket-custom', 'test', 'select') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', '1') self.env.config.set('ticket-custom', 'test.options', '|option1|option2') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual({'name': 'test', 'type': 'select', 'label': 'Test', 'value': '1', 'options': ['option1', 'option2'], 'order': 0, 'optional': True, 'custom': True}, fields[0]) def test_custom_field_textarea(self): self.env.config.set('ticket-custom', 'test', 'textarea') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', 'Foo bar') self.env.config.set('ticket-custom', 'test.cols', '60') self.env.config.set('ticket-custom', 'test.rows', '4') self.env.config.set('ticket-custom', 'test.format', 'wiki') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual({'name': 'test', 'type': 'textarea', 'label': 'Test', 'value': 'Foo bar', 'width': 60, 'height': 4, 'order': 0, 'format': 'wiki', 'custom': True}, fields[0]) def test_custom_field_time(self): self.env.config.set('ticket-custom', 'test', 'time') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', '') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual({'name': 'test', 'type': 'time', 'label': 'Test', 'value': '', 'order': 0, 'format': 'datetime', 'custom': True}, fields[0]) def test_custom_field_order(self): self.env.config.set('ticket-custom', 'test1', 'text') self.env.config.set('ticket-custom', 'test1.order', '2') self.env.config.set('ticket-custom', 'test2', 'text') self.env.config.set('ticket-custom', 'test2.order', '1') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual('test2', fields[0]['name']) self.assertEqual('test1', fields[1]['name']) def test_available_actions_full_perms(self): self.perm.grant_permission('anonymous', 'TICKET_CREATE') self.perm.grant_permission('anonymous', 'TICKET_MODIFY') self.req.perm = PermissionCache(self.env) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'new'})) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave', 'reopen'], self._get_actions({'status': 'closed'})) def test_available_actions_no_perms(self): self.req.perm = PermissionCache(self.env) self.assertEqual(['leave'], self._get_actions({'status': 'new'})) self.assertEqual(['leave'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave'], self._get_actions({'status': 'closed'})) def test_available_actions_create_only(self): self.perm.grant_permission('anonymous', 'TICKET_CREATE') self.req.perm = PermissionCache(self.env) self.assertEqual(['leave'], self._get_actions({'status': 'new'})) self.assertEqual(['leave'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave', 'reopen'], self._get_actions({'status': 'closed'})) def test_available_actions_chgprop_only(self): # CHGPROP is not enough for changing a ticket's state (#3289) self.perm.grant_permission('anonymous', 'TICKET_CHGPROP') self.req.perm = PermissionCache(self.env) self.assertEqual(['leave'], self._get_actions({'status': 'new'})) self.assertEqual(['leave'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave'], self._get_actions({'status': 'closed'}))
class DefaultTicketPolicyTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(enable=('trac.ticket.*', 'trac.perm.*')) self.env.config.set('trac', 'permission_policies', 'DefaultTicketPolicy,DefaultPermissionPolicy') self.perm_sys = PermissionSystem(self.env) self.policy = DefaultTicketPolicy(self.env) def tearDown(self): self.env.reset_db() def _insert_ticket(self, reporter): return insert_ticket(self.env, reporter=reporter, summary='The summary', description='The text.') def test_reporter_can_edit_own_ticket_description(self): """Authenticated user can modify description of ticket they reported. The authenticated user must have TICKET_CHGPROP or TICKET_APPEND. """ self.perm_sys.grant_permission('somebody1', 'TICKET_CHGPROP') self.perm_sys.grant_permission('somebody2', 'TICKET_APPEND') ticket1 = self._insert_ticket('somebody1') ticket2 = self._insert_ticket('somebody2') ticket3 = self._insert_ticket('somebody3') action = 'TICKET_EDIT_DESCRIPTION' perm_cache = PermissionCache(self.env, 'somebody1', ticket1.resource) self.assertIn(action, perm_cache) self.assertTrue( self.policy.check_permission(action, perm_cache.username, ticket1.resource, perm_cache)) perm_cache = PermissionCache(self.env, 'somebody2', ticket2.resource) self.assertIn(action, perm_cache) self.assertTrue( self.policy.check_permission(action, perm_cache.username, ticket2.resource, perm_cache)) perm_cache = PermissionCache(self.env, 'somebody3', ticket3.resource) self.assertNotIn(action, perm_cache) self.assertIsNone( self.policy.check_permission(action, perm_cache.username, ticket3.resource, perm_cache)) def test_reporter_cannot_edit_other_ticket_description(self): """Authenticated user cannot modify description of ticket they didn't report. """ ticket = self._insert_ticket('somebodyelse') perm_cache = PermissionCache(self.env, 'somebody', ticket.resource) action = 'TICKET_EDIT_DESCRIPTION' self.assertNotIn(action, perm_cache) self.assertIsNone( self.policy.check_permission(action, perm_cache.username, ticket.resource, perm_cache)) def test_anonymous_cannot_edit_ticket_description(self): """Anonymous user cannot modify description of ticket they reported. """ ticket = self._insert_ticket('anonymous') perm_cache = PermissionCache(self.env, 'anonymous') action = 'TICKET_EDIT_DESCRIPTION' self.assertNotIn('TICKET_EDIT_DESCRIPTION', perm_cache(ticket.resource)) self.assertIsNone( self.policy.check_permission(action, perm_cache.username, ticket.resource, perm_cache)) def _test_edit_ticket_comment(self, commenter, editor): ticket = self._insert_ticket(commenter) ticket.save_changes(commenter, comment='The comment') comment_resource = Resource('comment', 1, parent=ticket.resource) perm_cache = PermissionCache(self.env, editor, comment_resource) return perm_cache, comment_resource def test_user_can_edit_own_ticket_comment(self): """Authenticated user can modify their own ticket comment. """ self.perm_sys.grant_permission('somebody', 'TICKET_APPEND') perm_cache, resource = \ self._test_edit_ticket_comment('somebody', 'somebody') action = 'TICKET_EDIT_COMMENT' self.assertIn(action, perm_cache) self.assertTrue( self.policy.check_permission(action, perm_cache.username, resource, perm_cache)) def test_user_must_have_ticket_append_to_edit(self): """Authenticated user must have TICKET_APPEND to edit own ticket comment. """ perm_cache, resource = \ self._test_edit_ticket_comment('somebody', 'somebody') action = 'TICKET_EDIT_COMMENT' self.assertNotIn(action, perm_cache) self.assertIsNone( self.policy.check_permission(action, perm_cache.username, resource, perm_cache)) def test_user_cannot_edit_other_ticket_comment(self): """Authenticated user cannot modify the ticket comment of another user. """ self.perm_sys.grant_permission('somebody', 'TICKET_APPEND') perm_cache, resource = \ self._test_edit_ticket_comment('someother', 'somebody') action = 'TICKET_EDIT_COMMENT' self.assertNotIn(action, perm_cache) self.assertIsNone( self.policy.check_permission(action, perm_cache.username, resource, perm_cache)) def test_anonymous_cannot_edit_ticket_comment(self): """Anonymous user cannot modify a ticket comment. """ self.perm_sys.grant_permission('anonymous', 'TICKET_APPEND') perm_cache, resource = \ self._test_edit_ticket_comment('anonymous', 'anonymous') action = 'TICKET_EDIT_COMMENT' self.assertNotIn(action, perm_cache) self.assertIsNone( self.policy.check_permission(action, perm_cache.username, resource, perm_cache)) def _test_change_milestone(self, editor): milestone = Milestone(self.env) milestone.name = 'milestone1' milestone.insert() perm_cache = PermissionCache(self.env, editor, milestone.resource) return perm_cache, milestone.resource def test_user_with_milestone_view_can_change_milestone(self): """User with MILESTONE_VIEW can change the ticket milestone. """ self.perm_sys.grant_permission('user_w_mv', 'MILESTONE_VIEW') action = 'TICKET_CHG_MILESTONE' perm_cache, resource = self._test_change_milestone('user_w_mv') self.assertIn(action, perm_cache) self.assertTrue( self.policy.check_permission(action, perm_cache.username, resource, perm_cache)) def test_user_without_milestone_view_cannot_change_milestone(self): """User without MILESTONE_VIEW cannot change the ticket milestone. """ action = 'TICKET_CHG_MILESTONE' perm_cache, resource = self._test_change_milestone('user_w_mv') self.assertNotIn(action, perm_cache) self.assertIsNone( self.policy.check_permission(action, perm_cache.username, resource, perm_cache))
def render_admin_panel(self, req, cat, page, path_info): perm = PermissionSystem(self.env) all_permissions = perm.get_all_permissions() all_actions = perm.get_actions() if req.method == 'POST': subject = req.args.get('subject', '').strip() target = req.args.get('target', '').strip() action = req.args.get('action') group = req.args.get('group', '').strip() if subject and subject.isupper() or \ group and group.isupper() or \ target and target.isupper(): raise TracError( _("All upper-cased tokens are reserved for " "permission names.")) # Grant permission to subject if req.args.get('add') and subject and action: req.perm('admin', 'general/perm').require('PERMISSION_GRANT') if action not in all_actions: raise TracError(_("Unknown action")) req.perm.require(action) if (subject, action) not in all_permissions: perm.grant_permission(subject, action) add_notice( req, _( "The subject %(subject)s has been " "granted the permission %(action)s.", subject=subject, action=action)) req.redirect(req.href.admin(cat, page)) else: add_warning( req, _( "The permission %(action)s was already " "granted to %(subject)s.", action=action, subject=subject)) # Add subject to group elif req.args.get('add') and subject and group: req.perm('admin', 'general/perm').require('PERMISSION_GRANT') for action in perm.get_user_permissions(group): if not action in all_actions: # plugin disabled? self.env.log.warn( "Adding %s to group %s: " "Permission %s unavailable, skipping perm check.", subject, group, action) else: req.perm.require( action, message=_( "The subject %(subject)s was not added " "to the group %(group)s because the " "group has %(perm)s permission and " "users cannot grant permissions they " "don't possess.", subject=subject, group=group, perm=action)) if (subject, group) not in all_permissions: perm.grant_permission(subject, group) add_notice( req, _( "The subject %(subject)s has been added " "to the group %(group)s.", subject=subject, group=group)) req.redirect(req.href.admin(cat, page)) else: add_warning( req, _( "The subject %(subject)s was already " "added to the group %(group)s.", subject=subject, group=group)) # Copy permissions to subject elif req.args.get('copy') and subject and target: req.perm.require('PERMISSION_GRANT') subject_permissions = [ i[1] for i in all_permissions if i[0] == subject and i[1].isupper() ] if not subject_permissions: add_warning( req, _( "The subject %(subject)s does not " "have any permissions.", subject=subject)) for action in subject_permissions: if (target, action) in all_permissions: continue if not action in all_actions: # plugin disabled? self.env.log.warn( "Skipped granting %s to %s: " "permission unavailable.", action, target) else: if action not in req.perm: add_warning( req, _( "The permission %(action)s was " "not granted to %(subject)s " "because users cannot grant " "permissions they don't possess.", action=action, subject=subject)) continue perm.grant_permission(target, action) add_notice( req, _( "The subject %(subject)s has " "been granted the permission " "%(action)s.", subject=target, action=action)) req.redirect(req.href.admin(cat, page)) # Remove permissions action elif req.args.get('remove') and req.args.get('sel'): req.perm('admin', 'general/perm').require('PERMISSION_REVOKE') sel = req.args.get('sel') sel = sel if isinstance(sel, list) else [sel] for key in sel: subject, action = key.split(':', 1) subject = unicode_from_base64(subject) action = unicode_from_base64(action) if (subject, action) in perm.get_all_permissions(): perm.revoke_permission(subject, action) add_notice(req, _("The selected permissions have been " "revoked.")) req.redirect(req.href.admin(cat, page)) return 'admin_perms.html', { 'actions': all_actions, 'perms': perm.get_users_dict(), 'groups': perm.get_groups_dict(), 'unicode_to_base64': unicode_to_base64 }
class TicketSystemTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(default_data=True) self.perm = PermissionSystem(self.env) self.ticket_system = TicketSystem(self.env) self.req = Mock() def tearDown(self): self.env.reset_db() def _get_actions(self, ticket_dict): ts = TicketSystem(self.env) ticket = Ticket(self.env) ticket.populate(ticket_dict) id = ticket.insert() return ts.get_available_actions(self.req, Ticket(self.env, id)) def _get_ticket_field(self, field_name): fields = TicketSystem(self.env).get_ticket_fields() return (i for i in fields if i['name'] == field_name).next() def test_custom_field_text(self): self.env.config.set('ticket-custom', 'test', 'text') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', 'Foo bar') self.env.config.set('ticket-custom', 'test.format', 'wiki') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual( { 'name': 'test', 'type': 'text', 'label': 'Test', 'value': 'Foo bar', 'order': 0, 'format': 'wiki' }, fields[0]) def test_custom_field_select(self): self.env.config.set('ticket-custom', 'test', 'select') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', '1') self.env.config.set('ticket-custom', 'test.options', 'option1|option2') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual( { 'name': 'test', 'type': 'select', 'label': 'Test', 'value': '1', 'options': ['option1', 'option2'], 'order': 0 }, fields[0]) def test_custom_field_optional_select(self): self.env.config.set('ticket-custom', 'test', 'select') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', '1') self.env.config.set('ticket-custom', 'test.options', '|option1|option2') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual( { 'name': 'test', 'type': 'select', 'label': 'Test', 'value': '1', 'options': ['option1', 'option2'], 'order': 0, 'optional': True }, fields[0]) def test_custom_field_textarea(self): self.env.config.set('ticket-custom', 'test', 'textarea') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', 'Foo bar') self.env.config.set('ticket-custom', 'test.cols', '60') self.env.config.set('ticket-custom', 'test.rows', '4') self.env.config.set('ticket-custom', 'test.format', 'wiki') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual( { 'name': 'test', 'type': 'textarea', 'label': 'Test', 'value': 'Foo bar', 'width': 60, 'height': 4, 'order': 0, 'format': 'wiki' }, fields[0]) def test_custom_field_order(self): self.env.config.set('ticket-custom', 'test1', 'text') self.env.config.set('ticket-custom', 'test1.order', '2') self.env.config.set('ticket-custom', 'test2', 'text') self.env.config.set('ticket-custom', 'test2.order', '1') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual('test2', fields[0]['name']) self.assertEqual('test1', fields[1]['name']) def test_available_actions_full_perms(self): self.perm.grant_permission('anonymous', 'TICKET_CREATE') self.perm.grant_permission('anonymous', 'TICKET_MODIFY') self.req.perm = PermissionCache(self.env) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'new'})) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave', 'reopen'], self._get_actions({'status': 'closed'})) def test_available_actions_no_perms(self): self.req.perm = PermissionCache(self.env) self.assertEqual(['leave'], self._get_actions({'status': 'new'})) self.assertEqual(['leave'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave'], self._get_actions({'status': 'closed'})) def test_available_actions_create_only(self): self.perm.grant_permission('anonymous', 'TICKET_CREATE') self.req.perm = PermissionCache(self.env) self.assertEqual(['leave'], self._get_actions({'status': 'new'})) self.assertEqual(['leave'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave', 'reopen'], self._get_actions({'status': 'closed'})) def test_available_actions_chgprop_only(self): # CHGPROP is not enough for changing a ticket's state (#3289) self.perm.grant_permission('anonymous', 'TICKET_CHGPROP') self.req.perm = PermissionCache(self.env) self.assertEqual(['leave'], self._get_actions({'status': 'new'})) self.assertEqual(['leave'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave'], self._get_actions({'status': 'closed'})) def test_get_ticket_fields_version_rename(self): """Cached ticket fields are updated when version is renamed.""" fields = self.ticket_system.get_ticket_fields() version_field = self._get_ticket_field('version') v2 = Version(self.env, '2.0') v2.name = '0.0' v2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_version_field = self._get_ticket_field('version') self.assertNotEqual(fields, updated_fields) self.assertEqual(['2.0', '1.0'], version_field['options']) self.assertEqual(['1.0', '0.0'], updated_version_field['options']) def test_get_ticket_fields_version_update_time(self): """Cached ticket fields are updated when version release time is changed. """ fields = self.ticket_system.get_ticket_fields() version_field = self._get_ticket_field('version') v1 = Version(self.env, '1.0') v1.time = datetime_now(utc) v2 = Version(self.env, '2.0') v2.time = v1.time - timedelta(seconds=1) v1.update() v2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_version_field = self._get_ticket_field('version') self.assertNotEqual(fields, updated_fields) self.assertEqual(['2.0', '1.0'], version_field['options']) self.assertEqual(['1.0', '2.0'], updated_version_field['options']) def test_get_ticket_fields_milestone_rename(self): """Cached ticket fields are updated when milestone is renamed.""" fields = self.ticket_system.get_ticket_fields() milestone_field = self._get_ticket_field('milestone') m2 = Milestone(self.env, 'milestone2') m2.name = 'milestone5' m2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_milestone_field = self._get_ticket_field('milestone') self.assertNotEqual(fields, updated_fields) self.assertEqual( ['milestone1', 'milestone2', 'milestone3', 'milestone4'], milestone_field['options']) self.assertEqual( ['milestone1', 'milestone3', 'milestone4', 'milestone5'], updated_milestone_field['options']) def test_get_ticket_fields_milestone_update_completed(self): """Cached ticket fields are updated when milestone is completed date is changed. """ fields = self.ticket_system.get_ticket_fields() milestone_field = self._get_ticket_field('milestone') m2 = Milestone(self.env, 'milestone2') m2.completed = datetime_now(utc) m2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_milestone_field = self._get_ticket_field('milestone') self.assertNotEqual(fields, updated_fields) self.assertEqual( ['milestone1', 'milestone2', 'milestone3', 'milestone4'], milestone_field['options']) self.assertEqual( ['milestone2', 'milestone1', 'milestone3', 'milestone4'], updated_milestone_field['options']) def test_get_ticket_fields_milestone_update_due(self): """Cached ticket fields are updated when milestone due date is changed. """ fields = self.ticket_system.get_ticket_fields() milestone_field = self._get_ticket_field('milestone') m2 = Milestone(self.env, 'milestone2') m2.due = datetime_now(utc) m2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_milestone_field = self._get_ticket_field('milestone') self.assertNotEqual(fields, updated_fields) self.assertEqual( ['milestone1', 'milestone2', 'milestone3', 'milestone4'], milestone_field['options']) self.assertEqual( ['milestone2', 'milestone1', 'milestone3', 'milestone4'], updated_milestone_field['options']) def test_resource_exists_valid_resource_id(self): Ticket(self.env).insert() r1 = Resource('ticket', 1) r2 = Resource('ticket', 2) self.assertTrue(self.ticket_system.resource_exists(r1)) self.assertFalse(self.ticket_system.resource_exists(r2)) def test_resource_exists_invalid_resource_id(self): """Exception is trapped from resource with invalid id.""" r1 = Resource('ticket', None) r2 = Resource('ticket', 'abc') r3 = Resource('ticket', '2.') r4 = Resource('ticket', r2) self.assertFalse(self.ticket_system.resource_exists(r1)) self.assertFalse(self.ticket_system.resource_exists(r2)) self.assertFalse(self.ticket_system.resource_exists(r3)) self.assertFalse(self.ticket_system.resource_exists(r4)) def test_can_add_raw_fields_from_field_providers(self): testFieldProvider = self.env[TestFieldProvider] self.assertIsNotNone(testFieldProvider) testFieldProvider.raw_fields = [ { 'name': "test_name", 'type': 'some_type', 'label': "some_label", }, ] fields = TicketSystem(self.env).get_ticket_fields() row_added_fields = [ field for field in fields if field["name"] == "test_name" ] self.assertEqual(1, len(row_added_fields)) def test_does_not_add_duplicated_raw_fields_from_field_providers(self): testFieldProvider = self.env[TestFieldProvider] self.assertIsNotNone(testFieldProvider) testFieldProvider.raw_fields = [ { 'name': "test_name", 'type': 'some_type1', 'label': "some_label1", }, { 'name': "test_name", 'type': 'some_type2', 'label': "some_label2", }, ] fields = TicketSystem(self.env).get_ticket_fields() row_added_fields = [ field for field in fields if field["name"] == "test_name" ] self.assertEqual(1, len(row_added_fields))
def install(self): """Installer""" # Utility function to interpreted boolean option value getBool = lambda s: s.strip().lower() in ['true', 'yes'] # Utility function to parse a multi-line/multi-value parameter def cleanMultiParams(v): params = [s.split('|') for s in [l.strip() for l in v.split('\n')] if len(s) > 0] cleaned_params = [] for line in params: cleaned_params.append([row.strip() for row in line]) return cleaned_params # Utility function to transform any string to an ID getId = lambda s: ''.join([c for c in s if c.isalnum()]).lower() options = self.options ################# # eggs from the config options ################ requirements, ws = self.egg.working_set() for dist in pkg_resources.working_set: ws.add(dist) # Add command line scripts trac-admin and tracd into bin entry_points = [('trac-admin', 'trac.admin.console', 'run'), ('tracd', 'trac.web.standalone', 'main')] zc.buildout.easy_install.scripts( entry_points, ws, options['executable'], options['bin-directory'] ) #################### # Init Trac instance #################### # Generate the trac instance, if required location = options['location'] project_name = options.get('project-name', 'My project') project_url = options.get('project-url', 'http://example.com') db = 'sqlite:%s' % os.path.join('db', 'trac.db') if not os.path.exists(location): os.mkdir(location) trac = TracAdmin(location) if not trac.env_check(): trac.do_initenv('"%s" %s' % (project_name, db)) env = trac.env # Remove Trac default example data clean_up = getBool(options.get('remove-examples', 'True')) if clean_up: # Remove default milestones for mil in Milestone.select(env): if mil.name in ['milestone1', 'milestone2', 'milestone3', 'milestone4']: mil.delete() # Remove default components for comp in Component.select(env): if comp.name in ['component1', 'component2']: comp.delete() # Add custom milestones for mil_data in cleanMultiParams(options.get('milestones', '')): mil_name = mil_data[0] try: mil = Milestone(env, name=mil_name) except ResourceNotFound: mil = Milestone(env) mil.name = mil_name mil.insert() # Add custom components for comp_data in cleanMultiParams(options.get('components', '')): comp_name = comp_data[0] try: comp = Component(env, name=comp_name) except ResourceNotFound: comp = Component(env) comp.name = comp_name if len(comp_data) == 2 and comp_data[1] not in [None, '']: comp.owner = comp_data[1] comp.insert() ####################### # Generate the trac.ini ####################### # Read the trac.ini config file trac_ini = os.path.join(location, 'conf', 'trac.ini') parser = ConfigParser.ConfigParser() parser.read([trac_ini]) # Clean-up trac.ini: add missing stuff if 'components' not in parser.sections(): parser.add_section('components') # Force upgrade of informations used during initialization parser.set('project', 'name', project_name) # Set all repositories repos = cleanMultiParams(options.get('repos', None)) repo_names = [getId(r[0]) for r in repos] repo_types = {}.fromkeys([r[1].lower() for r in repos]).keys() if 'repositories' not in parser.sections(): parser.add_section('repositories') for repo in repos: repo_name = getId(repo[0]) repo_type = repo[1] repo_dir = repo[2] repo_url = repo[3] parser.set('repositories', '%s.type' % repo_name, repo_type) parser.set('repositories', '%s.dir' % repo_name, repo_dir) if repo_url not in ['', None]: parser.set('repositories', '%s.url' % repo_name, repo_url) # Set default repository default_repo = getId(options.get('default-repo', None)) if default_repo and default_repo in repo_names: parser.set('repositories', '.alias', default_repo) parser.set('repositories', '.hidden', 'true') # Set repository sync method sync_method = options.get('repos-sync', 'request').strip().lower() svn_repos = [getId(r[0]) for r in repos if r[1] == 'svn'] if sync_method == 'request': parser.set('trac', 'repository_sync_per_request', ', '.join(svn_repos)) # TODO # elif sync_method == 'hook': # do stuff... # Set project description project_descr = options.get('project-description', None) if project_descr: parser.set('project', 'descr', project_descr) parser.set('header_logo', 'alt', project_descr) # Setup logo header_logo = options.get('header-logo', '') header_logo = os.path.realpath(header_logo) if os.path.exists(header_logo): shutil.copyfile(header_logo, os.path.join(location, 'htdocs', 'logo')) parser.set('header_logo', 'src', 'site/logo') parser.set('header_logo', 'link', project_url) # Set footer message parser.set('project', 'footer', options.get('footer-message', 'This Trac instance was generated by <a href="http://pypi.python.org/pypi/pbp.recipe.trac">pbp.recipe.trac</a>.')) # SMTP parameters for name in ('always-bcc', 'always-cc', 'default-domain', 'enabled', 'from', 'from-name', 'password', 'port', 'replyto', 'server', 'subject-prefix', 'user'): param_name = "smtp-%s" % name default_value = None if param_name == "smtp-from-name": default_value = project_name value = options.get(param_name, default_value) if value is not None: parser.set('notification', param_name.replace('-', '_'), value) ############### # Plugins setup ############### # If one repository use Mercurial, hook its plugin if 'hg' in repo_types: parser.set('components', 'tracext.hg.*', 'enabled') # Configure the NavAdd plugin menu_items = cleanMultiParams(options.get('additional-menu-items', '')) item_list = [] for item in menu_items: item_title = item[0] item_url = item[1] item_id = getId(item_title) item_list.append((item_id, item_title, item_url)) if item_list > 0: parser.set('components', 'navadd.*', 'enabled') if 'navadd' not in parser.sections(): parser.add_section('navadd') parser.set('navadd', 'add_items', ','.join([i[0] for i in item_list])) for (uid, title, url) in item_list: parser.set('navadd', '%s.target' % uid, 'mainnav') parser.set('navadd', '%s.title' % uid, title) parser.set('navadd', '%s.url' % uid, url) # Enable and setup time tracking time_tracking = options.get('time-tracking-plugin', 'disabled').strip().lower() == 'enabled' if time_tracking: parser.set('components', 'timingandestimationplugin.*', 'enabled') # Enable and setup the stat plugin stats = options.get('stats-plugin', 'disabled').strip().lower() == 'enabled' if stats: parser.set('components', 'tracstats.*', 'enabled') ####################### # Final upgrades & sync ####################### # Apply custom parameters defined by the user custom_params = cleanMultiParams(options.get('trac-ini-additional', '')) for param in custom_params: if len(param) == 3: section = param[0] if section not in parser.sections(): parser.add_section(section) parser.set(section, param[1], param[2]) # Write the final trac.ini parser.write(open(trac_ini, 'w')) # Reload the environment env.shutdown() trac = TracAdmin(location) env = trac.env # Set custom permissions perm_sys = PermissionSystem(env) for cperm in cleanMultiParams(options.get('permissions', '')): if len(cperm) == 2: user = cperm[0] current_user_perms = perm_sys.get_user_permissions(user) perm_list = [p.upper() for p in cperm[1].split(' ') if len(p)] for perm in perm_list: if perm not in current_user_perms: perm_sys.grant_permission(user, perm) # Upgrade Trac instance to keep it fresh needs_upgrade = env.needs_upgrade() force_upgrade = getBool(options.get('force-instance-upgrade', 'False')) if needs_upgrade or force_upgrade: env.upgrade(backup=True) # Force repository resync repo_resync = getBool(options.get('force-repos-resync', 'False')) if repo_resync: rm = RepositoryManager(env) repositories = rm.get_real_repositories() for repos in sorted(repositories, key=lambda r: r.reponame): repos.sync(clean=True) # Upgrade default wiki pages embedded in Trac instance wiki_upgrade = getBool(options.get('wiki-doc-upgrade', 'False')) if wiki_upgrade: # Got the command below from trac/admin/console.py pages_dir = pkg_resources.resource_filename('trac.wiki', 'default-pages') WikiAdmin(env).load_pages( pages_dir , ignore=['WikiStart', 'checkwiki.py'] , create_only=['InterMapTxt'] ) # Return files that were created by the recipe. The buildout # will remove all returned files upon reinstall. return tuple()
class TicketSystemTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(default_data=True) self.perm = PermissionSystem(self.env) self.ticket_system = TicketSystem(self.env) self.req = Mock() def tearDown(self): self.env.reset_db() def _get_actions(self, ticket_dict): ts = TicketSystem(self.env) ticket = Ticket(self.env) ticket.populate(ticket_dict) id = ticket.insert() return ts.get_available_actions(self.req, Ticket(self.env, id)) def _get_ticket_field(self, field_name): fields = TicketSystem(self.env).get_ticket_fields() return (i for i in fields if i['name'] == field_name).next() def test_custom_field_text(self): self.env.config.set('ticket-custom', 'test', 'text') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', 'Foo bar') self.env.config.set('ticket-custom', 'test.format', 'wiki') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual({'name': 'test', 'type': 'text', 'label': 'Test', 'value': 'Foo bar', 'order': 0, 'format': 'wiki', 'custom': True}, fields[0]) def test_custom_field_select(self): self.env.config.set('ticket-custom', 'test', 'select') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', '1') self.env.config.set('ticket-custom', 'test.options', 'option1|option2') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual({'name': 'test', 'type': 'select', 'label': 'Test', 'value': '1', 'options': ['option1', 'option2'], 'order': 0, 'custom': True}, fields[0]) def test_custom_field_optional_select(self): self.env.config.set('ticket-custom', 'test', 'select') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', '1') self.env.config.set('ticket-custom', 'test.options', '|option1|option2') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual({'name': 'test', 'type': 'select', 'label': 'Test', 'value': '1', 'options': ['option1', 'option2'], 'order': 0, 'optional': True, 'custom': True}, fields[0]) def test_custom_field_textarea(self): self.env.config.set('ticket-custom', 'test', 'textarea') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', 'Foo bar') self.env.config.set('ticket-custom', 'test.rows', '4') self.env.config.set('ticket-custom', 'test.format', 'wiki') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual({'name': 'test', 'type': 'textarea', 'label': 'Test', 'value': 'Foo bar', 'height': 4, 'order': 0, 'format': 'wiki', 'custom': True}, fields[0]) def test_custom_field_time(self): self.env.config.set('ticket-custom', 'test', 'time') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', '') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual({'name': 'test', 'type': 'time', 'label': 'Test', 'value': '', 'order': 0, 'format': 'datetime', 'custom': True}, fields[0]) def test_custom_field_order(self): self.env.config.set('ticket-custom', 'test1', 'text') self.env.config.set('ticket-custom', 'test1.order', '2') self.env.config.set('ticket-custom', 'test2', 'text') self.env.config.set('ticket-custom', 'test2.order', '1') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual('test2', fields[0]['name']) self.assertEqual('test1', fields[1]['name']) def test_custom_field_label(self): self.env.config.set('ticket-custom', '_test_one', 'text') self.env.config.set('ticket-custom', 'test_two', 'text') self.env.config.set('ticket-custom', 'test_two.label', 'test_2') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual('Test one', fields[0]['label']) self.assertEqual('test_2', fields[1]['label']) def test_available_actions_full_perms(self): self.perm.grant_permission('anonymous', 'TICKET_CREATE') self.perm.grant_permission('anonymous', 'TICKET_MODIFY') self.req.perm = PermissionCache(self.env) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'new'})) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave', 'reopen'], self._get_actions({'status': 'closed'})) def test_available_actions_no_perms(self): self.req.perm = PermissionCache(self.env) self.assertEqual(['leave'], self._get_actions({'status': 'new'})) self.assertEqual(['leave'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave'], self._get_actions({'status': 'closed'})) def test_available_actions_create_only(self): self.perm.grant_permission('anonymous', 'TICKET_CREATE') self.req.perm = PermissionCache(self.env) self.assertEqual(['leave'], self._get_actions({'status': 'new'})) self.assertEqual(['leave'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave', 'reopen'], self._get_actions({'status': 'closed'})) def test_available_actions_chgprop_only(self): # CHGPROP is not enough for changing a ticket's state (#3289) self.perm.grant_permission('anonymous', 'TICKET_CHGPROP') self.req.perm = PermissionCache(self.env) self.assertEqual(['leave'], self._get_actions({'status': 'new'})) self.assertEqual(['leave'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave'], self._get_actions({'status': 'closed'})) def test_get_allowed_owners_restrict_owner_false(self): self.env.config.set('ticket', 'restrict_owner', False) self.assertIsNone(self.ticket_system.get_allowed_owners()) def test_get_allowed_owners_restrict_owner_true(self): self.env.config.set('ticket', 'restrict_owner', True) self.env.insert_known_users([('user3', None, None), ('user1', None, None)]) self.perm.grant_permission('user4', 'TICKET_MODIFY') self.perm.grant_permission('user3', 'TICKET_MODIFY') self.perm.grant_permission('user2', 'TICKET_VIEW') self.perm.grant_permission('user1', 'TICKET_MODIFY') self.assertEqual(['user1', 'user3'], self.ticket_system.get_allowed_owners()) def test_get_ticket_fields_version_rename(self): """Cached ticket fields are updated when version is renamed.""" fields = self.ticket_system.get_ticket_fields() version_field = self._get_ticket_field('version') v2 = Version(self.env, '2.0') v2.name = '0.0' v2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_version_field = self._get_ticket_field('version') self.assertNotEqual(fields, updated_fields) self.assertEqual(['2.0', '1.0'], version_field['options']) self.assertEqual(['1.0', '0.0'], updated_version_field['options']) def test_get_ticket_fields_version_update_time(self): """Cached ticket fields are updated when version release time is changed. """ fields = self.ticket_system.get_ticket_fields() version_field = self._get_ticket_field('version') v1 = Version(self.env, '1.0') v1.time = datetime.now(utc) v2 = Version(self.env, '2.0') v2.time = v1.time - timedelta(seconds=1) v1.update() v2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_version_field = self._get_ticket_field('version') self.assertNotEqual(fields, updated_fields) self.assertEqual(['2.0', '1.0'], version_field['options']) self.assertEqual(['1.0', '2.0'], updated_version_field['options']) def test_get_ticket_fields_milestone_rename(self): """Cached ticket fields are updated when milestone is renamed.""" fields = self.ticket_system.get_ticket_fields() milestone_field = self._get_ticket_field('milestone') m2 = Milestone(self.env, 'milestone2') m2.name = 'milestone5' m2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_milestone_field = self._get_ticket_field('milestone') self.assertNotEqual(fields, updated_fields) self.assertEqual(['milestone1', 'milestone2', 'milestone3', 'milestone4'], milestone_field['options']) self.assertEqual(['milestone1', 'milestone3', 'milestone4', 'milestone5'], updated_milestone_field['options']) def test_get_ticket_fields_milestone_update_completed(self): """Cached ticket fields are updated when milestone is completed date is changed. """ fields = self.ticket_system.get_ticket_fields() milestone_field = self._get_ticket_field('milestone') m2 = Milestone(self.env, 'milestone2') m2.completed = datetime.now(utc) m2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_milestone_field = self._get_ticket_field('milestone') self.assertNotEqual(fields, updated_fields) self.assertEqual(['milestone1', 'milestone2', 'milestone3', 'milestone4'], milestone_field['options']) self.assertEqual(['milestone2', 'milestone1', 'milestone3', 'milestone4'], updated_milestone_field['options']) def test_get_ticket_fields_milestone_update_due(self): """Cached ticket fields are updated when milestone due date is changed. """ fields = self.ticket_system.get_ticket_fields() milestone_field = self._get_ticket_field('milestone') m2 = Milestone(self.env, 'milestone2') m2.due = datetime.now(utc) m2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_milestone_field = self._get_ticket_field('milestone') self.assertNotEqual(fields, updated_fields) self.assertEqual(['milestone1', 'milestone2', 'milestone3', 'milestone4'], milestone_field['options']) self.assertEqual(['milestone2', 'milestone1', 'milestone3', 'milestone4'], updated_milestone_field['options']) def test_resource_exists_valid_resource_id(self): Ticket(self.env).insert() r1 = Resource('ticket', 1) r2 = Resource('ticket', 2) self.assertTrue(self.ticket_system.resource_exists(r1)) self.assertFalse(self.ticket_system.resource_exists(r2)) def test_resource_exists_invalid_resource_id(self): """Exception is trapped from resource with invalid id.""" r1 = Resource('ticket', None) r2 = Resource('ticket', 'abc') r3 = Resource('ticket', '2.') r4 = Resource('ticket', r2) self.assertFalse(self.ticket_system.resource_exists(r1)) self.assertFalse(self.ticket_system.resource_exists(r2)) self.assertFalse(self.ticket_system.resource_exists(r3)) self.assertFalse(self.ticket_system.resource_exists(r4))
class TicketSystemTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub() self.perm = PermissionSystem(self.env) self.ticket_system = TicketSystem(self.env) self.req = Mock() def tearDown(self): self.env.reset_db() def _get_actions(self, ticket_dict): ts = TicketSystem(self.env) ticket = Ticket(self.env) ticket.populate(ticket_dict) id = ticket.insert() return ts.get_available_actions(self.req, Ticket(self.env, id)) def test_custom_field_text(self): self.env.config.set("ticket-custom", "test", "text") self.env.config.set("ticket-custom", "test.label", "Test") self.env.config.set("ticket-custom", "test.value", "Foo bar") self.env.config.set("ticket-custom", "test.format", "wiki") fields = TicketSystem(self.env).get_custom_fields() self.assertEqual( { "name": "test", "type": "text", "label": "Test", "value": "Foo bar", "order": 0, "format": "wiki", "custom": True, }, fields[0], ) def test_custom_field_select(self): self.env.config.set("ticket-custom", "test", "select") self.env.config.set("ticket-custom", "test.label", "Test") self.env.config.set("ticket-custom", "test.value", "1") self.env.config.set("ticket-custom", "test.options", "option1|option2") fields = TicketSystem(self.env).get_custom_fields() self.assertEqual( { "name": "test", "type": "select", "label": "Test", "value": "1", "options": ["option1", "option2"], "order": 0, "custom": True, }, fields[0], ) def test_custom_field_optional_select(self): self.env.config.set("ticket-custom", "test", "select") self.env.config.set("ticket-custom", "test.label", "Test") self.env.config.set("ticket-custom", "test.value", "1") self.env.config.set("ticket-custom", "test.options", "|option1|option2") fields = TicketSystem(self.env).get_custom_fields() self.assertEqual( { "name": "test", "type": "select", "label": "Test", "value": "1", "options": ["option1", "option2"], "order": 0, "optional": True, "custom": True, }, fields[0], ) def test_custom_field_textarea(self): self.env.config.set("ticket-custom", "test", "textarea") self.env.config.set("ticket-custom", "test.label", "Test") self.env.config.set("ticket-custom", "test.value", "Foo bar") self.env.config.set("ticket-custom", "test.cols", "60") self.env.config.set("ticket-custom", "test.rows", "4") self.env.config.set("ticket-custom", "test.format", "wiki") fields = TicketSystem(self.env).get_custom_fields() self.assertEqual( { "name": "test", "type": "textarea", "label": "Test", "value": "Foo bar", "width": 60, "height": 4, "order": 0, "format": "wiki", "custom": True, }, fields[0], ) def test_custom_field_time(self): self.env.config.set("ticket-custom", "test", "time") self.env.config.set("ticket-custom", "test.label", "Test") self.env.config.set("ticket-custom", "test.value", "") fields = TicketSystem(self.env).get_custom_fields() self.assertEqual( { "name": "test", "type": "time", "label": "Test", "value": "", "order": 0, "format": "datetime", "custom": True, }, fields[0], ) def test_custom_field_order(self): self.env.config.set("ticket-custom", "test1", "text") self.env.config.set("ticket-custom", "test1.order", "2") self.env.config.set("ticket-custom", "test2", "text") self.env.config.set("ticket-custom", "test2.order", "1") fields = TicketSystem(self.env).get_custom_fields() self.assertEqual("test2", fields[0]["name"]) self.assertEqual("test1", fields[1]["name"]) def test_available_actions_full_perms(self): self.perm.grant_permission("anonymous", "TICKET_CREATE") self.perm.grant_permission("anonymous", "TICKET_MODIFY") self.req.perm = PermissionCache(self.env) self.assertEqual(["leave", "resolve", "reassign", "accept"], self._get_actions({"status": "new"})) self.assertEqual(["leave", "resolve", "reassign", "accept"], self._get_actions({"status": "assigned"})) self.assertEqual(["leave", "resolve", "reassign", "accept"], self._get_actions({"status": "accepted"})) self.assertEqual(["leave", "resolve", "reassign", "accept"], self._get_actions({"status": "reopened"})) self.assertEqual(["leave", "reopen"], self._get_actions({"status": "closed"})) def test_available_actions_no_perms(self): self.req.perm = PermissionCache(self.env) self.assertEqual(["leave"], self._get_actions({"status": "new"})) self.assertEqual(["leave"], self._get_actions({"status": "assigned"})) self.assertEqual(["leave"], self._get_actions({"status": "accepted"})) self.assertEqual(["leave"], self._get_actions({"status": "reopened"})) self.assertEqual(["leave"], self._get_actions({"status": "closed"})) def test_available_actions_create_only(self): self.perm.grant_permission("anonymous", "TICKET_CREATE") self.req.perm = PermissionCache(self.env) self.assertEqual(["leave"], self._get_actions({"status": "new"})) self.assertEqual(["leave"], self._get_actions({"status": "assigned"})) self.assertEqual(["leave"], self._get_actions({"status": "accepted"})) self.assertEqual(["leave"], self._get_actions({"status": "reopened"})) self.assertEqual(["leave", "reopen"], self._get_actions({"status": "closed"})) def test_available_actions_chgprop_only(self): # CHGPROP is not enough for changing a ticket's state (#3289) self.perm.grant_permission("anonymous", "TICKET_CHGPROP") self.req.perm = PermissionCache(self.env) self.assertEqual(["leave"], self._get_actions({"status": "new"})) self.assertEqual(["leave"], self._get_actions({"status": "assigned"})) self.assertEqual(["leave"], self._get_actions({"status": "accepted"})) self.assertEqual(["leave"], self._get_actions({"status": "reopened"})) self.assertEqual(["leave"], self._get_actions({"status": "closed"}))
def setUp(self): self.env = EnvironmentStub() ps = PermissionSystem(self.env) ps.grant_permission('admin', 'TICKET_ADMIN') self.plugin = CustomFieldAdminPage(self.env)
def render_admin_panel(self, req, cat, page, path_info): perm = PermissionSystem(self.env) all_permissions = perm.get_all_permissions() all_actions = perm.get_actions() if req.method == 'POST': subject = req.args.get('subject', '').strip() action = req.args.get('action') group = req.args.get('group', '').strip() if subject and subject.isupper() or \ group and group.isupper(): raise TracError(_('All upper-cased tokens are reserved for ' 'permission names')) # Grant permission to subject if req.args.get('add') and subject and action: req.perm.require('PERMISSION_GRANT') if action not in all_actions: raise TracError(_('Unknown action')) req.perm.require(action) if (subject, action) not in all_permissions: perm.grant_permission(subject, action) add_notice(req, _('The subject %(subject)s has been ' 'granted the permission %(action)s.', subject=subject, action=action)) req.redirect(req.href.admin(cat, page)) else: add_warning(req, _('The permission %(action)s was already ' 'granted to %(subject)s.', action=action, subject=subject)) # Add subject to group elif req.args.get('add') and subject and group: req.perm.require('PERMISSION_GRANT') for action in perm.get_user_permissions(group): if not action in all_actions: # plugin disabled? self.env.log.warn("Adding %s to group %s: " \ "Permission %s unavailable, skipping perm check." \ % (subject, group, action)) else: req.perm.require(action) if (subject, group) not in all_permissions: perm.grant_permission(subject, group) add_notice(req, _('The subject %(subject)s has been added ' 'to the group %(group)s.', subject=subject, group=group)) req.redirect(req.href.admin(cat, page)) else: add_warning(req, _('The subject %(subject)s was already ' 'added to the group %(group)s.', subject=subject, group=group)) # Remove permissions action elif req.args.get('remove') and req.args.get('sel'): req.perm.require('PERMISSION_REVOKE') sel = req.args.get('sel') sel = sel if isinstance(sel, list) else [sel] for key in sel: subject, action = key.split(':', 1) subject = unicode_from_base64(subject) action = unicode_from_base64(action) if (subject, action) in perm.get_all_permissions(): perm.revoke_permission(subject, action) add_notice(req, _('The selected permissions have been ' 'revoked.')) req.redirect(req.href.admin(cat, page)) perms = [perm for perm in all_permissions if perm[1].isupper()] groups = [perm for perm in all_permissions if not perm[1].isupper()] return 'admin_perms.html', { 'actions': all_actions, 'perms': perms, 'groups': groups, 'unicode_to_base64': unicode_to_base64 }
def render_admin_panel(self, req, cat, page, path_info): perm = PermissionSystem(self.env) all_permissions = perm.get_all_permissions() all_actions = perm.get_actions() if req.method == 'POST': subject = req.args.get('subject', '').strip() action = req.args.get('action') group = req.args.get('group', '').strip() if subject and subject.isupper() or \ group and group.isupper(): raise TracError(_('All upper-cased tokens are reserved for ' 'permission names')) # Grant permission to subject if req.args.get('add') and subject and action: req.perm.require('PERMISSION_GRANT') if action not in all_actions: raise TracError(_('Unknown action')) req.perm.require(action) if (subject, action) not in all_permissions: perm.grant_permission(subject, action) add_notice(req, _('The subject %(subject)s has been ' 'granted the permission %(action)s.', subject=subject, action=action)) req.redirect(req.href.admin(cat, page)) else: add_warning(req, _('The permission %(action)s was already ' 'granted to %(subject)s.', action=action, subject=subject)) # Add subject to group elif req.args.get('add') and subject and group: req.perm.require('PERMISSION_GRANT') for action in perm.get_user_permissions(group): if not action in all_actions: # plugin disabled? self.env.log.warn("Adding %s to group %s: " \ "Permission %s unavailable, skipping perm check." \ % (subject, group, action)) else: req.perm.require(action) if (subject, group) not in all_permissions: perm.grant_permission(subject, group) add_notice(req, _('The subject %(subject)s has been added ' 'to the group %(group)s.', subject=subject, group=group)) req.redirect(req.href.admin(cat, page)) else: add_warning(req, _('The subject %(subject)s was already ' 'added to the group %(group)s.', subject=subject, group=group)) # Remove permissions action elif req.args.get('remove') and req.args.get('sel'): req.perm.require('PERMISSION_REVOKE') sel = req.args.get('sel') sel = isinstance(sel, list) and sel or [sel] for key in sel: subject, action = key.split(':', 1) if (subject, action) in perm.get_all_permissions(): perm.revoke_permission(subject, action) add_notice(req, _('The selected permissions have been ' 'revoked.')) req.redirect(req.href.admin(cat, page)) return 'admin_perms.html', { 'actions': all_actions, 'perms': all_permissions }
class TicketSystemTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(default_data=True) self.perm = PermissionSystem(self.env) self.ticket_system = TicketSystem(self.env) self.req = MockRequest(self.env) def tearDown(self): self.env.reset_db() def _get_actions(self, ticket_dict): ts = TicketSystem(self.env) ticket = insert_ticket(self.env, **ticket_dict) return ts.get_available_actions(self.req, Ticket(self.env, ticket.id)) def _get_ticket_field(self, field_name): fields = TicketSystem(self.env).get_ticket_fields() return next((i for i in fields if i['name'] == field_name)) def test_custom_field_text(self): self.env.config.set('ticket-custom', 'test', 'text') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', 'Foo bar') self.env.config.set('ticket-custom', 'test.format', 'wiki') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual( { 'name': 'test', 'type': 'text', 'label': 'Test', 'value': 'Foo bar', 'max_size': 0, 'order': 0, 'format': 'wiki', 'custom': True }, fields[0]) def test_custom_field_select(self): self.env.config.set('ticket-custom', 'test', 'select') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', '1') self.env.config.set('ticket-custom', 'test.options', 'option1|option2') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual( { 'name': 'test', 'type': 'select', 'label': 'Test', 'value': '1', 'options': ['option1', 'option2'], 'order': 0, 'custom': True }, fields[0]) def test_custom_field_optional_select(self): self.env.config.set('ticket-custom', 'test', 'select') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', '1') self.env.config.set('ticket-custom', 'test.options', '|option1|option2') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual( { 'name': 'test', 'type': 'select', 'label': 'Test', 'value': '1', 'options': ['option1', 'option2'], 'order': 0, 'optional': True, 'custom': True }, fields[0]) def test_custom_field_textarea(self): self.env.config.set('ticket-custom', 'test', 'textarea') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', 'Foo bar') self.env.config.set('ticket-custom', 'test.rows', '4') self.env.config.set('ticket-custom', 'test.format', 'wiki') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual( { 'name': 'test', 'type': 'textarea', 'label': 'Test', 'value': 'Foo bar', 'height': 4, 'order': 0, 'max_size': 0, 'format': 'wiki', 'custom': True }, fields[0]) def test_description_field(self): field = self._get_ticket_field('description') self.assertEqual( { 'name': 'description', 'label': 'Description', 'type': 'textarea', 'format': 'wiki' }, field) def test_custom_field_checkbox(self): def add_checkbox(name, value): self.env.config.set('ticket-custom', name, 'checkbox') self.env.config.set('ticket-custom', '%s.value' % name, value) add_checkbox('checkbox0', 'true') add_checkbox('checkbox1', 1) add_checkbox('checkbox2', 'enabled') add_checkbox('checkbox3', 0) add_checkbox('checkbox4', 'tru') add_checkbox('checkbox5', 'off') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual( { 'name': 'checkbox0', 'type': 'checkbox', 'label': 'Checkbox0', 'value': '1', 'order': 0, 'custom': True }, fields[0]) self.assertEqual('1', fields[1]['value']) self.assertEqual('1', fields[2]['value']) self.assertEqual('0', fields[3]['value']) self.assertEqual('0', fields[4]['value']) self.assertEqual('0', fields[5]['value']) def test_custom_field_time(self): self.env.config.set('ticket-custom', 'test', 'time') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', '') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual( { 'name': 'test', 'type': 'time', 'label': 'Test', 'value': '', 'order': 0, 'format': 'datetime', 'custom': True }, fields[0]) def test_custom_field_with_invalid_name(self): ticket_custom = self.env.config['ticket-custom'] ticket_custom.set('_field1', 'text') ticket_custom.set('2field', 'text') ticket_custom.set('f3%^&*', 'text') ticket_custom.set('field4', 'text') ticket_custom.set('FiEld5', 'text') ts = TicketSystem(self.env) custom_fields = ts.custom_fields fields = ts.fields self.assertEqual(2, len(custom_fields)) self.assertIsNotNone(custom_fields.by_name('field4')) self.assertIsNotNone(custom_fields.by_name('field5')) self.assertIsNotNone(fields.by_name('field4')) self.assertIsNotNone(fields.by_name('field5')) self.assertIn(('WARNING', u'Invalid name for custom field: "_field1" (ignoring)'), self.env.log_messages) self.assertIn( ('WARNING', u'Invalid name for custom field: "2field" (ignoring)'), self.env.log_messages) self.assertIn( ('WARNING', u'Invalid name for custom field: "f3%^&*" (ignoring)'), self.env.log_messages) def test_custom_field_with_reserved_name(self): ticket_custom = self.env.config['ticket-custom'] ticket_custom.set('owner', 'select') ticket_custom.set('description', 'text') ts = TicketSystem(self.env) custom_fields = ts.custom_fields self.assertIn( ('WARNING', u'Field name "owner" is a reserved name (ignoring)'), self.env.log_messages) self.assertIn( ('WARNING', u'Field name "description" is a reserved name (ignoring)'), self.env.log_messages) self.assertEqual({ 'name': 'owner', 'label': 'Owner', 'type': 'text' }, ts.fields.by_name('owner')) self.assertEqual( { 'name': 'description', 'label': 'Description', 'type': 'textarea', 'format': 'wiki' }, ts.fields.by_name('description')) self.assertIsNone(custom_fields.by_name('owner')) self.assertIsNone(custom_fields.by_name('description')) def test_custom_field_order(self): self.env.config.set('ticket-custom', 'test1', 'text') self.env.config.set('ticket-custom', 'test1.order', '2') self.env.config.set('ticket-custom', 'test2', 'text') self.env.config.set('ticket-custom', 'test2.order', '1') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual('test2', fields[0]['name']) self.assertEqual('test1', fields[1]['name']) def test_custom_field_label(self): self.env.config.set('ticket-custom', 'test_one', 'text') self.env.config.set('ticket-custom', 'test_two', 'text') self.env.config.set('ticket-custom', 'test_two.label', 'test_2') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual('Test one', fields[0]['label']) self.assertEqual('test_2', fields[1]['label']) def _test_custom_field_with_enum(self, name, cls): tktsys = TicketSystem(self.env) instance = cls(self.env) instance.name = '%s 42' % name instance.insert() self.env.config.set('ticket-custom', name, 'text') field = self._get_ticket_field(name) self.assertFalse(field.get('custom')) with self.env.db_transaction: instances = list(cls.select(self.env)) if issubclass(cls, model.AbstractEnum): # delete from highest to lowest to avoid re-ordering enums instances.sort(reverse=True, key=lambda v: int(v.value)) for instance in instances: instance.delete() field = self._get_ticket_field(name) self.assertTrue(field.get('custom')) def test_custom_field_type(self): self._test_custom_field_with_enum('type', model.Type) def test_custom_field_priority(self): self._test_custom_field_with_enum('priority', model.Priority) def test_custom_field_milestone(self): self._test_custom_field_with_enum('milestone', Milestone) def test_custom_field_component(self): self._test_custom_field_with_enum('component', model.Component) def test_custom_field_version(self): self._test_custom_field_with_enum('version', Version) def test_custom_field_severity(self): self._test_custom_field_with_enum('severity', model.Severity) def test_custom_field_resolution(self): self._test_custom_field_with_enum('resolution', model.Resolution) def test_available_actions_full_perms(self): self.perm.grant_permission('anonymous', 'TICKET_CREATE') self.perm.grant_permission('anonymous', 'TICKET_MODIFY') self.req.perm = PermissionCache(self.env) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'new'})) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave', 'reopen'], self._get_actions({'status': 'closed'})) def test_available_actions_no_perms(self): self.req.perm = PermissionCache(self.env) self.assertEqual(['leave'], self._get_actions({'status': 'new'})) self.assertEqual(['leave'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave'], self._get_actions({'status': 'closed'})) def test_available_actions_create_only(self): self.perm.grant_permission('anonymous', 'TICKET_CREATE') self.req.perm = PermissionCache(self.env) self.assertEqual(['leave'], self._get_actions({'status': 'new'})) self.assertEqual(['leave'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave', 'reopen'], self._get_actions({'status': 'closed'})) def test_available_actions_chgprop_only(self): # CHGPROP is not enough for changing a ticket's state (#3289) self.perm.grant_permission('anonymous', 'TICKET_CHGPROP') self.req.perm = PermissionCache(self.env) self.assertEqual(['leave'], self._get_actions({'status': 'new'})) self.assertEqual(['leave'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave'], self._get_actions({'status': 'closed'})) def test_get_allowed_owners_restrict_owner_false(self): self.env.config.set('ticket', 'restrict_owner', False) self.assertIsNone(self.ticket_system.get_allowed_owners()) def test_get_allowed_owners_restrict_owner_true(self): self.env.config.set('ticket', 'restrict_owner', True) self.env.insert_users([('user3', None, None), ('user1', None, None)]) self.perm.grant_permission('user4', 'TICKET_MODIFY') self.perm.grant_permission('user3', 'TICKET_MODIFY') self.perm.grant_permission('user2', 'TICKET_VIEW') self.perm.grant_permission('user1', 'TICKET_MODIFY') self.assertEqual(['user1', 'user3'], self.ticket_system.get_allowed_owners()) def test_get_ticket_fields_version_rename(self): """Cached ticket fields are updated when version is renamed.""" fields = self.ticket_system.get_ticket_fields() version_field = self._get_ticket_field('version') v2 = Version(self.env, '2.0') v2.name = '0.0' v2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_version_field = self._get_ticket_field('version') self.assertNotEqual(fields, updated_fields) self.assertEqual(['2.0', '1.0'], version_field['options']) self.assertEqual(['1.0', '0.0'], updated_version_field['options']) def test_get_ticket_fields_version_update_time(self): """Cached ticket fields are updated when version release time is changed. """ fields = self.ticket_system.get_ticket_fields() version_field = self._get_ticket_field('version') v1 = Version(self.env, '1.0') v1.time = datetime_now(utc) v2 = Version(self.env, '2.0') v2.time = v1.time - timedelta(seconds=1) v1.update() v2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_version_field = self._get_ticket_field('version') self.assertNotEqual(fields, updated_fields) self.assertEqual(['2.0', '1.0'], version_field['options']) self.assertEqual(['1.0', '2.0'], updated_version_field['options']) def test_get_ticket_fields_milestone_rename(self): """Cached ticket fields are updated when milestone is renamed.""" fields = self.ticket_system.get_ticket_fields() milestone_field = self._get_ticket_field('milestone') m2 = Milestone(self.env, 'milestone2') m2.name = 'milestone5' m2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_milestone_field = self._get_ticket_field('milestone') self.assertNotEqual(fields, updated_fields) self.assertEqual( ['milestone1', 'milestone2', 'milestone3', 'milestone4'], milestone_field['options']) self.assertEqual( ['milestone1', 'milestone3', 'milestone4', 'milestone5'], updated_milestone_field['options']) def test_get_ticket_fields_milestone_update_completed(self): """Cached ticket fields are updated when milestone is completed date is changed. """ fields = self.ticket_system.get_ticket_fields() milestone_field = self._get_ticket_field('milestone') m2 = Milestone(self.env, 'milestone2') m2.completed = datetime_now(utc) m2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_milestone_field = self._get_ticket_field('milestone') self.assertNotEqual(fields, updated_fields) self.assertEqual( ['milestone1', 'milestone2', 'milestone3', 'milestone4'], milestone_field['options']) self.assertEqual( ['milestone2', 'milestone1', 'milestone3', 'milestone4'], updated_milestone_field['options']) def test_get_ticket_fields_milestone_update_due(self): """Cached ticket fields are updated when milestone due date is changed. """ fields = self.ticket_system.get_ticket_fields() milestone_field = self._get_ticket_field('milestone') m2 = Milestone(self.env, 'milestone2') m2.due = datetime_now(utc) m2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_milestone_field = self._get_ticket_field('milestone') self.assertNotEqual(fields, updated_fields) self.assertEqual( ['milestone1', 'milestone2', 'milestone3', 'milestone4'], milestone_field['options']) self.assertEqual( ['milestone2', 'milestone1', 'milestone3', 'milestone4'], updated_milestone_field['options']) def test_resource_exists_valid_resource_id(self): insert_ticket(self.env) r1 = Resource('ticket', 1) r2 = Resource('ticket', 2) self.assertTrue(self.ticket_system.resource_exists(r1)) self.assertFalse(self.ticket_system.resource_exists(r2)) def test_resource_exists_invalid_resource_id(self): """Exception is trapped from resource with invalid id.""" r1 = Resource('ticket', None) r2 = Resource('ticket', 'abc') r3 = Resource('ticket', '2.') r4 = Resource('ticket', r2) self.assertFalse(self.ticket_system.resource_exists(r1)) self.assertFalse(self.ticket_system.resource_exists(r2)) self.assertFalse(self.ticket_system.resource_exists(r3)) self.assertFalse(self.ticket_system.resource_exists(r4))
def render_admin_panel(self, req, cat, page, path_info): perm = PermissionSystem(self.env) all_actions = perm.get_actions() if req.method == 'POST': subject = req.args.get('subject', '').strip() target = req.args.get('target', '').strip() action = req.args.get('action') group = req.args.get('group', '').strip() if subject and subject.isupper() or \ group and group.isupper() or \ target and target.isupper(): raise TracError(_("All upper-cased tokens are reserved for " "permission names.")) # Grant permission to subject if 'add' in req.args and subject and action: req.perm('admin', 'general/perm').require('PERMISSION_GRANT') if action not in all_actions: raise TracError(_("Unknown action")) req.perm.require(action) try: perm.grant_permission(subject, action) except TracError as e: add_warning(req, e) else: add_notice(req, _("The subject %(subject)s has been " "granted the permission %(action)s.", subject=subject, action=action)) # Add subject to group elif 'add' in req.args and subject and group: req.perm('admin', 'general/perm').require('PERMISSION_GRANT') for action in sorted( perm.get_user_permissions(group, expand_meta=False)): req.perm.require(action, message=tag_( "The subject %(subject)s was not added to the " "group %(group)s. The group has %(perm)s " "permission and you cannot grant permissions you " "don't possess.", subject=tag.strong(subject), group=tag.strong(group), perm=tag.strong(action))) try: perm.grant_permission(subject, group) except TracError as e: add_warning(req, e) else: add_notice(req, _("The subject %(subject)s has been " "added to the group %(group)s.", subject=subject, group=group)) # Copy permissions to subject elif 'copy' in req.args and subject and target: req.perm('admin', 'general/perm').require('PERMISSION_GRANT') subject_permissions = perm.get_users_dict().get(subject, []) if not subject_permissions: add_warning(req, _("The subject %(subject)s does not " "have any permissions.", subject=subject)) for action in subject_permissions: if action not in all_actions: # plugin disabled? self.log.warning("Skipped granting %s to %s: " "permission unavailable.", action, target) else: if action not in req.perm: add_warning(req, _("The permission %(action)s was " "not granted to %(subject)s " "because users cannot grant " "permissions they don't possess.", action=action, subject=subject)) continue try: perm.grant_permission(target, action) except PermissionExistsError: pass else: add_notice(req, _("The subject %(subject)s has " "been granted the permission " "%(action)s.", subject=target, action=action)) req.redirect(req.href.admin(cat, page)) # Remove permissions action elif 'remove' in req.args and 'sel' in req.args: req.perm('admin', 'general/perm').require('PERMISSION_REVOKE') for key in req.args.getlist('sel'): subject, action = key.split(':', 1) subject = unicode_from_base64(subject) action = unicode_from_base64(action) if (subject, action) in perm.get_all_permissions(): perm.revoke_permission(subject, action) add_notice(req, _("The selected permissions have been " "revoked.")) req.redirect(req.href.admin(cat, page)) return 'admin_perms.html', { 'actions': all_actions, 'allowed_actions': [a for a in all_actions if a in req.perm], 'perms': perm.get_users_dict(), 'groups': perm.get_groups_dict(), 'unicode_to_base64': unicode_to_base64 }
class RestrictOwnerTestCase(unittest.TestCase): def setUp(self): tmpdir = os.path.realpath(tempfile.gettempdir()) self.env = EnvironmentStub(enable=['trac.*', AuthzPolicy], path=tmpdir) self.env.config.set('trac', 'permission_policies', 'AuthzPolicy, DefaultPermissionPolicy') self.env.config.set('ticket', 'restrict_owner', True) self.perm_sys = PermissionSystem(self.env) self.env.insert_known_users([ ('user1', '', ''), ('user2', '', ''), ('user3', '', ''), ('user4', '', '') ]) self.perm_sys.grant_permission('user1', 'TICKET_MODIFY') self.perm_sys.grant_permission('user2', 'TICKET_VIEW') self.perm_sys.grant_permission('user3', 'TICKET_MODIFY') self.perm_sys.grant_permission('user4', 'TICKET_MODIFY') self.authz_file = os.path.join(tmpdir, 'trac-authz-policy') create_file(self.authz_file) self.env.config.set('authz_policy', 'authz_file', self.authz_file) self.ctlr = TicketSystem(self.env).action_controllers[0] self.req1 = Mock(authname='user1', args={}, perm=PermissionCache(self.env, 'user1')) self.ticket = Ticket(self.env) self.ticket['status'] = 'new' self.ticket.insert() def tearDown(self): self.env.reset_db() os.remove(self.authz_file) def _reload_workflow(self): self.ctlr.actions = self.ctlr.get_all_actions() def test_set_owner(self): """Restricted owners list contains users with TICKET_MODIFY. """ ctrl = self.ctlr.render_ticket_action_control(self.req1, self.ticket, 'reassign') self.assertEqual('reassign', ctrl[0]) self.assertIn('value="user1">user1</option>', str(ctrl[1])) self.assertNotIn('value="user2">user2</option>', str(ctrl[1])) self.assertIn('value="user3">user3</option>', str(ctrl[1])) self.assertIn('value="user4">user4</option>', str(ctrl[1])) def test_set_owner_fine_grained_permissions(self): """Fine-grained permission checks when populating the restricted owners list (#10833). """ create_file(self.authz_file, """\ [ticket:1] user4 = !TICKET_MODIFY """) ctrl = self.ctlr.render_ticket_action_control(self.req1, self.ticket, 'reassign') self.assertEqual('reassign', ctrl[0]) self.assertIn('value="user1">user1</option>', str(ctrl[1])) self.assertNotIn('value="user2">user2</option>', str(ctrl[1])) self.assertIn('value="user3">user3</option>', str(ctrl[1])) self.assertNotIn('value="user4">user4</option>', str(ctrl[1]))
class TicketSystemTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub() self.perm = PermissionSystem(self.env) self.ticket_system = TicketSystem(self.env) self.req = Mock() def tearDown(self): self.env.reset_db() def _get_actions(self, ticket_dict): ts = TicketSystem(self.env) ticket = Ticket(self.env) ticket.populate(ticket_dict) id = ticket.insert() return ts.get_available_actions(self.req, Ticket(self.env, id)) def test_custom_field_text(self): self.env.config.set('ticket-custom', 'test', 'text') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', 'Foo bar') self.env.config.set('ticket-custom', 'test.format', 'wiki') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual( { 'name': 'test', 'type': 'text', 'label': 'Test', 'value': 'Foo bar', 'order': 0, 'format': 'wiki' }, fields[0]) def test_custom_field_select(self): self.env.config.set('ticket-custom', 'test', 'select') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', '1') self.env.config.set('ticket-custom', 'test.options', 'option1|option2') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual( { 'name': 'test', 'type': 'select', 'label': 'Test', 'value': '1', 'options': ['option1', 'option2'], 'order': 0 }, fields[0]) def test_custom_field_optional_select(self): self.env.config.set('ticket-custom', 'test', 'select') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', '1') self.env.config.set('ticket-custom', 'test.options', '|option1|option2') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual( { 'name': 'test', 'type': 'select', 'label': 'Test', 'value': '1', 'options': ['option1', 'option2'], 'order': 0, 'optional': True }, fields[0]) def test_custom_field_textarea(self): self.env.config.set('ticket-custom', 'test', 'textarea') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', 'Foo bar') self.env.config.set('ticket-custom', 'test.cols', '60') self.env.config.set('ticket-custom', 'test.rows', '4') self.env.config.set('ticket-custom', 'test.format', 'wiki') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual( { 'name': 'test', 'type': 'textarea', 'label': 'Test', 'value': 'Foo bar', 'width': 60, 'height': 4, 'order': 0, 'format': 'wiki' }, fields[0]) def test_custom_field_order(self): self.env.config.set('ticket-custom', 'test1', 'text') self.env.config.set('ticket-custom', 'test1.order', '2') self.env.config.set('ticket-custom', 'test2', 'text') self.env.config.set('ticket-custom', 'test2.order', '1') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual('test2', fields[0]['name']) self.assertEqual('test1', fields[1]['name']) def test_available_actions_full_perms(self): self.perm.grant_permission('anonymous', 'TICKET_CREATE') self.perm.grant_permission('anonymous', 'TICKET_MODIFY') self.req.perm = PermissionCache(self.env) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'new'})) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave', 'reopen'], self._get_actions({'status': 'closed'})) def test_available_actions_no_perms(self): self.req.perm = PermissionCache(self.env) self.assertEqual(['leave'], self._get_actions({'status': 'new'})) self.assertEqual(['leave'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave'], self._get_actions({'status': 'closed'})) def test_available_actions_create_only(self): self.perm.grant_permission('anonymous', 'TICKET_CREATE') self.req.perm = PermissionCache(self.env) self.assertEqual(['leave'], self._get_actions({'status': 'new'})) self.assertEqual(['leave'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave', 'reopen'], self._get_actions({'status': 'closed'})) def test_available_actions_chgprop_only(self): # CHGPROP is not enough for changing a ticket's state (#3289) self.perm.grant_permission('anonymous', 'TICKET_CHGPROP') self.req.perm = PermissionCache(self.env) self.assertEqual(['leave'], self._get_actions({'status': 'new'})) self.assertEqual(['leave'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave'], self._get_actions({'status': 'closed'})) def test_can_add_raw_fields_from_field_providers(self): testFieldProvider = self.env[TestFieldProvider] self.assertIsNotNone(testFieldProvider) testFieldProvider.raw_fields = [ { 'name': "test_name", 'type': 'some_type', 'label': "some_label", }, ] fields = TicketSystem(self.env).get_ticket_fields() row_added_fields = [ field for field in fields if field["name"] == "test_name" ] self.assertEqual(1, len(row_added_fields)) def test_does_not_add_duplicated_raw_fields_from_field_providers(self): testFieldProvider = self.env[TestFieldProvider] self.assertIsNotNone(testFieldProvider) testFieldProvider.raw_fields = [ { 'name': "test_name", 'type': 'some_type1', 'label': "some_label1", }, { 'name': "test_name", 'type': 'some_type2', 'label': "some_label2", }, ] fields = TicketSystem(self.env).get_ticket_fields() row_added_fields = [ field for field in fields if field["name"] == "test_name" ] self.assertEqual(1, len(row_added_fields))