class JiraIssues(object): APPLICATION = {"type": "www.hackerone.comr", "name": "Hacker One"} SCOPE = ''' h4.Scope ---- asset type: %(type)s asset identifier: %(identifier)s\n''' DESCRIPTION = ''' h4.Report Info ---- Report State: %(state)s Reporter: %(reporter)s Assignee: %(assignee)s Report Created: %(created)s Report Last Activity: %(last_activity)s h4.Weakness ---- name: %(name)s description: %(w_description)s id: %(id)s h4.Severity ---- rating: %(rating)s score: %(score)s' h4.Description ---- %(description)s ''' def __init__(self, server, username, password, project): """Inits jira client. This current setup requires a jira username to be setup with the appropriate permissions in the jira project :type server: string :param server: jira url :type username: string :param username: token :type password: string :param password: jira username password :type project: string :param project: jira project """ self.__jira_server = server self.__username = username self.__password = password self.jira_project = project self._init_jira_client() def _init_jira_client(self): options = {'server': self.__jira_server} def create_custom_field(fields=None): url = self._get_url('field') r = self._session.post(url, data=json.dumps(fields)) if r.status_code != 201: raise JIRAError(r.status_code, request=r) return r # Jira library doesn't have method for creating custom fields setattr(JIRA, 'create_custom_field', create_custom_field) self.jira_client = JIRA(options, basic_auth=(self.__username, self.__password)) def get_jira_projects(self): return self.jira_client.projects() def create_project(self, key, name, jira_type="Software"): return self.jira_client.create_project(key, name, jira_type) def get_jira_issue(self, report): """ Return Jira Issue based on HackerOne Report issue_tracker_reference_id :type report: h1.models.Report :param report: hackerone report :return: Jira Issue """ try: return self.jira_client.issue(report.issue_tracker_reference_id) except JIRAError as e: if e.text == "Issue Does Not Exist": return None else: raise @staticmethod def _get_jira_summary(report): return "%s - %s" % (report.id, report.title) def _get_jira_description(self, report): return self.DESCRIPTION % { 'description': report.vulnerability_information, 'reporter': report.reporter.name, 'assignee': report.assignee.name if report.assignee is not None else "", 'state': report.state, 'created': report.created_at, 'last_activity': report.last_activity_at, 'name': report.weakness.name, 'w_description': report.weakness.description, 'id': report.weakness.external_id, 'rating': report.severity.rating, 'score': report.severity.score } def create_jira_issue(self, report): """ Create Jira Issue https://developer.atlassian.com/server/jira/platform/jira-rest-api-example-create-issue-7897248/ :type report: h1.models.Report :param report: hackerone report :type :return: string :return: Jira ID """ issue_dict = { 'project': { 'key': self.jira_project }, 'summary': self._get_jira_summary(report), 'description': self._get_jira_description(report), 'issuetype': { 'name': 'Bug' }, 'labels': ['hackerOne'] } return self.jira_client.create_issue(fields=issue_dict, prefetch=True) def update_jira_issue(self, report, jira): fields = {} summary = self._get_jira_summary(report) if jira.fields.summary != summary: fields['summary'] = summary description = self._get_jira_description(report) if jira.fields.description != description: fields['description'] = description if fields: logging.info("Updating Existing Jira Issue: %s" % fields.keys()) jira.update(fields=fields) def search_for_jira_issues(self, report_id): """ Perform a Jira query search using JQL :param report_id: hacker one report id :return: returns jira issue match """ return self.jira_client.search_issues( '''project = %s AND summary ~ "%s"''' % (self.jira_project, report_id), maxResults=1) def get_fields(self): return self.jira_client.fields() def create_custom_field(self, fields): return self.jira_client.create_custom_field(fields) def get_remote_links(self, jira): return self.jira_client.remote_links(jira) def add_remote_link(self, report, jira, relationship="Relates"): links = set() # note all rmeote links have to have a global id for link in self.get_remote_links(jira): if hasattr(link, 'globalId'): links.add(link.globalId) if report.id not in links: destination = {'url': report.html_url, 'title': report.title} return self.jira_client.add_remote_link(jira, destination, report.id, self.APPLICATION, relationship) def add_simple_link(self, report, jira): """https://developer.atlassian.com/server/jira/platform/jira-rest-api-for-remote-issue-links/""" link = {'url': report.html_url, 'title': report.title} return self.jira_client.add_simple_link(jira, object=link) def add_jira_attachment(self, jira, attachment, filename): """Add H1 Attachment in Jira :param jira: Jira object that has attachments :param attachment: hacker one attachment object content :param filename: attachment file name :return: return """ return self.jira_client.add_attachment(issue=jira.id, attachment=attachment, filename=filename) def create_comments(self, jira, comment): return self.jira_client.add_comment(jira, comment)
def main(): context = utils.collect_context() tmpl_host_summary = 'Check_MK: $HOSTNAME$ - $HOSTSHORTSTATE$' tmpl_service_summary = 'Check_MK: $HOSTNAME$/$SERVICEDESC$ $SERVICESHORTSTATE$' tmpl_label = 'monitoring' for necessary in [ 'PARAMETER_URL', 'PARAMETER_USERNAME', 'PARAMETER_PASSWORD', 'PARAMETER_HOST_CUSTOMID', 'PARAMETER_SERVICE_CUSTOMID' ]: if necessary not in context: sys.stderr.write("%s not set" % necessary) return 2 if "PARAMETER_IGNORE_SSL" in context: sys.stdout.write( "Unverified HTTPS request warnings are ignored. Use with caution.\n" ) jira = JIRA(server=context['PARAMETER_URL'], basic_auth=(context['PARAMETER_USERNAME'], context['PARAMETER_PASSWORD']), options={'verify': False}) else: jira = JIRA(server=context['PARAMETER_URL'], basic_auth=(context['PARAMETER_USERNAME'], context['PARAMETER_PASSWORD'])) if context['WHAT'] == 'HOST': summary = context.get('PARAMETER_HOST_SUMMARY') or tmpl_host_summary svc_desc = context['HOSTOUTPUT'] custom_field = int(context['PARAMETER_HOST_CUSTOMID']) custom_field_value = int(context['HOSTPROBLEMID']) else: summary = context.get( 'PARAMETER_SERVICE_SUMMARY') or tmpl_service_summary svc_desc = context['SERVICEOUTPUT'] custom_field = int(context['PARAMETER_SERVICE_CUSTOMID']) custom_field_value = int(context['SERVICEPROBLEMID']) context['SUBJECT'] = utils.substitute_context(summary, context) label = context.get('PARAMETER_LABEL') or tmpl_label newissue = { u'labels': [label], u'summary': context['SUBJECT'], u'description': svc_desc, } if 'PARAMETER_PROJECT' in context: newissue[u'project'] = {u'id': context['PARAMETER_PROJECT']} if 'CONTACT_JIRAPROJECT' in context: newissue[u'project'] = {u'id': context['CONTACT_JIRAPROJECT']} if 'PARAMETER_ISSUETYPE' in context: newissue[u'issuetype'] = {u'id': context['PARAMETER_ISSUETYPE']} if 'CONTACT_JIRAISSUETYPE' in context: newissue[u'issuetype'] = {u'id': context['CONTACT_JIRAISSUETYPE']} if 'PARAMETER_PRIORITY' in context: newissue[u'priority'] = {u'id': context['PARAMETER_PRIORITY']} if 'CONTACT_JIRAPRIORITY' in context: newissue[u'priority'] = {u'id': context['CONTACT_JIRAPRIORITY']} if 'project' not in newissue: sys.stderr.write("No JIRA project ID set, discarding notification") return 2 if 'issuetype' not in newissue: sys.stderr.write("No JIRA issue type ID set") return 2 try: custom_field_exists = jira.search_issues( "cf[%d]=%d" % (custom_field, custom_field_value)) except JIRAError as err: sys.stderr.write( 'Unable to query custom field search, JIRA response code %s, %s' % (err.status_code, err.text)) return 2 if not custom_field_exists: newissue[u'customfield_%d' % custom_field] = custom_field_value if context['NOTIFICATIONTYPE'] == 'PROBLEM': try: issue = jira.create_issue(fields=newissue) except JIRAError as err: sys.stderr.write( 'Unable to create issue, JIRA response code %s, %s' % (err.status_code, err.text)) return 2 sys.stdout.write('Created %s\n' % issue.permalink()) if 'PARAMETER_MONITORING' in context: if context['PARAMETER_MONITORING'].endswith('/'): # remove trailing slash context['PARAMETER_MONITORING'] = context[ 'PARAMETER_MONITORING'][:-1] if context['WHAT'] == 'SERVICE': url = context['PARAMETER_MONITORING'] + context['SERVICEURL'] else: url = context['PARAMETER_MONITORING'] + context['HOSTURL'] try: rl = jira.add_simple_link(issue, { 'url': url, 'title': 'Monitoring' }) except JIRAError as err: sys.stderr.write( 'Unable to create link in issue, JIRA response code %s, %s\n' % (err.status_code, err.text)) return 2 sys.stdout.write('Created JIRA simple link: %s' % rl) if context['NOTIFICATIONTYPE'] == 'RECOVERY' and custom_field_exists: if "PARAMETER_RESOLUTION" not in context: sys.stderr.write( "Ticket resolution not enabled in wato rule. Don't send a resolution to jira\n" ) return 0 else: resolution = None if 'PARAMETER_RESOLUTION' in context: resolution = context['PARAMETER_RESOLUTION'] if 'CONTACT_JIRARESOLUTION' in context: resolution = context['CONTACT_JIRARESOLUTION'] if resolution is None: sys.stderr.write("No JIRA resolution ID set") return 2 for issue in custom_field_exists: try: jira.transition_issue(issue, resolution, comment=newissue['description']) sys.stdout.write('Resolved %s' % issue.permalink()) except JIRAError as err: sys.stderr.write( 'Unable to resolve %s, JIRA response code %s, %s' % (issue.permalink(), err.status_code, err.text)) return 2