def filter_events(self, events, user, project): """ Filter event list based on user's permissions """ filtered_events = [] policy = CQDEPermissionPolicy(self.env) permission_map = { 'newticket': 'TICKET_VIEW', 'closedticket': 'TICKET_VIEW', 'reopenedticket': 'TICKET_VIEW', 'changeset': 'CHANGESET_VIEW', 'wiki': 'WIKI_VIEW', 'attachment': 'ATTACHMENT_VIEW', 'newmessage': 'DISCUSSION_VIEW', 'newtopic': 'DISCUSSION_VIEW', 'newforum': 'DISCUSSION_VIEW', 'milestone': 'MILESTONE_VIEW' } for event in events: event_type = event[1]['kind'] perm = permission_map.get(event_type) if perm and policy.check_permission(project.trac_environment_key, perm, user.username): filtered_events.append(event) return filtered_events
def __init__(self, verbose=False): """ If verbose = None, be absolutely quiet """ self.verbose = verbose self.policy = CQDEPermissionPolicy(MockEnvironment()) self.papi = Projects() self.batch_size = conf.visibility_db_batch_size self.required_permission = 'PROJECT_VIEW'
def process_request(self, req): """ Render welcome page """ # Cast into bool directly, since match object properties are not needed viewing_user_profile = bool(RE_HOME_USER.match(req.path_info)) userstore = get_userstore() if (req.authname == 'anonymous' and not self.env.config.getbool('multiproject', 'allow_public_projects')): conf.redirect(req, req.href('/user')) if viewing_user_profile: username = req.path_info.rsplit("/")[-1] else: username = req.authname user = userstore.getUser(username) if not user: raise TracError("User not found.") if req.authname == 'anonymous' and not viewing_user_profile: conf.redirect(req, conf.url_home_path + '/user') if user.username == 'anonymous' or user.username == 'authenticated': raise TracError("User not found.") # Possible values sort_cols = {'DATE': 5, 'PRIORITY': 6, 'PROJECT': 7} desc_asc = {'DESC': True, 'ASC': False} sort_tasks_by = req.args.get('sort_tasks_by','DATE') if sort_tasks_by not in sort_cols: sort_tasks_by = 'DATE' sort_col = sort_cols[sort_tasks_by] sort_options = {'DESC':'DESC', 'ASC':'ASC'} sort_tasks_order = sort_options.get(req.args.get('sort_tasks_order'), 'ASC') data = {} data['username'] = user.getDisplayName() if viewing_user_profile: if username != req.authname: # TODO: i18n support data['usernames'] = username + "'s" else: data['usernames'] = "Your" else: data['username'] = "******".format(user.givenName, user.lastName) data['usernames'] = "My" data['baseurl'] = conf.url_projects_path data['userpage'] = viewing_user_profile data['base_path'] = req.base_path # Prepare data for template prjs = Projects() default_projects, default_names = prjs.get_default_projects() data['default_projects'] = default_projects try: if viewing_user_profile: projects = prjs.get_participated_projects(user, by_ldap=False, public_only=True) else: projects = prjs.get_participated_projects(user, by_ldap=True, public_only=False) except TracError as e: projects = [] if viewing_user_profile: all_projects = projects else: all_projects = default_projects + [p for p in projects if p.env_name not in default_names] admin_projects = [] other_projects = [] if viewing_user_profile: other_projects = all_projects else: for project in all_projects: if project.is_admin(user.username): admin_projects.append(project) else: other_projects.append(project) # admin_projects is [] in public profile or if there are not public, administrated projects data['projects_where_admin'] = admin_projects data['projects'] = other_projects # Get tickets and posts # [project, row[URL], row[SUMMARY], row[DESCRIPTION], row[PRIORITY], to_datetime(row[TIME]/1000000)] policy = CQDEPermissionPolicy(self.env) ticket_projects = [] post_projects = [] if viewing_user_profile: for project in all_projects: self.log.warning('project %s' % project.env_name) if policy.check_permission(project.env_name, 'TICKET_VIEW', 'anonymous'): ticket_projects.append(project) if policy.check_permission(project.env_name, 'DISCUSSION_VIEW', 'anonymous'): post_projects.append(project) else: ticket_projects = all_projects post_projects = all_projects tasks = prjs.get_all_user_tasks(user.username, ticket_projects) do_reverse = desc_asc[sort_tasks_order] tasks = sorted(tasks, key = lambda task: task[sort_col], reverse = do_reverse) # Get posts posts = self._get_posts(user.username, post_projects) data['tasks'] = tasks (totaltickets, totalclosed) = prjs.get_all_user_task_sums(user.username, all_projects) data['user'] = user data['known_priorities'] = ['blocker', 'critical', 'major', 'minor', 'trivial'] data['sort_tasks_by'] = sort_tasks_by data['sort_tasks_order'] = sort_tasks_order data['posts'] = posts[:10] data['userpage'] = viewing_user_profile data['to_web_time'] = to_web_time # Check if user can create a project data['can_create_project'] = user.can_create_project() if viewing_user_profile: if username != req.authname: data['title'] = username + "'s profile" data['badgelinktitle'] = "View profile" else: data['title'] = "This is your public profile" data['badgelinktitle'] = "View your profile" else: data['title'] = "My projects" data['badgelinktitle'] = "View my public profile" topics_started = 0 for post in posts: if post['topic_id'] == 0: topics_started += 1 data['user'].details = {'Total tickets':totaltickets, 'Total tickets closed':totalclosed, 'Discussions started': topics_started} if not viewing_user_profile: data['watchlist'] = self._get_watchlist_events(user) return "myprojects.html", data, None
class ProjectUserVisibilityGenerator(): def __init__(self, verbose=False): """ If verbose = None, be absolutely quiet """ self.verbose = verbose self.policy = CQDEPermissionPolicy(MockEnvironment()) self.papi = Projects() self.batch_size = conf.visibility_db_batch_size self.required_permission = 'PROJECT_VIEW' def get_anonymous_user_id(self): anon = get_userstore().getUser('anonymous') if anon: anon_id = safe_int(anon.id) else: anon_id = None return anon_id def get_public_project_pairs(self, anon_id): """ :param anon_id: Anonymous user_id :return: List of tuples (project_id, trac_environment_key) """ # TODO: it's not clearly defined when project is public perms = ['PROJECT_VIEW', 'TRAC_ADMIN'] perm_ids = [get_permission_id(perm) for perm in perms] query = """SELECT DISTINCT p.project_id, p.trac_environment_key FROM projects p INNER JOIN `group` g ON g.trac_environment_key = p.trac_environment_key INNER JOIN user_group ON user_group.group_key = g.group_id INNER JOIN group_permission ON group_permission.group_key = g.group_id WHERE user_group.user_key = {0} AND group_permission.permission_key IN ({1}) """.format(anon_id, ', '.join([str(int(id)) for id in perm_ids])) id_pairs = [] with admin_query() as cursor: try: cursor.execute(query) for row in cursor: id_pair = (int(row[0]), int(row[1])) id_pairs.append(id_pair) except Exception as e: if self.verbose is not None: print "Exception. In method get_public_project_pairs, the following query failed." print query print e conf.log.exception("Exception. ProjectUserVisibilityGenerator.get_public_project_pairs " "failed with query '''%s'''." % query) return id_pairs def get_private_project_pairs(self, public_id_pairs = None): query = "SELECT project_id, trac_environment_key FROM projects " if public_id_pairs: public_project_ids = [pair[0] for pair in public_id_pairs] # TODO: What if there are really many public projects? pub_projs = ','.join(str(id) for id in public_project_ids) query += "WHERE projects.project_id NOT IN (%s)" % pub_projs id_pairs = [] with admin_query() as cursor: try: cursor.execute(query) for row in cursor: id_pair = (int(row[0]), int(row[1])) id_pairs.append(id_pair) except Exception as e: if self.verbose is not None: print "Exception. In method get_private_project_pairs, the following query failed." print query print e conf.log.exception("Exception. ProjectUserVisibilityGenerator.get_private_project_pairs failed " "with query '''%s'''." % query) return id_pairs def get_all_users(self, except_anon=True): """ Returns :py:class:`User` objects for every user except 'authenticated' and, if anon_id is given, 'anonymous'. No row for 'authenticated' users are generated currently, this is why the 'authenticated' user is left out. :param bool except_anon: when True, don't include anon user :returns: A list of :py:class:`User` objects """ if except_anon: query = """SELECT user_id, username FROM `user` WHERE username NOT IN ('anonymous', 'authenticated') """ else: query = """SELECT user_id, username FROM `user` WHERE username NOT IN ('authenticated') """ users = [] with admin_query() as cursor: try: cursor.execute(query) for row in cursor: user = User.from_sql_row(row) users.append(user) except Exception as e: if self.verbose is not None: print "Exception. In method get_all_users, the following query failed." print query print e conf.log.exception("Exception. ProjectUserVisibilityGenerator.get_all_users failed " "with query '''%s'''." % query) return users def user_can_view_project(self, trac_environment_key, username): return self.policy.check_permission(trac_environment_key, self.required_permission, username) def clear_project_visibilities(self, project_id): query = "DELETE FROM project_user_visibility WHERE project_id = %s" with admin_transaction() as cursor: try: cursor.execute(query, project_id) except Exception as e: if self.verbose is not None: print "Exception. In method clear_visibilities, the following query failed." print query print e conf.log.exception("Exception. ProjectUserVisibilityGenerator.clear_visibilities " "failed with query '''%s'''." % query) def batch_insert(self, visibilities): query = "INSERT INTO project_user_visibility (project_id, user_id) VALUES " query += ",".join(["(%d,%d)" % (safe_int(visibility.project_id), safe_int(visibility.user_id)) for visibility in visibilities]) with admin_transaction() as cursor: try: cursor.execute(query) except Exception as e: if self.verbose is not None: print "Exception. In method batch_insert, the following query failed." print query print e conf.log.exception("Exception. ProjectUserVisibilityGenerator.batch_insert '''%s'''." % query) def flush_buffer(self): if len(self.buffer) > 0: self.batch_insert(self.buffer) self.buffer = [] def insert_visibilities(self, buffer): project_visibilities = [] project_id = buffer[0].project_id for visibility in buffer: if project_id == visibility.project_id: project_visibilities.append(visibility) else: self.clear_project_visibilities(project_visibilities[0].project_id) self.buffered_insert(project_visibilities) project_visibilities = [visibility] project_id = visibility.project_id if project_visibilities: self.clear_project_visibilities(project_visibilities[0].project_id) self.buffered_insert(project_visibilities) def buffered_insert(self, buffer): while len(buffer) >= self.batch_size: self.batch_insert(buffer[:self.batch_size]) buffer = buffer[self.batch_size:] if len(buffer) > 0: self.batch_insert(buffer) def pct_str(self, figure, total): if total == 0: return "NaN" percentage = 100.0 * float(figure) / float(total) return str(round(percentage, 1)) + "%" def generate_list(self, users, anon_id, pub_project_pairs, priv_project_pairs): """ See the module documentation. :param pub_project_pairs: (project id, trac environment key) pairs of the projects which anon user can see :param priv_project_pairs: (project id, trac environment key) pairs of other than public projects :param users: A list of :py:class:`User` objects :returns: A list of table ProjectUserVisibility objects for (project_id, user_id) """ # TODO: Horribly slow way to implement, optimize! # For each user, search ldap groups and organization where the # user belongs. For each project, search every group having the # required permission, and obtain the single user, ldap group or # organization members of those groups. Add a cache-awareness # into CQDEPermissionStore.check_permission() method (for example, # allow passing groups, organization_members and ldap_groups). if self.verbose: print "using cnc/pfnc rules to generate the visibility list" iter = 0 target = len(users) * len(priv_project_pairs) percent = int(target / 100) if percent == 0: percent = 1 big_list = [] if pub_project_pairs: for proj_id, trac_environment_key in pub_project_pairs: big_list.append(ProjectUserVisibility(proj_id, anon_id)) trues = len(pub_project_pairs or []) falses = 0 last = time() for proj_id, trac_environment_key in priv_project_pairs: for user in users: iter += 1 if self.user_can_view_project(trac_environment_key, user.username): trues += 1 big_list.append(ProjectUserVisibility(proj_id, user.user_id)) else: falses += 1 if self.verbose and iter % percent == 0: print "progress:", ((100 * iter) / target + 1) , "%, delta time for", percent, "iterations: ", (time() - last), " seconds" last = time() return big_list, trues, falses
class ProjectUserVisibilityGenerator(): def __init__(self, verbose=False): """ If verbose = None, be absolutely quiet """ self.verbose = verbose self.policy = CQDEPermissionPolicy(MockEnvironment()) self.papi = Projects() self.batch_size = conf.visibility_db_batch_size self.required_permission = 'PROJECT_VIEW' def get_anonymous_user_id(self): anon = get_userstore().getUser('anonymous') if anon: anon_id = safe_int(anon.id) else: anon_id = None return anon_id def get_public_project_pairs(self, anon_id): """ :param anon_id: Anonymous user_id :return: List of tuples (project_id, trac_environment_key) """ # TODO: it's not clearly defined when project is public perms = ['PROJECT_VIEW', 'TRAC_ADMIN'] perm_ids = [get_permission_id(perm) for perm in perms] query = """SELECT DISTINCT p.project_id, p.trac_environment_key FROM projects p INNER JOIN `group` g ON g.trac_environment_key = p.trac_environment_key INNER JOIN user_group ON user_group.group_key = g.group_id INNER JOIN group_permission ON group_permission.group_key = g.group_id WHERE user_group.user_key = {0} AND group_permission.permission_key IN ({1}) """.format( anon_id, ', '.join([str(int(id)) for id in perm_ids])) id_pairs = [] with admin_query() as cursor: try: cursor.execute(query) for row in cursor: id_pair = (int(row[0]), int(row[1])) id_pairs.append(id_pair) except Exception as e: if self.verbose is not None: print "Exception. In method get_public_project_pairs, the following query failed." print query print e conf.log.exception( "Exception. ProjectUserVisibilityGenerator.get_public_project_pairs " "failed with query '''%s'''." % query) return id_pairs def get_private_project_pairs(self, public_id_pairs=None): query = "SELECT project_id, trac_environment_key FROM projects " if public_id_pairs: public_project_ids = [pair[0] for pair in public_id_pairs] # TODO: What if there are really many public projects? pub_projs = ','.join(str(id) for id in public_project_ids) query += "WHERE projects.project_id NOT IN (%s)" % pub_projs id_pairs = [] with admin_query() as cursor: try: cursor.execute(query) for row in cursor: id_pair = (int(row[0]), int(row[1])) id_pairs.append(id_pair) except Exception as e: if self.verbose is not None: print "Exception. In method get_private_project_pairs, the following query failed." print query print e conf.log.exception( "Exception. ProjectUserVisibilityGenerator.get_private_project_pairs failed " "with query '''%s'''." % query) return id_pairs def get_all_users(self, except_anon=True): """ Returns :py:class:`User` objects for every user except 'authenticated' and, if anon_id is given, 'anonymous'. No row for 'authenticated' users are generated currently, this is why the 'authenticated' user is left out. :param bool except_anon: when True, don't include anon user :returns: A list of :py:class:`User` objects """ if except_anon: query = """SELECT user_id, username FROM `user` WHERE username NOT IN ('anonymous', 'authenticated') """ else: query = """SELECT user_id, username FROM `user` WHERE username NOT IN ('authenticated') """ users = [] with admin_query() as cursor: try: cursor.execute(query) for row in cursor: user = User.from_sql_row(row) users.append(user) except Exception as e: if self.verbose is not None: print "Exception. In method get_all_users, the following query failed." print query print e conf.log.exception( "Exception. ProjectUserVisibilityGenerator.get_all_users failed " "with query '''%s'''." % query) return users def user_can_view_project(self, trac_environment_key, username): return self.policy.check_permission(trac_environment_key, self.required_permission, username) def clear_project_visibilities(self, project_id): query = "DELETE FROM project_user_visibility WHERE project_id = %s" with admin_transaction() as cursor: try: cursor.execute(query, project_id) except Exception as e: if self.verbose is not None: print "Exception. In method clear_visibilities, the following query failed." print query print e conf.log.exception( "Exception. ProjectUserVisibilityGenerator.clear_visibilities " "failed with query '''%s'''." % query) def batch_insert(self, visibilities): query = "INSERT INTO project_user_visibility (project_id, user_id) VALUES " query += ",".join([ "(%d,%d)" % (safe_int(visibility.project_id), safe_int(visibility.user_id)) for visibility in visibilities ]) with admin_transaction() as cursor: try: cursor.execute(query) except Exception as e: if self.verbose is not None: print "Exception. In method batch_insert, the following query failed." print query print e conf.log.exception( "Exception. ProjectUserVisibilityGenerator.batch_insert '''%s'''." % query) def flush_buffer(self): if len(self.buffer) > 0: self.batch_insert(self.buffer) self.buffer = [] def insert_visibilities(self, buffer): project_visibilities = [] project_id = buffer[0].project_id for visibility in buffer: if project_id == visibility.project_id: project_visibilities.append(visibility) else: self.clear_project_visibilities( project_visibilities[0].project_id) self.buffered_insert(project_visibilities) project_visibilities = [visibility] project_id = visibility.project_id if project_visibilities: self.clear_project_visibilities(project_visibilities[0].project_id) self.buffered_insert(project_visibilities) def buffered_insert(self, buffer): while len(buffer) >= self.batch_size: self.batch_insert(buffer[:self.batch_size]) buffer = buffer[self.batch_size:] if len(buffer) > 0: self.batch_insert(buffer) def pct_str(self, figure, total): if total == 0: return "NaN" percentage = 100.0 * float(figure) / float(total) return str(round(percentage, 1)) + "%" def generate_list(self, users, anon_id, pub_project_pairs, priv_project_pairs): """ See the module documentation. :param pub_project_pairs: (project id, trac environment key) pairs of the projects which anon user can see :param priv_project_pairs: (project id, trac environment key) pairs of other than public projects :param users: A list of :py:class:`User` objects :returns: A list of table ProjectUserVisibility objects for (project_id, user_id) """ # TODO: Horribly slow way to implement, optimize! # For each user, search ldap groups and organization where the # user belongs. For each project, search every group having the # required permission, and obtain the single user, ldap group or # organization members of those groups. Add a cache-awareness # into CQDEPermissionStore.check_permission() method (for example, # allow passing groups, organization_members and ldap_groups). if self.verbose: print "using cnc/pfnc rules to generate the visibility list" iter = 0 target = len(users) * len(priv_project_pairs) percent = int(target / 100) if percent == 0: percent = 1 big_list = [] if pub_project_pairs: for proj_id, trac_environment_key in pub_project_pairs: big_list.append(ProjectUserVisibility(proj_id, anon_id)) trues = len(pub_project_pairs or []) falses = 0 last = time() for proj_id, trac_environment_key in priv_project_pairs: for user in users: iter += 1 if self.user_can_view_project(trac_environment_key, user.username): trues += 1 big_list.append( ProjectUserVisibility(proj_id, user.user_id)) else: falses += 1 if self.verbose and iter % percent == 0: print "progress:", ( (100 * iter) / target + 1), "%, delta time for", percent, "iterations: ", ( time() - last), " seconds" last = time() return big_list, trues, falses
def check_permission(self, action, username, resource, perm): """ Checks permissions - Actual checking is done on CQDEPermissionPolicy class """ # FIXME: Dirty hack to screw ILegacyAttachmentPolicy. perm_maps = { "ATTACHMENT_CREATE": { "ticket": "TICKET_APPEND", "wiki": "WIKI_MODIFY", "milestone": "MILESTONE_MODIFY", "discussion": "DISCUSSION_ATTACH", }, "ATTACHMENT_VIEW": { "ticket": "TICKET_VIEW", "wiki": "WIKI_VIEW", "milestone": "MILESTONE_VIEW", "discussion": "DISCUSSION_ATTACH", }, "ATTACHMENT_DELETE": { "ticket": "TICKET_ADMIN", "wiki": "WIKI_DELETE", "milestone": "MILESTONE_DELETE", "discussion": "DISCUSSION_ATTACH", }, } perm_map = perm_maps.get(action) if perm_map and resource and resource.realm == "attachment": action = perm_map.get(resource.parent.realm) policy = CQDEPermissionPolicy(self.env) # Project context check if resource and resource.realm == "project": # NOTE: Load project to get environment key required by check_permission # NOTE: Internal TracEnvironment cannot be used because env can be home, whereas project id is not project = Project.get(id=resource.id) if project and policy.check_permission(project.trac_environment_key, action, username): return True return False # Ticket authors should be able to edit their own tickets # (excluding 'anonymous') if ( username != "anonymous" and resource and resource.id and resource.realm == "ticket" and action in ("TICKET_CHGPROP", "TICKET_EDIT_DESCRIPTION") ): ticket = Ticket(self.env, int(resource.id)) if ticket.exists and username == ticket["reporter"]: return True # Load lightweight trac environment to get environment id, required by internal check_permission env_name = conf.resolveProjectName(self.env) environment = TracEnvironment.read(env_name) # Check permission using global permission policy and storage if not policy.check_permission(environment.environment_id, action, username): return False # Additional, resources based checks # User author check if action in ("USER_ADMIN", "USER_AUTHOR", "USER_VIEW", "USER_MODIFY", "USER_DELETE") and resource: # Check if USER_ADMIN permission in home project home_perm = PermissionCache(conf.home_env, username) if "USER_ADMIN" in home_perm: return True userstore = conf.getUserStore() resource_user = userstore.getUserWhereId(resource.id) user = userstore.getUser(username) # Allow manage own and authored account if action in ("USER_ADMIN", "USER_AUTHOR"): return resource_user.author_id == user.id or resource_user.id == user.id # Allow to manage itself return resource_user.id == user.id return True