Beispiel #1
0
    def testProcessIssueReply_NoEditIssuePerm(self):
        perms = permissions.USER_PERMISSIONSET
        mock_uia = commitlogcommands.UpdateIssueAction(self.issue.local_id)

        self.mox.StubOutWithMock(commitlogcommands, 'UpdateIssueAction')
        commitlogcommands.UpdateIssueAction(
            self.issue.local_id).AndReturn(mock_uia)

        self.mox.StubOutWithMock(mock_uia, 'Parse')
        mock_uia.Parse(self.cnxn,
                       self.project.project_name,
                       111L, ['awesome!'],
                       self.services,
                       strip_quoted_lines=True)
        self.mox.StubOutWithMock(mock_uia, 'Run')
        # Allow edit is false here because the permission set does not contain
        # EDIT_ISSUE.
        mock_uia.Run(self.cnxn, self.services, allow_edit=False)

        self.mox.ReplayAll()
        ret = self.inbound.ProcessIssueReply(self.cnxn, self.project,
                                             self.issue.local_id,
                                             self.project_addr, 'from_addr',
                                             111L, [1, 2, 3], perms,
                                             'awesome!')
        self.mox.VerifyAll()
        self.assertIsNone(ret)
Beispiel #2
0
    def testProcessIssueReply_Success(self):
        perms = permissions.COMMITTER_ACTIVE_PERMISSIONSET
        mock_uia = commitlogcommands.UpdateIssueAction(self.issue.local_id)

        self.mox.StubOutWithMock(commitlogcommands, 'UpdateIssueAction')
        commitlogcommands.UpdateIssueAction(
            self.issue.local_id).AndReturn(mock_uia)

        self.mox.StubOutWithMock(mock_uia, 'Parse')
        mock_uia.Parse(self.cnxn,
                       self.project.project_name,
                       111L, ['awesome!'],
                       self.services,
                       strip_quoted_lines=True)
        self.mox.StubOutWithMock(mock_uia, 'Run')
        mock_uia.Run(self.cnxn, self.services, allow_edit=True)

        self.mox.ReplayAll()
        ret = self.inbound.ProcessIssueReply(self.cnxn, self.project,
                                             self.issue.local_id,
                                             self.project_addr, 'from_addr',
                                             111L, [1, 2, 3], perms,
                                             'awesome!')
        self.mox.VerifyAll()
        self.assertIsNone(ret)
Beispiel #3
0
    def testProcessIssueReply_Success(self):
        self.services.user.TestAddUser('*****@*****.**', 111)
        mc = monorailcontext.MonorailContext(self.services,
                                             cnxn=self.cnxn,
                                             requester='*****@*****.**')
        mc.perms = permissions.COMMITTER_ACTIVE_PERMISSIONSET
        mock_uia = commitlogcommands.UpdateIssueAction(self.issue.local_id)

        self.mox.StubOutWithMock(commitlogcommands, 'UpdateIssueAction')
        commitlogcommands.UpdateIssueAction(
            self.issue.local_id).AndReturn(mock_uia)

        self.mox.StubOutWithMock(mock_uia, 'Parse')
        mock_uia.Parse(self.cnxn,
                       self.project.project_name,
                       111, ['awesome!'],
                       self.services,
                       strip_quoted_lines=True)
        self.mox.StubOutWithMock(mock_uia, 'Run')
        mock_uia.Run(mc, self.services)

        self.mox.ReplayAll()
        ret = self.inbound.ProcessIssueReply(mc, self.project,
                                             self.issue.local_id,
                                             self.project_addr, 'awesome!')
        self.mox.VerifyAll()
        self.assertIsNone(ret)
Beispiel #4
0
    def testProcessAlert_NewIssue(self, fake_pasicn, fake_pasibn):
        """When an alert for a new incident comes in, create a new issue."""
        self.mox.StubOutWithMock(tracker_helpers, 'LookupComponentIDs')
        tracker_helpers.LookupComponentIDs(['Infra'],
                                           mox.IgnoreArg()).AndReturn([1])

        self.mox.StubOutWithMock(self.services.config, 'LookupLabelID')
        self.services.config.LookupLabelID(
            self.cnxn, self.project.project_id,
            'Incident-Id-incident-1').AndReturn(None)

        # Mock command parsing.
        mock_uia = commitlogcommands.UpdateIssueAction(101)
        self.mox.StubOutWithMock(commitlogcommands, 'UpdateIssueAction')
        commitlogcommands.UpdateIssueAction(101).AndReturn(mock_uia)

        self.mox.StubOutWithMock(mock_uia, 'Parse')
        mock_uia.Parse(self.cnxn,
                       self.project.project_name,
                       111, ['issue body'],
                       self.services,
                       strip_quoted_lines=True)

        self.mox.ReplayAll()

        auth = authdata.AuthData(user_id=111, email='*****@*****.**')
        ret = self.inbound.ProcessAlert(self.cnxn, self.project,
                                        self.project_addr, '*****@*****.**',
                                        auth, 'issue title', 'issue body',
                                        'incident-1')

        self.mox.VerifyAll()
        self.assertIsNone(ret)

        actual_issue = self.services.issue.GetIssueByLocalID(
            self.cnxn, self.project.project_id, 101)
        actual_comments = self.services.issue.GetCommentsForIssue(
            self.cnxn, actual_issue.issue_id)
        self.assertEqual('issue title', actual_issue.summary)
        self.assertEqual('Available', actual_issue.status)
        self.assertEqual(111, actual_issue.reporter_id)
        self.assertEqual([1], actual_issue.component_ids)
        self.assertEqual(None, actual_issue.owner_id)
        self.assertEqual([
            'Infra-Troopers-Alerts', 'Restrict-View-Google', 'Pri-2',
            'Incident-Id-incident-1'
        ], actual_issue.labels)
        self.assertEqual(
            'Filed by [email protected] on behalf of [email protected]\n\nissue body',
            actual_comments[0].content)
        self.assertEqual(1, len(fake_pasicn.mock_calls))
        self.assertEqual(1, len(fake_pasibn.mock_calls))
Beispiel #5
0
    def testProcessAlert_ExistingIssue(self):
        """When an alert for an ongoing incident comes in, add a comment."""
        self.issue.labels = ['Incident-Id-incident-1']
        self.mox.StubOutWithMock(self.services.config, 'LookupLabelID')
        self.services.config.LookupLabelID(
            self.cnxn, self.project.project_id,
            'Incident-Id-incident-1').AndReturn(1234)

        self.mox.StubOutWithMock(self.services.issue, 'GetIIDsByLabelIDs')
        self.services.issue.GetIIDsByLabelIDs(self.cnxn, [1234],
                                              self.project.project_id,
                                              None).AndReturn([1])

        self.mox.StubOutWithMock(self.services.issue, 'GetOpenAndClosedIssues')
        self.services.issue.GetOpenAndClosedIssues(self.cnxn, [1]).AndReturn(
            ([self.issue], []))

        self.mox.StubOutWithMock(self.services.issue, 'GetComments')
        self.services.issue.GetComments(self.cnxn,
                                        issue_id=[self.issue.issue_id],
                                        commenter_id=[111]).AndReturn([])

        self.mox.StubOutWithMock(self.services.issue, 'CreateIssueComment')
        self.services.issue.CreateIssueComment(
            self.cnxn, self.issue, 111,
            'Filed by [email protected] on behalf of [email protected]\n\nissue body'
        ).AndReturn(None)

        # Mock command parsing.
        mock_uia = commitlogcommands.UpdateIssueAction(self.issue.local_id)
        self.mox.StubOutWithMock(commitlogcommands, 'UpdateIssueAction')
        commitlogcommands.UpdateIssueAction(
            self.issue.local_id).AndReturn(mock_uia)

        self.mox.StubOutWithMock(mock_uia, 'Parse')
        mock_uia.Parse(self.cnxn,
                       self.project.project_name,
                       111, ['issue body'],
                       self.services,
                       strip_quoted_lines=True)

        self.mox.ReplayAll()

        auth = authdata.AuthData(user_id=111, email='*****@*****.**')
        ret = self.inbound.ProcessAlert(self.cnxn, self.project,
                                        self.project_addr, '*****@*****.**',
                                        auth, 'issue title', 'issue body',
                                        'incident-1')

        self.mox.VerifyAll()
        self.assertIsNone(ret)
Beispiel #6
0
    def setUp(self):
        self.cnxn = 'fake cnxn'
        self.services = service_manager.Services(
            issue=fake.IssueService(),
            user=fake.UserService(),
            usergroup=fake.UserGroupService(),
            project=fake.ProjectService(),
            config=fake.ConfigService())

        self.member = self.services.user.TestAddUser('*****@*****.**', 111)
        self.outsider = self.services.user.TestAddUser('*****@*****.**',
                                                       222)
        self.project = self.services.project.TestAddProject(
            'proj',
            project_id=987,
            process_inbound_email=True,
            committer_ids=[self.member.user_id])
        self.issue = tracker_pb2.Issue()
        self.issue.issue_id = 98701
        self.issue.project_id = 987
        self.issue.local_id = 1
        self.issue.owner_id = 0
        self.issue.summary = 'summary'
        self.issue.status = 'Assigned'
        self.services.issue.TestAddIssue(self.issue)

        self.uia = commitlogcommands.UpdateIssueAction(self.issue.local_id)
Beispiel #7
0
    def testProcessAlert_NewIssue_Codesearch(self, fake_pasicn, fake_pasibn):
        """When an alert for a new incident comes in, create a new issue.

    If the body contains the string 'codesearch' then we should auto-assign to
    the Infra>Codesearch component."""
        self.mox.StubOutWithMock(tracker_helpers, 'LookupComponentIDs')
        tracker_helpers.LookupComponentIDs(['Infra>Codesearch'],
                                           mox.IgnoreArg()).AndReturn([2])

        self.mox.StubOutWithMock(self.services.config, 'LookupLabelID')
        self.services.config.LookupLabelID(
            self.cnxn, self.project.project_id,
            'Incident-Id-incident-1').AndReturn(None)

        # Mock command parsing.
        mock_uia = commitlogcommands.UpdateIssueAction(101)
        self.mox.StubOutWithMock(commitlogcommands, 'UpdateIssueAction')
        commitlogcommands.UpdateIssueAction(101).AndReturn(mock_uia)

        self.mox.StubOutWithMock(mock_uia, 'Parse')
        mock_uia.Parse(self.cnxn,
                       self.project.project_name,
                       111, ['issue body codesearch'],
                       self.services,
                       strip_quoted_lines=True)

        self.mox.ReplayAll()

        auth = authdata.AuthData(user_id=111, email='*****@*****.**')
        ret = self.inbound.ProcessAlert(self.cnxn, self.project,
                                        self.project_addr, '*****@*****.**',
                                        auth, 'issue title',
                                        'issue body codesearch', 'incident-1')

        self.mox.VerifyAll()
        self.assertIsNone(ret)

        actual_issue = self.services.issue.GetIssueByLocalID(
            self.cnxn, self.project.project_id, 101)
        self.assertEqual([2], actual_issue.component_ids)
        self.assertEqual(1, len(fake_pasicn.mock_calls))
        self.assertEqual(1, len(fake_pasibn.mock_calls))
Beispiel #8
0
  def ProcessIssueReply(
      self, mc, project, local_id, project_addr, body):
    """Examine an issue reply email body and add a comment to the issue.

    Args:
      mc: MonorailContext with cnxn and the requester email, user_id, perms.
      project: Project PB for the project containing the issue.
      local_id: int ID of the issue being replied to.
      project_addr: string email address used for outbound emails from
          that project.
      body: string email body text of the reply email.

    Returns:
      A list of follow-up work items, e.g., to notify other users of
      the new comment, or to notify the user that their reply was not
      processed.

    Side-effect:
      Adds a new comment to the issue, if no error is reported.
    """
    try:
      issue = self.services.issue.GetIssueByLocalID(
          mc.cnxn, project.project_id, local_id)
    except exceptions.NoSuchIssueException:
      issue = None

    if not issue or issue.deleted:
      # The referenced issue was not found, e.g., it might have been
      # deleted, or someone messed with the subject line.  Reject it.
      return _MakeErrorMessageReplyTask(
          project_addr, mc.auth.email, self._templates['no_artifact'],
          artifact_phrase='issue %d' % local_id,
          project_name=project.project_name)

    can_view = mc.perms.CanUsePerm(
        permissions.VIEW, mc.auth.effective_ids, project,
        permissions.GetRestrictions(issue))
    can_comment = mc.perms.CanUsePerm(
        permissions.ADD_ISSUE_COMMENT, mc.auth.effective_ids, project,
        permissions.GetRestrictions(issue))
    if not can_view or not can_comment:
      return _MakeErrorMessageReplyTask(
          project_addr, mc.auth.email, self._templates['no_perms'],
          artifact_phrase='issue %d' % local_id,
          project_name=project.project_name)

    # TODO(jrobbins): if the user does not have EDIT_ISSUE and the inbound
    # email tries to make an edit, send back an error message.

    lines = body.strip().split('\n')
    uia = commitlogcommands.UpdateIssueAction(local_id)
    uia.Parse(mc.cnxn, project.project_name, mc.auth.user_id, lines,
              self.services, strip_quoted_lines=True)
    uia.Run(mc, self.services)
    def setUp(self):
        self.cnxn = 'fake cnxn'
        self.services = service_manager.Services(issue=fake.IssueService(),
                                                 project=fake.ProjectService(),
                                                 config=fake.ConfigService())

        self.project = self.services.project.TestAddProject(
            'proj', project_id=987, process_inbound_email=True)
        self.issue = tracker_pb2.Issue()
        self.issue.project_id = 987
        self.issue.summary = 'summary'
        self.issue.status = 'Assigned'
        self.services.issue.TestAddIssue(self.issue)

        self.uia = commitlogcommands.UpdateIssueAction(self.issue.local_id)
        self.mox = mox.Mox()
Beispiel #10
0
def ProcessEmailNotification(services,
                             cnxn,
                             project,
                             project_addr,
                             from_addr,
                             auth,
                             subject,
                             body,
                             incident_id,
                             msg,
                             trooper_queue=None):
    """Process an alert notification email to create or update issues.""

  Args:
    cnxn: connection to SQL database.
    project: Project PB for the project containing the issue.
    project_addr: string email address the alert email was sent to.
    from_addr: string email address of the user who sent the alert email
        to our server.
    auth: AuthData object with user_id and email address of the user who
        will file the alert issue.
    subject: the subject of the email message
    body: the body text of the email message
    incident_id: string containing an optional unique incident used to
        de-dupe alert issues.
    msg: the email.Message object that the notification was delivered via.
    trooper_queue: the label specifying the trooper queue that the alert
      notification was sent to. If not given, the notification is sent to
      Infra-Troopers-Alerts.

  Side-effect:
    Creates an issue or issue comment, if no error was reported.
  """
    # Make sure the email address is whitelisted.
    if not IsWhitelisted(from_addr):
        logging.info('Unauthorized %s tried to send alert to %s', from_addr,
                     project_addr)
        return

    formatted_body = 'Filed by %s on behalf of %s\n\n%s' % (auth.email,
                                                            from_addr, body)

    mc = monorailcontext.MonorailContext(services, auth=auth, cnxn=cnxn)
    mc.LookupLoggedInUserPerms(project)
    with work_env.WorkEnv(mc, services) as we:
        alert_props = GetAlertProperties(services, cnxn, project.project_id,
                                         incident_id, trooper_queue, msg)
        alert_issue = FindAlertIssue(services, cnxn, project.project_id,
                                     alert_props['incident_label'])

        if alert_issue:
            # Add a reply to the existing issue for this incident.
            services.issue.CreateIssueComment(cnxn, alert_issue, auth.user_id,
                                              formatted_body)
        else:
            # Create a new issue for this incident.
            alert_issue, _ = we.CreateIssue(
                project.project_id, subject, alert_props['status'],
                alert_props['owner_id'], alert_props['cc_ids'],
                alert_props['labels'], alert_props['field_values'],
                alert_props['component_ids'], formatted_body)

        # Update issue using commands.
        lines = body.strip().split('\n')
        uia = commitlogcommands.UpdateIssueAction(alert_issue.local_id)
        commands_found = uia.Parse(cnxn,
                                   project.project_name,
                                   auth.user_id,
                                   lines,
                                   services,
                                   strip_quoted_lines=True)

        if commands_found:
            uia.Run(mc, services)
Beispiel #11
0
  def ProcessIssueReply(
      self, cnxn, project, local_id, project_addr, from_addr, author_id,
      effective_ids, perms, body):
    """Examine an issue reply email body and add a comment to the issue.

    Args:
      cnxn: connection to SQL database.
      project: Project PB for the project containing the issue.
      local_id: int ID of the issue being replied to.
      project_addr: string email address used for outbound emails from
          that project.
      from_addr: string email address of the user who sent the email
          reply to our server.
      author_id: int user ID of user who sent the reply email.
      effective_ids: set of int user IDs for the user (including any groups),
          or an empty set if user is not signed in.
      perms: PermissionSet for the user who sent the reply email.
      body: string email body text of the reply email.

    Returns:
      A list of follow-up work items, e.g., to notify other users of
      the new comment, or to notify the user that their reply was not
      processed.

    Side-effect:
      Adds a new comment to the issue, if no error is reported.
    """
    try:
      issue = self.services.issue.GetIssueByLocalID(
          cnxn, project.project_id, local_id)
    except issue_svc.NoSuchIssueException:
      issue = None

    if not issue or issue.deleted:
      # The referenced issue was not found, e.g., it might have been
      # deleted, or someone messed with the subject line.  Reject it.
      return _MakeErrorMessageReplyTask(
          project_addr, from_addr, self._templates['no_artifact'],
          artifact_phrase='issue %d' % local_id,
          project_name=project.project_name)

    can_view = perms.CanUsePerm(
        permissions.VIEW, effective_ids, project,
        permissions.GetRestrictions(issue))
    can_comment = perms.CanUsePerm(
        permissions.ADD_ISSUE_COMMENT, effective_ids, project,
        permissions.GetRestrictions(issue))
    if not can_view or not can_comment:
      return _MakeErrorMessageReplyTask(
          project_addr, from_addr, self._templates['no_perms'],
          artifact_phrase='issue %d' % local_id,
          project_name=project.project_name)
    allow_edit = permissions.CanEditIssue(
        effective_ids, perms, project, issue)
    # TODO(jrobbins): if the user does not have EDIT_ISSUE and the inbound
    # email tries to make an edit, send back an error message.

    lines = body.strip().split('\n')
    uia = commitlogcommands.UpdateIssueAction(local_id)
    uia.Parse(cnxn, project.project_name, author_id, lines, self.services,
              strip_quoted_lines=True)
    uia.Run(cnxn, self.services, allow_edit=allow_edit)
Beispiel #12
0
    def ProcessAlert(self,
                     cnxn,
                     project,
                     project_addr,
                     from_addr,
                     auth,
                     subject,
                     body,
                     incident_id,
                     owner_email=None,
                     labels=None):
        """Examine an an alert issue email and create an issue based on the email.

    Args:
      cnxn: connection to SQL database.
      project: Project PB for the project containing the issue.
      project_addr: string email address the alert email was sent to.
      from_addr: string email address of the user who sent the alert email
          to our server.
      auth: AuthData object with user_id and email address of the user who
          will file the alert issue.
      body: string email body text of the reply email.
      incident_id: string containing an optional unique incident used to
          de-dupe alert issues.
      owner_email: string email address of the user the bug will be assigned to.

    Returns:
      A list of follow-up work items, e.g., to notify other users of
      the new comment, or to notify the user that their reply was not
      processed.

    Side-effect:
      Adds a new comment to the issue, if no error is reported.
    """
        # Make sure the email address is whitelisted.
        if not self.IsWhitelisted(from_addr):
            logging.info('Unauthorized %s tried to send alert to %s',
                         from_addr, project_addr)
            return None

        # Create the actual issue from the email data.
        # TODO(zhangtiff): Set labels, components, etc based on email content.
        cc_ids = []
        status = 'Available'

        if not labels:
            labels = ['Infra-Troopers-Alerts']

        labels += ['Restrict-View-Google', 'Pri-2']
        field_values = []

        # TODO(zhangtiff): Remove this special casing once components can be set via
        # the email header.
        if 'codesearch' in body:
            components = ['Infra>Codesearch']
        else:
            components = ['Infra']

        formatted_body = 'Filed by %s on behalf of %s\n\n%s' % (
            auth.email, from_addr, body)

        # Lookup components.
        config = self.services.config.GetProjectConfig(cnxn,
                                                       project.project_id)
        component_ids = tracker_helpers.LookupComponentIDs(components, config)

        mc = monorailcontext.MonorailContext(self.services,
                                             auth=auth,
                                             cnxn=cnxn)
        mc.LookupLoggedInUserPerms(project)
        with work_env.WorkEnv(mc, self.services) as we:
            updated_issue = None
            owner_id = None
            if owner_email:
                owner_id = self.services.user.LookupUserID(cnxn,
                                                           owner_email,
                                                           autocreate=True)
                status = 'Assigned'

            if incident_id:
                incident_label = 'Incident-Id-' + incident_id
                labels.append(incident_label)

                label_id = self.services.config.LookupLabelID(
                    cnxn, project.project_id, incident_label)

                if label_id:
                    issue_ids = self.services.issue.GetIIDsByLabelIDs(
                        cnxn, [label_id], project.project_id, None)

                    issues, _ = self.services.issue.GetOpenAndClosedIssues(
                        cnxn, issue_ids)

                    latest_issue = None
                    # Find the most recently modified open issue.
                    for issue in issues:
                        if not latest_issue:
                            latest_issue = issue
                        elif issue.modified_timestamp > latest_issue.modified_timestamp:
                            latest_issue = issue

                    if latest_issue:
                        updated_issue = latest_issue
                        # Find all comments on the issue by the current user.
                        comments = self.services.issue.GetComments(
                            cnxn,
                            issue_id=[updated_issue.issue_id],
                            commenter_id=[auth.user_id])

                        # Timestamp for 24 hours ago in seconds from epoch.
                        yesterday = int(time.time()) - 24 * 60 * 60

                        for comment in comments:
                            # Stop early if we find a comment created in the last 24 hours.
                            if comment.timestamp > yesterday:
                                logging.info(
                                    'Alert fired again with incident id: %s',
                                    incident_id)
                                return None

                        # Add a reply to the existing issue for this incident.
                        self.services.issue.CreateIssueComment(
                            cnxn, updated_issue, auth.user_id, formatted_body)

            if not updated_issue:
                updated_issue, _ = we.CreateIssue(project.project_id, subject,
                                                  status, owner_id, cc_ids,
                                                  labels, field_values,
                                                  component_ids,
                                                  formatted_body)

            # Update issue using commands.
            lines = body.strip().split('\n')
            uia = commitlogcommands.UpdateIssueAction(updated_issue.local_id)
            commands_found = uia.Parse(cnxn,
                                       project.project_name,
                                       auth.user_id,
                                       lines,
                                       self.services,
                                       strip_quoted_lines=True)

            if commands_found:
                uia.Run(cnxn, self.services, allow_edit=True)