def CheckAssertBasePermissions( self, restriction, expect_admin_ok, expect_nonadmin_ok): old_group_creation_restriction = settings.group_creation_restriction settings.group_creation_restriction = restriction # Anon users can never do it mr = testing_helpers.MakeMonorailRequest( perms=permissions.GetPermissions(None, {}, None)) self.assertRaises( permissions.PermissionException, self.servlet.AssertBasePermission, mr) mr = testing_helpers.MakeMonorailRequest() if expect_admin_ok: self.servlet.AssertBasePermission(mr) else: self.assertRaises( permissions.PermissionException, self.servlet.AssertBasePermission, mr) mr = testing_helpers.MakeMonorailRequest( perms=permissions.GetPermissions(mr.auth.user_pb, {111}, None)) if expect_nonadmin_ok: self.servlet.AssertBasePermission(mr) else: self.assertRaises( permissions.PermissionException, self.servlet.AssertBasePermission, mr) settings.group_creation_restriction = old_group_creation_restriction
def GetPermissionsInAllProjects(user, effective_ids, projects): """Look up the permissions for the given user in each project.""" return { project.project_id: permissions.GetPermissions(user, effective_ids, project) for project in projects }
def FilterIssues(mr, issues, services): """Return a list of issues that the user is allowed to view.""" allowed_issues = [] project_ids = GetAllProjectsOfIssues(issues) issue_projects = services.project.GetProjects(mr.cnxn, project_ids) configs_by_project_id = services.config.GetProjectConfigs( mr.cnxn, project_ids) perms_by_project_id = { pid: permissions.GetPermissions(mr.auth.user_pb, mr.auth.effective_ids, p) for pid, p in issue_projects.items() } for issue in issues: if (mr.can == 1) or not issue.closed_timestamp: issue_project = issue_projects[issue.project_id] config = configs_by_project_id[issue.project_id] perms = perms_by_project_id[issue.project_id] granted_perms = tracker_bizobj.GetGrantedPerms( issue, mr.auth.effective_ids, config) permit_view = permissions.CanViewIssue(mr.auth.effective_ids, perms, issue_project, issue, granted_perms=granted_perms) if permit_view: allowed_issues.append(issue) return allowed_issues
def testAssertBasePermission_IgnoreNoSuchGroup(self): """The permission check does not crash for non-existent user groups.""" mr = testing_helpers.MakeMonorailRequest( perms=permissions.GetPermissions(None, {}, None)) mr.viewed_user_auth.user_id = 404 mr.auth.effective_ids = set([111]) self.servlet.AssertBasePermission(mr)
def UsersWithPermsInProject(project, perms_needed, users_by_id, effective_ids_by_user): # Users that have the given permission are stored in direct_users_for_perm, # users whose effective ids have the given permission are stored in # indirect_users_for_perm. direct_users_for_perm = {perm: set() for perm in perms_needed} indirect_users_for_perm = {perm: set() for perm in perms_needed} # Iterate only over users that have extra permissions, so we don't # have to search the extra perms more than once for each user. for extra_perm_pb in project.extra_perms: extra_perms = set(perm.lower() for perm in extra_perm_pb.perms) for perm, users in direct_users_for_perm.items(): if perm.lower() in extra_perms: users.add(extra_perm_pb.member_id) # Then, iterate over all users, but don't compute extra permissions. for user_id, user_view in users_by_id.items(): effective_ids = effective_ids_by_user[user_id].union([user_id]) user_perms = permissions.GetPermissions(user_view.user, effective_ids, project) for perm, users in direct_users_for_perm.items(): if not effective_ids.isdisjoint(users): indirect_users_for_perm[perm].add(user_id) if user_perms.HasPerm(perm, None, None, []): users.add(user_id) for perm, users in direct_users_for_perm.items(): users.update(indirect_users_for_perm[perm]) return direct_users_for_perm
def testProcessFormData_OwnerPermission(self): """Group owners cannot edit group.""" self.services.usergroup.TestAddMembers(888, [111], 'owner') mr = testing_helpers.MakeMonorailRequest( perms=permissions.GetPermissions(None, {}, None)) mr.viewed_user_auth.user_id = 888 mr.auth.effective_ids = set([111]) self.servlet.ProcessFormData(mr, {})
def testAssertBasePermission(self): mr = testing_helpers.MakeMonorailRequest( perms=permissions.GetPermissions(None, {}, None)) mr.viewed_user_auth.user_id = 888 self.assertRaises(permissions.PermissionException, self.servlet.AssertBasePermission, mr) self.services.usergroup.TestAddMembers(888, [111], 'owner') self.servlet.AssertBasePermission(self.mr)
def testAssertBasePermission(self): mr = testing_helpers.MakeMonorailRequest( perms=permissions.GetPermissions(None, {}, None)) mr.viewed_user_auth.user_id = 888L mr.auth.effective_ids = set([111L]) self.assertRaises(permissions.PermissionException, self.servlet.AssertBasePermission, mr) self.services.usergroup.TestAddMembers(888L, [111L], 'member') self.servlet.AssertBasePermission(mr)
def testProcessFormData_NoPermission(self): """Group members cannot edit group.""" self.services.usergroup.TestAddMembers(888L, [111L], 'member') mr = testing_helpers.MakeMonorailRequest( perms=permissions.GetPermissions(None, {}, None)) mr.viewed_user_auth.user_id = 888L mr.auth.effective_ids = set([111L]) self.assertRaises(permissions.PermissionException, self.servlet.ProcessFormData, mr, {})
def ValidateCustomField(mr, project, services, field_def, field_val): """Validate one custom field value and return an error string or None.""" if field_def.field_type == tracker_pb2.FieldTypes.INT_TYPE: if (field_def.min_value is not None and field_val.int_value < field_def.min_value): return 'Value must be >= %d' % field_def.min_value if (field_def.max_value is not None and field_val.int_value > field_def.max_value): return 'Value must be <= %d' % field_def.max_value elif field_def.field_type == tracker_pb2.FieldTypes.STR_TYPE: if field_def.regex and field_val.str_value: try: regex = re.compile(field_def.regex) if not regex.match(field_val.str_value): return 'Value must match regular expression: %s' % field_def.regex except re.error: logging.info('Failed to process regex %r with value %r. Allowing.', field_def.regex, field_val.str_value) return None elif field_def.field_type == tracker_pb2.FieldTypes.USER_TYPE: field_val_user = services.user.GetUser(mr.cnxn, field_val.user_id) auth = authdata.AuthData.FromUser(mr.cnxn, field_val_user, services) if auth.user_pb.user_id == INVALID_USER_ID: return 'User not found' if field_def.needs_member: user_value_in_project = framework_bizobj.UserIsInProject( project, auth.effective_ids) if not user_value_in_project: return 'User must be a member of the project' if field_def.needs_perm: user_perms = permissions.GetPermissions( auth.user_pb, auth.effective_ids, project) has_perm = user_perms.CanUsePerm( field_def.needs_perm, auth.effective_ids, project, []) if not has_perm: return 'User must have permission "%s"' % field_def.needs_perm return None elif field_def.field_type == tracker_pb2.FieldTypes.DATE_TYPE: # TODO(jrobbins): date validation pass elif field_def.field_type == tracker_pb2.FieldTypes.URL_TYPE: if field_val.url_value: if not (validate.IsValidURL(field_val.url_value) or autolink_constants.IS_A_SHORT_LINK_RE.match( field_val.url_value) or autolink_constants.IS_A_NUMERIC_SHORT_LINK_RE.match( field_val.url_value) or autolink_constants.IS_IMPLIED_LINK_RE.match( field_val.url_value)): return 'Value must be a valid url' return None
def testAssertBasePermission_IndirectMembership(self): self.services.usergroup.TestAddGroupSettings(999, '*****@*****.**') mr = testing_helpers.MakeMonorailRequest( perms=permissions.GetPermissions(None, {}, None)) mr.viewed_user_auth.user_id = 888 mr.auth.effective_ids = set([111]) self.assertRaises(permissions.PermissionException, self.servlet.AssertBasePermission, mr) self.services.usergroup.TestAddMembers(888, [999], 'member') self.services.usergroup.TestAddMembers(999, [111], 'member') self.servlet.AssertBasePermission(mr)
def _LookupLoggedInUser(self, services, prof): """Get information about the signed-in user (if any) from the request.""" with prof.Phase('get user info, if any'): self.auth = AuthData.FromRequest(self.cnxn, services) self.me_user_id = (self.GetIntParam('me') or self.viewed_user_auth.user_id or self.auth.user_id) with prof.Phase('looking up signed in user permissions'): self.perms = permissions.GetPermissions(self.auth.user_pb, self.auth.effective_ids, self.project)
def __init__(self, request, services, perms=None): self.cnxn = None self.auth = monorailrequest.AuthData.FromEmail( self.cnxn, request['requester'], services) self.me_user_id = self.auth.user_id self.project_name = None self.project = None self.viewed_username = None self.viewed_user_auth = None self.config = None if 'userId' in request: self.viewed_username = request['userId'] self.viewed_user_auth = monorailrequest.AuthData.FromEmail( self.cnxn, self.viewed_username, services) elif 'groupName' in request: self.viewed_username = request['groupName'] try: self.viewed_user_auth = monorailrequest.AuthData.FromEmail( self.cnxn, self.viewed_username, services) except user_svc.NoSuchUserException: self.viewed_user_auth = None if 'projectId' in request: self.project_name = request['projectId'] self.project = services.project.GetProjectByName( self.cnxn, self.project_name) self.config = services.config.GetProjectConfig( self.cnxn, self.project_id) self.perms = perms or permissions.GetPermissions( self.auth.user_pb, self.auth.effective_ids, self.project) self.granted_perms = set() self.params = { 'can': request.get('can', 1), 'start': request.get('startIndex', 0), 'num': request.get('maxResults', 100), 'q': request.get('q', ''), 'sort': request.get('sort', ''), 'groupby': '', 'projects': request.get('additionalProject', []) + [self.project_name]} self.use_cached_searches = True self.errors = template_helpers.EZTError() self.mode = None self.query_project_names = self.GetParam('projects') self.group_by_spec = self.GetParam('groupby') self.sort_spec = self.GetParam('sort') self.query = self.GetParam('q') self.can = self.GetParam('can') self.start = self.GetParam('start') self.num = self.GetParam('num')
def _ValidateOneCustomField(mr, services, field_def, field_val): """Validate one custom field value and return an error string or None.""" if field_def.field_type == tracker_pb2.FieldTypes.INT_TYPE: if (field_def.min_value is not None and field_val.int_value < field_def.min_value): return 'Value must be >= %d' % field_def.min_value if (field_def.max_value is not None and field_val.int_value > field_def.max_value): return 'Value must be <= %d' % field_def.max_value elif field_def.field_type == tracker_pb2.FieldTypes.STR_TYPE: if field_def.regex and field_val.str_value: try: regex = re.compile(field_def.regex) if not regex.match(field_val.str_value): return 'Value must match regular expression: %s' % field_def.regex except re.error: logging.info( 'Failed to process regex %r with value %r. Allowing.', field_def.regex, field_val.str_value) return None elif field_def.field_type == tracker_pb2.FieldTypes.USER_TYPE: if field_val.user_id == INVALID_USER_ID: return 'User not found' if field_def.needs_member: auth = monorailrequest.AuthData.FromUserID(mr.cnxn, field_val.user_id, services) user_value_in_project = framework_bizobj.UserIsInProject( mr.project, auth.effective_ids) if not user_value_in_project: return 'User must be a member of the project' if field_def.needs_perm: field_val_user = services.user.GetUser(mr.cnxn, field_val.user_id) user_perms = permissions.GetPermissions( field_val_user, auth.effective_ids, mr.project) has_perm = user_perms.CanUsePerm(field_def.needs_perm, auth.effective_ids, mr.project, []) if not has_perm: return 'User must have permission "%s"' % field_def.needs_perm elif field_def.field_type == tracker_pb2.FieldTypes.DATE_TYPE: # TODO(jrobbins): date validation pass return None
def CheckPermForProject(mr, perm, project, art=None): """Convenience method that makes permission checks for projects easier. Args: mr: common information parsed from the HTTP request. perm: A permission constant, defined in module framework.permissions project: The project to enforce permissions for. art: Optional artifact pb Returns: A boolean, whether the request can be satisfied, given the permission. """ perms = permissions.GetPermissions( mr.auth.user_pb, mr.auth.effective_ids, project) return perms.CanUsePerm( perm, mr.auth.effective_ids, project, permissions.GetRestrictions(art))
def HandleRequest(self, mr): """Get all the user IDs that the specified user cannot view. Args: mr: common information parsed from the HTTP request. Returns: Results dictionary {project_id: [issue_id]} in JSON format. """ if mr.shard_id is None: return {'message': 'Cannot proceed without a valid shard_id.'} user_id = mr.specified_logged_in_user_id user = self.services.user.GetUser(mr.cnxn, user_id) effective_ids = self.services.usergroup.LookupMemberships(mr.cnxn, user_id) if user_id: effective_ids.add(user_id) project_id = mr.specified_project_id project = self.services.project.GetProject(mr.cnxn, project_id) perms = permissions.GetPermissions(user, effective_ids, project) nonviewable_iids = self.GetNonviewableIIDs( mr.cnxn, user, effective_ids, project, perms, mr.shard_id) cached_ts = mr.invalidation_timestep if mr.specified_project_id: memcache.set( 'nonviewable:%d;%d;%d' % (project_id, user_id, mr.shard_id), (nonviewable_iids, cached_ts), time=NONVIEWABLE_MEMCACHE_EXPIRATION) else: memcache.set( 'nonviewable:all;%d;%d' % (user_id, mr.shard_id), (nonviewable_iids, cached_ts), time=NONVIEWABLE_MEMCACHE_EXPIRATION) logging.info('set nonviewable:%s;%d;%d to %r', project_id, user_id, mr.shard_id, nonviewable_iids) return { 'nonviewable': nonviewable_iids, # These are not used in the frontend, but useful for debugging. 'project_id': project_id, 'user_id': user_id, 'shard_id': mr.shard_id, }
def testProcessFormData_noPermission(self): self.servlet.services.user.TestAddUser('member', 222L) self.servlet.services.user.TestAddUser('*****@*****.**', 111L) mr = testing_helpers.MakeMonorailRequest( path='/u/[email protected]/banSpammer.do', perms=permissions.GetPermissions(None, {}, None)) mr.viewed_user_auth.user_view = framework_views.MakeUserView(mr.cnxn, self.servlet.services.user, 111L) mr.auth.user_id = 222L self.assertRaises(permissions.PermissionException, self.servlet.AssertBasePermission, mr) try: self.servlet.ProcessFormData(mr, {}) except permissions.PermissionException: pass tasks = self.taskqueue_stub.get_filtered_tasks( url=urls.BAN_SPAMMER_TASK + '.do') self.assertEqual(0, len(tasks))
def testProcessMail_Success(self): self.services.user.TestAddUser('*****@*****.**', 111L) class MockAuthData: def __init__(self): self.user_pb = user_pb2.MakeUser(111L) self.effective_ids = set([1, 2, 3]) self.user_id = 111L mock_auth_data = MockAuthData() self.mox.StubOutWithMock(emailfmt, 'ValidateReferencesHeader') emailfmt.ValidateReferencesHeader(mox.IgnoreArg(), self.project, mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(True) self.mox.StubOutWithMock(monorailrequest.AuthData, 'FromEmail') monorailrequest.AuthData.FromEmail( mox.IgnoreArg(), '*****@*****.**', self.services, autocreate=False).AndReturn(mock_auth_data) self.mox.StubOutWithMock(permissions, 'GetPermissions') permissions.GetPermissions(mock_auth_data.user_pb, mock_auth_data.effective_ids, self.project).AndReturn('test permissions') self.mox.StubOutWithMock(self.inbound, 'ProcessIssueReply') self.inbound.ProcessIssueReply(mox.IgnoreArg(), self.project, 123, self.project_addr, '*****@*****.**', 111L, mock_auth_data.effective_ids, 'test permissions', 'awesome!') self.mox.ReplayAll() ret = self.inbound.ProcessMail(self.msg, self.project_addr) self.mox.VerifyAll() self.assertIsNone(ret)
def LookupLoggedInUserPerms(self, project): """Look up perms for user making a request in project (can be None).""" with self.profiler.Phase('looking up signed in user permissions'): self.perms = permissions.GetPermissions( self.auth.user_pb, self.auth.effective_ids, project)
def api_base_checks(request, requester, services, cnxn, auth_client_ids, auth_emails): """Base checks for API users. Args: request: The HTTP request from Cloud Endpoints. requester: The user who sends the request. services: Services object. cnxn: connection to the SQL database. auth_client_ids: authorized client ids. auth_emails: authorized emails when client is anonymous. Returns: Client ID and client email. Raises: endpoints.UnauthorizedException: If the requester is anonymous. user_svc.NoSuchUserException: If the requester does not exist in Monorail. project_svc.NoSuchProjectException: If the project does not exist in Monorail. permissions.BannedUserException: If the requester is banned. permissions.PermissionException: If the requester does not have permisssion to view. """ valid_user = False auth_err = '' client_id = None try: client_id = oauth.get_client_id(framework_constants.OAUTH_SCOPE) logging.info('Oauth client ID %s', client_id) except oauth.Error as ex: auth_err = 'oauth.Error: %s' % ex if not requester: try: requester = oauth.get_current_user(framework_constants.OAUTH_SCOPE) logging.info('Oauth requester %s', requester.email()) except oauth.Error as ex: auth_err = 'oauth.Error: %s' % ex if client_id and requester: if client_id != 'anonymous': if client_id in auth_client_ids: valid_user = True else: auth_err = 'Client ID %s is not whitelisted' % client_id # Some service accounts may have anonymous client ID else: if requester.email() in auth_emails: valid_user = True else: auth_err = 'Client email %s is not whitelisted' % requester.email() if not valid_user: raise endpoints.UnauthorizedException('Auth error: %s' % auth_err) else: logging.info('API request from user %s:%s', client_id, requester.email()) project_name = None if hasattr(request, 'projectId'): project_name = request.projectId issue_local_id = None if hasattr(request, 'issueId'): issue_local_id = request.issueId # This could raise user_svc.NoSuchUserException requester_id = services.user.LookupUserID(cnxn, requester.email()) requester_pb = services.user.GetUser(cnxn, requester_id) requester_view = framework_views.UserView(requester_pb) if permissions.IsBanned(requester_pb, requester_view): raise permissions.BannedUserException( 'The user %s has been banned from using Monorail' % requester.email()) if project_name: project = services.project.GetProjectByName( cnxn, project_name) if not project: raise project_svc.NoSuchProjectException( 'Project %s does not exist' % project_name) if project.state != project_pb2.ProjectState.LIVE: raise permissions.PermissionException( 'API may not access project %s because it is not live' % project_name) requester_effective_ids = services.usergroup.LookupMemberships( cnxn, requester_id) requester_effective_ids.add(requester_id) if not permissions.UserCanViewProject( requester_pb, requester_effective_ids, project): raise permissions.PermissionException( 'The user %s has no permission for project %s' % (requester.email(), project_name)) if issue_local_id: # This may raise a NoSuchIssueException. issue = services.issue.GetIssueByLocalID( cnxn, project.project_id, issue_local_id) perms = permissions.GetPermissions( requester_pb, requester_effective_ids, project) config = services.config.GetProjectConfig(cnxn, project.project_id) granted_perms = tracker_bizobj.GetGrantedPerms( issue, requester_effective_ids, config) if not permissions.CanViewIssue( requester_effective_ids, perms, project, issue, granted_perms=granted_perms): raise permissions.PermissionException( 'User is not allowed to view this issue %s:%d' % (project_name, issue_local_id)) return client_id, requester.email()
def ComputeIssueChangeAddressPermList( cnxn, ids_to_consider, project, issue, services, omit_addrs, users_by_id, pref_check_function=lambda u: u.notify_issue_change): """Return a list of user email addresses to notify of an issue change. User email addresses are determined by looking up the given user IDs in the given users_by_id dict. Args: cnxn: connection to SQL database. ids_to_consider: list of user IDs for users interested in this issue. project: Project PB for the project containing this issue. issue: Issue PB for the issue that was updated. services: Services. omit_addrs: set of strings for email addresses to not notify because they already know. users_by_id: dict {user_id: user_view} user info. pref_check_function: optional function to use to check if a certain User PB has a preference set to receive the email being sent. It defaults to "If I am in the issue's owner or cc field", but it can be set to check "If I starred the issue." Returns: A list of AddrPerm objects. """ memb_addr_perm_list = [] logging.info('Considering %r ', ids_to_consider) all_user_prefs = services.user.GetUsersPrefs(cnxn, ids_to_consider) for user_id in ids_to_consider: if user_id == framework_constants.NO_USER_SPECIFIED: continue user = services.user.GetUser(cnxn, user_id) # Notify people who have a pref set, or if they have no User PB # because the pref defaults to True. if user and not pref_check_function(user): logging.info('Not notifying %r: user preference', user.email) continue # TODO(jrobbins): doing a bulk operation would reduce DB load. auth = authdata.AuthData.FromUserID(cnxn, user_id, services) perms = permissions.GetPermissions(user, auth.effective_ids, project) config = services.config.GetProjectConfig(cnxn, project.project_id) granted_perms = tracker_bizobj.GetGrantedPerms( issue, auth.effective_ids, config) if not permissions.CanViewIssue( auth.effective_ids, perms, project, issue, granted_perms=granted_perms): logging.info('Not notifying %r: user cannot view issue', user.email) continue addr = users_by_id[user_id].email if addr in omit_addrs: logging.info('Not notifying %r: user already knows', user.email) continue recipient_is_member = bool(framework_bizobj.UserIsInProject( project, auth.effective_ids)) reply_perm = REPLY_NOT_ALLOWED if project.process_inbound_email: if permissions.CanEditIssue(auth.effective_ids, perms, project, issue): reply_perm = REPLY_MAY_UPDATE elif permissions.CanCommentIssue( auth.effective_ids, perms, project, issue): reply_perm = REPLY_MAY_COMMENT memb_addr_perm_list.append( AddrPerm(recipient_is_member, addr, user, reply_perm, all_user_prefs[user_id])) logging.info('For %s %s, will notify: %r', project.project_name, issue.local_id, [ap.address for ap in memb_addr_perm_list]) return memb_addr_perm_list
def GatherPageData(self, mr): """Build up a dictionary of data values to use when rendering the page.""" member_id = self.ValidateMemberID(mr.cnxn, mr.specified_user_id, mr.project) group_ids = self.services.usergroup.DetermineWhichUserIDsAreGroups( mr.cnxn, [member_id]) users_by_id = framework_views.MakeAllUserViews(mr.cnxn, self.services.user, [member_id]) framework_views.RevealAllEmailsToMembers(mr.auth, mr.project, users_by_id) project_commitments = self.services.project.GetProjectCommitments( mr.cnxn, mr.project_id) (ac_exclusion_ids, no_expand_ids ) = self.services.project.GetProjectAutocompleteExclusion( mr.cnxn, mr.project_id) member_view = project_views.MemberView( mr.auth.user_id, member_id, users_by_id[member_id], mr.project, project_commitments, ac_exclusion=(member_id in ac_exclusion_ids), no_expand=(member_id in no_expand_ids), is_group=(member_id in group_ids)) member_user = self.services.user.GetUser(mr.cnxn, member_id) # This ignores indirect memberships, which is ok because we are viewing # the page for a member directly involved in the project role_perms = permissions.GetPermissions(member_user, {member_id}, mr.project) # TODO(jrobbins): clarify in the UI which permissions are built-in to # the user's direct role, vs. which are granted via a group membership, # vs. which ones are extra_perms that have been added specifically for # this user. member_perms = template_helpers.EZTItem() for perm in CHECKBOX_PERMS: setattr( member_perms, perm, ezt.boolean(role_perms.HasPerm(perm, member_id, mr.project))) displayed_extra_perms = [ perm for perm in member_view.extra_perms if perm not in CHECKBOX_PERMS ] viewing_self = mr.auth.user_id == member_id warn_abandonment = (viewing_self and permissions.ShouldCheckForAbandonment(mr)) return { 'subtab_mode': None, 'member': member_view, 'role_perms': role_perms, 'member_perms': member_perms, 'displayed_extra_perms': displayed_extra_perms, 'offer_edit_perms': ezt.boolean(self.CanEditPerms(mr)), 'offer_edit_member_notes': ezt.boolean(self.CanEditMemberNotes(mr, member_id)), 'offer_remove_role': ezt.boolean(self.CanRemoveRole(mr, member_id)), 'expand_perms': ezt.boolean(mr.auth.user_pb.keep_people_perms_open), 'warn_abandonment': ezt.boolean(warn_abandonment), 'total_num_owners': len(mr.project.owner_ids), }
def HandleRequest(self, mr): """Process the task to notify users after an issue blocking change. Args: mr: common information parsed from the HTTP request. Returns: Results dictionary in JSON format which is useful just for debugging. The main goal is the side-effect of sending emails. """ issue_ids = mr.GetIntListParam('issue_ids') hostport = mr.GetParam('hostport') if not issue_ids: return { 'params': {}, 'notified': [], 'message': 'Cannot proceed without a valid issue IDs.', } old_owner_ids = mr.GetIntListParam('old_owner_ids') comment_text = mr.GetParam('comment_text') commenter_id = mr.GetPositiveIntParam('commenter_id') amendments = mr.GetParam('amendments') send_email = bool(mr.GetIntParam('send_email')) params = dict( issue_ids=issue_ids, commenter_id=commenter_id, hostport=hostport, old_owner_ids=old_owner_ids, comment_text=comment_text, send_email=send_email, amendments=amendments) logging.info('bulk edit params are %r', params) issues = self.services.issue.GetIssues(mr.cnxn, issue_ids) # TODO(jrobbins): For cross-project bulk edits, prefetch all relevant # projects and configs and pass a dict of them to subroutines. For # now, all issue must be in the same project. project_id = issues[0].project_id project = self.services.project.GetProject(mr.cnxn, project_id) config = self.services.config.GetProjectConfig(mr.cnxn, project_id) issues = [issue for issue in issues if not issue.is_spam] anon_perms = permissions.GetPermissions(None, set(), project) users_by_id = framework_views.MakeAllUserViews( mr.cnxn, self.services.user, [commenter_id]) ids_in_issues = {} starrers = {} non_private_issues = [] for issue, old_owner_id in zip(issues, old_owner_ids): # TODO(jrobbins): use issue_id consistently rather than local_id. starrers[issue.local_id] = self.services.issue_star.LookupItemStarrers( mr.cnxn, issue.issue_id) named_ids = set() # users named in user-value fields that notify. for fd in config.field_defs: named_ids.update(notify_reasons.ComputeNamedUserIDsToNotify( issue.field_values, fd)) direct, indirect = self.services.usergroup.ExpandAnyUserGroups( mr.cnxn, list(issue.cc_ids) + list(issue.derived_cc_ids) + [issue.owner_id, old_owner_id, issue.derived_owner_id] + list(named_ids)) ids_in_issues[issue.local_id] = set(starrers[issue.local_id]) ids_in_issues[issue.local_id].update(direct) ids_in_issues[issue.local_id].update(indirect) ids_in_issue_needing_views = ( ids_in_issues[issue.local_id] | tracker_bizobj.UsersInvolvedInIssues([issue])) new_ids_in_issue = [user_id for user_id in ids_in_issue_needing_views if user_id not in users_by_id] users_by_id.update( framework_views.MakeAllUserViews( mr.cnxn, self.services.user, new_ids_in_issue)) anon_can_view = permissions.CanViewIssue( set(), anon_perms, project, issue) if anon_can_view: non_private_issues.append(issue) commenter_view = users_by_id[commenter_id] omit_addrs = {commenter_view.email} tasks = [] if send_email: email_tasks = self._BulkEditEmailTasks( mr.cnxn, issues, old_owner_ids, omit_addrs, project, non_private_issues, users_by_id, ids_in_issues, starrers, commenter_view, hostport, comment_text, amendments, config) tasks = email_tasks notified = notify_helpers.AddAllEmailTasks(tasks) return { 'params': params, 'notified': notified, }
def _BulkEditEmailTasks( self, cnxn, issues, old_owner_ids, omit_addrs, project, non_private_issues, users_by_id, ids_in_issues, starrers, commenter_view, hostport, comment_text, amendments, config): """Generate Email PBs to notify interested users after a bulk edit.""" # 1. Get the user IDs of everyone who could be notified, # and make all their user proxies. Also, build a dictionary # of all the users to notify and the issues that they are # interested in. Also, build a dictionary of additional email # addresses to notify and the issues to notify them of. users_by_id = {} ids_to_notify_of_issue = {} additional_addrs_to_notify_of_issue = collections.defaultdict(list) users_to_queries = notify_reasons.GetNonOmittedSubscriptions( cnxn, self.services, [project.project_id], {}) config = self.services.config.GetProjectConfig( cnxn, project.project_id) for issue, old_owner_id in zip(issues, old_owner_ids): issue_participants = set( [tracker_bizobj.GetOwnerId(issue), old_owner_id] + tracker_bizobj.GetCcIds(issue)) # users named in user-value fields that notify. for fd in config.field_defs: issue_participants.update( notify_reasons.ComputeNamedUserIDsToNotify(issue.field_values, fd)) for user_id in ids_in_issues[issue.local_id]: # TODO(jrobbins): implement batch GetUser() for speed. if not user_id: continue auth = authdata.AuthData.FromUserID( cnxn, user_id, self.services) if (auth.user_pb.notify_issue_change and not auth.effective_ids.isdisjoint(issue_participants)): ids_to_notify_of_issue.setdefault(user_id, []).append(issue) elif (auth.user_pb.notify_starred_issue_change and user_id in starrers[issue.local_id]): # Skip users who have starred issues that they can no longer view. starrer_perms = permissions.GetPermissions( auth.user_pb, auth.effective_ids, project) granted_perms = tracker_bizobj.GetGrantedPerms( issue, auth.effective_ids, config) starrer_can_view = permissions.CanViewIssue( auth.effective_ids, starrer_perms, project, issue, granted_perms=granted_perms) if starrer_can_view: ids_to_notify_of_issue.setdefault(user_id, []).append(issue) logging.info( 'ids_to_notify_of_issue[%s] = %s', user_id, [i.local_id for i in ids_to_notify_of_issue.get(user_id, [])]) # Find all subscribers that should be notified. subscribers_to_consider = notify_reasons.EvaluateSubscriptions( cnxn, issue, users_to_queries, self.services, config) for sub_id in subscribers_to_consider: auth = authdata.AuthData.FromUserID(cnxn, sub_id, self.services) sub_perms = permissions.GetPermissions( auth.user_pb, auth.effective_ids, project) granted_perms = tracker_bizobj.GetGrantedPerms( issue, auth.effective_ids, config) sub_can_view = permissions.CanViewIssue( auth.effective_ids, sub_perms, project, issue, granted_perms=granted_perms) if sub_can_view: ids_to_notify_of_issue.setdefault(sub_id, []) if issue not in ids_to_notify_of_issue[sub_id]: ids_to_notify_of_issue[sub_id].append(issue) if issue in non_private_issues: for notify_addr in issue.derived_notify_addrs: additional_addrs_to_notify_of_issue[notify_addr].append(issue) # 2. Compose an email specifically for each user, and one email to each # notify_addr with all the issues that it. # Start from non-members first, then members to reveal email addresses. email_tasks = [] needed_user_view_ids = [uid for uid in ids_to_notify_of_issue if uid not in users_by_id] users_by_id.update(framework_views.MakeAllUserViews( cnxn, self.services.user, needed_user_view_ids)) member_ids_to_notify_of_issue = {} non_member_ids_to_notify_of_issue = {} member_additional_addrs = {} non_member_additional_addrs = {} addr_to_addrperm = {} # {email_address: AddrPerm object} all_user_prefs = self.services.user.GetUsersPrefs( cnxn, ids_to_notify_of_issue) # TODO(jrobbins): Merge ids_to_notify_of_issue entries for linked accounts. for user_id in ids_to_notify_of_issue: if not user_id: continue # Don't try to notify NO_USER_SPECIFIED if users_by_id[user_id].email in omit_addrs: logging.info('Omitting %s', user_id) continue user_issues = ids_to_notify_of_issue[user_id] if not user_issues: continue # user's prefs indicate they don't want these notifications auth = authdata.AuthData.FromUserID( cnxn, user_id, self.services) is_member = bool(framework_bizobj.UserIsInProject( project, auth.effective_ids)) if is_member: member_ids_to_notify_of_issue[user_id] = user_issues else: non_member_ids_to_notify_of_issue[user_id] = user_issues addr = users_by_id[user_id].email omit_addrs.add(addr) addr_to_addrperm[addr] = notify_reasons.AddrPerm( is_member, addr, users_by_id[user_id].user, notify_reasons.REPLY_NOT_ALLOWED, all_user_prefs[user_id]) for addr, addr_issues in additional_addrs_to_notify_of_issue.items(): auth = None try: auth = authdata.AuthData.FromEmail(cnxn, addr, self.services) except: # pylint: disable=bare-except logging.warning('Cannot find user of email %s ', addr) if auth: is_member = bool(framework_bizobj.UserIsInProject( project, auth.effective_ids)) else: is_member = False if is_member: member_additional_addrs[addr] = addr_issues else: non_member_additional_addrs[addr] = addr_issues omit_addrs.add(addr) addr_to_addrperm[addr] = notify_reasons.AddrPerm( is_member, addr, None, notify_reasons.REPLY_NOT_ALLOWED, None) for user_id, user_issues in non_member_ids_to_notify_of_issue.items(): addr = users_by_id[user_id].email email = self._FormatBulkIssuesEmail( addr_to_addrperm[addr], user_issues, users_by_id, commenter_view, hostport, comment_text, amendments, config, project) email_tasks.append(email) logging.info('about to bulk notify non-member %s (%s) of %s', users_by_id[user_id].email, user_id, [issue.local_id for issue in user_issues]) for addr, addr_issues in non_member_additional_addrs.items(): email = self._FormatBulkIssuesEmail( addr_to_addrperm[addr], addr_issues, users_by_id, commenter_view, hostport, comment_text, amendments, config, project) email_tasks.append(email) logging.info('about to bulk notify non-member additional addr %s of %s', addr, [addr_issue.local_id for addr_issue in addr_issues]) framework_views.RevealAllEmails(users_by_id) commenter_view.RevealEmail() for user_id, user_issues in member_ids_to_notify_of_issue.items(): addr = users_by_id[user_id].email email = self._FormatBulkIssuesEmail( addr_to_addrperm[addr], user_issues, users_by_id, commenter_view, hostport, comment_text, amendments, config, project) email_tasks.append(email) logging.info('about to bulk notify member %s (%s) of %s', addr, user_id, [issue.local_id for issue in user_issues]) for addr, addr_issues in member_additional_addrs.items(): email = self._FormatBulkIssuesEmail( addr_to_addrperm[addr], addr_issues, users_by_id, commenter_view, hostport, comment_text, amendments, config, project) email_tasks.append(email) logging.info('about to bulk notify member additional addr %s of %s', addr, [addr_issue.local_id for addr_issue in addr_issues]) # 4. Add in the project's issue_notify_address. This happens even if it # is the same as the commenter's email address (which would be an unusual # but valid project configuration). Only issues that any contributor could # view are included in emails to the all-issue-activity mailing lists. if (project.issue_notify_address and project.issue_notify_address not in omit_addrs): non_private_issues_live = [] for issue in issues: contributor_could_view = permissions.CanViewIssue( set(), permissions.CONTRIBUTOR_ACTIVE_PERMISSIONSET, project, issue) if contributor_could_view: non_private_issues_live.append(issue) if non_private_issues_live: project_notify_addrperm = notify_reasons.AddrPerm( True, project.issue_notify_address, None, notify_reasons.REPLY_NOT_ALLOWED, None) email = self._FormatBulkIssuesEmail( project_notify_addrperm, non_private_issues_live, users_by_id, commenter_view, hostport, comment_text, amendments, config, project) email_tasks.append(email) omit_addrs.add(project.issue_notify_address) logging.info('about to bulk notify all-issues %s of %s', project.issue_notify_address, [issue.local_id for issue in non_private_issues]) return email_tasks
def ProcessMail(self, msg, project_addr): """Process an inbound email message.""" # TODO(jrobbins): If the message is HUGE, don't even try to parse # it. Silently give up. (from_addr, to_addrs, cc_addrs, references, incident_id, subject, body) = emailfmt.ParseEmailMessage(msg) logging.info('Proj addr: %r', project_addr) logging.info('From addr: %r', from_addr) logging.info('Subject: %r', subject) logging.info('To: %r', to_addrs) logging.info('Cc: %r', cc_addrs) logging.info('References: %r', references) logging.info('Incident Id: %r', incident_id) logging.info('Body: %r', body) # If message body is very large, reject it and send an error email. if emailfmt.IsBodyTooBigToParse(body): return _MakeErrorMessageReplyTask( project_addr, from_addr, self._templates['body_too_long']) # Make sure that the project reply-to address is in the To: line. if not emailfmt.IsProjectAddressOnToLine(project_addr, to_addrs): return None project_name, verb = emailfmt.IdentifyProjectAndVerb(project_addr) is_alert = bool(verb and verb.lower() == 'alert') error_addr = from_addr local_id = None author_addr = from_addr if is_alert: error_addr = settings.alert_escalation_email author_addr = settings.alert_service_account else: local_id = emailfmt.IdentifyIssue(project_name, subject) if not local_id: logging.info('Could not identify issue: %s %s', project_addr, subject) # No error message, because message was probably not intended for us. return None cnxn = sql.MonorailConnection() if self.services.cache_manager: self.services.cache_manager.DoDistributedInvalidation(cnxn) project = self.services.project.GetProjectByName(cnxn, project_name) # TODO(zhangtiff): Add separate email templates for alert error cases. if not project or project.state != project_pb2.ProjectState.LIVE: return _MakeErrorMessageReplyTask( project_addr, error_addr, self._templates['project_not_found']) if not project.process_inbound_email: return _MakeErrorMessageReplyTask( project_addr, error_addr, self._templates['replies_disabled'], project_name=project_name) # Verify that this is a reply to a notification that we could have sent. is_development = os.environ['SERVER_SOFTWARE'].startswith('Development') if not (is_alert or is_development): for ref in references: if emailfmt.ValidateReferencesHeader(ref, project, from_addr, subject): break # Found a message ID that we could have sent. else: # for-else: if loop completes with no valid reference found. return _MakeErrorMessageReplyTask( project_addr, from_addr, self._templates['not_a_reply']) # Authenticate the author_addr and perm check. # Note: If the issue summary line is changed, a new thread is created, # and replies to the old thread will no longer work because the subject # line hash will not match, which seems reasonable. try: auth = monorailrequest.AuthData.FromEmail( cnxn, author_addr, self.services, autocreate=is_alert) author_id = auth.user_id except user_svc.NoSuchUserException: author_id = None if not author_id: return _MakeErrorMessageReplyTask( project_addr, error_addr, self._templates['no_account']) if auth.user_pb.banned: logging.info('Banned user %s tried to post to %s', from_addr, project_addr) return _MakeErrorMessageReplyTask( project_addr, error_addr, self._templates['banned']) perms = permissions.GetPermissions( auth.user_pb, auth.effective_ids, project) # If the email is an alert, switch to the alert handling path. if is_alert: self.ProcessAlert(cnxn, project, project_addr, from_addr, author_addr, author_id, subject, body, incident_id) return None # This email is a response to an email about a comment. self.ProcessIssueReply( cnxn, project, local_id, project_addr, from_addr, author_id, auth.effective_ids, perms, body) return None
def HandleRequest(self, mr): """Provide the UI with info used in auto-completion. Args: mr: common information parsed from the HTTP request. Returns: Results dictionary in JSON format """ # Issue options data can be cached separately in each user's browser. When # the project changes, a new cached_content_timestamp is set and it will # cause new requests to use a new URL. self.SetCacheHeaders(self.response) member_data = project_helpers.BuildProjectMembers( mr.cnxn, mr.project, self.services.user) owner_views = member_data['owners'] committer_views = member_data['committers'] contributor_views = member_data['contributors'] config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id) open_statuses = [] closed_statuses = [] for wks in config.well_known_statuses: if not wks.deprecated: item = dict(name=wks.status, doc=wks.status_docstring) if wks.means_open: open_statuses.append(item) else: closed_statuses.append(item) # TODO(jrobbins): restrictions on component definitions? components = [{'name': cd.path, 'doc': cd.docstring} for cd in config.component_defs if not cd.deprecated] labels = [] field_names = [ fd.field_name for fd in config.field_defs if not fd.is_deleted] non_masked_labels = tracker_helpers.LabelsNotMaskedByFields( config, field_names) for wkl in non_masked_labels: if not wkl.commented: item = dict(name=wkl.name, doc=wkl.docstring) labels.append(item) # TODO(jrobbins): omit fields that they don't have permission to view. field_def_views = [ tracker_views.FieldDefView(fd, config) for fd in config.field_defs if not fd.is_deleted] fields = [ dict(field_name=fdv.field_name, field_type=fdv.field_type, field_id=fdv.field_id, needs_perm=fdv.needs_perm, is_required=fdv.is_required, is_multivalued=fdv.is_multivalued, choices=[dict(name=c.name, doc=c.docstring) for c in fdv.choices], docstring=fdv.docstring) for fdv in field_def_views] frequent_restrictions = _FREQUENT_ISSUE_RESTRICTIONS[:] custom_permissions = permissions.GetCustomPermissions(mr.project) if not custom_permissions: frequent_restrictions.extend( _EXAMPLE_ISSUE_RESTRICTIONS) labels.extend(_BuildRestrictionChoices( mr.project, frequent_restrictions, permissions.STANDARD_ISSUE_PERMISSIONS)) group_ids = self.services.usergroup.DetermineWhichUserIDsAreGroups( mr.cnxn, [mem.user_id for mem in member_data['all_members']]) logging.info('group_ids is %r', group_ids) acexclusion_ids = self.services.project.GetProjectAutocompleteExclusion( mr.cnxn, mr.project_id) # TODO(jrobbins): Normally, users will be allowed view the members # of any user group if the project From: email address is listed # as a group member, as well as any group that they are personally # members of. member_ids, owner_ids = self.services.usergroup.LookupVisibleMembers( mr.cnxn, group_ids, mr.perms, mr.auth.effective_ids, self.services) indirect_ids = set() for gid in group_ids: indirect_ids.update(member_ids.get(gid, [])) indirect_ids.update(owner_ids.get(gid, [])) indirect_user_ids = list(indirect_ids) indirect_member_views = framework_views.MakeAllUserViews( mr.cnxn, self.services.user, indirect_user_ids).values() visible_member_views = _FilterMemberData( mr, owner_views, committer_views, contributor_views, indirect_member_views) # Filter out servbice accounts visible_member_views = [m for m in visible_member_views if not framework_helpers.IsServiceAccount(m.email) and not m.user_id in acexclusion_ids] visible_member_email_list = list({ uv.email for uv in visible_member_views}) user_indexes = {email: idx for idx, email in enumerate(visible_member_email_list)} visible_members_dict = {} for uv in visible_member_views: visible_members_dict[uv.email] = uv.user_id group_ids = self.services.usergroup.DetermineWhichUserIDsAreGroups( mr.cnxn, visible_members_dict.values()) for field_dict in fields: needed_perm = field_dict['needs_perm'] if needed_perm: qualified_user_indexes = [] for uv in visible_member_views: # TODO(jrobbins): Similar code occurs in field_helpers.py. user = self.services.user.GetUser(mr.cnxn, uv.user_id) auth = monorailrequest.AuthData.FromUserID( mr.cnxn, uv.user_id, self.services) user_perms = permissions.GetPermissions( user, auth.effective_ids, mr.project) has_perm = user_perms.CanUsePerm( needed_perm, auth.effective_ids, mr.project, []) if has_perm: qualified_user_indexes.append(user_indexes[uv.email]) field_dict['user_indexes'] = sorted(set(qualified_user_indexes)) excl_prefixes = [prefix.lower() for prefix in config.exclusive_label_prefixes] members_def_list = [dict(name=email, doc='') for email in visible_member_email_list] members_def_list = sorted( members_def_list, key=lambda md: md['name']) for md in members_def_list: md_id = visible_members_dict[md['name']] if md_id in group_ids: md['is_group'] = True return { 'open': open_statuses, 'closed': closed_statuses, 'statuses_offer_merge': config.statuses_offer_merge, 'components': components, 'labels': labels, 'fields': fields, 'excl_prefixes': excl_prefixes, 'strict': ezt.boolean(config.restrict_to_known), 'members': members_def_list, 'custom_permissions': custom_permissions, }
def __init__(self, request, services): requester = (endpoints.get_current_user() or oauth.get_current_user( framework_constants.OAUTH_SCOPE)) requester_email = requester.email().lower() self.cnxn = sql.MonorailConnection() self.auth = AuthData.FromEmail(self.cnxn, requester_email, services) self.me_user_id = self.auth.user_id self.viewed_username = None self.viewed_user_auth = None self.project_name = None self.project = None self.issue = None self.config = None self.granted_perms = set() # query parameters self.params = { 'can': 1, 'start': 0, 'num': 100, 'q': '', 'sort': '', 'groupby': '', 'projects': [], 'hotlists': [] } self.use_cached_searches = True self.warnings = [] self.errors = template_helpers.EZTError() self.mode = None if hasattr(request, 'projectId'): self.project_name = request.projectId self.project = services.project.GetProjectByName( self.cnxn, self.project_name) self.params['projects'].append(self.project_name) self.config = services.config.GetProjectConfig( self.cnxn, self.project_id) if hasattr(request, 'additionalProject'): self.params['projects'].extend(request.additionalProject) self.params['projects'] = list(set(self.params['projects'])) if hasattr(request, 'issueId'): self.issue = services.issue.GetIssueByLocalID( self.cnxn, self.project_id, request.issueId) self.granted_perms = tracker_bizobj.GetGrantedPerms( self.issue, self.auth.effective_ids, self.config) if hasattr(request, 'userId'): self.viewed_username = request.userId.lower() if self.viewed_username == 'me': self.viewed_username = requester_email self.viewed_user_auth = AuthData.FromEmail(self.cnxn, self.viewed_username, services) elif hasattr(request, 'groupName'): self.viewed_username = request.groupName.lower() try: self.viewed_user_auth = AuthData.FromEmail( self.cnxn, self.viewed_username, services) except user_svc.NoSuchUserException: self.viewed_user_auth = None self.perms = permissions.GetPermissions(self.auth.user_pb, self.auth.effective_ids, self.project) # Build q. if hasattr(request, 'q') and request.q: self.params['q'] = request.q if hasattr(request, 'publishedMax') and request.publishedMax: self.params['q'] += ' opened<=%d' % request.publishedMax if hasattr(request, 'publishedMin') and request.publishedMin: self.params['q'] += ' opened>=%d' % request.publishedMin if hasattr(request, 'updatedMax') and request.updatedMax: self.params['q'] += ' modified<=%d' % request.updatedMax if hasattr(request, 'updatedMin') and request.updatedMin: self.params['q'] += ' modified>=%d' % request.updatedMin if hasattr(request, 'owner') and request.owner: self.params['q'] += ' owner:%s' % request.owner if hasattr(request, 'status') and request.status: self.params['q'] += ' status:%s' % request.status if hasattr(request, 'label') and request.label: self.params['q'] += ' label:%s' % request.label if hasattr(request, 'can') and request.can: if request.can == api_pb2_v1.CannedQuery.all: self.params['can'] = 1 elif request.can == api_pb2_v1.CannedQuery.new: self.params['can'] = 6 elif request.can == api_pb2_v1.CannedQuery.open: self.params['can'] = 2 elif request.can == api_pb2_v1.CannedQuery.owned: self.params['can'] = 3 elif request.can == api_pb2_v1.CannedQuery.reported: self.params['can'] = 4 elif request.can == api_pb2_v1.CannedQuery.starred: self.params['can'] = 5 elif request.can == api_pb2_v1.CannedQuery.to_verify: self.params['can'] = 7 else: # Endpoints should have caught this. raise InputException('Canned query %s is not supported.', request.can) if hasattr(request, 'startIndex') and request.startIndex: self.params['start'] = request.startIndex if hasattr(request, 'maxResults') and request.maxResults: self.params['num'] = request.maxResults if hasattr(request, 'sort') and request.sort: self.params['sort'] = request.sort self.query_project_names = self.GetParam('projects') self.group_by_spec = self.GetParam('groupby') self.sort_spec = self.GetParam('sort') self.query = self.GetParam('q') self.can = self.GetParam('can') self.start = self.GetParam('start') self.num = self.GetParam('num')
def GatherPageData(self, mr): """Build up a dictionary of data values to use when rendering the page.""" viewed_user = mr.viewed_user_auth.user_pb if self.services.usergroup.GetGroupSettings( mr.cnxn, mr.viewed_user_auth.user_id): url = framework_helpers.FormatAbsoluteURL(mr, '/g/%s/' % viewed_user.email, include_project=False) self.redirect(url, abort=True) # Show group page instead. with work_env.WorkEnv(mr, self.services) as we: project_lists = we.GetUserProjects( mr.viewed_user_auth.effective_ids) (visible_ownership, visible_archived, visible_membership, visible_contrib) = project_lists with mr.profiler.Phase('Getting user groups'): group_settings = self.services.usergroup.GetAllGroupSettings( mr.cnxn, mr.viewed_user_auth.effective_ids) member_ids, owner_ids = self.services.usergroup.LookupAllMembers( mr.cnxn, list(group_settings.keys())) friend_project_ids = [] # TODO(issue 4202): implement this. visible_group_ids = [] for group_id in group_settings: if permissions.CanViewGroupMembers(mr.perms, mr.auth.effective_ids, group_settings[group_id], member_ids[group_id], owner_ids[group_id], friend_project_ids): visible_group_ids.append(group_id) user_group_views = framework_views.MakeAllUserViews( mr.cnxn, self.services.user, visible_group_ids) user_group_views = sorted(list(user_group_views.values()), key=lambda ugv: ugv.email) with mr.profiler.Phase('Getting linked accounts'): linked_parent = None linked_children = [] linked_views = framework_views.MakeAllUserViews( mr.cnxn, self.services.user, [viewed_user.linked_parent_id], viewed_user.linked_child_ids) if viewed_user.linked_parent_id: linked_parent = linked_views[viewed_user.linked_parent_id] if viewed_user.linked_child_ids: linked_children = [ linked_views[child_id] for child_id in viewed_user.linked_child_ids ] offer_unlink = (mr.auth.user_id == viewed_user.user_id or mr.auth.user_id in linked_views) incoming_invite_users = [] outgoing_invite_users = [] possible_parent_accounts = [] can_edit_invites = mr.auth.user_id == mr.viewed_user_auth.user_id display_link_invites = can_edit_invites or mr.auth.user_pb.is_site_admin # TODO(jrobbins): allow site admin to edit invites for other users. if display_link_invites: with work_env.WorkEnv(mr, self.services, phase='Getting link invites'): incoming_invite_ids, outgoing_invite_ids = we.GetPendingLinkedInvites( user_id=viewed_user.user_id) invite_views = framework_views.MakeAllUserViews( mr.cnxn, self.services.user, incoming_invite_ids, outgoing_invite_ids) incoming_invite_users = [ invite_views[uid] for uid in incoming_invite_ids ] outgoing_invite_users = [ invite_views[uid] for uid in outgoing_invite_ids ] possible_parent_accounts = _ComputePossibleParentAccounts( we, mr.viewed_user_auth.user_view, linked_parent, linked_children) viewed_user_display_name = framework_views.GetViewedUserDisplayName(mr) with work_env.WorkEnv(mr, self.services) as we: starred_projects = we.ListStarredProjects( viewed_user_id=mr.viewed_user_auth.user_id) logged_in_starred = we.ListStarredProjects() logged_in_starred_pids = {p.project_id for p in logged_in_starred} starred_user_ids = self.services.user_star.LookupStarredItemIDs( mr.cnxn, mr.viewed_user_auth.user_id) starred_user_dict = framework_views.MakeAllUserViews( mr.cnxn, self.services.user, starred_user_ids) starred_users = list(starred_user_dict.values()) starred_users_json = json.dumps( [uv.display_name for uv in starred_users]) is_user_starred = self._IsUserStarred(mr.cnxn, mr.auth.user_id, mr.viewed_user_auth.user_id) if viewed_user.last_visit_timestamp: last_visit_str = timestr.FormatRelativeDate( viewed_user.last_visit_timestamp, days_only=True) last_visit_str = last_visit_str or 'Less than 2 days ago' else: last_visit_str = 'Never' if viewed_user.email_bounce_timestamp: last_bounce_str = timestr.FormatRelativeDate( viewed_user.email_bounce_timestamp, days_only=True) last_bounce_str = last_bounce_str or 'Less than 2 days ago' else: last_bounce_str = None can_ban = permissions.CanBan(mr, self.services) viewed_user_is_spammer = viewed_user.banned.lower() == 'spam' viewed_user_may_be_spammer = not viewed_user_is_spammer all_projects = self.services.project.GetAllProjects(mr.cnxn) for project_id in all_projects: project = all_projects[project_id] viewed_user_perms = permissions.GetPermissions( viewed_user, mr.viewed_user_auth.effective_ids, project) if (viewed_user_perms != permissions.EMPTY_PERMISSIONSET and viewed_user_perms != permissions.USER_PERMISSIONSET): viewed_user_may_be_spammer = False ban_token = None ban_spammer_token = None if mr.auth.user_id and can_ban: form_token_path = mr.request.path + 'ban.do' ban_token = xsrf.GenerateToken(mr.auth.user_id, form_token_path) form_token_path = mr.request.path + 'banSpammer.do' ban_spammer_token = xsrf.GenerateToken(mr.auth.user_id, form_token_path) page_data = { 'user_tab_mode': 'st2', 'viewed_user_display_name': viewed_user_display_name, 'viewed_user_may_be_spammer': ezt.boolean(viewed_user_may_be_spammer), 'viewed_user_is_spammer': ezt.boolean(viewed_user_is_spammer), 'viewed_user_is_banned': ezt.boolean(viewed_user.banned), 'owner_of_projects': [ project_views.ProjectView(p, starred=p.project_id in logged_in_starred_pids) for p in visible_ownership ], 'committer_of_projects': [ project_views.ProjectView(p, starred=p.project_id in logged_in_starred_pids) for p in visible_membership ], 'contributor_to_projects': [ project_views.ProjectView(p, starred=p.project_id in logged_in_starred_pids) for p in visible_contrib ], 'owner_of_archived_projects': [project_views.ProjectView(p) for p in visible_archived], 'starred_projects': [ project_views.ProjectView(p, starred=p.project_id in logged_in_starred_pids) for p in starred_projects ], 'starred_users': starred_users, 'starred_users_json': starred_users_json, 'is_user_starred': ezt.boolean(is_user_starred), 'viewing_user_page': ezt.boolean(True), 'last_visit_str': last_visit_str, 'last_bounce_str': last_bounce_str, 'vacation_message': viewed_user.vacation_message, 'can_ban': ezt.boolean(can_ban), 'ban_token': ban_token, 'ban_spammer_token': ban_spammer_token, 'user_groups': user_group_views, 'linked_parent': linked_parent, 'linked_children': linked_children, 'incoming_invite_users': incoming_invite_users, 'outgoing_invite_users': outgoing_invite_users, 'possible_parent_accounts': possible_parent_accounts, 'can_edit_invites': ezt.boolean(can_edit_invites), 'offer_unlink': ezt.boolean(offer_unlink), } viewed_user_prefs = None if mr.perms.HasPerm(permissions.EDIT_OTHER_USERS, None, None): with work_env.WorkEnv(mr, self.services) as we: viewed_user_prefs = we.GetUserPrefs( mr.viewed_user_auth.user_id) user_settings = ( framework_helpers.UserSettings.GatherUnifiedSettingsPageData( mr.auth.user_id, mr.viewed_user_auth.user_view, viewed_user, viewed_user_prefs)) page_data.update(user_settings) return page_data
def GatherPageData(self, mr): """Build up a dictionary of data values to use when rendering the page.""" viewed_user = mr.viewed_user_auth.user_pb if self.services.usergroup.GetGroupSettings( mr.cnxn, mr.viewed_user_auth.user_id): url = framework_helpers.FormatAbsoluteURL(mr, '/g/%s/' % viewed_user.email, include_project=False) self.redirect(url, abort=True) # Show group page instead. with self.profiler.Phase('GetUserProjects'): project_lists = sitewide_helpers.GetUserProjects( mr.cnxn, self.services, mr.auth.user_pb, mr.auth.effective_ids, mr.viewed_user_auth.effective_ids) (visible_ownership, visible_archived, visible_membership, visible_contrib) = project_lists viewed_user_display_name = framework_views.GetViewedUserDisplayName(mr) with self.profiler.Phase('GetStarredProjects'): starred_projects = sitewide_helpers.GetViewableStarredProjects( mr.cnxn, self.services, mr.viewed_user_auth.user_id, mr.auth.effective_ids, mr.auth.user_pb) logged_in_starred_pids = [] if mr.auth.user_id: logged_in_starred_pids = self.services.project_star.LookupStarredItemIDs( mr.cnxn, mr.auth.user_id) starred_user_ids = self.services.user_star.LookupStarredItemIDs( mr.cnxn, mr.viewed_user_auth.user_id) starred_user_dict = framework_views.MakeAllUserViews( mr.cnxn, self.services.user, starred_user_ids) starred_users = starred_user_dict.values() is_user_starred = self._IsUserStarred(mr.cnxn, mr.auth.user_id, mr.viewed_user_auth.user_id) if viewed_user.last_visit_timestamp: last_visit_str = timestr.FormatRelativeDate( viewed_user.last_visit_timestamp, days_only=True) last_visit_str = last_visit_str or 'Less than 2 days ago' else: last_visit_str = 'Never' if viewed_user.email_bounce_timestamp: last_bounce_str = timestr.FormatRelativeDate( viewed_user.email_bounce_timestamp, days_only=True) last_bounce_str = last_bounce_str or 'Less than 2 days ago' else: last_bounce_str = None can_ban = permissions.CanBan(mr, self.services) viewed_user_is_spammer = viewed_user.banned.lower() == 'spam' viewed_user_may_be_spammer = not viewed_user_is_spammer all_projects = self.services.project.GetAllProjects(mr.cnxn) for project_id in all_projects: project = all_projects[project_id] viewed_user_perms = permissions.GetPermissions( viewed_user, mr.viewed_user_auth.effective_ids, project) if (viewed_user_perms != permissions.EMPTY_PERMISSIONSET and viewed_user_perms != permissions.USER_PERMISSIONSET): viewed_user_may_be_spammer = False ban_token = None ban_spammer_token = None if mr.auth.user_id and can_ban: form_token_path = mr.request.path + 'ban.do' ban_token = xsrf.GenerateToken(mr.auth.user_id, form_token_path) form_token_path = mr.request.path + 'banSpammer.do' ban_spammer_token = xsrf.GenerateToken(mr.auth.user_id, form_token_path) page_data = { 'user_tab_mode': 'st2', 'viewed_user_display_name': viewed_user_display_name, 'viewed_user_may_be_spammer': ezt.boolean(viewed_user_may_be_spammer), 'viewed_user_is_spammer': ezt.boolean(viewed_user_is_spammer), 'viewed_user_is_banned': ezt.boolean(viewed_user.banned), 'viewed_user_ignore_action_limits': (ezt.boolean(viewed_user.ignore_action_limits)), 'owner_of_projects': [ project_views.ProjectView(p, starred=p.project_id in logged_in_starred_pids) for p in visible_ownership ], 'committer_of_projects': [ project_views.ProjectView(p, starred=p.project_id in logged_in_starred_pids) for p in visible_membership ], 'contributor_to_projects': [ project_views.ProjectView(p, starred=p.project_id in logged_in_starred_pids) for p in visible_contrib ], 'owner_of_archived_projects': [project_views.ProjectView(p) for p in visible_archived], 'starred_projects': [ project_views.ProjectView(p, starred=p.project_id in logged_in_starred_pids) for p in starred_projects ], 'starred_users': starred_users, 'is_user_starred': ezt.boolean(is_user_starred), 'viewing_user_page': ezt.boolean(True), 'last_visit_str': last_visit_str, 'last_bounce_str': last_bounce_str, 'vacation_message': viewed_user.vacation_message, 'can_ban': ezt.boolean(can_ban), 'ban_token': ban_token, 'ban_spammer_token': ban_spammer_token } settings = framework_helpers.UserSettings.GatherUnifiedSettingsPageData( mr.auth.user_id, mr.viewed_user_auth.user_view, viewed_user) page_data.update(settings) return page_data